Extremely poor performance in my simulation made in PyGame - python

I followed a PyGame tile-based tutorial for a project in school, but I never intended to make a game, but a simulation of an ecosystem. Unfortunately, when I run my program the performance is very bad and it only manages to run for a few seconds, before the windows to stop answering.
The only thing I want to do at the moment is to place a new patch of grass, when the energy of a grass patch reaches 80.
What is there to do? Is it bad that everything is inside of the update method? Can I use events or something to make the checks happen with a greater interval? I know there is a lot of maths going on each frame, but don't know how to do it another way.
Here is my code:
main.py:
#!/usr/bin/python3
#Importing necessary libraries
import pygame as pg, sys, random
from settings import *
from sprites import *
class Sim:
#Initialize the game window, etc.
def __init__(self):
pg.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption(TITLE)
self.clock = pg.time.Clock()
self.running = True
def new_grass(self, pos):
for g in self.grass:
if pos != g.pos:
Grass(self, pos)
#Start a new generation
def new(self):
self.all_sprites = pg.sprite.Group()
self.grass = pg.sprite.Group()
Grass(self, (10, 15))
self.run()
#Main loop
def run(self):
self.simulating = True
while self.simulating:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
#Update things on screen
def update(self):
self.all_sprites.update()
#Draw a grid on screen
def draw_grid(self):
for x in range(0, WIDTH, TILESIZE):
pg.draw.line(self.screen, BLACK, (x, 0), (x, HEIGHT))
for y in range(0, HEIGHT, TILESIZE):
pg.draw.line(self.screen, BLACK, (0, y), (WIDTH, y))
#Draw things on screen
def draw(self):
pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
self.screen.fill(DARK_GREEN)
self.draw_grid()
self.all_sprites.draw(self.screen)
#After drawing everything, flip the display
pg.display.flip()
#Events that might happen
def events(self):
for event in pg.event.get():
#Check for the user closing the window
if event.type == pg.QUIT:
if self.simulating:
self.simulating = False
self.running = False
s = Sim()
while s.running:
s.new()
pg.quit()
sprites.py:
#!/usr/bin/python3
import pygame as pg, random
from settings import *
vec = pg.math.Vector2
class Grass(pg.sprite.Sprite):
def __init__(self, sim, cord):
self.groups = sim.all_sprites, sim.grass
pg.sprite.Sprite.__init__(self, self.groups)
self.sim = sim
self.image = pg.Surface((TILESIZE/2, TILESIZE/2))
self.image.fill(GREEN)
self.cord = cord
self.rect = self.image.get_rect()
self.pos = vec(cord) * TILESIZE / 2
self.rect.topleft = self.pos
self.spread = vec(random.randint(-1, 1), random.randint(-1, 1))
self.energy = 20
def update(self):
if self.energy <= 80:
self.energy += 10
if self.energy >= 80:
self.sim.new_grass((self.cord + self.spread))
settings.py:
#Options/settings
TITLE = "EcoSim"
WIDTH = 480
HEIGHT = 600
FPS = 30
TILESIZE = 32
GRID_WIDTH = WIDTH / TILESIZE
GRID_HEIGHT = HEIGHT / TILESIZE
#Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 100, 0)
BROWN = (150,75,0)

