Properly close the window using mouse in Ursina - python

For the past couple of days, I have been playing around in the Ursina Engine in Python, used for creating both 3D and 2D games. But the recurring problem I have been facing with this engine when making 3D games, is that I can't close the window properly. This is happening because mouse is being used inside of the game, to control the player, so if I try to go to the close button, the mouse will always stay in the game. The workaround for this is to move to a different window, position the mouse so it's outside of the window, and then finally hit the close button. But this is a lot of work for the user to do, to simply close the window.
Here is some simple code to demonstrate:
from ursina import *
from ursina.prefabs.first_person_controller import FirstPersonController
import random
game = Ursina()
class Block(Button):
def __init__(self, position = (0,0,0)):
super().__init__(parent = scene, position = position, model = 'cube', color = color.white)
for z in range(20):
for x in range(20):
block = Block(position = (x, 0, z))
player = FirstPersonController()
game.run()
I believe this import statement is causing this:
from ursina.prefabs.first_person_controller import FirstPersonController
How can I close the window properly in Ursina?

One solution to this problem would be to create a way for you to exit the game by pressing a key.
def input(key):
if key == 'escape':
quit()
With this code you can close the game by pressing 'escape'.

Quick solution, Shift+Q (if the exit_button is enabled).
Usually in first person shooters you make a pause menu and put an exit button there. The FirstPersonController hides the mouse cursor and locks the position to the center of the screen. To reverse this, do:
mouse.locked = False
mouse.visible = True

I usually press the "Windows" key. Pressing this button open the windows menu, but it also makes the mouse visible.
Another tip, you can put:
window.exit_button.visible = False
In your code to make it easier to close the window.

Just disable the FPC (First Person Controller)
player.disable()
or just
make the mouse unlocked mouse.locked = False, so it can move

Related

Issue with multithreading in Tkinter

I'm currently trying to create a program where it detects the mouse movement on my computer and pops up a window if any movement is detected. However, I've run into some issues with Tkinter threading. I have read multiple other posts on the same issue and understand that Tkinter isn't thread-safe and that it can only be run in the main thread. However, I'm still unsure about the fix for my program. I'd appreciate it if someone could help me take a look.
Essentially, my (simplified) program has two scripts, access_detection.py and password_prompt.py. The first file is responsible for using pynput.mouse to track mouse movement, and the second file is responsible for popping up a Tkinter window and prompting the user for a password whenever mouse movement from the first script is detected.
> password_prompt.py (pops up a window for the user to enter their passcode)
def confirm_passcode(self):
self.access_granted = True
def prompt_password(self) -> bool:
# initialize window
entry = Entry(self.win, width=25, textvariable=self.password, show="*")
entry.pack(pady=((int(self.win.winfo_screenheight()/2) - 100), 0))
button = ttk.Button(self.win, text="Confirm Password", command=self.confirm_passcode)
button.pack(pady=10)
while True:
self.win.attributes('-fullscreen', True)
self.win.attributes('-topmost', True)
if self.access_granted == True:
return True
elif self.access_granted == False and self.access_attempts >= 3:
return False
self.win.update()
> access_detection.py (tracks mouse movement)
def on_move_mouse(self, x, y):
self.security_passed = popup_obj.prompt_password()
if self.security_passed == False:
# do something here
else:
# stops this program
def detection_start(self):
with mouse_listener(on_move=self.on_move_mouse) as mouse_monitor:
mouse_monitor.join()
# driver
if __name__ == "__main__":
monitor = # a class with detection_start()
monitor.detection_start()
The issue raised: RuntimeError: main thread is not in main loop
Currently, the main method is in access_detection.py and I believe the pynput.mouse threading is using the main thread, causing the error since Tkinter isn't thread-safe. Is there a way that I could have these two threads running simultaneously and still accomplish the goal of tracking mouse movement and popping up a window when movement is detected? (with Tkinter in the main thread if it has to be necessary).
The original code is a bit lengthy. It could be found here if you need more info. (ignore main.py)

