Related
I'm trying to create sensors for a car to keep track of the distances from the car to the borders of the track. My goal is to have 5 sensors (see image below) and use them to train a machine learning algorithm.
But I can't figure out a way to calculate these distances. For now, I just need a sample of code and a logical explanation of how to implement this with PyGame. But a mathematical and geometrical explanation would be really nice as well for further reading. I'm using this code from a YouTuber tutorial series.
My biggest issue is how to get the points in blue. (last picture) I need them to create the red lines from the car to the points and to calculate the length of these lines. These points are taking the car's position and rotation into account and they have a specific angle at which they get out of the car. I've managed to create the lines, but could not get the point the line would collide with the track.
What I want to accomplish:
I've tried different approaches to this problem, but for now, my biggest problem is how to get the position of the blue dots:
--- Edit from the feedback ------
I added a new paragraph to better explain the problem. This way I hope it is clearer why this problem is different from those said to be related to it. The other problem we have the desired final position (mouse or enemy) in this one we have to figure out which point is the one we are going to use to create the line, and this is my issue.
My GitHub repo of the project
https://github.com/pedromello/ml-pygame/blob/main/main.py
The part of the code where I'm trying to implement this:
class AbstractCar:
def __init__(self, max_vel, rotation_vel):
self.img = self.IMG
self.max_vel = max_vel
self.vel = 0
self.rotation_vel = rotation_vel
self.angle = 0
self.x, self.y = self.START_POS
self.acceleration = 0.1
def rotate(self, left=False, right=False):
if left:
self.angle += self.rotation_vel
elif right:
self.angle -= self.rotation_vel
def draw(self, win):
blit_rotate_center(win, self.img, (self.x, self.y), self.angle)
def move_forward(self):
self.vel = min(self.vel + self.acceleration, self.max_vel)
self.move()
def move_backward(self):
self.vel = max(self.vel - self.acceleration, -self.max_vel/2)
self.move()
def move(self):
radians = math.radians(self.angle)
vertical = math.cos(radians) * self.vel
horizontal = math.sin(radians) * self.vel
self.y -= vertical
self.x -= horizontal
def collide(self, mask, x=0, y=0):
car_mask = pygame.mask.from_surface(self.img)
offset = (int(self.x - x), int(self.y - y))
poi = mask.overlap(car_mask, offset)
return poi
def reset(self):
self.x, self.y = self.START_POS
self.angle = 0
self.vel = 0
class PlayerCar(AbstractCar):
IMG = RED_CAR
START_POS = (180, 200)
def reduce_speed(self):
self.vel = max(self.vel - self.acceleration / 2, 0)
self.move()
def bounce(self):
self.vel = -self.vel
self.move()
def drawSensors(self):
radians = math.radians(self.angle)
vertical = -math.cos(radians)
horizontal = math.sin(radians)
car_center = pygame.math.Vector2(self.x + CAR_WIDTH/2, self.y + CAR_HEIGHT/2)
pivot_sensor = pygame.math.Vector2(car_center.x + horizontal * -100, car_center.y - vertical * -100)
#sensor1 = Vector(30, 0).rotate(self.angle) #+ self.pos # atualiza a posição do sensor 1
#sensor2 = Vector(30, 0).rotate((self.angle+30)%360) #+ self.pos # atualiza a posição do sensor 2
#sensor3 = Vector(30, 0).rotate((self.angle-30)%360) #+ self.pos # atualiza a posição do sensor 3
#rotate pivot sensor around car center
sensor_2 = pivot_sensor.rotate((self.angle+30)%360)
# Sensor 1
pygame.draw.line(WIN, (255, 0, 0), car_center, pivot_sensor, 2)
# Sensor 2
pygame.draw.line(WIN, (255, 0, 0), car_center, sensor_2, 2)
# Sensor 3
#pygame.draw.line(WIN, (255, 0, 0), (self.x, self.y), (self.x + horizontal * 100, self.y - vertical * 100), 2)
Thank you for the comments, I solved my problem using the idea of firing sensors so I can get the point on the wall when the "bullet" hits it.
As we can see when the bullet hits the wall we can create a line that connects the point to the car. This is not the best solution, as it takes time for the bullet to hit the wall and in the meantime, the car is "blind".
As Rabbid76 commented, using raycasting may be the solution I was looking for.
Code for reference:
Sensor Bullet class
class SensorBullet:
def __init__(self, car, base_angle, vel, color):
self.x = car.x + CAR_WIDTH/2
self.y = car.y + CAR_HEIGHT/2
self.angle = car.angle
self.base_angle = base_angle
self.vel = vel
self.color = color
self.img = pygame.Surface((4, 4))
self.fired = False
self.hit = False
self.last_poi = None
def draw(self, win):
pygame.draw.circle(win, self.color, (self.x, self.y), 2)
def fire(self, car):
self.angle = car.angle + self.base_angle
self.x = car.x + CAR_WIDTH/2
self.y = car.y + CAR_HEIGHT/2
self.fired = True
self.hit = False
def move(self):
if(self.fired):
radians = math.radians(self.angle)
vertical = math.cos(radians) * self.vel
horizontal = math.sin(radians) * self.vel
self.y -= vertical
self.x -= horizontal
def collide(self, x=0, y=0):
bullet_mask = pygame.mask.from_surface(self.img)
offset = (int(self.x - x), int(self.y - y))
poi = TRACK_BORDER_MASK.overlap(bullet_mask, offset)
if poi:
self.fired = False
self.hit = True
self.last_poi = poi
return poi
def draw_line(self, win, car):
if self.hit:
pygame.draw.line(win, self.color, (car.x + CAR_WIDTH/2, car.y + CAR_HEIGHT/2), (self.x, self.y), 1)
pygame.display.update()
def get_distance_from_poi(self, car):
if self.last_poi is None:
return -1
return math.sqrt((car.x - self.last_poi[0])**2 + (car.y - self.last_poi[1])**2)
Methods the car must perform to use the sensor
# Inside car's __init__ method
self.sensors = [SensorBullet(self, 25, 12, (100, 0, 255)), SensorBullet(self, 10, 12, (200, 0, 255)), SensorBullet(self, 0, 12, (0, 255, 0)), SensorBullet(self, -10, 12, (0, 0, 255)), SensorBullet(self, -25, 12, (0, 0, 255))]
# ------
# Cars methods
def fireSensors(self):
for bullet in self.sensors:
bullet.fire(self)
def sensorControl(self):
#print(contains(self.sensors, lambda x: x.hit))
for bullet in self.sensors:
if not bullet.fired:
bullet.fire(self)
for bullet in self.sensors:
bullet.move()
def get_distance_array(self):
return [bullet.get_distance_from_poi(self) for bullet in self.sensors]
This question already has an answer here:
Pygame game help: Easing/Acceleration
(1 answer)
Closed 2 years ago.
I'm trying to make blobs move in a random direction for several frames rather than just once so that it appears less jerky and more smooth, but have been unable to do so. Is there any way to make each object move in the same direction for several ticks before choosing another random direction and doing the same?
My code (most is irrelevant):
import pygame
import random
import numpy as np
WIDTH = 1800
HEIGHT = 1000
BLUE = (15,15,180)
RED = (150,0,0)
class Blob:
def __init__(self, colour, x_boundary, y_boundary, size):
self.colour = colour
self.size = size
self.x_boundary = x_boundary
self.y_boundary = y_boundary
self.x = random.randrange(0, self.x_boundary)
self.y = random.randrange(0, self.y_boundary)
def move(self):
self.x += random.randrange(-6,7)
self.y += random.randrange(-6,7)
def limits(self):
if self.x < 0:
self.x = 0
elif self.x > self.x_boundary:
self.x = self.x_boundary
if self.y < 0:
self.y = 0
elif self.y > self.y_boundary:
self.y = self.y_boundary
def __add__(self, other_blob):
if other_blob.size > self.size:
other_blob.size += int(self.size * 0.5)
self.size = 0
class FastBlob(Blob):
def __init__(self, colour, x_boundary, y_boundary, size):
super().__init__(colour, x_boundary, y_boundary, size)
def move(self):
self.x += random.randrange(-20,21)
self.y += random.randrange(-20,21)
pygame.init()
game_display = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Blob world')
clock = pygame.time.Clock()
def is_touching(b1,b2):
return np.linalg.norm(np.array([b1.x,b1.y])-np.array([b2.x,b2.y])) < (b1.size + b2.size)
def handle_collisions(blob_list):
blues, reds, slow_reds = blob_list
for first_blobs in blues, reds, slow_reds:
for first_blob_id, first_blob in first_blobs.copy().items():
for other_blobs in blues, reds, slow_reds:
for other_blob_id, other_blob in other_blobs.copy().items():
if first_blob == other_blob:
pass
else:
if is_touching(first_blob, other_blob):
first_blob + other_blob
return blues, reds, slow_reds
def draw_environment(blob_list):
game_display.fill((210,210,210))
handle_collisions(blob_list)
for blob_dict in blob_list:
for blob_id in blob_dict:
blob = blob_dict[blob_id]
pygame.draw.circle(game_display, blob.colour, [blob.x, blob.y], blob.size)
blob.move()
blob.limits()
pygame.display.update()
def main():
blue_blobs = dict(enumerate([FastBlob(BLUE, WIDTH, HEIGHT, random.randrange(10,15)) for i in range(20)]))
red_blobs = dict(enumerate([FastBlob(RED, WIDTH, HEIGHT, random.randrange(5,10)) for i in range(30)]))
slow_red_blobs = dict(enumerate([Blob(RED, WIDTH, HEIGHT, random.randrange(20,30)) for i in range(5)]))
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
draw_environment([blue_blobs, red_blobs, slow_red_blobs])
clock.tick(7)
if __name__ == '__main__':
main()
Here, I have similar problem in my game, when enemy has to randomly change directions so it is unpredictable to the player.
def goblin_move(): #Goblin auto (random) movement
if goblin.x < 0:
goblin.go_right()
elif goblin.x > 500:
goblin.go_left()
else:
if goblin.x > (rnd_1 * win_width) and goblin.move_flag == -1:
goblin.go_left()
goblin.move_flag = -1
else:
goblin.go_right()
goblin.move_flag = 1
if goblin.x > (rnd_2 * win_width):
goblin.move_flag = -1
def set_random(rnd_1, rnd_2): #Random function generator
rnd_1 = round(random.uniform(0, 0.45), 2)
rnd_2 = round(random.uniform(0.65, 0.95), 2)
return rnd_1, rnd_2
And this is how I set it in the main loop:
if round(pg.time.get_ticks()/1000) % 3 == 0: #Calling random function
(rnd_1, rnd_2) = set_random(rnd_1, rnd_2)
Hope you will find it useful.
Use pygame.math.Vector2 to do the computations. Store the coordinates of the blob to a Vector2 and define a maximum distance (self.maxdist), a velocity (self.speed), a random distance (self.dist) a nd a random direction (self.dir). The random direction is a vector with length 1 (Unit vector) and a random angel (rotate()):
class Blob:
def __init__(self, colour, x_boundary, y_boundary, size):
self.colour = colour
self.size = size
self.x_boundary = x_boundary
self.y_boundary = y_boundary
self.x = random.randrange(0, self.x_boundary)
self.y = random.randrange(0, self.y_boundary)
self.pos = pygame.math.Vector2(self.x, self.y)
self.maxdist = 7
self.speed = 1
self.dist = random.randrange(self.maxdist)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
When the blob moves, then scale the direction by the speed and add it to the position (self.pos += self.dir * self.speed). Decrement the distance (self.dist -= self.speed) and update self.x, self.y by the rounded (round) position. If self.dist falls below 0, the create a new random direction and distance:
class Blob:
# [...]
def move(self):
self.pos += self.dir * self.speed
self.dist -= self.speed
self.x, self.y = round(self.pos.x), round(self.pos.y)
if self.dist <= 0:
self.dist = random.randrange(self.maxdist)
self.dir = pygame.math.Vector2(1, 0).rotate(random.randrange(360))
In the method limit you have to ensure that self.pos is in bounds. Finally you have to update self.x, self.y:
class Blob:
# [...]
def limits(self):
if self.pos.x < 0:
self.pos.x = 0
elif self.pos.x > self.x_boundary:
self.pos.x = self.x_boundary
if self.pos.y < 0:
self.pos.y = 0
elif self.pos.y > self.y_boundary:
self.pos.y = self.y_boundary
self.x, self.y = round(self.pos.x), round(self.pos.y)
The class FastBlob does not need its own move method. It is sufficient do define its own self.maxdist and self.speed:
class FastBlob(Blob):
def __init__(self, colour, x_boundary, y_boundary, size):
super().__init__(colour, x_boundary, y_boundary, size)
self.maxdist = 35
self.speed = 5
I found a nice image of space that I'd like sitting in the background of this tiny game I'm working on and can't figure out what and where to write it. It needs to be placed behind all classes to make sure that it doesn't block the screen. I thought it might be in class Window, but I'm not sure. I am brand new to python so any help is much appreciated! This is the entire project so far.
import sys, logging, os, random, math, open_color, arcade
#check to make sure we are running the right version of Python
version = (3,7)
assert sys.version_info >= version, "This script requires at least Python {0}.{1}".format(version[0],version[1])
#turn on logging, in case we have to leave ourselves debugging messages
logging.basicConfig(format='[%(filename)s:%(lineno)d] %(message)s', level=logging.DEBUG)
logger = logging.getLogger(__name__)
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
MARGIN = 30
SCREEN_TITLE = "Intergalactic slam"
NUM_ENEMIES = 5
STARTING_LOCATION = (400,100)
BULLET_DAMAGE = 10
ENEMY_HP = 10
HIT_SCORE = 10
KILL_SCORE = 100
PLAYER_HP = 100
class Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
'''
initializes the bullet
Parameters: position: (x,y) tuple
velocity: (dx, dy) tuple
damage: int (or float)
'''
super().__init__("PNG/laserPink3.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
'''
Moves the bullet
'''
self.center_x += self.dx
self.center_y += self.dy
class Enemy_Bullet(arcade.Sprite):
def __init__(self, position, velocity, damage):
super().__init__("PNG/laserGreen1.png", 0.5)
(self.center_x, self.center_y) = position
(self.dx, self.dy) = velocity
self.damage = damage
def update(self):
self.center_x += self.dx
self.center_y += self.dy
class Player(arcade.Sprite):
def __init__(self):
super().__init__("PNG/shipYellow_manned.png", 0.5)
(self.center_x, self.center_y) = STARTING_LOCATION
self.hp = PLAYER_HP
class Enemy(arcade.Sprite):
def __init__(self, position):
'''
initializes an alien enemy
Parameter: position: (x,y) tuple
'''
super().__init__("PNG/shipGreen_manned.png", 0.5)
self.hp = ENEMY_HP
(self.center_x, self.center_y) = position
class Window(arcade.Window):
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
def setup(self):
'''
Set up enemies
'''
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
def update(self, delta_time):
self.bullet_list.update()
self.enemy_bullet_list.update()
if (not (self.win or self.lose)):
for e in self.enemy_list:
for b in self.bullet_list:
if (abs(b.center_x - e.center_x) <= e.width / 2 and abs(b.center_y - e.center_y) <= e.height / 2):
self.score += HIT_SCORE
e.hp -= b.damage
b.kill()
if (e.hp <= 0):
e.kill()
self.score += KILL_SCORE
if (len(self.enemy_list) == 0):
self.win = True
if (random.randint(1, 75) == 1):
self.enemy_bullet_list.append(Enemy_Bullet((e.center_x, e.center_y - 15), (0, -10), BULLET_DAMAGE))
for b in self.enemy_bullet_list:
if (abs(b.center_x - self.player.center_x) <= self.player.width / 2 and abs(b.center_y - self.player.center_y) <= self.player.height / 2):
self.player.hp -= b.damage
b.kill()
if (self.player.hp <= 0):
self.lose = True
def on_draw(self):
arcade.start_render()
arcade.draw_text(str(self.score), 20, SCREEN_HEIGHT - 40, open_color.white, 16)
arcade.draw_text("HP: {}".format(self.player.hp), 20, 40, open_color.white, 16)
if (self.player.hp > 0):
self.player.draw()
self.bullet_list.draw()
self.enemy_bullet_list.draw()
self.enemy_list.draw()
if (self.lose):
self.draw_game_loss()
elif (self.win):
self.draw_game_won()
def draw_game_loss(self):
arcade.draw_text(str("LOSER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def draw_game_won(self):
arcade.draw_text(str("WINNER!"), SCREEN_WIDTH / 2 - 90, SCREEN_HEIGHT / 2 - 10, open_color.white, 30)
def on_mouse_motion(self, x, y, dx, dy):
'''
The player moves left and right with the mouse
'''
self.player.center_x = x
def on_mouse_press(self, x, y, button, modifiers):
if button == arcade.MOUSE_BUTTON_LEFT:
x = self.player.center_x
y = self.player.center_y + 15
bullet = Bullet((x,y),(0,10),BULLET_DAMAGE)
self.bullet_list.append(bullet)
def main():
window = Window(SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_TITLE)
window.setup()
arcade.run()
if __name__ == "__main__":
main()
One way to do it would be to load the .jpg or .png as a texture, and draw that texture each frame, as big as the screen is (or bigger!).
I haven't tested this, but as an example, loading the texture could be done in Window.__init__, like so (reference):
self.background = arcade.load_texture('PNG/background.png')
And then in on_draw, just after you call start_render, you would draw it (reference), passing the required center coordinates, as well as width and height:
self.background.draw(SCREEN_WIDTH/2, SCREEN_HEIGHT/2, SCREEN_WIDTH, SCREEN_HEIGHT)
The reason it needs to be the first thing is because everything is drawn back-to-front, like you would do in a painting.
If the image is not the exact same size as your screen/window, your background will probably be stretched/squished. If that's not what you want, the easiest fix would be to change the image so that it's the right size.
Yes, you should be able to add it to class window...
You could do something like this to add it:
def __init__(self, width, height, title):
super().__init__(width, height, title)
file_path = os.path.dirname(os.path.abspath(__file__))
os.chdir(file_path)
self.set_mouse_visible(True)
arcade.set_background_color(open_color.black)
self.bullet_list = arcade.SpriteList()
self.enemy_list = arcade.SpriteList()
self.enemy_bullet_list = arcade.SpriteList()
self.player = Player()
self.score = 0
self.win = False
self.lose = False
self.background = None
def setup(self):
'''
Set up enemies
'''
self.background = arcade.load_texture("images/background.jpg")
for i in range(NUM_ENEMIES):
x = 120 * (i+1) + 40
y = 500
enemy = Enemy((x,y))
self.enemy_list.append(enemy)
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 what command I should use. 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()
You can use a threaded timer to prevent the method to be called everytime you click the button, and just two attributes in your SpaceShip class.
Everytime the method fireBullet is called, a check is made on the variable can_shoot. If it's true, the bullet is spawned like you did and then a timer runs (with a thread, for not blocking the main flow) that just put can_shoot to False, sleep for any amount of ms you want, and then put can_shoot to True and the method is callable again.
import time
import threading
def __init__(self):
# your stuff
self.wait_between_fire = 300 / 1000 # amount of ms / 1000 to convert in seconds
self.can_shoot = True
class TimerThread(threading.Thread):
def __init__(self, ref):
threading.Thread.__init__(self)
self.ref = ref
def run():
self.ref.can_shoot = False
time.sleep(ref.wait_between_fire)
self.ref.can_shoot = True
def set_timer(self):
TimerThread(self).start()
def fireBullet(self):
if self.can_shoot:
self.bullets.append(Bullet(self.screen, self.xcor(), self.ycor(), self.heading()))
self.set_timer()
We don't need to introduce time nor threading to solve this. We can use turtle's own timer events to control the rate of fire:
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
Here I've limited the rate of fire to 4 rounds per second. (250 / 1000 milliseconds.) Adjust as you see fit. Below is a rework of your program with this modification as well as other fixes and style tweaks:
from turtle import Screen, Turtle
from random import random, randint, randrange, choice
from math import radians, sin as sine, cos as cosine
class Bullet(Turtle):
MAX_DISTANCE = 500
DELTA = 20
RADIUS = 4
def __init__(self, position, heading):
super().__init__(shape="bullet")
self.hideturtle()
self.penup()
self.goto(position)
self.setheading(heading)
self.color('yellow')
self.showturtle()
self.distance = 0
def move(self):
self.distance += self.DELTA
self.forward(self.DELTA)
if self.done():
self.reset()
def getRadius(self):
''' collision detection helper method '''
return self.RADIUS
def blowUp(self):
''' method that makes something go off the screen '''
self.hideturtle()
def done(self):
return self.distance >= self.MAX_DISTANCE
class Asteroid(Turtle):
def __init__(self, dx, dy, position, size, emoji):
super().__init__()
self.hideturtle()
self.penup()
self.goto(position)
self.color('lightgrey')
emoji = choice(emojis)
# screen.addshape(emoji) # for StackOverflow debugging
self.shape(emoji)
self.showturtle()
self.size = size
self.dx = dx
self.dy = dy
def move(self):
x, y = self.position()
# 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):
''' method that makes something go off the screen '''
self.hideturtle()
def getRadius(self):
return self.size * 10 - 5
class SpaceShip(Turtle):
RADIUS = 10
def __init__(self, screen, dx, dy, x, y):
super().__init__(shape='turtle')
self.hideturtle()
self.penup()
self.color("white")
self.goto(x, y)
self.showturtle()
self.dx = dx
self.dy = dy
self.screen = screen
self.bullets = []
def move(self):
x, y = self.position()
x = (self.dx + x - screenMinX) % (screenMaxX - screenMinX) + screenMinX
y = (self.dy + y - screenMinY) % (screenMaxY - screenMinY) + screenMinY
self.goto(x, y)
def powPow(self, asteroids):
''' logic for collision '''
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()
hit = True
if not bullet.done() and not hit:
dasBullets.append(bullet)
else:
bullet.blowUp()
self.bullets = dasBullets
def fireBullet(self):
bullet = Bullet(self.position(), self.heading())
self.bullets.append(bullet)
def fireEngine(self):
angle = self.heading() # how turtle moves
x = cosine(radians(angle))
y = sine(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
def getRadius(self):
return self.RADIUS
def turnLeft(self):
self.left(7)
def turnRight(self):
self.right(7)
def intersect(object1, object2):
''' collision detection '''
return object1.distance(object2) <= object1.getRadius() + object2.getRadius()
def play():
# Tell all the elements of the game to move
ship.move()
gameover = False
for asteroid in asteroids:
r = randint(0, 1)
if r == 1:
asteroid.right(50)
else:
asteroid.left(20)
asteroid.move()
if intersect(ship, asteroid):
turtle.write("You Got Killed :(", font=("Verdana", 25), align="center")
gameover = True
ship.powPow(asteroids)
screen.update()
if not asteroids:
turtle.color('green')
turtle.write("You Killed the Emojis!!", font=("Arial", 30), align="center")
if not gameover:
screen.ontimer(play, 30)
# controls
def fire():
screen.onkey(None, 'space')
ship.fireBullet()
screen.ontimer(lambda: screen.onkey(fire, 'space'), 250)
# create a link to the object (creates the environment)
speed1 = 1.3
amountOfEmojis = 11
# establish important data for screen environment
screen = Screen()
screen.bgcolor("black")
# 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
screen.setworldcoordinates(screenMinX, screenMinY, screenMaxX, screenMaxY)
# 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)))
screen.tracer(0)
# turtle setup
turtle = Turtle()
turtle.hideturtle()
turtle.penup()
turtle.goto(0, screenMaxY - 50)
turtle.color('white')
turtle.write("Welcome to Emoji Run!", align="center", font=("Courier New", 26))
turtle.goto(0, screenMaxY - 70)
turtle.write("Use the arrow keys to move, and space to fire. The point of the game is to kill the emojis.", align="center", font=("Courier New", 13))
turtle.goto(0, 0)
turtle.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"
]
emojis = ['rock1', 'rock2', 'rock3'] # for StackOverflow debugging purposes
# 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, dy = random() * 6 - 3, random() * 6 - 3
x = randrange(10) * (screenMaxX - screenMinX) + screenMinX
y = random() * (screenMaxY - screenMinY) + screenMinY
asteroid = Asteroid(dx, dy, (x, y), randint(1, 3), emojis)
asteroids.append(asteroid)
screen.onkey(ship.turnLeft, 'Left')
screen.onkey(ship.turnRight, 'Right')
screen.onkey(ship.fireEngine, 'Up')
screen.onkey(fire, 'space')
screen.listen()
screen.update()
play()
screen.mainloop()
Something to consider is that turtles are global entities that don't get garbage collected. So, you might want to collect your spent bullets in a list to reuse, only creating new ones when you need them.
import numpy as np
import random
import pygame
background_colour = (255,255,255)
width, height = 300, 325
eps = 1
sigma = 1
dt = 0.05
class Particle():
def __init__(self):
self.x = random.uniform(0,400)
self.y = random.uniform(0,500)
self.vx = random.uniform(-.1,.1)
self.vy = random.uniform(-.1,.1)
self.fx = 0
self.fy = 0
self.m = 1
self.size = 10
self.colour = (0, 0, 255)
self.thickness = 0
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
elif self.x < self.size:
self.x = 2*self.size - self.x
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
elif self.y < self.size:
self.y = 2*self.size - self.y
def getForce(self, p2):
dx = self.x - p2.x
dy = self.y - p2.y
self.fx = 500*(-8*eps*((3*sigma**6*dx/(dx**2+dy**2)**4 - 6*sigma**12*dx/(dx**2+dy**2)**7)))
self.fy = 500*(-8*eps*((3*sigma**6*dy/(dx**2+dy**2)**4 - 6*sigma**12*dy/(dx**2+dy**2)**7)))
return self.fx, self.fy
def verletUpdate(self,dt):
self.x = self.x + dt*self.vx+0.5*dt**2*self.fx/self.m
self.y = self.y + dt*self.vy+0.5*dt**2*self.fy/self.m
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
screen = pygame.display.set_mode((width, height))
screen.fill(background_colour)
partList = []
for k in range(10):
partList.append(Particle())
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for k, particle in enumerate(partList):
for p2 in partList[k+1:]:
particle.getForce(p2)
particle.verletUpdate(dt)
particle.bounce()
particle.display()
pygame.display.flip()
pygame.quit()
Is my code correct? I tried to simulate particles in 2D move with Lennard Jones forces. I think calculating forces works okay but why my particles are moving to one point? Ocasionally I also get error OverflowError: Python int too large to convert to C long Any advice would be useful.
I can not comment on the physics of the simulation, but as far as the display is concerned following are my observations:
Your particles move to one point because the update condition for the x and y parameter in your code in verletUpdate are slowly moving to values beyond the display area. Also to values out of the range of the int() function which is causing your error. You can see this with the statement:
def verletUpdate(self,dt):
self.x = self.x + dt*self.vx+0.5*dt**2*self.fx/self.m
self.y = self.y + dt*self.vy+0.5*dt**2*self.fy/self.m
print self.x
print self.y
Sample Output:
290.034892392
9.98686293664
290.028208837
9.99352484332
-2.55451579742e+19
1.12437640586e+19
Also they saturate and with iterations, the update gets smaller and smaller:
def display(self):
print ' %s + %s '%(self.x,self.y)
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Output:
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
...
10.0009163718 + 10.0000322065
10.0009120033 + 10.0042647307
10.0009163718 + 10.0000322065
This is also why your bounce functions and your limit checking is not working. And after a lot of iterations on occasion your self.x and self.y are far exceeding the limits of int().
The code seems fine, but you can get rid of the overflow error by adding some checks above the draw line. For instance I initialized them randomly again to simulate a particle going off screen and us tracking a new one. Feel free to change it.
def display(self):
if(self.x<0 or self.x>height):
self.__init__()
print "reset"
if(self.y<0 or self.y>width):
self.__init__()
print "reset"
print ' %s + %s '%(self.x,self.y)
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Also at one point you adress the array as [k+1:], and addressing the zero element caused a divide by zero error. You might want to look at that.