The problem is here:
def new_grass(self, pos):
for g in self.grass:
if pos != g.pos:
Grass(self, pos)
This is because you'll add a Grass object for every time there is a grass that already exists on another position. I think you meant to add one if the position isn't present at all.
You have a few bugs in your program, mainly the one mentioned above, but also, the parameter pos should actually be coord. I've highlighted your code with some comments on improvements:
#!/usr/bin/python3
import pygame as pg, random
TITLE = "EcoSim"
WIDTH = 480
HEIGHT = 600
FPS = 30
TILESIZE = 32
GRID_WIDTH = WIDTH / TILESIZE
GRID_HEIGHT = HEIGHT / TILESIZE
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GREEN = (0, 255, 0)
DARK_GREEN = (0, 100, 0)
BROWN = (150,75,0)
vec = pg.math.Vector2
class Grass(pg.sprite.Sprite):
# This creates the image just once!
IMAGE = pg.Surface((TILESIZE/2, TILESIZE/2))
IMAGE.fill(GREEN)
def __init__(self, cord): # Remove 'sim'.
# self.groups = sim.all_sprites, sim.grass
# pg.sprite.Sprite.__init__(self, self.groups)
# self.sim = sim
super().__init__()
self.image = Grass.IMAGE # All reference the same image.
self.cord = cord
self.rect = self.image.get_rect()
self.pos = vec(cord) * TILESIZE / 2
self.rect.topleft = self.pos
self.energy = 20
self.spread = vec(random.randint(-1, 1), random.randint(-1, 1))
def update(self):
if self.energy <= 80:
self.energy += 10
# Make Sim check for this.
# if self.energy >= 80:
# self.sim.new_grass((self.cord + self.spread))
class Sim:
def __init__(self):
pg.init()
pg.display.set_caption(TITLE)
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
self.all_sprites = pg.sprite.Group() # Create *ALL* attributes in `__init__`
self.grass = pg.sprite.Group()
self.running = True
self.simulating = False
# You're passing coord here, not pos! And you also want to add
# the grass only if the coord is not already present in the list.
def new_grass(self, coord):
if coord not in (x.cord for x in self.grass):
grass = Grass(coord)
self.grass.add(grass)
self.all_sprites.add(grass)
def new(self):
self.all_sprites = pg.sprite.Group()
self.grass = pg.sprite.Group()
grass = Grass((10, 15)) # Grass is now pure and doesn't have any side-effects, which makes the code much clearer.
self.grass.add(grass)
self.all_sprites.add(grass)
self.run()
def run(self):
self.simulating = True
while self.simulating:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
self.all_sprites.update()
# Let Sim decide the fate of the grass. Don't let Grass add
# itself.
for grass in self.grass:
if grass.energy >= 80:
self.new_grass((grass.cord + grass.spread))
def draw_grid(self):
for x in range(0, WIDTH, TILESIZE):
pg.draw.line(self.screen, BLACK, (x, 0), (x, HEIGHT))
for y in range(0, HEIGHT, TILESIZE):
pg.draw.line(self.screen, BLACK, (0, y), (WIDTH, y))
def draw(self):
pg.display.set_caption("{:.2f}".format(self.clock.get_fps()))
self.screen.fill(DARK_GREEN)
self.draw_grid()
self.all_sprites.draw(self.screen)
pg.display.flip()
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.simulating = False # There was an unnecessary if here.
self.running = False
s = Sim()
while s.running:
s.new()
pg.quit()

The major issue is the method Sim.new_grass:
class Sim:
# [...]
def new_grass(self, pos):
for g in self.grass:
if pos != g.pos:
Grass(self, pos)
The method generates many more Grass objects than expected. It event generates multiple grass objects at the same location. For every object (g) where pos != g.pos a new instance of Grass is constructed.
You need to create a new instance of Grass if there is not any Grass object in the desired location:
class Sim:
# [...]
def new_grass(self, pos):
if not any(pos == g.pos for g in self.grass):
Grass(self, pos)

Related

How do my make my image (rect) collide with my randomly generated circles to end the game? [duplicate]

