My pygame is running way too slow. Without using class oop it was running perfectly but now using oop its very slow.
I have tested putting that separate class file in main file also but the result was same.
import pygame
from snake import Snake
pygame.init()
surf_width = 800
surf_height = 600
clock = pygame.time.Clock()
dis_surf = pygame.display.set_mode((surf_width, surf_height))
pygame.display.set_caption("snake game")
run = True
def game_loop():
x = 255
y = 255
x_change = 0
y_change = 0
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
dis_surf.fill((255, 255, 255))
game = Snake(dis_surf, x, y, x_change, y_change)
x = game.x
y = game.y
another file:
import pygame
class Snake():
def __init__(self, dis_surf, x, y, x_change, y_change):
self.dis_surf = dis_surf
self.x = x
self.y = y
self.width = 20
self.height = 20
self.x_change = x_change
self.y_change = y_change
self.vel = 5
self.draw()
def draw(self):
pygame.draw.rect(self.dis_surf, (0, 255, 0), (self.x, self.y, self.width, self.height))
self.run()
pygame.display.update()
def run(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.x_change = self.vel
self.y_change = 0
elif event.key == pygame.K_LEFT:
self.x_change = -self.vel
self.y_change = 0
elif event.key == pygame.K_UP:
self.y_change = -self.vel
self.x_change = 0
elif event.key == pygame.K_DOWN:
self.y_change = self.vel
self.x_change = 0
print(event)
self.x += self.x_change
self.y += self.y_change
x_change = game.x_change
y_change = game.y_change
pygame.display.update()
clock.tick(60)
game_loop()
A few things are wrong.
1) You are instantiating a new Snake class every game loop when you do game = Snake() inside of the while loop. This in combination with number 2 is your main problem. I moved this line outside of the while loop for you.
2) You are calling run() inside of __init__. This is something you should never do in a constructor, constructors generally should only be used for setting initial data. This also contributed to problem number 1 significantly because this was happening every game loop. I removed the call self.run() inside __init__ for you.
3) pygame.display.update() was being called twice. Not the cause of your problem, but still unnecessary.
Made some small corrections for you.
import pygame
pygame.init()
surf_width = 800
surf_height = 600
clock = pygame.time.Clock()
dis_surf = pygame.display.set_mode((surf_width, surf_height))
pygame.display.set_caption("snake game")
run = True
def game_loop():
x = 255
y = 255
x_change = 0
y_change = 0
game = Snake(dis_surf, x, y, x_change, y_change)
while run:
dis_surf.fill((255, 255, 255))
game.draw()
class Snake():
def __init__(self, dis_surf, x, y, x_change, y_change):
self.dis_surf = dis_surf
self.x = x
self.y = y
self.width = 20
self.height = 20
self.x_change = x_change
self.y_change = y_change
self.vel = 5
def draw(self):
pygame.draw.rect(self.dis_surf, (0, 255, 0), (self.x, self.y, self.width, self.height))
self.run()
def run(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.x_change = self.vel
self.y_change = 0
elif event.key == pygame.K_LEFT:
self.x_change = -self.vel
self.y_change = 0
elif event.key == pygame.K_UP:
self.y_change = -self.vel
self.x_change = 0
elif event.key == pygame.K_DOWN:
self.y_change = self.vel
self.x_change = 0
self.x += self.x_change
self.y += self.y_change
pygame.display.update()
clock.tick(60)
game_loop()
If you want to use OOP in pygame, use pygame's Sprite class. It's made exactly for this purpose.
Your code should look like this (I tried to change not too much):
import pygame
pygame.init()
surf_width = 800
surf_height = 600
clock = pygame.time.Clock()
screen = pygame.display.set_mode((surf_width, surf_height))
pygame.display.set_caption("snake game")
class Snake(pygame.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = pygame.Surface((20, 20))
self.image.fill(pygame.Color('orange'))
self.rect = self.image.get_rect(topleft=pos)
self.x_change = 0
self.y_change = 0
self.vel = 5
def update(self, events):
for event in events:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
self.x_change = self.vel
self.y_change = 0
elif event.key == pygame.K_LEFT:
self.x_change = -self.vel
self.y_change = 0
elif event.key == pygame.K_UP:
self.y_change = -self.vel
self.x_change = 0
elif event.key == pygame.K_DOWN:
self.y_change = self.vel
self.x_change = 0
self.rect.move_ip(self.x_change, self.y_change)
def main():
snake = Snake((0, 0))
snakes = pygame.sprite.Group(snake)
while True:
events = pygame.event.get()
for event in events:
if event.type == pygame.QUIT:
return
snakes.update(events)
screen.fill((30, 30, 30))
snakes.draw(screen)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Ensure to only call pygame.display.flip() and pygame.event.get() once every frame.
If you want to handle events in other parts of your code, just store the current frame's events in a variable and pass them around. Using a Group makes this easy.
See how we cleanly seperated the logic of the game:
The main loop does only the three things it's supposed to do. Handling events, updating the game state, and drawing to the screen. It does so without "knowing what actually happens" in the game.
The Snake sprite only reacts when told so by the main loop (when its update method is called), and it does not care where the events come from and how and where it is actually displayed.
Related
I am making a game in pygame, and I really don't understand why the code isn't running this for loop. It definitely gets to the events() method, but it just isn't running the for loop inside. Does anyone know why?
def events(self):
print('events') # This prints out
for event in pygame.event.get():
print('for loop') # This doesn't print out
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
x_change = -10
y_change = 0
if event.key == pygame.K_RIGHT:
x_change = 10
y_change = 0
if event.key == pygame.K_UP:
y_change = -10
x_change = 0
if event.key == pygame.K_DOWN:
y_change = 10
x_change = 0
self.x += x_change
self.y += y_change
If the line
print('for loop') # This doesn't print out
does not get executed, it means that pygame.event.get() did not return an event.
A common beginner mistake is to call pygame.event.get() multiple times per frame, so make sure you only have one pygame.event.get() call in your game.
pygame.event.get() will clear the event queue once called, so if you don't handle all events that are returned you will miss them and another call will not return them again.
If you need to listen for events in other places than your main loop, just store the events in a variable. Here's an example that I wrote for another answer:
import pygame
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((32, 32))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
pygame.draw.polygon(self.image, pygame.Color('dodgerblue'), ((0, 0), (32, 16), (0, 32)))
self.org_image = self.image.copy()
self.angle = 0
self.direction = pygame.Vector2(1, 0)
self.rect = self.image.get_rect(center=(200, 200))
self.pos = pygame.Vector2(self.rect.center)
def update(self, events, dt):
for e in events:
if e.type == pygame.KEYDOWN:
if e.key == pygame.K_SPACE:
self.groups()[0].add(Projectile(self.rect.center, self.direction.normalize()))
pressed = pygame.key.get_pressed()
if pressed[pygame.K_a]:
self.angle += 3
if pressed[pygame.K_d]:
self.angle -= 3
self.direction = pygame.Vector2(1, 0).rotate(-self.angle)
self.image = pygame.transform.rotate(self.org_image, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
class Projectile(pygame.sprite.Sprite):
def __init__(self, pos, direction):
super().__init__()
self.image = pygame.Surface((8, 8))
self.image.fill((0, 0, 0))
self.image.set_colorkey((0, 0, 0))
pygame.draw.circle(self.image, pygame.Color('orange'), (4, 4), 4)
self.rect = self.image.get_rect(center=pos)
self.direction = direction
self.pos = pygame.Vector2(self.rect.center)
def update(self, events, dt):
self.pos += self.direction * dt
self.rect.center = self.pos
if not pygame.display.get_surface().get_rect().contains(self.rect):
self.kill()
def main():
pygame.init()
screen = pygame.display.set_mode((500, 500))
sprites = pygame.sprite.Group(Player())
clock = pygame.time.Clock()
dt = 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
sprites.update(events, dt)
screen.fill((30, 30, 30))
sprites.draw(screen)
pygame.display.update()
dt = clock.tick(60)
if __name__ == '__main__':
main()
As you can see, pygame.event.get() is called only once. The result is stored in a variable events and then passed down to all sprites via the update method.
The issue is when i press the left or right key while i press it, the sprite crosses the left and right boundaries of the screen. But when i tap it, it will not cross only when i hold the key continuosly
this is the class for the humanship
class Human:
y = display_height * 0.8
x = display_width * 0.45
width = 120
image = pygame.image.load('yasin/alien1.png')
def run(self):
gameDisplay.blit(Human.image, (Human.x, Human.y))
This is the main loop which iterates throughout the game
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
if human.x > 0:
x_change = -8
else:
x_change = 0
elif event.key == pygame.K_RIGHT:
if human.x < display_width - human.width:
x_change = 8
else:
x_change = 0
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
x_change = 0
human.x += x_change
human.run()
Move the if human.x > 0: and if human.x < display_width - human.width: out of the event loop because they'll only be executed once per event in the event queue. Rather check in the main while loop if the player is still inside of the game area, otherwise stop it.
I've also changed a few more things: The attributes should be defined in the __init__ method to make them instance attributes instead of class attributes. Use self.x instead of Human.x in the class. The x_change and y_change variables belong to the human object, so they should be attributes as well. Then you can add an update method to the Human in which you do the bounds checking and the movement.
import pygame
display_width, display_height = 640, 480
class Human:
def __init__(self):
self.image = pygame.image.load('yasin/alien1.png')
self.y = display_height * 0.8
self.x = display_width * 0.45
self.x_change = 0
self.y_change = 0
self.width = 120
def run(self, gameDisplay):
gameDisplay.blit(self.image, (self.x, self.y))
def update(self):
self.x += self.x_change
# Check if the human is outside of the game area.
if self.x < 0:
self.x_change = 0 # Stop it.
self.x = 0 # Reset the position, so that we can move again.
elif self.x > display_width - self.width:
self.x_change = 0
self.x = display_width - self.width
def main():
pygame.init()
gameDisplay = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
human = Human()
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
human.x_change = -8
elif event.key == pygame.K_RIGHT:
human.x_change = 8
elif event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
human.x_change = 0
human.update()
gameDisplay.fill((30, 30, 30))
human.run(gameDisplay)
pygame.display.flip()
clock.tick(30)
if __name__ == '__main__':
main()
pygame.quit()
When I try to move my character and press any other key at the same time, the movement suddenly stops. For example, the space key in my code is used to shoot a tiny orb out of the spaceship. Every time I press space and I am moving left and right quickly, the orb will shoot out but the player movement will be frozen for a second or two.
I have tried to switch to different ways of handling the way keys are input, but all of them seem to lead to this same problem. pygame.key.get_pressed() also has this problem when in my code.
I am not quite sure if this is a problem with my laptop keyboard or something in the code, so the code for the entire file is below.
import pygame, sys, decimal
# Screen Size
SCREEN_X = 400
SCREEN_Y = 400
# Loading Images
backgroundImg = pygame.image.load('StarBackground.png')
menuBar = pygame.image.load('Menu_Bar.png')
shipImg = pygame.image.load('PowerShip.png')
orb = pygame.image.load('Orb00.png')
class Ship(pygame.sprite.Sprite):
# Movement rate of change
change_x = 0
# Methods
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = shipImg.convert_alpha()
self.rect = self.image.get_rect()
self.rect.x = SCREEN_X / 2 - 8
self.rect.y = SCREEN_Y - 40
def move(self, speed):
self.change_x = speed
def stop(self):
self.change_x = 0
def update(self, screen):
self.rect.x += self.change_x
if self.rect.x < 0:
self.rect.x = 0
elif self.rect.right > SCREEN_X:
self.rect.x -= 1
screen.blit(self.image, self.rect)
class MenuBar(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = menuBar.convert_alpha()
self.rect = self.image.get_rect()
self.rect.x = 10
self.rect.y = 0
def update(self, screen):
screen.blit(self.image,self.rect)
class Bullet1(pygame.sprite.Sprite):
def __init__(self,x,y):
pygame.sprite.Sprite.__init__(self)
self.image = orb.convert_alpha()
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.alive = True
def update(self):
if self.alive == True:
self.rect.y -= 1
if self.alive == False:
self.rect.y = -10000
class HealthBar(pygame.sprite.Sprite):
pass
class EnergyBar(pygame.sprite.Sprite):
pass
class PointsBar(pygame.sprite.Sprite):
pass
class Background(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = backgroundImg.convert_alpha()
self.rect = self.image.get_rect()
self.rect.x = 0
self.rect.y = 0
def update(self, screen):
if self.rect.top > 0:
self.rect.y = SCREEN_Y * -1
self.rect.y += 1
screen.blit(self.image, self.rect)
def main():
pygame.init()
size = [SCREEN_X, SCREEN_Y]
screen = pygame.display.set_mode(size, pygame.DOUBLEBUF) # Set the height and width of the screen
pygame.display.set_caption("Space Adventure") # Setting the game name in the title bar
background = Background() # Creating the game objects
menubar = MenuBar()
ship = Ship()
finished = False # Close button exit code
bullet1Enabled = True
bullet1Count = 1
spacePressed = False
clock = pygame.time.Clock() # Manages the frames per second
lastkey = None # Variable that stores the last key pressed
bulletlist = []
# Game loop
while not finished:
for event in pygame.event.get():
print(lastkey)
if event.type == pygame.QUIT:
finished = True
pygame.event.set_blocked(pygame.MOUSEMOTION)
if event.type == pygame.KEYDOWN:
if lastkey != pygame.K_SPACE:
lastkey = event.key
if event.key == pygame.K_SPACE:
spacePressed = True
if bullet1Enabled == True:
bullet1 = Bullet1(ship.rect.x, ship.rect.y)
bulletlist.append(bullet1)
bullet1Count = 1
else:
spacePressed = False
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT and lastkey != pygame.K_LEFT:
lastkey = None
ship.move(0)
if event.key == pygame.K_LEFT and lastkey != pygame.K_RIGHT:
lastkey = None
ship.move(0)
if event.key == pygame.K_RIGHT or lastkey == pygame.K_LEFT:
spacePressed = False
if event.key == pygame.K_LEFT or lastkey == pygame.K_RIGHT:
spacePressed = False
#Bullet Delay
if spacePressed == True:
bullet1Count = True
if spacePressed == False:
bullet1Count = False
if lastkey == pygame.K_RIGHT:
ship.move(1)
if lastkey == pygame.K_LEFT:
ship.move(-1)
clock.tick(240) # Frames per second
background.update(screen) # Background update
# Menu Bar update
ship.update(screen) # Ship update
for b in bulletlist:
if b.rect.bottom <= 0:
b.alive = False
b.update()
screen.blit(b.image, b.rect)
menubar.update(screen)
pygame.display.flip() # Updates the display for everything
pygame.quit() # Clean shutdown on IDLE
if __name__ == "__main__":
main()
The problem occurs because you don't reset lastkey to None after you release the space bar, so you have to press left or right twice.
if event.type == pygame.KEYUP:
if event.key == pygame.K_SPACE:
lastkey = None
I don't see why you need the lastkey variable at all. I'd remove these lines from the main loop,
if lastkey == pygame.K_RIGHT:
ship.move(1)
if lastkey == pygame.K_LEFT:
ship.move(-1)
insert them in the event loop and change lastkey to event.key:
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.move(1)
if event.key == pygame.K_LEFT:
ship.move(-1)
Now you should be able to remove the lastkey completely.
I want to move a rectangle with the arrow keys, so far I only can draw the rectangle but I don't get the keyhandler to work.
I'm a newbie in python and pygame.
This is my main.py:
import pygame
import time
import random
from Player import *
# Initialize Pygame
pygame.init()
game_over = False
# Color
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
GRAY = (192, 192, 192)
# Setting up the window
surfaceWidth = 800
surfaceHeight = 500
surface = pygame.display.set_mode((surfaceWidth, surfaceHeight))
pygame.display.set_caption("Rectangle Runner")
clock = pygame.time.Clock()
#Player(surface, surface_height, surface_width, size, jump_height, color)
player = Player(surface, surfaceHeight, surfaceWidth, 50, 200, BLACK)
# Game Loop
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
if game_over == True:
pygame.quit()
quit()
surface.fill(GRAY)
#Player.draw()
player.keyHandling()
player.checkBoundaries()
player.draw()
#Obstacle.draw()
pygame.display.update()
clock.tick(60)
# This code shall be unreachable
pygame.quit()
quit()
This is the Player.py:
import pygame
class Player:
def __init__(self, surface, surface_height, surface_width, size, jump_height, color):
self.surface = surface
self.surface_height = surface_height
self.surface_width = surface_width
self.size = size
self.jump_height = jump_height
self.color = color
self.x = (0 + (self.surface_width/10))
self.y = self.surface_height - self.size
self.x_move = 0
self.y_move = 0
self.rect = pygame.draw.rect(self.surface, self.color, [self.x, self.y, self.size, self.size])
def keyHandling(self):
# Draw the player
#pygame.draw.rect(self.surface, self.color,
#[self.x, self.y, self.size, self.size])
# KeyListener
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
self.y_move = -10
#self.y += self.y_move
#self.rect.move_ip(0, -10)
if event.key == pygame.K_LEFT:
self.x_move = -5
#self.x += self.x_move
#self.rect.move_ip(-5, 0)
if event.key == pygame.K_RIGHT:
self.x_move = 5
#self.x += self.x_move
#self.rect.move_ip(5, 0)
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
self.y_move = 0
#self.y += self.y_move
#self.rect.move_ip(0, 10)
if event.key == pygame.K_LEFT:
self.x_move = 0
if event.key == pygame.K_RIGHT:
self.x_move = 0
self.x += self.x_move
self.y += self.y_move
def checkBoundaries(self):
# Checking for Boundaries
#if self.y <= self.jump_height:
# self.y_move = -10
if self.y > self.surface_height - self.size:
self.y_move = 0
if self.y <= self.jump_height + self.size:
self.y_move = -10
def draw(self):
pygame.draw.rect(self.surface, self.color, [self.x, self.y, self.size, self.size])
#pygame.draw.rect(self.surface, self.color,[self.x, self.y, self.size, self.size])
I need help. I'm thinking thru this the whole day.
Thanks in advance :D
The problem in your code is that pygame.event.get() is a generator. Each item that is obtained from it can ONLY be obtained once. Hence, each time the clock ticks, you are grabbing everything within the events generator and they are all destroyed. Next, when the Player object iterates over pygame.event.get, it does nothing, as the generator was already made empty. See this about generators.
What you need to do is catch the KEYDOWN event in the main.py module and then pass key information onto the Player object.
Replace the main loop in main.py with the following:
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
game_over = True
if event.type == pygame.KEYDOWN:
player.keyDown(event.key)
if game_over == True:
pygame.quit()
quit()
surface.fill(GRAY)
player.checkBoundaries()
player.draw()
pygame.display.update()
clock.tick(60)
Remove the keyHandling method from Player.py and replace with the following method:
def keyDown(self, key):
if key == pygame.K_w:
self.y -= 10
elif key == pygame.K_a:
self.x -= 10
elif key == pygame.K_d:
self.x += 10
This now removes the issue with the generator.
Full code
So im trying the make my sprite move constantly when im holding my arrow keys, but i need to keep pressing it to move it. any idea why?
here's my code:
yes i have imported pygame and everything
class Block(pygame.sprite.Sprite):
def __init__(self, color = blue,widht = 64, height = 64):
super(Block, self).__init__()
self.image = pygame.Surface((widht, height))
self.image.fill(color)
self.rect = self.image.get_rect()
self.sound = pygame.mixer.Sound("2dSounds/Walk.wav")
self.hspeed = 0
self.vspeed = 0
to update the sprite, so it changes places depending what key i press
def update(self):
self.rect.x += self.hspeed
self.rect.y += self.vspeed
to change the speed using a_block.change_speed(...)
def change_speed(self, hspeed, vspeed):
self.hspeed += hspeed
self.vspeed += vspeed
to set the position of the sprite when i first create it
def set_position(self, x, y):
self.rect.x = x
self.rect.y = y
to set a image for my sprite i just created
def set_image(self, filename = None):
if(filename != None):
self.image = pygame.image.load(filename)
self.rect = self.image.get_rect()
to play a sound
def play_sound():
self.sound.play()
the gameloop
def game_loop():
a_block = Block()
global event
gameDisplay.fill(white)
#Quit
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
the controls that dont work
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
a_block.change_speed(-20, 0)
if event.key == pygame.K_RIGHT:
a_block.change_speed(20, 0)
if event.key == pygame.K_UP:
a_block.change_speed(0, -20)
if event.key == pygame.K_DOWN:
a_block.change_speed(0, 20)
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
a_block.change_speed(0, 0)
if event.key == pygame.K_RIGHT:
a_block.change_speed(0, 0)
if event.key == pygame.K_UP:
a_block.change_speed(0, 0)
if event.key == pygame.K_DOWN:
a_block.change_speed(0, 0)
To draw a_block and other things
block_group = pygame.sprite.Group()
gameDisplay.fill(white)
a_block.set_image('2dImages/brick.png')
a_block.set_position(display_width/2, display_height/2)
a_block.update()
block_group.add(a_block)
block_group.draw(gameDisplay)
update display
pygame.display.update()
clock.tick(60)
thanks alot in advance!!
At the beginning of your code (but after you call pygame.init()), add the following line of code:
pygame.key.set_repeat(10)
This will post keyboard events to the event queue every 10 milliseconds even if the key was already pressed.