I am new to Python and using Zelle's graphics to create a game. I need the two while loops below to run at the same time, however I am running into difficulty. I tried nesting the while loops but then the horses and civilians would only move if the mouse was clicked, which I don't want. What I want is for the horses and civilians to always be moving, and the princess to move only when the mouse is clicked, and stop the game with a "game over" when she has saved 10 civilians.
# animation loop.
while True==True:
for horse in horseList:
if horse.leg.getX() > -187:
horse.move( -1, 20 )
else:
horse.move( 5, 28 )
for civilian in civiliansList:
if civilian.getX() < 800:
civilian.move( 20, 0 )
else:
civilian.move( -100, 0 )
while civiliansSaved != 10:
mouse = win.getMouse()
princess.move( mouse, civilianCounter)
civilianCounter = princess.move( mouse, civilianCounter)
# move is a method that will return an updated civilianCounter ( it is initially 0 and defined outside of the while loop ), depending on whether princess runs into civilians
else:
print( "Game over" )
win.getMouse()
win.close()
Just use checkMouse() instead of getMouse() inside your animation loop.
Simple as that, I think.
while civiliansSaved < 11:
for horse in horseList:
if horse.leg.getX() > -187
horse.move( -1, 20 )
else:
horse.move( 5, 28 )
for civilian in civiliansList:
if civilian.getX() < 800:
civilian.move( 20, 0 )
else:
civilian.move( -100, 0 )
mouse = win.checkMouse()
if mouse:
princess.move( mouse, civilianCounter)
civilianCounter = princess.move( mouse, civilianCounter)
print( "Game over" )
win.getMouse()
win.close()
Doco:
checkMouse() Similar to getMouse, but does not pause for a user click.
Returns the latest point where the mouse was clicked or None if the
window as not been clicked since the previous call to checkMouse or
getMouse. This is particularly useful for controlling simple animation loops.
Here is an example that should do what you want without the need for parallel processing (which is tricky in python):
while True: # you don't need to write True==True
for horse in horseList:
if horse.leg.getX() > -187:
horse.move( -1, 20 )
else:
horse.move( 5, 28 )
for civilian in civiliansList:
if civilian.getX() < 800:
civilian.move( 20, 0 )
else:
civilian.move( -100, 0 )
mouse = win.getMouse()
princess.move( mouse, civilianCounter)
civilianCounter = princess.move( mouse, civilianCounter)
if civiliansSaved >= 10: # check to see if 10 or more have been saved
break
print( "Game over" )
win.getMouse()
win.close()
What you want to do is to keep the game running until the civilianSaved counter is at least 10. But you can't do that in a separate loop from your main game loop, so it makes more sense to not stop the game until the count is at least 10. This if statement can be included in your main game loop.
Zelle's graphics is not an event driven graphics tool, which I think is what you are looking for. Maybe consider switching to tkinter which is part of the Python standard library. You can use a callback function to handle mouse events so that your app is free to do other things while it is waiting to process a mouse event.
tkinter has an after() feature which allows you to call a function after a specified period of time. You can create separate functions for civilian movement and horse movement and call these functions after a specified period of time (say 50 ms). This effectively simulates running the functions in parallel and allows horses and civilians to move at the same time, at least from the user's perspective. You would need another call to after() within the movement functions so that they are continuously called.
self.root.after(50, self.process_horse_movement)
self.root.after(50, self.process_civilian_movement)
Then at the end of each movement function:
def process_horse_movement():
...
self.root.after(50, self.process_horse_movement)
def process_civilian_movement():
...
self.root.after(50, self.process_civilian_movement)
Also, a minor point. Use while True: instead of while True == True:.
Related
I'm trying to write a program with Python to emulate an 'old' online game in which you drive a worm through the screen with some inputs from the keyboard.
import turtle
# Set screen and background
wn = turtle.Screen()
wn.title("Turn with Left and Right buttons your keyboard. Click on screen to EXIT.")
wn.bgcolor("black")
# Snake settings
snake = turtle.Turtle()
snake.color("purple")
snake.shape("circle")
snake.shapesize(0.25,0.25)
snake.pensize(5)
snake.speed(10)
t = 0
# Define Go loop, turn Left and Right
def go():
t = 0
while t < 1000:
snake.forward(1)
t += 1
def left():
snake.circle(1,8)
go()
def right():
snake.circle(1,-8)
go()
# Inputs and Exit on click
wn.onkey(right, "Right")
wn.onkeypress(right, "Right")
wn.onkey(left, "Left")
wn.onkeypress(left, "Left")
wn.listen()
wn.exitonclick()
turtle.done()
The problem here is that, after some moves, the program crashes returning:
RecursionError: maximum recursion depth exceeded while calling a Python object.
I'm still a beginner so i don't get what I'm doing wrong. How can I fix the error?
What you're experiencing is a faux recursion due to events stacking. However, your code design:
while t < 1000:
snake.forward(1)
t += 1
actually relies on event stacking! That is, you expect left and right commands to come in during the go() portion of the event handler which keeps the turtle moving. This is a problematic design. Let's rework your code using an event timer instead:
from turtle import Screen, Turtle
def go():
snake.forward(1)
screen.ontimer(go, 50)
def left():
screen.onkeypress(None, 'Left') # disable handler inside handler
snake.circle(1, 8)
screen.onkeypress(left, 'Left') # reenable handler
def right():
screen.onkeypress(None, 'Right')
snake.circle(1, -8)
screen.onkeypress(right, 'Right')
screen = Screen()
screen.title("Turn with Left and Right buttons your keyboard. Click on screen to EXIT.")
screen.bgcolor('black')
snake = Turtle()
snake.color('purple')
snake.shape('circle')
snake.shapesize(0.25)
snake.pensize(5)
snake.speed('fastest')
screen.onkeypress(left, 'Left')
screen.onkeypress(right, 'Right')
screen.listen()
go()
screen.exitonclick()
From testing, what is apparent is that if your go function hasn't completed, and you are still holding down a key, it's called again, meaning that now there's two go functions on the call stack (plus a bunch of other functions like eventfun and update). Because holding the key calls the go function many times a second, and the fact that the go function takes 8+ seconds to complete, you're filling up the call stack with calls to go, hence the crash. The call stack is just something like this repeated over and over:
update, __init__.py:1314
_update, turtle.py:561
_update, turtle.py:2663
_goto, turtle.py:3197
_go, turtle.py:1606
forward, turtle.py:1638
go, script.py:21
left, script.py:26
eventfun, turtle.py:700
__call__, __init__.py:1892
Even holding down left for just 3 seconds, my call stack grows to over 600, which is really high.
To fix this you could make it so that left or right cannot be called again while that key is still held down. That's one possible way.
I want to simulate mouse movement through Python, so that the perspective of the Unity game rotates accordingly.
I used pydirectinput.moveTo() to move mouse. Used pydirectinput.keyDown() and pydirectinput.keyUp() to input key.
It can work in 《Borderlands 2》. I can move forward and backward and turn the camera.
But it can't work in 《Aim Hero》, which is a unity game. I can also move forward and backward. The characters in the game move with my control. But the character's perspective doesn't move and shoot.
I execute the command to move the mouse, and the mouse actually does move. However, the mouse will move out of the game's window, and the in-game perspective does not move with it.
Initial suspicion is the difference between Unity games and DirectX games.
This is my code:
import pydirectinput
import pyautogui
import time
def func1():
# ------------this can work in borderlands 2 and Aim Hero(unity)
time.sleep(4)
pydirectinput.keyDown('w')
time.sleep(1)
pydirectinput.keyUp('w')
time.sleep(1)
pydirectinput.keyDown('d')
time.sleep(0.5)
pydirectinput.keyUp('d')
time.sleep(1)
# ------------
# ------------this all can't work in Aim Hero(unity)
pos = pyautogui.position()
pydirectinput.moveTo(pos.x + 100, pos.y) # can't work in borderlands 2
# pos = pyautogui.position()
pydirectinput.moveTo(pos.x + 200, pos.y) # can work in borderlands 2
time.sleep(0.5)
pydirectinput.click()
# -------------
def func2():
time.sleep(4)
# in borderlands 2:
# If the command to move the mouse is executed once, the in-game camera does not move.
# If the command to move the mouse is executed n times, the in-game camera will move n-1 times
# in Aim Hero(Unity Game):
# The mouse keeps moving and eventually moves out of the game window.
# But the in-game perspective has never changed.
for i in range(2):
pos = pyautogui.position()
pydirectinput.moveTo(pos.x + 10, pos.y)
# pydirectinput.click()
print(pos, '\n')
time.sleep(0.1)
if __name__ == '__main__':
print("Start!\n")
func1()
# func2()
print("Done.")
Can you try
# ------------this all can't work in Aim Hero(unity)
pos = pyautogui.position()
pydirectinput.moveTo(900, 600) # estimated center position of screen
time.sleep(1.5) #'cause this code working slow
pydirectinput.click()
pydirectinput.click()
pydirectinput.click()
# -------------
instead of
# ------------this all can't work in Aim Hero(unity)
pos = pyautogui.position()
pydirectinput.moveTo(pos.x + 100, pos.y) # can't work in borderlands 2
# pos = pyautogui.position()
pydirectinput.moveTo(pos.x + 200, pos.y) # can work in borderlands 2
time.sleep(0.5)
pydirectinput.click()
# -------------
EDIT
Okay I found the problem! It's not about Python, it's about Unity. Let me show you a video from my computer.
As far as I understand, if you want to use the pydirectinput.moveTo() method in Unity game, you should not lock your cursor. Be careful about your cursor state and use CursorLockMode.None instead of CursorLockMode.Locked. But it wont be effective for real game :( And I don't know any alternative for now
I can't figure out how to move sideways and jump at the same time, only one or the other.
I have tried asyncio and multithreading/multiprocessing and couldn't get it to work. I am all out of ideas and can't find anymore online. I also have another issue where I can jump and if I reach the apex of the jump and hold a or d I can move side to side floating.
class Player():
def __init__(self,health,boosts,height,width):
self.health = health
self.booosts = boosts
self.height = height
self.width = width
def jump():
global gravtest, press
press.remove("'w'")
gravtest = False
y = player[0].ycor()
for i in range (1, 10):
player[0].sety(y+(i*5))
time.sleep(0.05)
#while player[0]
gravtest = True
# def powers(self, boosts):
import turtle as t
import time, random
from pynput.keyboard import Key, Listener
t.ht()
press = []
gravtest = True
wn = t.Screen()
wn.title("Jump Man")
wn.bgcolor("white")
wn.screensize(250, 250)
wn.setup(width=1.0, height=1.0)
player = [t.Turtle(), Player(100, [], 25, 25)]
player[0].speed(0)
player[0].shapesize(0.5)
player[0].shape("square")
player[0].color("black")
player[0].up()
player[0].goto(0, 0)
floor = t.Turtle()
floor.speed(0)
floor.shape("square")
floor.shapesize(100)
floor.color("black")
floor.up()
floor.goto(0, -1150)
def gravity():
global gravtest
if gravtest == True:
grav = 0
while player[0].distance(floor) > 1007:
y = player[0].ycor()
player[0].sety(y + grav)
if grav > -5:
grav -= 1
player[0].sety(y + grav)
gravtest = False
if player[0].distance(floor) < 1045:
player[0].sety(-145)
def show(key):
global press
if not(format(key) in press):
press.append(format(key))
print(key)
def rem(key):
global press
if format(key) in press:
press.remove(format(key))
def move():
global press
while "'a'" in press:
player[0].setx(player[0].xcor()-2)
while "'d'" in press:
player[0].setx(player[0].xcor()+2)
if press == '\'s\'':
print()
gravity()
if "'w'" in press:
jump()
with Listener(on_press = show, on_release = rem) as listener:
while 1:
move()
Your problem with moving and jumping is that you have separate loops for each that try to handle one kind of movement of the movement in one place. That won't work properly if other stuff (like jumping while moving, or moving while falling under gravity) are supposed to be possible.
Instead, you need to have just one main loop. On each run of that loop, you should do one frame worth of movement of whatever kinds is appropriate (e.g. moving horizontally, falling or jumping). This may require some bookkeeping to keep track of how long the vertical acceleration from a jump lasts.
I'd make the main loop something like this:
on_the_ground = True
jumping = False
while True:
horizontal_movement() # this is always possible if the buttons are pressed
if on_the_ground or jumping:
jump() # can start or continue a jump if a button is pressed
else:
gravity() # fall, if you're not grounded or accelerating in a jump
handle_collisions_etc()
time.sleep(1/FRAMERATE)
Note, I've made some assumptions about the game logic you want. If you don't want to be able to move horizontally while in the air (as you can in many platformer games), you'll need to change this a little bit.
The jump function will need to keep track of how long you've been jumping for, since you probably want the player to be limited in how high they can go. I'm not exactly sure I understand the logic of your current code, so I'll leave it up to you to figure out exactly what to track.
A final suggestion is to move some of the global variables into the Player class. Even the turtle object you're using to draw things could be stored as an attribute on the instance you create.
In a program I am writing, turtle's onkey() method doesn't do exactly what I need. I know there are also onkeypress and onkeyrelease but neither do what I need. Is there a method for continuously running a function while a key is held? Such as
import turtle
num = 0
def add():
global num
num += 1
print(num)
turtle.onkey(add, "Up")
turtle.listen()
turtle.mainloop()
If you did something like this, for taking the keyboard input, onkey only responds once, and it only responds on the release of the key. Is there a method that would continuously run the function while it is being held down?
Below's a rough example that does what you describe using onkeypress() and onkeyrelease() in combination. Once you press 'Up' it will start counting whole seconds until you release that key. Then it will write to the screen the number of whole seconds the key was held:
from turtle import Turtle, Screen
FONT = ('Arial', 36, 'normal')
def add_start():
global seconds
print('pressed')
if seconds < 0:
turtle.undo() # remove previous time
seconds = 0
screen.ontimer(add, 1000)
def add():
global seconds
if seconds >= 0:
seconds += 1
screen.ontimer(add, 1000)
def add_stop():
global seconds
print('released')
turtle.write(seconds, align='center', font=FONT)
seconds = -1
screen = Screen()
turtle = Turtle(visible=False)
turtle.write(0, align='center', font=FONT)
seconds = -1
screen.onkeypress(add_start, 'Up')
screen.onkeyrelease(add_stop, 'Up')
screen.listen()
screen.mainloop()
Here's the catch: you need to turn off key repeat at the operating system level on your system. For example, On my Mac, in System Preferences / Keyboard, I'd do:
Otherwise the system will generate press and release events as part of standard key repeat.
I have Turtle commands in my code as you see below. I would like to put a timer on these commands inside an if statement. My current code just works like so:
'# WHEN USER PRESSES SPACE, LIGHT TURNS GREEN
'# WHEN LIGHT IS GREEN ARROW MOVES 50 PIXELS
player1.pendown()
player1.forward(50)
player2.pendown()
player2.forward(50)
The arrow really only moves once every time I press the space key.
I would like to turn that into a timer so the arrow would move every 60 milliseconds until the user presses the space key again.
I tried using an wn.ontimer but I keep on messing up something. Below is how the code looks now:
def advance_state_machine():
global state_num
if state_num == 0:
tess.forward(70)
tess.fillcolor("red")
state_num = 1
else:
tess.back(70)
tess.fillcolor("green")
state_num = 0
player1.pendown()
player1.forward(50)
player2.pendown()
player2.forward(50)
wn.onkey(advance_state_machine, "space")
wn.listen()
wn.mainloop()
Your description of your problem is inconsistent and your code is out of sync with your description. Here's what I believe you stated you wanted your code to do:
The turtle moves forward 1 step every 60 milliseconds and alternates between red/forward and green/backward whenever the space bar is pressed.
I believe this code implements that, a key event is used to detect the space bar and a timer event is used to keep the turtle in motion:
from turtle import Turtle, Screen, mainloop
def advance_state_machine():
global state_num
wn.onkey(None, 'space') # avoid overlapping events
if state_num > 0:
tess.fillcolor('green')
else:
tess.fillcolor('red')
state_num = -state_num
wn.onkey(advance_state_machine, 'space')
def advance_tess():
tess.forward(state_num)
wn.ontimer(advance_tess, 60)
wn = Screen()
tess = Turtle()
tess.fillcolor('red')
state_num = 1
wn.onkey(advance_state_machine, 'space')
wn.ontimer(advance_tess, 60)
wn.listen()
mainloop()
Make sure to click on the window to make it active before trying the space bar. Left unattended, the turtle will eventually go off screen.