I thought the canonical way to do animation with Python Turtle Graphics was to do something like
def animate():
# move stuff
ontimer(animate, delay)
Looking into the source code for turtle this implements tkinter after() in the background.
Can someone explain why in the program below the animation accelerates and decelerates dramatically when it is left running?
My theory is that since a new .after() id is created each time ontimer() is called, there are somehow multiple timers in existence which interfere with each other? Or maybe it's just a result of the randomness in the program? Or maybe the short interval between callbacks causes problems?
from random import *
from turtle import *
import math
class Vector(object):
def __init__(self, x = 0.0, y = 0.0):
self.x = x
self.y = y
def move(self, other):
""" Move vector by other (in-place)."""
self.__iadd__(other)
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
def rotate(self, angle):
"""Rotate vector counter-clockwise by angle (in-place)."""
radians = angle * math.pi / 180.0
cosine = math.cos(radians)
sine = math.sin(radians)
x = self.x
y = self.y
self.x = x * cosine - y * sine
self.y = y * cosine + x * sine
ant = Vector(0, 0)
aim = Vector(2, 0)
def wrap(value):
"Wrap value around -200 and 200."
if value > 200:
value = -200
elif value < -200:
value = 200
return value
def draw():
"Move ant and draw screen."
ant.move(aim)
ant.x = wrap(ant.x)
ant.y = wrap(ant.y)
aim.move(random() - 0.5)
aim.rotate(random() * 10 - 5)
clear()
goto(ant.x, ant.y)
dot(10)
if running:
ontimer(draw, 50)
setup(420, 420, 370, 0)
hideturtle()
tracer(False)
up()
running = True
draw()
done()
My belief is that your animation accelerates and decelerates because you use tracer() but fail to do an explicit update(). The tracer() function turns off animation but some turtle operations do an implicit update() as a side effect. Since you didn't do an explicit update() you're only getting random updates caused by those side effects.
Below I've added an explicit update() and simplified the code to make the turtle itself the moving object, rather than stamping and clearing. (BTW, if you save the result of stamp() you can ask it to clear itself.)
I've also switched from a circle to a turtle cursor image and added in logic to set the heading to the direction of motion:
from random import random
from turtle import Screen, Turtle, Vec2D
from math import pi, cos, sin
class Vector(object):
def __init__(self, x=0.0, y=0.0):
self.x = x
self.y = y
def move(self, other):
""" Move vector by other (in-place)."""
self.__iadd__(other)
self.wrap()
def __iadd__(self, other):
if isinstance(other, Vector):
self.x += other.x
self.y += other.y
else:
self.x += other
self.y += other
def rotate(self, degrees):
""" Rotate vector counter-clockwise by angle (in-place). """
radians = degrees * pi / 180.0
cosine = cos(radians)
sine = sin(radians)
x = self.x
y = self.y
self.x = x * cosine - y * sine
self.y = y * cosine + x * sine
def position(self):
return Vec2D(self.x, self.y)
def wrap(self):
""" Wrap value around -200 and 200. """
x = self.x
y = self.y
if x > 200:
self.x = -200
elif x < -200:
self.x = 200
if y > 200:
self.y = -200
elif y < -200:
self.y = 200
def draw():
""" Move ant and draw screen. """
ant.move(aim)
position = ant.position()
turtle.setheading(turtle.towards(position))
turtle.setposition(position)
screen.update()
aim.move(random() - 0.5)
aim.rotate(random() * 10 - 5)
screen.ontimer(draw, 75)
screen = Screen()
screen.setup(420, 420)
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.shape('turtle')
turtle.shapesize(0.5)
turtle.penup()
turtle.showturtle()
ant = Vector(0, 0)
aim = Vector(2, 0)
draw()
screen.mainloop()
Related
I am writing a program for an exercise that simulates a projectile launch while tracking down every x and y position per second.
While the program technically works, and it does track down every x and y position, the text that tracks down the x and y position flickers for each step.
I understand the problem is that I am using the .clear() function (which, as its name implies, clears the text generated by the turtle). However, I believe that the program only functions properly with it.
Here is my code:
"""Program to track specific location of turtle (for every step)"""
from turtle import *
from math import *
def cball_graphics():
leonardo = Turtle()
leonardo.color("dark blue")
leonardo.shape("turtle")
leonardo.speed(1)
return leonardo
def show_position():
pos = Turtle()
pos.color("white")
pos.goto(30, -50)
pos.color("red")
return pos
class cannon_ball:
def __init__(self, angle, vel, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = vel * cos(theta)
self.y_vel = vel * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def get_x(self):
return self.x_pos
def get_y(self):
return self.y_pos
def variables():
angle = 55
vel = 10
height = 100
time = .01
return cannon_ball(angle, vel, height, time)
def main():
leonardo = cball_graphics()
"""pos is a variable that writes the position on the screen using x and y pos"""
pos = show_position()
pos.hideturtle()
projectile = variables()
while projectile.y_pos >= 0:
pos.write(f"{'%0.0f' % projectile.x_pos}, {'%0.0f' % projectile.y_pos}")
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
pos.clear()
main()
I believe that the tracer() and update() methods of the turtle screen will solve your problem. They allow you to write to the backing store and then copy it to the screen once it's the way you want. Particularly useful for eliminating flicker:
from turtle import Screen, Turtle
from math import pi, sin, cos
FONT = ('Arial', 18, 'normal')
def cball_graphics():
leonardo = Turtle()
leonardo.color('dark blue')
leonardo.shape('turtle')
return leonardo
def show_position():
pos = Turtle()
pos.hideturtle()
pos.goto(30, -50)
pos.color('red')
return pos
class cannon_ball:
def __init__(self, angle, velocity, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = velocity * cos(theta)
self.y_vel = velocity * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def freefall():
if projectile.y_pos >= 0:
pos.clear()
pos.write("({:0.0f}, {:0.0f})".format(projectile.x_pos, projectile.y_pos), font=FONT)
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
screen.update()
screen.ontimer(freefall)
variables = {
'angle': 55,
'velocity': 10,
'height': 100,
'time': 0.01,
}
screen = Screen()
screen.tracer(False)
leonardo = cball_graphics()
# pos is a turtle that writes the position on the screen using x and y pos
pos = show_position()
projectile = cannon_ball(**variables)
freefall()
screen.mainloop()
I also made several style changes to the code while I was at it...
i am trying to make a game where you can shoot bullets to kill emojis. However, i can't manage to figure out how to stop spamming the space key to shoot bullets. If you keep on spamming, the game would be too easy. I am not exactly sure if I should use the sleep command or something else.Please help! thanks!
Here is my code:
# import everything from turtle
from turtle import *
import random
import math
#create a link to the object (creates the environment)
screen = Screen()
speed1 = 1.3
ht()
amountOfEmojis = 11
#set a boundary for screen, if touches end, goes to the other side
screenMinX = -screen.window_width()/2
screenMinY = -screen.window_height()/2
screenMaxX = screen.window_width()/2
screenMaxY = screen.window_height()/2
#establish important data for screen environment
screen.setworldcoordinates(screenMinX,screenMinY,screenMaxX,screenMaxY)
screen.bgcolor("black")
#turtle setup
penup()
ht()
speed(0)
goto(0, screenMaxY - 50)
color('white')
write("Welcome to Emoji Run!", align="center", font=("Courier New",26))
goto(0, screenMaxY - 70)
write("Use the arrow keys to move and space to fire. The point of the game is to kill the emojis", align="center")
goto(0, 0)
color("red")
emojis = ["Poop_Emoji_7b204f05-eec6-4496-91b1-351acc03d2c7_grande.png", "1200px-Noto_Emoji_KitKat_263a.svg.png",
"annoyningface.png", "Emoji_Icon_-_Sunglasses_cool_emoji_large.png"]
class Bullet(Turtle):
#constructor, object for a class, pass in information
def __init__(self,screen,x,y,heading):
#create a bullet
Turtle.__init__(self)#clones bullet
self.speed(0)
self.penup()
self.goto(x,y)
self.seth(heading)#pointing to itself
self.screen = screen
self.color('yellow')
self.max_distance = 500
self.distance = 0
self.delta = 20
self.shape("bullet")
#logic to move bullet
def move(self):
self.distance = self.distance + self.delta#how fast it's going to move
self.forward(self.delta)
if self.done():
self.reset()
def getRadius(self):
return 4#collision detection helper function
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def done(self):
return self.distance >= self.max_distance # append to list
class Asteroid(Turtle):
def __init__(self,screen,dx,dy,x,y,size,emoji):#spawn asteroid randomly
Turtle.__init__(self)#clone itself
self.speed(0)
self.penup()
self.goto(x,y)
self.color('lightgrey')
self.size = size
self.screen = screen
self.dx = dx
self.dy = dy
r = random.randint(0, len(emoji) - 1)
screen.addshape(emojis[r])
self.shape(emojis[r])
#self.shape("rock" + str(size)) #sets size and shape for asteroid
def getSize(self):#part of collision detection
return self.size
#getter and setter functions
def getDX(self):
return self.dx
def getDY(self):
return self.dy
def setDX(self,dx):
self.dx = dx
def setDY(self,dy):
self.dy = dy
def move(self):
x = self.xcor()
y = self.ycor()
#if on edge of screen. go to opposite side
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
def blowUp(self):
self.goto(-300,0)#function that makes something go off the screen
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
def __init__(self,screen,dx,dy,x,y):
Turtle.__init__(self)
self.speed(0)
self.penup()
self.color("white")
self.goto(x,y)
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
self.shape("turtle")
def move(self):
x = self.xcor()
y = self.ycor()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x,y)
#logic for collision
def powPow(self, asteroids):
dasBullets = []
for bullet in self.bullets:
bullet.move()
hit = False
for asteroid in asteroids:
if intersect(asteroid, bullet):#counts every asteroid to see if it hits
asteroids.remove(asteroid)
asteroid.blowUp()
bullet.blowUp()
hit = True
if (not bullet.done() and not hit):
dasBullets.append(bullet)
self.bullets = dasBullets
def fireBullet(self):
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
def fireEngine(self):#how turtle moves
angle = self.heading()
x = math.cos(math.radians(angle))
y = math.sin(math.radians(angle))
self.dx = self.dx + x#how it rotates
self.dy = self.dy + y
self.dx = self.dx / speed1
self.dy = self.dy / speed1
#extra function
def turnTowards(self,x,y):
if x < self.xcor():
self.left(7)
if x > self.xcor():
self.right(7)
def getRadius(self):
return 10
def getDX(self):
return self.dx
def getDY(self):
return self.dy
#collision detection
def intersect(object1,object2):
dist = math.sqrt((object1.xcor() - object2.xcor())**2 + (object1.ycor() - object2.ycor())**2)
radius1 = object1.getRadius()
radius2 = object2.getRadius()
# The following if statement could be written as
# return dist <= radius1+radius2
if dist <= radius1+radius2:
return True
else:
return False
#adds object to screen
screen.register_shape("rock3",((-20, -16),(-21, 0), (-20,18),(0,27),(17,15),(25,0),(16,-15),(0,-21)))
screen.register_shape("rock2",((-15, -10),(-16, 0), (-13,12),(0,19),(12,10),(20,0),(12,-10),(0,-13)))
screen.register_shape("rock1",((-10,-5),(-12,0),(-8,8),(0,13),(8,6),(14,0),(12,0),(8,-6),(0,-7)))
screen.register_shape("ship",((-10,-10),(0,-5),(10,-10),(0,10)))
screen.register_shape("bullet",((-2,-4),(-2,4),(2,4),(2,-4)))
#ship spawn exactly the middle everytime
ship = SpaceShip(screen,0,0,(screenMaxX-screenMinX)/2+screenMinX,(screenMaxY-screenMinY)/2 + screenMinY)
#randomize where they spawn
asteroids = []
for k in range(amountOfEmojis):
dx = random.random() * 6 - 3
dy = random.random() * 6 - 3
x = random.randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random.random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(screen,dx,dy,x,y,random.randint(1,3), emojis)
asteroids.append(asteroid)
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = random.randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship,asteroid):
write("You Got Killed :(",font=("Verdana",25),align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
color('green')
write("You Killed the Emojis!!",font=("Arial",30),align="center")
ht()
if not gameover:
screen.ontimer(play, 30)
bullets = []
#controls
def turnLeft():
ship.left(7)
def turnRight():
ship.right(7)
def go():
ship.fireEngine()
def fire():
ship.fireBullet()
ht()
screen.tracer(0);
screen.onkey(turnLeft, 'left')
screen.onkey(turnRight, 'right')
screen.onkey(go, 'up')
screen.onkey(fire, 'space')
screen.listen()
play()
Thanks!
You could try something like this:
import time
FIRE_DELAY = 0.1 # number of seconds between bullets
LAST_FIRE = 0
def fire():
t = time.time()
if t - LAST_FIRE > FIRE_DELAY:
LAST_FIRE = t
ship.fireBullet()
I just checked here to make sure this question was allowed, and it seems that it is so here I go:
I am currently making a 2D physics engine as a small project. I have a class called circle which has properties such as radius, rotation, position, and velocity:
class circle():
def __init__(self, radius = 10, r = 0.0, x = 0, y = 0, Vr = 0, Vx = 0, Vy = 0):
self.radius = radius
self.r = r
self.x = x
self.y = y
self.Vr = Vr
self.Vx = Vx
self.Vy = Vy
The class has a method called CheckCollisions(), which checks if the distance between its centre and another circle's centre is less than the sum of their radii:
def CheckCollisions(self):
for c in circles:
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
if distance < self.radius + c.radius:
print('!')
else:
print('')
The idea is that on detecting the collision, forces can be applied as vectors to each object as a response to the impact.
When my code runs, I see constant exclamation marks appearing in the shell, despite the circles not colliding. What is causing this? Perhaps something in my calculation of distance is incorrect?
Full code:
import pygame, random, math
from pygame.locals import*
# set up pygame window
(width, height) = (1000, 800)
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption('Impulse Physics v0.1 BETA')
pen = pygame.image.load('Pen.png').convert()
background = (0, 0, 0)
class circle():
def __init__(self, radius = 10, r = 0.0, x = 0, y = 0, Vr = 0, Vx = 0, Vy = 0):
self.radius = radius
# position and rotation
self.r = r
self.x = x
self.y = y
# velocity
self.Vr = Vr
self.Vx = Vx
self.Vy = Vy
def CheckCollisions(self):
for c in circles:
# use pythagoras to find direct distance between centres
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
if distance < self.radius + c.radius:
print('!')
else:
print('')
def Move(self):
# apply slight "air resistance"
self.Vx = self.Vx * 0.9999
# gravity. REMEMBER y axis is inverted in pygame!
self.Vy = self.Vy + 0.15
# move object
self.x = self.x + self.Vx
self.y = self.y + self.Vy
self.r = self.r + self.Vr
self.CheckCollisions()
# check if colliding with the sides of the window
if self.y + self.radius > height:
self.Vy = self.Vy * -0.98
self.y = self.y + self.Vy
if (self.x + self.radius > width) or (self.x - self.radius < 0):
self.Vx = self.Vx * -0.98
self.x = self.x + self.Vx
def Render(self):
penX = self.x
penY = self.y
penR = self.r
screen.blit(pen, (penX, penY))
# draw the radius of the circle
for counter in range(self.radius):
penX = self.x + (math.sin(penR) * counter)
penY = self.y - (math.cos(penR) * counter)
screen.blit(pen, (penX, penY))
# draw the circumference of the circle
for counter in range(self.radius * 20):
penR = counter * (360 / self.radius * 20)
penX = self.x + (math.sin(penR) * self.radius)
penY = self.y + (math.cos(penR) * self.radius)
screen.blit(pen, (penX, penY))
circles = []
#create objects here
c1 = circle(100, 0, 400, 400, 0.1, 4)
circles.append(c1)
c2 = circle(50, 0, 50, 50, 0.08, 10)
circles.append(c2)
c3 = circle(10, 0, 300, 200, 0.02, -3)
circles.append(c3)
running = True
while running:
screen.fill(background)
for obj in circles:
obj.Move()
obj.Render()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
pygame.quit()
In short: a circle collides with itself. The reason is simply that the circles list contains [c1,c2,c3] and thus checks are done against the circles themselves.
Now for c1 you check whether there is a collision so it iterates over the circles and the first thing it checks is whether it collides with itself (since c1 is the first element in the list). And obviously it does (your test looks if the distance is less than the sum of the circles radiuses, but the distance is zero). If none of the circles collide, there will thus be three exclamation marks (one for each circle).
You can resolve this error by performing a reference equality check first:
def CheckCollisions(self):
for c in circles:
if c is not self:
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
#...
I am making some kind of a ripoff of turtle module for drawing.
This is what i have so far:
class Turtle:
def __init__(self):
self.x = drawer.maxX / 2
self.y = drawer.maxY / 2
self.angle = 0
self.pen_active = True
self.pause = 0
self.body = drawer.circle(0, 0, 5, drawer.green, 3)
self.head = drawer.circle(0, 0, 3, drawer.green, 3)
self.length = 0
self.update()
def set_pause(self, n):
self.pause = n
def no_pause(self):
self.set_pause(0)
def hide(self):
self.body.hide()
self.head.hide()
def show(self):
self.body.show()
self.head.show()
def update(self):
self.body.setPos(self.x, self.y)
phi = radians(90 - self.angle)
self.head.setPos(self.x + 5 * cos(phi), self.y + 5 * sin(phi))
drawer.wait(self.pause)
def pen_up(self):
self.pen_active = False
def pen_down(self):
self.pen_active = True
def fly(self, x, y, angle):
self.x = x
self.y = y
self.angle = angle
self.update()
def forward(self, a):
phi = radians(90 - self.angle)
nx = self.x + a * cos(phi)
ny = self.y + a * sin(phi)
if self.pen_active:
drawer.line(self.x, self.y, nx, ny)
self.x = nx
self.y = ny
self.update()
def backward(self, a):
self.forward(-a)
def turn(self, phi):
self.angle += phi
self.update()
def left(self):
self.turn(-90)
def right(self):
self.turn(90)
def length(self):
pass
Now i am trying to write a method to compute the length of the line drawn but i don't know how to do it. The line is drawn when the pen is down and the turtle is going forwards or backwards. If the turtle flys or turns around the line is not drawn. Any ideas.
And thank your for all your help.
If I understand correctly, you want to maintain a record of the length of all the forward and backward movements your Turtle makes from the time it is created.
This is pretty easy, you just need to be a bit careful with your variable names. Currently you're using length both as an instance variable (to hold the running length sum) and as a method name. The former will shadow the latter, so you probably want two different names.
If we rename the instance variable to _length, you can make it work like this:
class Turtle:
def __init__(self):
# unmodified stuff omitted
self._length = 0
# unmodified methods skipped
def forward(self, a):
# all the current stuff
self._length += abs(a) # absolute value to count backing up as movement
def length():
return self._length
You may decide you don't need the trivial length method, in which case you can go back to using self.length as your instance variable name (and just access the attribute when you want the value).
Extend the projectile and tracker to implement the following graphics game:
a. There is square box of a given dimension (choose one reasonable size box) in which
there is a projectile which randomly bounces of the four walls.
b. When a projectile hits a wall, it is launched again by randomly selecting an angle in
the allowed range and a random velocity in a given range. For example, if the
projectile hits the bottom boundary, it selects a random angle in the range 0 to 180
degrees; if it hits the right vertical wall then, it selects random angle between 90 and
270 degrees; and so on.
c. Instead of drawing the circle, move the circle and also fill it with some color.
d. Extend to two projectiles.
Don't ask user the parameters. Use a set of parameters that you think the best.
So I am having trouble trying to find how to make two cballs (the projectiles) bounce from wall to wall. I figured how to bounce them off the bottom wall of the window but it doesn't stop at the right wall and bounce from there. Not only that I have no idea on how to make the balls follow instead of continuously printing circles to Track the movement of the balls. I use ".undraw()" but its very jumpy and not smooth at all.
I would appreciate the help very much! The assignment is due tomorrow and this is what i have:
# projectilebounce.py
# Projectile hits a wall and again is launced
from math import sin, cos, radians
from graphics import *
class Projectile:
def __init__(self, angle, velocity, height):
self.xpos = 0.0
self.ypos = height
theta = radians(angle)
self.xvel = velocity * cos(theta)
self.yvel = velocity * sin(theta)
def update(self, time):
self.xpos = self.xpos + time * self.xvel
yvel1 = self.yvel - 9.8 * time
self.ypos = self.ypos + time * (self.yvel + yvel1) / 2.0
self.yvel = yvel1
def getY(self):
return self.ypos
def getX(self):
return self.xpos
def reset(self, angle, velocity, height):
self.xpos = self.getX()
self.ypos = height
theta = radians(angle)
self.xvel = velocity * cos(theta)
self.yvel = velocity * sin(theta)
class Tracker:
def __init__(self, window, objToTrack):
self.win = window
self.obj = objToTrack
def update(self):
a = Circle( Point(self.obj.getX(), self.obj.getY()), 5)
a.draw(self.win)
a.setFill("Black")
a.undraw()
def getInputs():
a1 = 60
a2 = 30
v1 = 60
v2 = 60
h1 = 10
h2 = 10
t = 0.1
return a1, a2, v1, v2, h1, h2, t
def main():
print("This program graphically depicts the flight of a cannonball.\n")
win = GraphWin("TRACKER", 600, 500)
win.setCoords(0.0, 0.0, 600, 500)
angle1, angle2, vel1, vel2, h01, h02, time = getInputs()
cball1 = Projectile(angle1, vel1, h01)
cball2 = Projectile(angle2, vel2, h02)
tracker_cball1 = Tracker(win, cball1)
tracker_cball2 = Tracker(win, cball2)
while True:
if cball1.getY() <= 0: #<---ball 1
cball1.reset(60, 60, 10)
else:
cball1.update(time)
tracker_cball1.update()
if cball2.getY() <= 0: #<---ball 2
cball2.reset(30, 60, 10)
else:
cball2.update(time)
tracker_cball2.update()
if __name__ == '__main__' : main()
Thank you!, I am a big noob at coding at the moment. Much appreciated!