Tab out of Pygame fullscreen window without crashing it

So I'm using Pygame to create a fancy display for a program I am writing. I chose Pygame because it's easy to get started and does a great job with animations. I want the display to be as big as I can make it so as much information can be shown as possible. Here is the kicker however, I still want to be able to get to the console of the program.
Pygame forces a fullscreen window to the front, so you cant tab out, and moving the windows to another windows desktop crashes the display. I would do a key trick to switch the pygame mode, but I cannot use pygame.event.get() because of how the program the threaded.
Is there a way to make it a full-screen window so that I can tab out and leave it up in the background? I dont really want it to just be a normal window because it is not as big that way.
The display crashes after I tab out and back in, here is what that looks like:
I also get a non-zero exit code: -805306369 (0xCFFFFFFF)
Here is a broken down version of the code that still gives me this error, you'll notice there are some things in here you wouldn't have if this was your full program, but I wanted to retain as much architecture as I could.
import pygame
import os
BACKGROUND = (9, 17, 27)
os.environ['SDL_VIDEO_WINDOW_POS'] = "0,0"
pygame.init()
pygame.font.init()
infoObject = pygame.display.Info()
SIZE = (infoObject.current_w, infoObject.current_h)
X_CENTER = SIZE[0]/2
Y_CENTER = SIZE[1]/2
# create a borderless window that's as big as the entire screen
SCREEN = pygame.display.set_mode((SIZE[0], SIZE[1]), pygame.NOFRAME)
clock = pygame.time.Clock()
TextFont = pygame.font.SysFont('Courant', 30)
class DisplayState:
state = type(bool)
def __init__(self):
self.state = True
def get_state(self):
return self.state
def change_state(self, new_state):
self.state = new_state
def main(display_state_object):
running = True
while running:
if display_state_object.get_state():
SCREEN.fill(BACKGROUND)
pygame.display.flip()
else:
return 1
return
if __name__ == "__main__":
main(DisplayState())
EDIT
I think it is a multi-threading problem! See this code:
Produces Error
def start_display():
display(params)
def display(params):
pygame loop
if __name__ == "__main__":
display_thread = threading.Thread(target=start_display)
display_thread.start()
Does not produce error
def display(params):
pygame loop
if __name__ == "__main__":
display_thread = threading.Thread(target=display(params))
display_thread.start
# marker
One problem with the version that does work, the program does not seem to be continuing forwards outside the thread (ie the marker is never reached). Is this how the threading library works? It may explain why I had the middle man function present. Maybe this is a different problem and deserves its own question?
EDIT
Setting up the thread like this allows the main thread to continue, but brings back the pygame error:
threading.Thread(target=display, args=(DisplayState(),))
There's no easy way to do this on windows/sdl using the real fullscreen mode, and the usual way to solve this is to use a borderless window.
Here's how to create such a "fake" fullscreen window in pygame:
import pygame
import os
# you can control the starting position of the window with the SDL_VIDEO_WINDOW_POS variable
os.environ['SDL_VIDEO_WINDOW_POS'] = "0,0"
pygame.init()
# now let's see how big our screen is
info = pygame.display.Info()
# and create a borderless window that's as big as the entire screen
screen = pygame.display.set_mode((info.current_w, info.current_h), pygame.NOFRAME)
You have to call one of the pygame event functions (e.g. pygame.event.pump() or pygame.event.get()) each frame or the window will become unresponsive and the program will appear to have crashed. If you call one of those functions, you should be able to press Alt+Tab (in Windows) to get back to the desktop without crashing the program (if you select the desktop, the window will be minimized and if you select another window, it will just be brought to the front).
def main(display_state_object):
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
# Press Esc to quit.
if event.key == pygame.K_ESCAPE:
running = False
if display_state_object.get_state():
SCREEN.fill(BACKGROUND)
pygame.display.flip()
else:
return 1
return

Pygame Window created in the background

