Related
I am a novice to Python trying to make the game Pong. I have created a Paddle class with the Turtle Graphics module, but I can't get the paddle to move. I just want to start with one direction and then down shouldn't be too hard from there. Can anyone see what I am doing wrong with my method?
from turtle import Turtle
COORDINATES = [(350, 20), (350, 0), (350, -20)]
X_COORDINATES = [350, 350, 350]
Y_COORDINATES = [20, 0, -20]
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.paddle = []
self.create_paddles()
self.coordinate_number = 0
def create_paddles(self):
for coordinates in COORDINATES:
self.paddle_block = Turtle(shape='square')
self.paddle_block.goto(coordinates)
self.paddle_block.color('white')
self.paddle.append(self.paddle_block)
def w(self):
global Y_COORDINATES
Y_COORDINATES = [coordinate + 100 for coordinate in Y_COORDINATES]
for self.paddle_block in self.paddle:
self.paddle_block.goto(X_COORDINATES[self.coordinate_number], Y_COORDINATES[self.coordinate_number])
self.coordinate_number += 1
self.coordinate_number = 0
I tried to iterate through the y-coordinates and add to each of them with my function. From there, I tried to iterate through each paddle block and move it's current location to a new one, taking in the newly updated y coordinate. I expect movement, but I am not seeing any movement whatsoever.
This isn't the usual approach to this problem, but I can see why it might be advantageous. Your primary issue seems to be not being able to determine what should be global, what should be local, and what should be a property. Let's make this work to demonstrate the use of all three:
from turtle import Screen, Turtle
COORDINATES = [(350, 20), (350, 0), (350, -20)]
class Paddle(Turtle):
def __init__(self):
super().__init__()
self.paddle = []
self.coordinates = list(COORDINATES) # make copy
self.create_paddles()
def create_paddles(self):
for coordinate in self.coordinates:
paddle_block = Turtle(shape='square', visible=False)
paddle_block.penup()
paddle_block.color('white')
paddle_block.goto(coordinate)
paddle_block.showturtle()
self.paddle.append(paddle_block)
def move_up(self):
self.coordinates = [(x, y + 10) for x, y in self.coordinates]
for coordinate_number, paddle_block in enumerate(self.paddle):
paddle_block.goto(self.coordinates[coordinate_number])
def move_down(self):
self.coordinates = [(x, y - 10) for x, y in self.coordinates]
for coordinate_number, paddle_block in enumerate(self.paddle):
paddle_block.goto(self.coordinates[coordinate_number])
screen = Screen()
screen.bgcolor('black')
paddle_1 = Paddle()
screen.onkey(paddle_1.move_up, 'w')
screen.onkey(paddle_1.move_down, 's')
screen.listen()
screen.mainloop()
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()
I am trying to have a circle that, when clicked, moves somewhere else on the screen. However, when I click the circle, nothing happens.
#IMPORT STUFF
import pyglet as pg
from random import randint
mouse = pg.window.mouse
#VARS
window = pg.window.Window(width = 640, height = 480)
score = 0
circleImg = pg.image.load("circle.png")
circle = pg.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
text = pg.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if x == circle.x and y == circle.y:
circle.x = randint(1, window.width)
circle.y = randint(1, window.height)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pg.app.run()
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
mouse = pyglet.window.mouse
#VARS
window = pyglet.window.Window(width = 640, height = 480)
score = 0
#circleImg = pyglet.image.load("circle.png")
#circle = pyglet.sprite.Sprite(circleImg, randint(1, window.width), randint(1, window.height))
circle = Circle(x=50, y=50)
text = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
#DETECT MOUSE PRESS ON CIRCLE
#window.event
def on_mouse_press(x, y, button, modifiers):
if circle.click(x, y):
print('Clicked in circle')
circle.x = randint(0, window.width - 10)
circle.y = randint(0, window.height - 10)
#window.event
def on_draw():
window.clear()
text.draw()
circle.draw()
pyglet.app.run()
A short description of what this does is it creates a custom class called Circle that inherits the Sprite class. It loads the circle.png as a texture with a alpha channel that gets blended by the GL library.
We add a custom function called click that checks if the lowest x,y coordinates are higher than the circles lowest x,y, then we check if the cursor is below x+width and same for y of the image region.
If that's the case, we return the circle sprite class as a True value in case we want to use the sprite.
Future enhancements:
You should draw the circle using gl functions, hence why I've defined radiance in the class definitions. However radiance here is never used, it's a placeholder for the future.
This is so you can use math to defined if you actually clicked within the circle, but this is beyond my scope of quick answers.. I would have to do a lot of debugging myself in order to get the math to add up (it's not my strong side).
What makes it work now is that we use the image width, height, x and y data to crudely check if we're within the image, aka "the circle".
trying to draw over sprite or change picture pyglet
As a bonus, I'll add this answer to the list of enhancements because it contains some stuff that might be useful. One would be to replace 90% of your code with a custom pyglet.window.Window class to replace global variables and decorators and stuff.
And it would look something like this:
import pyglet
from pyglet.gl import *
from random import randint
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
key = pyglet.window.key
class Circle(pyglet.sprite.Sprite):
def __init__(self, radiance=5, x=0, y=0):
self.texture = pyglet.image.load('circle.png')
super(Circle, self).__init__(self.texture)
def click(self, x, y):
if x >= self.x and y >= self.y:
if x <= self.x + self.texture.width and y <= self.y + self.texture.height:
return self
class MainScreen(pyglet.window.Window):
def __init__ (self):
super(MainScreen, self).__init__(800, 600, fullscreen = False)
self.x, self.y = 0, 0
self.bg = pyglet.sprite.Sprite(pyglet.image.load('background.jpg'))
self.sprites = {}
self.sprites['circle'] = Circle(x=50, y=50)
self.sprites['label'] = pyglet.text.Label("Click red!", font_name = "Times New Roman", font_size = 18, x = 260, y = 10)
self.alive = 1
def on_draw(self):
self.render()
def on_close(self):
self.alive = 0
def on_mouse_press(self, x, y, button, modifiers):
if self.sprites['circle'].click(x, y):
print('Clicked in circle')
self.sprites['circle'].x = randint(0, self.width - 10)
self.sprites['circle'].y = randint(0, self.height - 10)
def on_key_press(self, symbol, modifiers):
if symbol == key.ESCAPE: # [ESC]
self.alive = 0
def render(self):
self.clear()
self.bg.draw()
for sprite_name, sprite_obj in self.sprites.items():
sprite_obj.draw()
self.flip()
def run(self):
while self.alive == 1:
self.render()
# -----------> This is key <----------
# This is what replaces pyglet.app.run()
# but is required for the GUI to not freeze
#
event = self.dispatch_events()
x = MainScreen()
x.run()
I'm not familiar with pyglet, but I'm guessing the problem is that you're checking whether x == circle.x etc, which means it only moves when you click the single pixel at the exact centre of the circle. Try some kind of maximum distance from the centre (e.g. a hypotenuse math.sqrt( (x-circle.x)**2 + (y-circle.y)**2) < circle.radius
I am currently trying to digitalize an boardgame I invented (repo: https://github.com/zutn/King_of_the_Hill). To make it work I need to check if one of the tiles (the arcs) on this board have been clicked. So far I have not been able to figure a way without giving up the pygame.arc function for drawing. If I use the x,y position of the position clicked, I can't figure a way out to determine the exact outline of the arc to compare to. I thought about using a color check, but this would only tell me if any of the tiles have been clicked. So is there a convenient way to test if an arc has been clicked in pygame or do I have to use sprites or something completely different? Additionally in a later step units will be included, that are located on the tiles. This would make the solution with the angle calculation postet below much more diffcult.
This is a simple arc class that will detect if a point is contained in the arc, but it will only work with circular arcs.
import pygame
from pygame.locals import *
import sys
from math import atan2, pi
class CircularArc:
def __init__(self, color, center, radius, start_angle, stop_angle, width=1):
self.color = color
self.x = center[0] # center x position
self.y = center[1] # center y position
self.rect = [self.x - radius, self.y - radius, radius*2, radius*2]
self.radius = radius
self.start_angle = start_angle
self.stop_angle = stop_angle
self.width = width
def draw(self, canvas):
pygame.draw.arc(canvas, self.color, self.rect, self.start_angle, self.stop_angle, self.width)
def contains(self, x, y):
dx = x - self.x # x distance
dy = y - self.y # y distance
greater_than_outside_radius = dx*dx + dy*dy >= self.radius*self.radius
less_than_inside_radius = dx*dx + dy*dy <= (self.radius- self.width)*(self.radius- self.width)
# Quickly check if the distance is within the right range
if greater_than_outside_radius or less_than_inside_radius:
return False
rads = atan2(-dy, dx) # Grab the angle
# convert the angle to match up with pygame format. Negative angles don't work with pygame.draw.arc
if rads < 0:
rads = 2 * pi + rads
# Check if the angle is within the arc start and stop angles
return self.start_angle <= rads <= self.stop_angle
Here's some example usage of the class. Using it requires a center point and radius instead of a rectangle for creating the arc.
pygame.init()
black = ( 0, 0, 0)
width = 800
height = 800
screen = pygame.display.set_mode((width, height))
distance = 100
tile_num = 4
ring_width = 20
arc = CircularArc((255, 255, 255), [width/2, height/2], 100, tile_num*(2*pi/7), (tile_num*(2*pi/7))+2*pi/7, int(ring_width*0.5))
while True:
fill_color = black
for event in pygame.event.get():
# quit if the quit button was pressed
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
x, y = pygame.mouse.get_pos()
# Change color when the mouse touches
if arc.contains(x, y):
fill_color = (200, 0, 0)
screen.fill(fill_color)
arc.draw(screen)
# screen.blit(debug, (0, 0))
pygame.display.update()
I've seen the pages on like scrolling cameras, and the camera movement, but I just want it simpler. I don't want the camera to follow the player off screen, I just want the camera to always center on the player, like the player is always in the center of the screen.
If you need, heres the files + code:
my main.py
import pygame, sys
from pygame.locals import *
from engineTiles import Tile
from classlibEntities import *
import Functions
from interaction import interaction
pygame.init()
pygame.font.init()
invalids = (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,
19,37,55,73,91,109,127,145,163,181,
182,183,184,185,186,187,188,189,190,
191,192,193,194,195,196,197,198,
36,54,72,90,108,126,144,162,180,198)
SCREENWIDTH = 720
SCREENHEIGHT = 440
DrawNumbers = True
window = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
for y in range(0, window.get_height(), 40):
for x in range(0, window.get_width(), 40):
if Tile.total_tiles in invalids:
Tile(x, y, 'solid')
else:
Tile(x, y, 'empty')
pygame.display.set_caption("Until Death - Core Architecture Build 04")
fpsclock = pygame.time.Clock()
FPS = 60
zombie1 = EntityZombie(200, 240)
player = EntityPlayer(400, 120)
# ------- Main Game Loop -------
while True:
interaction(window, player)
# ------- All GameLogic -------
Tile.draw_tiles(window)
if DrawNumbers == True:
Tile.draw_tilenumbers(window)
player.draw(window)
EntityZombie.draw_zombies(window)
pygame.display.update()
fpsclock.tick(FPS)
Any my tile class
import pygame, Functions
class Tile(pygame.Rect):
List = []
width, height = 40, 40
total_tiles = 1
H, V = 1, 18
def __init__(self, x, y, Type):
self.type = Type
self.number = Tile.total_tiles
Tile.total_tiles += 1
if Type == 'empty':
self.walkable = True
else:
self.walkable = False
pygame.Rect.__init__(self, (x, y), (Tile.width, Tile.height))
Tile.List.append(self)
#staticmethod
def get_tile(number):
for tile in Tile.List:
if tile.number == number:
return tile
#staticmethod
def draw_tiles(window):
for tile in Tile.List:
if not(tile.type == 'empty'):
if tile.type == 'solid':
pygame.draw.rect(window, [40, 40, 40], tile)
else:
pygame.draw.rect(window, [0, 0, 0], tile)
#staticmethod
def draw_tilenumbers(window):
for tile in Tile.List:
Functions.text_to_screen(window, tile.number, tile.x, tile.y)
And finally the class that has the player stuff in it.
import pygame
from engineTiles import Tile
class Entity(pygame.Rect):
width, height = 40, 40
def __init__(self, x, y):
pygame.Rect.__init__(self, x, y, EntityPlayer.width, EntityPlayer.height)
def __str__(self):
return str(self.get_number())
def get_number(self):
return ((self.x / self.width) + Tile.H) + (self.y / self.height) * Tile.V
def get_tile(self):
return Tile.get_tile(self.get_number())
class EntityZombie(Entity):
List = []
def __init__(self, x, y):
Entity.__init__(self, x, y)
EntityZombie.List.append(self)
#staticmethod
def draw_zombies(window):
for zombie in EntityZombie.List:
pygame.draw.rect(window, [210, 24, 77], zombie)
class EntityPlayer(Entity):
def __init__(self, x, y):
Entity.__init__(self, x, y)
def draw(self, window):
r = int (self.width / 2)
pygame.draw.circle(window, [77, 234, 156], (self.x + r, self.y + r), r)
What you need to do is have all the co-ordinates of the objects when you blit them, but before you blit them, subtract the player position. So if the player had moved 50 pixels down, subtract 50 pixels from all your objects before you blit them ie. move them 50 pixels up. Make sure you measure the amount the player has moved is measured in pixels, not in tiles!