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.
Related
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.
I could do with some help on how to make a lives counter in Python turtle graphics.
My code:
def draw_lives():
global lives
lives = turtle.Turtle()
lives.penup
lives.hideturtle
lives.goto(-200, 400)
while True:
lives.write("Lives: " + str(lives), font=("Arial", 50, "normal"))
if lives > 0:
lives.write("You have lost all lives. Try again.", font=("Arial", 50, "normal"))
break
I thought on making my lives counter a Turtle and not just a random counter somewhere (which would actually sound better).
Furthermore did I get the error for my if lives > 0: that the > is not supported between instances of Turtle and int.
Can someone help?
Your code is badly constructed -- let's look at specifics. The primary issue is that you use the same variable name, lives, for both the counter and the turtle that displays the counter. Name them differently. If you do so, you don't need:
global lives
The next problem is basic Python:
lives.penup
lives.hideturtle
These are method calls so they should be:
lives.penup()
lives.hideturtle()
Finally your while True: has no business here, or anywere in a turtle event-driven program. And you're missing a line of code or two in your if statement.
Let's rework your code so that it updates the value of the lives counter on the screen:
from turtle import Screen, Turtle
FONT = ("Arial", 24, "normal")
def draw_lives():
lives_pen.clear()
if lives > 0:
lives_pen.write("Lives: " + str(lives), font=FONT)
else:
lives_pen.write("You have lost all lives. Try again.", font=FONT)
lives = 5
lives_pen = Turtle() # this turtle is only used to update lives counter display
lives_pen.hideturtle()
lives_pen.penup()
lives_pen.goto(-200, 300)
lives_pen.write("Lives: " + str(lives), font=FONT)
if __name__ == '__main__':
from time import sleep
# test code
screen = Screen()
for _ in range(lives + 1):
draw_lives()
sleep(1)
lives -= 1
screen.exitonclick()
The __main__ section is just test code to confirm that draw_lives() works the way we want -- so toss it.
Utility turtles like lives_pen should only be created once, not every time you need to update the counter as they are global entities and are not garbage collected when the function exits.
It's a bad practice to use global in your code. Instead, you can create a custom attribute for your turtles.
It's super easy, barely an inconvenience:
from turtle import Turtle
pen = Turtle()
pen.lives = 5 # Here, the lives attribute is created
You may even do the same for the font, though it may be unnecessary:
pen.font = ("Arial", 30, "normal")
If loosing lives is the only situation where the life count will be updated, do not keep rewriting them in the loop
(unless, of course, the something gets in the way of the text, and you want the text to be displayed on top),
only rewrite it when a life is lost.
We can redraw and update the lives in a function like this:
def update_lives():
pen.clear()
if pen.lives:
pen.write(f"Lives: {pen.lives}", font=pen.font)
else:
pen.write("You have lost all lives. Try again.", font=pen.font)
pen.lives -= 1 # Remove this line and this becomes your typical text updater
To see this in action, I implemented a demo where a life is lost whenever the user presses the SPACE bar:
from turtle import Turtle, Screen
wn = Screen()
def update_lives():
pen.clear()
if pen.lives:
pen.write(f"Lives: {pen.lives}", font=pen.font)
else:
pen.write("You have lost all lives. Try again.", font=pen.font)
pen.lives -= 1
pen = Turtle(visible=False)
pen.penup()
pen.goto(-300, 200)
pen.lives = 5
pen.font = ("Arial", 30, "normal")
update_lives()
wn.listen()
wn.onkey(update_lives, 'space')
With the above code, when the user reaches 0, pressing SPACE again will make the function proceed to display negative values.
To fix that, for your main game loop, use while pen.lives to tell python to only keep looping until the number of lives remaining is greater than 0:
while pen.lives:
# Your game code here
wn.update()
Output:
Cannot implement onkeyrelease() from Python's turtle module. Please advise.
Error message: 'module' object has no attribute 'onkeyrelease'.
Tried replacing turtle.onkeyrelease(stay, 'd') with wn.onkeyrelease(stay, 'd') to no avail.
import turtle
speed = 0
wn = turtle.Screen()
wn.tracer(0)
box = turtle.Turtle()
box.shape('square')
box.penup()
def move_right():
global speed
speed = 2
def stay():
global speed
speed = 0
turtle.listen()
turtle.onkey(move_right, 'd')
turtle.onkey(stay, 's')
turtle.onkeyrelease(stay, 'd')
while True:
wn.update()
box.setx(box.xcor() + speed)
My guess, based on the error message, is that you are running Python 2 and onkeyrelease() is a Python 3 method. Even so:
An artifact of the transition from Python 2 to Python 3, onkey() and onkeyrelease() are synonyms. What you probably want is onkeypress() and onkeyrelease(). Even so:
That said, it's iffy whether trying to do different things on the key press and release is going to work. On my system, both press and release are triggered by a key press. Your results, due to OS perhaps, might vary.
You may be better off using two keys, 'd' to start the motion, 's' to stop it:
from turtle import Screen, Turtle, mainloop
speed = 0
def move_faster():
global speed
speed = 2
def stay():
global speed
speed = 0
def move():
box.forward(speed)
screen.update()
screen.ontimer(move)
screen = Screen()
screen.tracer(False)
box = Turtle()
box.shape('square')
box.penup()
screen.onkey(stay, 's')
screen.onkey(move_faster, 'd')
screen.listen()
move()
mainloop()
This code should work under Python 2 and Python 3.
I am attempting to create a study game where a question will fall downward and you type in the answer, however I don't understand how to record key inputs without stopping the motion of the lowering question.
to put simply, I want to be able to lower the question and use my keyboard to input at the same time without stopping the motion.
text_letter = 0
def text_insert(answer):
global text_letter
print("hello")
text_letter += 1
def text_lower(question,answer):
global text_letter
text.penup()
text.goto(random.randint(-250,250),355)
text.pendown()
text.color("white")
text.write("Start", font=("Arial", 20, "normal"))
x,y = text.pos()
delay = .01
wn.textinput("Answer", "Answer:")
turtle.listen()
turtle.onkey(text_insert(answer),answer[text_letter])
while y > -355:
time.sleep(delay)
y -= 1
text.goto(x,y)
text.write(question, font=("Arial", 20, "normal"))
text.clear()
This may be a more complicated answer than you anticipated: If you leave off the second, key, argument to the turtle's onkeypress() function, it will call your key press handler code when any key is pressed. It simply won't tell you which key!
We can work around this poor design by rewriting the underlying code to pass tkinter's event.char to the turtle's event handler in the case where no key has been set.
Once that's done, we can use a turtle timed event to lower the question from the top of the window while the user's typed input shows up at the bottom of the window.
Here's my one question simulation of such to help get you started:
from turtle import Screen, Turtle
from functools import partial
FONT_SIZE = 20
FONT = ("Arial", FONT_SIZE, "normal")
def text_lower(question):
question_turtle.forward(1)
question_turtle.clear()
question_turtle.write(question, align="center", font=FONT)
screen.update()
if question_turtle.ycor() - answer_turtle.ycor() > FONT_SIZE:
screen.ontimer(lambda: text_lower(question), 15)
else:
question_turtle.clear()
def _onkeypress(self, fun, key=None):
if fun is None:
if key is None:
self.cv.unbind("<KeyPress>", None)
else:
self.cv.unbind("<KeyPress-%s>" % key, None)
else:
if key is None:
def eventfun(event):
fun(event.char)
self.cv.bind("<KeyPress>", eventfun)
else:
def eventfun(event):
fun()
self.cv.bind("<KeyPress-%s>" % key, eventfun)
def display_character(character):
global answer
if not character:
return
if ord(character) == 13:
answer_turtle.clear()
answer_turtle.setx(0)
# do something with answer and then:
answer = ""
else:
answer += character
answer_turtle.write(character, move=True, font=FONT)
screen.update()
screen = Screen()
screen.tracer(False)
screen._onkeypress = partial(_onkeypress, screen)
question_turtle = Turtle(visible=False)
question_turtle.penup()
question_turtle.setheading(270)
question_turtle.sety(screen.window_height()/2 - FONT_SIZE)
answer_turtle = Turtle(visible=False)
answer_turtle.penup()
answer_turtle.sety(FONT_SIZE - screen.window_height()/2)
answer = ""
screen.onkeypress(display_character)
screen.listen()
text_lower("What is the air-speed velocity of an unladen swallow?") # A: An African or European swallow?
screen.mainloop()
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.