When I start my program that has to open pygame window, it opens it behind the current window. What have I to write to make a focus on pygame window and it won't be as background? This is for a game for mathematics revision so I need to use the python shell as well, in this example for (1+1).
This is the code I run. (just as an example)
import pygame
play = input("DO you want to play the game(y,n): ")
if play == "y":
pygame.init()
font = pygame.font.SysFont("None", 24, bold = False, italic = False )
Screen = pygame.display.set_mode((800, 600))
black = (0,0,0)
Screen.fill(black)
pygame.display.set_caption("Snake Game")
pygame.draw.rect(Screen, (218,123,255), ((0,0),(600,20)),0)
pygame.draw.rect(Screen, (0,197,243), ((0,20),(800, 580)), 16)
pygame.display.update()
#Play game for some time
Add = input("1 + 1 = ")
#Continue game on the pygame window
The only way to have the system position the pygame window in front of all the other ones is to not use the Python Shell.
Aside from doing that I do not think that there is any way to change how the system positions the window in pygame. I haven't found anything that changes whether it is positioned behind or in front of the current window. Correct me if I am wrong but I would guess that is the OS's job.
Thus, you must do everything in Pygame or else you will have the user switching between windows.
To fix this problem:
Remove all uses of the input function and use of the Python shell.
Implement a way to ask the mathematics questions in Pygame using font rendering
Implement a way to let the user type in an answer and enter it in. This can be done using font rendering and input handling.
I hope this answer helped you and if you have any further questions please feel free to post a comment below!

0 How do I assign a button to a mouse position and bring the mouse back to that position with another button press (for VR purposes/python/freepie)

To make a long story short, I have an oculus dk2 vr headset, a blue tooth adapter and psmove motion controller. I play a game doom 3 fully possesed in vr, and you can control your aiming separetely from your view with the mouse.
Now I was able to make my psmove act like a mouse with the built in gyroscope, which sounds more difficult than it is. I use an app called psmoveservice, wich can connect the psmove to the pc through bluetooth, then I use another app called psmovefreepiebridge which sends the raw data to an app called freepie.
Freepie is based on python syntax and you can import libraries. I started off with this code which assigns some buttons to the psmove, and makes the psmove act like a mouse.
def update():
#define globals
#define constants
mag = 1000
dz = 0.005
i=0
j=0
#bind left mouse to cross button
#Right mouse to circle button
#Middle mouse to move
mouse.leftButton = joystick[j].getDown(14)
mouse.rightButton = joystick[j].getDown(13)
mouse.middleButton = joystick[j].getDown(19)
#Mouse movement using Gryoscope
# Only moves when the trigger is held down
mdX = -1 * filters.deadband(filters.delta(freePieIO[i].yaw),dz) * mag
mdY = -1 * filters.deadband(filters.delta(freePieIO[i].pitch),dz) * mag
if joystick[j].getDown(20):
mouse.deltaX = mdX
mouse.deltaY = mdY
if starting:
freePieIO[0].update += update
Now of course because the psmove doesn't use any positional tracking here it loses alignment with the aiming in game a lot, especially after changing direction. I can just align it back by aiming where the gun is and holding a button but I thought this was a bit cumbersome and changed this button in a toggle button. It works perfectly but the problem is that sometimes the aim is out of my view, and that makes it quite annoying when I have to search where my gun is.
What I would want is that when I press a button the aim moves to the centre, since you mostly aim where you looking, I know what you're thinking, why not align the headset with the crosshair, but the thing is, if you want to aim at something you will look at it first but the finer aiming you do with your eyeballs. It also isn't as fun as aiming with gun :)
So I thought it would work quite well, and then changed my code so when I press a button the mouse goes to the center of the screen. This is the code (it also has some other code to map buttons)
def update():
#define globals
#define constants
mag = 1000
dz = 0.005
i=0
j=0
#bind left mouse to trigger button
#Right mouse to circle
#Middle mouse to triangle
#up arrow key to square
#down arrow key to cross
#B Key to select ps button
#N key to select button
#Esc key to start button
mouse.leftButton = joystick[j].getDown(20)
mouse.rightButton = joystick[j].getDown(13)
mouse.middleButton = joystick[j].getDown(12)
keyboard.setKey(Key.UpArrow, joystick[j].getDown(15))
keyboard.setKey(Key.DownArrow, joystick[j].getDown(14))
keyboard.setKey(Key.B, joystick[j].getDown(16))
keyboard.setKey(Key.N, joystick[j].getDown(0))
keyboard.setKey(Key.Escape, joystick[j].getDown(3))
#Mouse movement using Gryoscope
# move button centers aim
mdX = -1 * filters.deadband(filters.delta(freePieIO[i].yaw),dz) * mag
mdY = -1 * filters.deadband(filters.delta(freePieIO[i].pitch),dz) * mag
mouse.deltaX = mdX
mouse.deltaY = mdY
if joystick[j].getDown(19):
import ctypes
ctypes.windll.user32.SetCursorPos(1000, 500)
if starting:
freePieIO[0].update += update
Now the command used to set the mouse to the center of the screen is the setcursor command, which works perfectly, only it doesn't work in game.
With doing some research I realized games don't use the mouse position of windows but rather use the raw data from the mouse driver, or something like that anyway.
So I think I can only solve this problem by using a code that remembers the mouse position when I press a button and then goes back to that position when I press another button. I can figure out the button mapping, the code for remembering and going back to a certain position I cannot.
It's either that or communicating with one of the drivers (mouse driver, directinput) which is even harder.
So if anyone would have any idea where I need to start I would be very happy :)
I think you could use pyautogui to do this.
import pyautogui
pyautogui.position() # gets mouse position returns pixel value E.G: (975,400)
pyautogui.moveTo(975,400) # move mouse to this location
You could put these in a function as needed and map it to a button as needed.
Make one that centers the mouse, grabs the position, place it in a variable for later use.
Just an idea.