This question already has answers here:
How do I detect collision in pygame?
(5 answers)
Closed 1 year ago.
I have been trying to solve this for weeks. This is a free-falling pit game, where my character (in this case a chimpanzee png) falls from the top of the screen to the bottom while trying to dodge the random black circles. I have tried so many angles at tackling this, I have tried the standard collision I was taught (pygame.sprite.groupcollide(Group1, Group2, False, True, collided = None) I have tried doing collisions with the colour black only, different formats of spawning my image and all that, and I haven't been able to find anything that works with my code. It has resulted in the code being very messy. I have tried to clean it up for this, but it still might be hard to understand, but if anybody has any solution to this, please let me know as I have been stumped for weeks. I just want the game to close or "game over" when they touch a circle Code:
import sys
import pygame as pg
RED = (255, 0, 0)
GRAY = (150, 150, 150)
GREEN =(34, 177, 76)
BLACK = (0,0,0)
import random
import math
def main():
width, height = 1024, 768
hbox, vbox = 80, 80
w, h = 640, 240
screen = pg.display.set_mode((width, height))
BG = pg.image.load('jungle.jpg').convert()
img = pg.image.load('MONKEY.png')
img = pg.transform.scale(img, (80, 80))
img.convert()
rect = img.get_rect()
rect.center = w//2, h//2
clock = pg.time.Clock()
Score = 0
class Circle(pg.sprite.Sprite):
def __init__(self):
#You can initialise the size to a random value
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
def draw(self):
pg.draw.circle(screen, self.color, (self.pos[0], self.pos[1]), self.radius)
circles = []
for i in range(20):
circles.append(Circle())
def checkIntersection(c1, c2):
dx = c1.pos[0] - c2.pos[0]
dy = c1.pos[1] - c2.pos[1]
d = math.hypot(dx, dy)
if d < c1.radius + c2.radius:
return True
return False
for i in range(19):
while checkIntersection(circles[i], circles[i + 1]):
circles[i].pos = random.randint(0, 1000), random.randint(0, 700)
velocity = (0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
# booster
move = 8 if keys[pg.K_LSHIFT] else 4
if keys[pg.K_a]: #to move left
rect.x -= move
if rect.x < 0 : rect.x = 0
if keys[pg.K_d]: #to move right
rect.x += move
if rect.x > width-hbox : rect.x = width - hbox
Score += 1
rect.y += 2.5
screen.blit(BG, (0,0))
screen.blit(img,rect)
pg.draw.rect(screen, RED, rect, 1)
pg.draw.polygon(screen, GREEN, ((1024,768), (0,768), (0,640),(1024,640)))
font = pg.font.SysFont("comicsansms", 45)
text = font.render (" " + str(Score), 1, BLACK)
screen.blit(text,(480,700))
pg.event.get()
for circle in circles:
circle.draw()
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()
Use a "mask" collision. See How can I made a collision mask? and Pygame mask collision
Create a Sprite class with mask (see pygame.mask.from_surface):
class Circle(pg.sprite.Sprite):
def __init__(self):
super().__init__()
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
self.image = pg.Surface((self.radius*2, self.radius*2), pg.SRCALPHA)
pg.draw.circle(self.image , self.color, (self.radius, self.radius), self.radius)
self.rect = self.image.get_rect(center = self.pos)
self.mask = pg.mask.from_surface(self.image)
Create a Sprite class for the player (also with a mask)
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pg.image.load('MONKEY.png')
self.image = pg.transform.scale(self.image, (80, 80)).convert()
self.rect = self.image.get_rect()
self.rect.center = x, y
self.mask = pg.mask.from_surface(self.image)
Manage the Sprites in pygame.sprite.Group and use pygame.sprite.spritecollide and pygame.sprite.collide_mask() for the collision test:
if pg.sprite.spritecollide(player, circles, False, collided = pg.sprite.collide_mask):
done = True
Complete example:
import sys
import pygame as pg
RED = (255, 0, 0)
GRAY = (150, 150, 150)
GREEN =(34, 177, 76)
BLACK = (0,0,0)
import random
import math
class Circle(pg.sprite.Sprite):
def __init__(self):
super().__init__()
self.pos = [random.randint(0, 1000), random.randint(180, 600)]
self.color = (0,0, 0)
self.radius = 20
self.image = pg.Surface((self.radius*2, self.radius*2), pg.SRCALPHA)
pg.draw.circle(self.image , self.color, (self.radius, self.radius), self.radius)
self.rect = self.image.get_rect(center = self.pos)
self.mask = pg.mask.from_surface(self.image)
class Player(pg.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pg.image.load('MONKEY.png')
self.image = pg.transform.scale(self.image, (80, 80)).convert()
self.rect = self.image.get_rect()
self.rect.center = x, y
self.mask = pg.mask.from_surface(self.image)
def main():
width, height = 1024, 768
hbox, vbox = 80, 80
w, h = 640, 240
screen = pg.display.set_mode((width, height))
BG = pg.image.load('jungle.jpg').convert()
clock = pg.time.Clock()
Score = 0
player = Player(w//2, h//2)
all_sprites = pg.sprite.Group(player)
circles = pg.sprite.Group()
for i in range(20):
circle = Circle()
circles.add(circle)
all_sprites.add(circle)
def checkIntersection(c1, c2):
dx = c1.pos[0] - c2.pos[0]
dy = c1.pos[1] - c2.pos[1]
d = math.hypot(dx, dy)
if d < c1.radius + c2.radius:
return True
return False
c = circles.sprites()
for i in range(19):
while checkIntersection(c[i], c[i + 1]):
c[i].pos = random.randint(0, 1000), random.randint(0, 700)
velocity = (0, 0)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
# booster
move = 8 if keys[pg.K_LSHIFT] else 4
if keys[pg.K_a]: #to move left
player.rect.x -= move
if player.rect.x < 0 : player.rect.x = 0
if keys[pg.K_d]: #to move right
player.rect.x += move
if player.rect.x > width-hbox : player.rect.x = width - hbox
Score += 1
player.rect.y += 2.5
screen.blit(BG, (0,0))
pg.draw.rect(screen, RED, player.rect, 1)
pg.draw.polygon(screen, GREEN, ((1024,768), (0,768), (0,640),(1024,640)))
font = pg.font.SysFont("comicsansms", 45)
text = font.render (" " + str(Score), 1, BLACK)
screen.blit(text,(480,700))
pg.event.get()
all_sprites.draw(screen)
pg.display.update()
clock.tick(30)
if pg.sprite.spritecollide(player, circles, False, collided = pg.sprite.collide_mask):
done = True
if __name__ == '__main__':
pg.init()
main()
pg.quit()
sys.exit()

Pygame - creating a rotating projectile arount the center [duplicate]

This code is mostly just the generic start up of a pygame window but I'm trying to make it so the object moves (the planet object I've made) in the orbit around the sun object I've made for the coordinates I've given it. I know the x and y values are updating but I don't understand why the object doesn't move.
#import the library
import pygame
import math
#classes
class button:
def _init_ (self,screen, colour, x, y, width,height, letter):
self.screen = screen
self.colour = colour
self.x = x
self.y = y
self.width = width
self.height = height
self.letter = letter
self.radius = radius
def draw(self):
pygame.draw.rect(self.screen, self.colour,(self.x,self.y, self.width, self.height))
if self.letter!= '+' and self.letter!= '-':
font = pygame.font.SysFont('agencyfb',15,True,False)
else:
font = pygame.font.SysFont('agencyfb',25,True,False)
text = font.render(self.letter, True, black)
text_rect = text.get_rect(center=(self.x+self.width/2,self.y+self.height/2))
screen.blit(text, text_rect)
class orbit:
def __init__(self,screen,colour,x,y,radius,width):
self.screen = screen
self.colour = colour
self.x = x
self.y = y
self.width = width
self.radius = radius
def draw_circle(self):
pygame.draw.circle(self.screen,self.colour,(self.x,self.y),self.radius,self.width)
#define colours
##Sun = pygame.draw.circle(screen,Sun,[1000,450],100,0)
Black = (0,0,0)
White = (255,255,255)
Green = (0,255,0)
Red = (255,0,0)
Blue = (0,0,255)
Sun = (255,69,0)
Sun = []
Planet = []
#initialise the engine
pygame.init()
#Opening a window
size = (1920,1080)
screen = pygame.display.set_mode(size)
#set window title
pygame.display.set_caption("Orbit Simulator")
#loop unti the user clicks the close button
done = False
#
x=1000
y=450
Sun.append(orbit(screen,Red,1000,450,100,0))
Planet.append(orbit(screen,White,x,y,50,0))
#
#used to manage how fast the screen updates
clock = pygame.time.Clock()
#------ Main program Loop ------
while not done:
#--- Main event loop
for event in pygame.event.get(): #user did something
if event.type == pygame.QUIT: #if user clicked close
done = True #flag that we are done and exit the loop
#------ Game logic should go here ------
#------ Drawing code should go here -------
#first, clear the screen to white. Don't put other drawing commands above this or they will be erased with this command.
screen.fill(Black)
for i in Sun:
i.draw_circle()
for i in Planet:
r=150
angle=0
count = 0
while angle <= 360:
angle_radians = math.radians(angle)
x = int(math.cos(angle_radians)*r)
y = int(math.sin(angle_radians)*r)
angle +=1
count +=1
print(count)
x+=1000
y+=450
pygame.draw.circle(screen,White,[x,y],10,0)
print("Position [",x,",",y,"]")
#update the screen
pygame.display.flip()
#------ Limit to 60 frames per second ------
clock.tick(60)
#------ When the loop ends, quit ------
pygame.quit()
You can make an object rotate around another by using trigonometry or vectors. With vectors you just have to rotate a vector which defines the offset from the rotation center each frame and add it to the position vector (self.pos which is the rotation center) to get the desired self.rect.center coordinates of the orbiting object.
import pygame as pg
from pygame.math import Vector2
class Planet(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((40, 40), pg.SRCALPHA)
pg.draw.circle(self.image, pg.Color('dodgerblue'), (20, 20), 20)
self.rect = self.image.get_rect(center=pos)
self.pos = Vector2(pos)
self.offset = Vector2(200, 0)
self.angle = 0
def update(self):
self.angle -= 2
# Add the rotated offset vector to the pos vector to get the rect.center.
self.rect.center = self.pos + self.offset.rotate(self.angle)
def main():
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
planet = Planet(screen_rect.center, all_sprites)
yellow = pg.Color('yellow')
while True:
for event in pg.event.get():
if event.type == pg.QUIT:
return
all_sprites.update()
screen.fill((30, 30, 30))
pg.draw.circle(screen, yellow, screen_rect.center, 60)
all_sprites.draw(screen)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
pg.quit()

Ship moves up and left faster than down and right when rotating in pygame

I am working on a simple game. I created a pygame sprite which and tested it by making it move forward and rotating at a consistent speed. However, it seams to be moving left and up (where sin and cos are negative) quicker than right and down (where sin and cos are positive). I tested it without moving and just rotating and it works.
Here is the code:
import pygame
import sys
from math import cos, sin, pi
from time import sleep
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 800
FPS = 60
pygame.init()
DISPLAY = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
CLOCK = pygame.time.Clock()
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
SHIP_BLUE_IMG = pygame.image.load('./spaceshooter/PNG/playerShip1_blue.png').convert()
SHIP_RED_IMG = pygame.image.load('./spaceshooter/PNG/playerShip1_red.png').convert()
LASER_BLUE_IMG = pygame.image.load('./spaceshooter/PNG/Lasers/laserBlue16.png').convert()
LASER_RED_IMG = pygame.image.load('./spaceshooter/PNG/Lasers/laserRed16.png').convert()
LASER_SPEED = 10
PLAYER_SHIP_SPEED = 5
all_sprites = pygame.sprite.Group()
class Player(pygame.sprite.Sprite):
def __init__(self, img, pos, angle):
super().__init__()
img.set_colorkey((0, 0, 0, 0))
self.angle = angle
self.original_img = pygame.transform.rotate(img, 180) # Becase these images come upside down
self.image = self.original_img
self.rect = self.image.get_rect()
self.rect.center = pos
self._update_image()
def _update_image(self):
x, y = self.rect.center
self.image = pygame.transform.rotate(self.original_img, self.angle)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
def _get_radeons(self):
return (self.angle*pi)/180
def rotate(self, degrees):
self.angle += degrees
self._update_image()
def update(self):
self.rotate(5)
x, y = self.rect.center
nx = sin(self._get_radeons())*PLAYER_SHIP_SPEED + x
ny = cos(self._get_radeons())*PLAYER_SHIP_SPEED + y
self.rect.center = (nx, ny)
player = Player(SHIP_BLUE_IMG, (300, 300), 45)
all_sprites.add(player)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
DISPLAY.fill(BLACK)
all_sprites.update()
all_sprites.draw(DISPLAY)
pygame.display.update()
CLOCK.tick(FPS)
To describe what it looks like when I run it, it is a ship on a black background that rotates counter-clockwise while moving forward in what should be a circle. But instead, it is creating a sort of spiral, slowly getting closer to the top left corner of the screen.
This is an accuracy issue. Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the new position of the player is assigned to the Rect object:
self.rect.center = (nx, ny)
Since this is done every frame, the position error will accumulate over time.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes (self.pos) and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .center) of the rectangle:
class Player(pygame.sprite.Sprite):
def __init__(self, img, pos, angle):
# [...]
self.pos = pos
# [...]
def update(self):
self.rotate(5)
nx = sin(self._get_radeons())*PLAYER_SHIP_SPEED + self.pos[0]
ny = cos(self._get_radeons())*PLAYER_SHIP_SPEED + self.pos[1]
self.pos = (nx, ny)
self.rect.center = round(nx), round(ny)
See also Move and rotate.
Minimal example:
import math
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self, img, pos, angle):
super().__init__()
img.set_colorkey((0, 0, 0, 0))
self.angle = angle
self.original_img = pygame.transform.rotate(img, 180)
self.image = self.original_img
self.rect = self.image.get_rect()
self.rect.center = pos
self.pos = pos
self.speed = 5
self.angle_step = 5
self._update_image()
def _update_image(self):
x, y = self.rect.center
self.image = pygame.transform.rotate(self.original_img, self.angle)
self.rect = self.image.get_rect()
self.rect.center = (x, y)
def update(self):
self.angle += self.angle_step
self._update_image()
nx = math.sin(math.radians(self.angle)) * self.speed + self.pos[0]
ny = math.cos(math.radians(self.angle)) * self.speed + self.pos[1]
self.pos = (nx, ny)
self.rect.center = round(nx), round(ny)
pygame.init()
window = pygame.display.set_mode((300, 300))
clock = pygame.time.Clock()
background = pygame.Surface(window.get_size())
background.set_alpha(5)
all_sprites = pygame.sprite.Group()
ship_image = pygame.image.load('Rocket64.png').convert_alpha()
player = Player(ship_image, (window.get_width()//2-40, window.get_height()//2+40), 45)
all_sprites.add(player)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_sprites.update()
window.blit(background, (0, 0))
all_sprites.draw(window)
pygame.display.update()
pygame.quit()
exit()

How can I make my player move whilst still being able to rotate?

I am currently writing a program for my first python game (a simple top down shooter) and I have so far gotten to here using tutorials and examples online to help me build the code.
My character is able to rotate using vectors and that is all working how I want it to.
However, I cannot seem to make the player move using the arrow keys. I converted the velocity to a vector and when running the code and using the arrow keys, the players coordinates are actually changing and so in my head the player should be moving but isn't.
My initial thoughts are that player isn't being redrawn to the surface each time and hence staying in the same position, but I am not sure if this is correct.
What amendments/changes can I make in order to make this work as this problem has been driving me crazy the last few days.
import math
from random import randint
import pygame
from pygame.math import Vector2
pygame.init()
screen = pygame.display.set_mode((600, 600))
screen_width = 600
screen_height = 600
black = (0, 0, 0)
pygame.display.set_caption("gang")
clock = pygame.time.Clock()
class Character:
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
class Player(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((50, 30), pygame.SRCALPHA)
pygame.draw.polygon(self.image, pygame.Color('steelblue2'),
[(0, 0), (50, 15), (0, 30)])
self.orig_image = self.image
self.rect = self.image.get_rect(center=pos)
self.pos = Vector2(pos)
self.velocityx = Vector2(12, 0)
self.velocityy = Vector2(0, 12)
def update(self):
self.rotate()
def rotate(self):
direction = pygame.mouse.get_pos() - self.pos
radius, angle = direction.as_polar()
self.image = pygame.transform.rotate(self.orig_image, -angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Enemy(Character):
def __init__(self, x, y, width, height):
Character.__init__(self, x, y, width, height)
def draw(self, win):
pygame.draw.rect(win, (0, 255, 0), (jeff.x, jeff.y, jeff.width, jeff.height))
def draw_window():
screen.fill((30, 30, 30))
jeff.draw(screen)
all_sprites.update()
all_sprites.draw(screen)
pygame.display.update()
def xor(a, b):
if bool(a) != bool(b):
return True
else:
return False
def game_end():
pygame.font.init()
text = pygame.font.Font('freesansbold.ttf', 32)
text_surface = text.render('Game Over', False, (0, 0, 0))
text_rect = text_surface.get_rect()
text_rect.center = (86, 86)
screen.blit(text_surface, (172, 172))
pygame.display.update()
mag = Player((150, 150))
playersprite = pygame.sprite.RenderPlain(mag)
all_sprites = pygame.sprite.Group(Player((300, 220)))
jeff = Enemy(randint(300, 500), randint(300, 500), 60, 60)
bullets = []
def main():
run = True
while run:
clock.tick(60)
keys = pygame.key.get_pressed()
if xor(keys[pygame.K_a], keys[pygame.K_LEFT]):
mag.pos -= mag.velocityx
print(mag.pos)
elif xor(keys[pygame.K_d], keys[pygame.K_RIGHT]):
mag.pos += mag.velocityx
print(mag.pos)
elif xor(keys[pygame.K_w], keys[pygame.K_UP]):
mag.pos -= mag.velocityy
print(mag.pos)
elif xor(keys[pygame.K_s], keys[pygame.K_DOWN]):
mag.pos += mag.velocityy
print(mag.pos)
draw_window()
main()
There are 2 issues in your code.
The Player object mag is not rendered at all, because you've created a 2nd Player object:
mag = Player((150, 150))
playersprite = pygame.sprite.RenderPlain(mag)
all_sprites = pygame.sprite.Group(Player((300, 220)))
Create 1 Player object and add it to the pygame.sprite.Group all_sprites:
mag = Player((150, 150))
playersprite = pygame.sprite.RenderPlain(mag)
all_sprites = pygame.sprite.Group(mag)
When the pygame.sprite.Sprite of a .sprite.Group are drawn by draw(), then the .image is drawn at the location which is defined by .rect.
You've to update the position of the .rect attribute by .pos in .update:
class Player(pygame.sprite.Sprite):
# [...]
def update(self):
# update position
self.rect.center = (int(self.pos.x), int(self.pos.y))
# update orientation
self.rotate()
def rotate(self):
direction = pygame.mouse.get_pos() - self.pos
radius, angle = direction.as_polar()
self.image = pygame.transform.rotate(self.orig_image, -angle)
self.rect = self.image.get_rect(center=self.rect.center)

Pygame Class Variables

Im making a flappy bird clone game in pygame and im using classes, because I want to learn how to use classes.
I created 2 classes a "FlappyBird" and "Pipes" class.
The problem is with the collision. The variable BIRD_Y which is passed into the Pipes class from the MainWindow class and the variable BIRD_Y is chaning its value, but the problem here is that the variable does not change in the Pipes class! I tried with inheritance, still no luck.
I'm sorry if this is like the easy question but I don't have the practice and experience in pygame classes.
import pygame
pygame.init()
WIDTH = 800
HEIGHT = 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("FlapPY Bird")
clock = pygame.time.Clock()
# colors
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)
# --> variables
FPS = 60
# classes
class MainWindow(object):
def __init__(self, w, h):
self.width = w
self.height = h
self.Main()
def Main(self):
loop = True
bird_width = 25
bird_height = 25
bird_x = 150
bird_y = HEIGHT/2 - int(bird_height/2)
bird_x_move = 0
bird_y_move = 0
bird = FlappyBird(bird_x, bird_y, bird_width, bird_height)
pipe_spacing = 350
pipe_speed = 3
space = 100
p1_x = 300
p1_y = 250
p1_w = 50
p1_h = HEIGHT
p2_x = p1_x + pipe_spacing
p2_y = 250
p2_w = 50
p2_h = HEIGHT
p3_x = p2_x + pipe_spacing
p3_y = 250
p3_w = 50
p3_h = HEIGHT
pipe1 = Pipes(p1_x, p1_y, p1_w, p1_h, space)
pipe2 = Pipes(p2_x, p2_y, p2_w, p2_h, space)
pipe3 = Pipes(p3_x, p3_y, p3_w, p3_h, space)
while loop:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
bird_y_move = -7
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
bird_y_move = 3
screen.fill(white)
bird.draw()
bird.move(bird_x_move, bird_y_move)
pipe1.draw_pipes()
pipe2.draw_pipes()
pipe3.draw_pipes()
pipe1.pipe_move(pipe_speed)
pipe2.pipe_move(pipe_speed)
pipe3.pipe_move(pipe_speed)
pipe1.check_if(bird_y)
pipe2.check_if(bird_y)
pipe3.check_if(bird_y)
pygame.display.update()
clock.tick(FPS)
class FlappyBird(object):
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
def draw(self):
pygame.draw.rect(screen, red, (self.x, self.y, self.width, self.height))
def move(self, dx, dy):
self.y += dy
self.x += dx
class Pipes(object):
def __init__(self, x, y, width, height, space):
self.x = x
self.y = y
self.width = width
self.height = height
self.space = space
def draw_pipes(self):
pygame.draw.rect(screen, black, (self.x, self.y, self.width, self.height))
pygame.draw.rect(screen, black, (self.x, self.y-self.space-self.height, self.width, self.height))
def pipe_move(self, speed):
self.x -= speed
def check_if(self, bird_y):
if self.x < 0:
self.x = 1000
print(bird_y)
MainWindow(WIDTH, HEIGHT)
The variable you are giving to Pipe.check_if() is not updated.
You should get the real coordinate used by the bird, you can do this by calculate the new value before update the bird position (and give directly the new position, new_x and new_y).
The other solution is to add a return statement in the method FlappyBird.move() with the new coordinates, so when you call bird.move(bird_x_move, bird_y_move), you can assign the returned value to new_x and new_y.
# class bird
def move(self, dx, dy):
self.y += dy
self.x += dx
return(self.x, self.y)
# class MainWindow
new_x, new_y = bird.move(bird_x_move, bird_y_move)
#[...]
pipe1.check_if(new_y)
pipe2.check_if(new_y)
pipe3.check_if(new_y)

Categories

Resources