Looping trouble in Sikuli/Python

Not sure where I'm going wrong:
mm = list(r.findAll(rButton))# find all rButtons on main screen
print len(mm) #check how many are detected
for x in range(0,len(mm)):
r.click(mm[x])
if(not r.exists(rButtonDisabled)):
print "this is a test"
r.wait(BeginTask,FOREVER)
r.click(BeginTask)
r.wait(rButton,FOREVER)
else: click(Cancel)
There are 2 screens. Let's call them main screen and screen2. On main screen there are identical buttons, rButton. I want to find all visible rButtons and then start clicking them. Ideally I want it to click on first rButton, which takes it to screen2, if the button on screen2 is disabled, click on cancel which moves us back to main screen, then go to the second rButton on main screen, which again takes us to screen2. Depending on rButtons on main screen, buttons on screen2 can be either disabled or enabled.
My code isn't working to that effect. Not sure where I'm going wrong.
I'm not sure how you've defined Region 'r', but as a default, Sikuli won't search outside the screen that is native to the OS. You need to first make sikuli find the other screen, then define the bounds of that screen.
As it appears now, you're searching Region 'r' no matter what screen you intended... You should define the two screens separately, or Sikuli won't know to switch screens to look for the button you want. For example, you can use the Screen class to define which screen is which--
numScreens = getNumberScreens()
r = SCREEN #in all caps, this is the reserve word for the whole screen native to the OS
#can also use r = Screen(0)
if numScreens > 0 #make sure second screen was counted by sikuli
r2 = Screen(1).getBounds()
else: raise the appropriate error
#Here's your code with some adjustments for multiple monitors
#on main screen
mm = list(r.findAll(rButton))# find all rButtons on main screen
print len(mm) #check how many are detected
for x in range(0,len(mm)):
r.click(mm[x])
#on secondary screen
if(not r2.exists(rButtonDisabled)):
print "this is a test"
r2.wait(BeginTask,FOREVER)
r2.click(BeginTask)
#back to main screen
r.wait(rButton,FOREVER)
#click the cancel button on secondary screen
else: r2.click(Cancel) # <-- the defining region was missing here in your original code
Here's the Sikuli documentation on multi-monitor environments

Categories

Resources