I'm trying to create a simple game where u have to dodge enemies(asteroids), with pygame, but now I'm having trouble spawning them and I don't know if i should use lists or other things, or the enemy class(asteroidClass) is enough. The interval between spawning them is pretty simple I just don't how to deal with the spawn part (dealing with this for 3 days).
import pygame
import random
pygame.init()
#images
background = pygame.image.load('#path')
asteroid = pygame.image.load('#path')
display = pygame.display.set_mode((300,500))
FPS = 50
display.blit(background,(0,0))
#player everything is fine
#asteroid
class asteroidClass:
def __init__(self,asteroidX,asteroidY,asteroidVel):
self.x = asteroidX
self.y = asteroidY
self.vel = asteroidVel
def asteroid_advancing(self):
self.y += self.vel
display.blit(asteroid, (self.x, self.y))
def update():
pygame.display.update()
pygame.time.Clock().tick(FPS)
#variables
asteroidX = random.randint(0,250)
asteroidY, asteroidVel = 0, 2
asteroidOutClass = asteroidClass(asteroidX,asteroidY,asteroidVel)
#main loop
run = True
while run:
#should spawn multiple I don't know with what logic
#spawning in the same x
#saw in web they're using lists, maybe i should too?
#when i print i it just do 0123456 like it should, then restart to 0123456, is it wrong? kinda 100%
for i in range(7):
asteroidOutClass.asteroid_advancing() #everytime it's called should spawn and asteroid in random x?
update()
display.blit(background, (0, 0))
A few things:
Create the asteroids before the main loop
Use the init method to randomly set asteroid position and speed
Be sure to call an event method in the main loop to prevent freezing
Here's the updated code. I removed the background calls and used a circle for the asteroid.
import pygame
import random
pygame.init()
#images pygame.load() not in here
display = pygame.display.set_mode((300,500))
FPS = 50
#display.blit(background,(0,0))
#player everything is fine
#asteroid
class asteroidClass:
def __init__(self):
self.x = random.randrange(0, 300)
self.y = 0 # always top
self.velx = random.randrange(1,15)/3
self.vely = random.randrange(1,15)/3
def asteroid_advancing(self):
self.y += self.vely
self.x += self.velx
# wrap around screen
if self.x < 0: self.x=300
if self.x > 300: self.x=0
if self.y < 0: self.y=500
if self.y > 500: self.y=0
pygame.draw.circle(display, (200, 0, 0), (int(self.x), int(self.y)), 20) # draw asteroid
def update():
pygame.display.update()
pygame.time.Clock().tick(FPS)
#variables
# create list of 5 asteroids
roidlist = [asteroidClass() for x in range(7)]
#main loop
run = True
while run:
for event in pygame.event.get(): # required for OS events
if event.type == pygame.QUIT:
pygame.quit()
display.fill((0,0,0))
#should spawn multiple I don't know with what logic
#spawning in the same x
#saw in web they're using lists, maybe i should too?
#when i print i it just do 0123456 like it should, then restart to 0123456, is it wrong? kinda 100%
for r in roidlist:
r.asteroid_advancing() #everytime it's called should spawn and asteroid in random x?
update()
# display.blit(background, (0, 0))
Related
I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()
I am working on a school project with pygame and have unfortunately run into trouble with animating my sprites, or more specifically, changing the sprite from one to another, and I'm unsure on how to do it. Currently, I have a class of my spaceship sprite and background sprite:
game_folder = os.path.dirname(__file__)
img_folder = os.path.join(game_folder, 'img')
space_background = pygame.image.load('background.png').convert()
starship_1 = pygame.image.load('starship2.png').convert()
starship_2 = pygame.image.load('starship3.png').convert()
starship_3 = pygame.image.load('starship4.png').convert()
class starship(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
pygame.sprite.Sprite.__init__(self)
self.image = pygame.transform.scale(starship_1, (150,150))
self.image.set_colorkey(black)
self.rect = self.image.get_rect()
self.rect.center = (400, 400)
all_sprites = pygame.sprite.Group()
BackGround = background()
StarShip = starship()
all_sprites.add(BackGround)
all_sprites.add(StarShip)
My while loop looks like this:
run = True
while run:
pygame.time.delay(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and StarShip.rect.x > 25:
StarShip.rect.x -= vel
if keys[pygame.K_RIGHT] and StarShip.rect.x < 600:
StarShip.rect.x += vel
if keys[pygame.K_UP] and StarShip.rect.y > 25:
StarShip.rect.y -= vel
if keys[pygame.K_DOWN] and StarShip.rect.y < 600:
StarShip.rect.y += vel
win.fill((0,0,0))
all_sprites.update()
all_sprites.draw(win)
pygame.display.update()
pygame.quit()
This has basic movement of left/right/up/down. What I want to do is have my StarShip object constantly change between the variables starship_1, starship_2, starship_3 (which contain my 3 sprites of my starship), so it looks like the starship is moving.
My sprites look like this:
As you can see, it is the engine fire that is the difference between those sprites. How would I change between those 3 sprites every 1 second?
FYI: When the program is launched, this is what appears:
Thank you!
There are 2 parts to achieve this effect.
Create a function to change image of the sprite. ( Easy Task)
Call the above function periodically every x seconds. ( Intermediate Task)
Step 1.
You can achieve this by just setting/loading the self.image variable with the next image.
Step 2.
clock = pygame.time.Clock()
time_counter = 0
images = ['starship_1', 'starship_2', 'starship_3']
current_img_index = 0
while run:
# Your Code
time_counter = clock.tick()
# 1000 is milliseconds == 1 second. Change this as desired
if time_counter > 1000:
# Incrementing index to next image but reseting back to zero when it hits 3
current_img_index = (current_img_index + 1) % 3
set_img_function(current_img_index) # Function you make in step 1
# Reset the timer
time_counter = 0
A good approach will be to complete step 1 and then bind it to a button. Test if it works and then move on to step 2.
Some good read about the functions used in this code to fully understand them is here
In simple words my spawning system doesnt work
I am making a dodge the blocks game and I want 1 out of 5 spawnpoints to be empty so that the player can dodge the others. Of course that spawnpoint is random. I tried changing the numbers and looking at some of my older and similar games but nothing works
EDIT: I posted the other 2 files so that you can run the game. I dont think that they are part of the problem since they don't contain anything related to the spawning process
Here my main file:
import pygame as pg
import random
from sprites import *
from game_options import *
class Game:
def __init__(self):
pg.init()
pg.mixer.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption("BRUH")
self.clock = pg.time.Clock()
self.running = True
def new(self):
self.SPAWNENEMIES = 1
self.my_event = pg.event.Event(self.SPAWNENEMIES)
pg.event.post(self.my_event)
pg.time.set_timer(self.SPAWNENEMIES, 3000)
self.spawnpoint1 = 20, -80
self.spawnpoint2 = 140, -80
self.spawnpoint3 = 260, -80
self.spawnpoint4 = 380, -80
self.spawnpoint5 = 500, -80
self.spawnpoints = (self.spawnpoint1,self.spawnpoint2,self.spawnpoint3,self.spawnpoint4,self.spawnpoint5)
self.all_sprites = pg.sprite.Group()
self.blocks = pg.sprite.Group()
self.player = Player()
self.all_sprites.add(self.player)
self.all_sprites.add(self.blocks)
g.run()
def run(self):
self.running = True
while self.running:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
self.all_sprites.update()
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.running = False
if event.type == self.SPAWNENEMIES:
num = random.randint(0,len(self.spawnpoints))
#print(num)
for i in range(5):
if num != i:
print(i)
self.block = Block(self.spawnpoints[i])
self.blocks.add(self.block)
self.all_sprites.add(self.blocks)
dead_blocks = pg.sprite.spritecollide(self.player, self.blocks, True)
# if dead_blocks:
# self.running = False
def draw(self):
self.screen.fill(MY_RED)
self.all_sprites.draw(self.screen)
pg.display.flip()
g = Game()
while g.running:
g.new()
g.quit()
Here is game_options.py:
WIDTH = 580
HEIGHT = 800
FPS = 30
# Simple colors
WHITE = (255,255,255)
BLACK = (0,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
RED = (255,0,0)
MY_RED = (255, 67, 67)
GREY = (108,106,106)
and the sprites.py
import pygame as pg
from game_options import *
class Player(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((70,70))
self.image.fill(WHITE)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2, HEIGHT - 100)
self.speedx = 0
def update(self):
keystate = pg.key.get_pressed()
if keystate[pg.K_LEFT]:
self.speedx += -30
if keystate[pg.K_RIGHT]:
self.speedx += 30
self.rect.x = self.speedx
class Block(pg.sprite.Sprite):
def __init__(self,position):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((100,70))
self.image.fill(GREY)
self.rect = self.image.get_rect()
self.rect.center = position
self.speedy = 20
def update(self):
self.rect.y += self.speedy
if self.rect.x > HEIGHT:
self.kill()
I expected to have 1 out of 5 spawnpoints empty but for some reason when I run the game, the first "wave" of sprites always has no empty space. Meanwhile the game continues with the expected results. No error messages
Any help would be appreciated
the first "wave" of sprites always has no empty space
This is due to:
self.SPAWNENEMIES = 1
Change it to:
self.SPAWNENEMIES = pg.USEREVENT
The id of the event is an integer. The pygame event system use a set of integers to identify ordinary actions (mouse motion or button clicks, key press and so on). As the docs says:
User defined events should have a value in the inclusive range of USEREVENT to NUMEVENTS - 1.
You should not define self.SPAWNENEMIES equal to 1, because 1 is reserved another type of event (not sure which one actually), this creates confusion and unexpected behaviour.
Seems that in your case yourv event in fact is posted multiple times, especially at the beginning, so you have two waves superimposed. Unless by chance both waves have the missing block at the same position, you'll see 5 blocks.
Another thing you should fix is:
num = random.randint(0,len(self.spawnpoints))
it should be:
num = random.randint(0,len(self.spawnpoints)-1)
or, alternatively:
num = random.randrange(0,len(self.spawnpoints))
The function random.randint:
Return a random integer N such that a <= N <= b. Alias for randrange(a, b+1).
The endpoint is inclusive.
You have 5 blocks, whose index goes from 0 to 4. When random.randint returns 5, no one of them is removed from the following spawning loop.
I'm not familiar with pygame, but i don't see where you reset the blocks once they pass through the dodging block. I'd guess that everytime you are adding blocks over the existing blocks and they are redrawn on top of the old ones, therefore it's not easily clear that there're bazillion of blocks raining down on each run.
And adding a print(self.blocks) puts out this, proving my guess :D
...
<Group(15 sprites)>
<Group(19 sprites)>
<Group(23 sprites)>
...
so I need to remove one sprite from the group every time I press the - (minus) key. However, I am confused as to how to do it. Another issue I've run into is when I press the + key it is adding more than one sprite at a time. I'm guessing I need to also check for KEYUP ? I also have a bug where some of the sprites will be created on top of each and will stay stuck bouncing off one another. How can I check for existing sprites where the new will be generated? Here is my code
dragon.py
import pygame
from pygame.locals import *
class Dragon(pygame.sprite.Sprite):
def __init__(self, x,y, vx, vy):
super().__init__();
self.image = pygame.image.load("images/dragon.png").convert()
self.image.set_colorkey(pygame.Color(255,0,255))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.vx = vx
self.vy = vy
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.rect.x, self.rect.y))
def move(self, SCREEN):
r_collide = self.rect.x + self.image.get_width() + self.vx >= SCREEN.get_width()
l_collide = self.rect.x + self.vx <= 0
t_collide = self.rect.y + self.vy <= 0
b_collide = self.rect.y + self.image.get_height() + self.vy >= SCREEN.get_height()
# Check collision on right and left sides of screen
if l_collide or r_collide:
self.vx *= -1
# Check collision on top and bottom sides of screen
if t_collide or b_collide:
self.vy *= -1
self.rect.x += self.vx
self.rect.y += self.vy
def bounce(self, SCREEN):
self.vy *= -1
self.vx *= -1
dragon_animation.py
import pygame
import sys
from random import *
from pygame.locals import *
from flying_dragon.dragon import Dragon
def main():
pygame.init()
FPS = 30
FPS_CLOCK = pygame.time.Clock()
''' COLOR LIST '''
WHITE = pygame.Color(255,255,255)
''' create initial window '''
window_size = (500, 500)
SCREEN = pygame.display.set_mode(window_size)
''' set the title '''
pygame.display.set_caption("Flying Dragons")
''' fill background color in white '''
SCREEN.fill(WHITE)
''' group to store dragons '''
dragons = pygame.sprite.Group()
d1 = Dragon(0,0,3,2)
d1.draw(SCREEN)
dragons.add(d1)
''' main game loop '''
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
SCREEN.fill(WHITE)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_PLUS or pygame.K_KP_PLUS:
d = Dragon(randint(1, SCREEN.get_width() - dragon.rect.x), randint(1, SCREEN.get_height()- dragon.rect.y), randint(1, 5), randint(1, 5))
dragons.add(d)
for dragon in dragons:
dragons.remove(dragon)
collisions = pygame.sprite.spritecollide(dragon, dragons, False)
dragons.add(dragon)
for dragon in collisions:
dragon.bounce(SCREEN)
for dragon in dragons:
dragon.move(SCREEN)
dragons.update(SCREEN)
dragons.draw(SCREEN)
pygame.display.update()
FPS_CLOCK.tick(FPS)
if __name__ == "__main__":
main()
if event.key == pygame.K_MINUS or pygame.K_KP_MINUS:
dragons.remove(dragons[0])
Should work for removing a sprite. Sprites aren't ordered in the group so will just delete a random one.
As another pointer, you should look at redefining your Dragon.move() function as dragon.update() then the Group.update() call will move all your dragons.
you don't need to draw the dragon immediately after it's creation before the while loop. The draw inside the while loop is sufficient.
When you press a key the event list will hold which key you are holding down and as long as this is down a dragon will be created. As the loop runs pretty fast several dragons will be created before you remove your finger from the keyboard.
also good to do
pygame.event.pump()
before the for event in pygame.event.get(): so that you clear the event list beofre the next run.
read about the pygame event pump here.
Use key = pygame.key.get_pressed() instead of event.get as it will read a key press ONCE.
can't run your code because of this error.
ImportError: No module named flying_dragon.dragon
I'm a newb when it comes to programming, but I'm learned a lot so far and I'm trying to make a very basic RPG.
I want to move my image object oPC with a mouse click. I've been able to accomplish this with the code I'm sharing below, however, no matter where I click on the screen the image takes the same amount of steps/time to get there. For instance, if I click a few inches away from the object it will gradually shift across the screen towards the target location just as fast as if I'd click right off the image.
I've been stuck trying to figure out a way to solve this issue for the last few days. Is there someway to use time for movement as opposed to the steps I've used?
Thanks!
import pygame, sys
import oPC
pygame.init()
WINDOWSIZE = (1000, 800)
BLACK = (0, 0, 0)
screen = pygame.display.set_mode((WINDOWSIZE))
pygame.display.set_caption("Game")
screen.fill(BLACK)
terrain = pygame.image.load("terrain.jpg").convert()
terrainRect = terrain.get_rect()
terrain = pygame.transform.scale(terrain, ((WINDOWSIZE)))
screen.blit(terrain, terrainRect)
oPC = oPC.Player()
oPC.draw(screen)
pygame.display.flip()
running = True
n_steps = 80
while running == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
mlocX, mlocY = pygame.mouse.get_pos()
while mlocX != oPC.rect.x and mlocY != oPC.rect.y:
clock.tick(60)
oPC.update(mlocX, mlocY, n_steps)
if n_steps > 1:
screen.fill(BLACK)
screen.blit(terrain, terrainRect)
n_steps -= 1
oPC.draw(screen)
pygame.display.flip()
n_steps = 80
pygame.quit()
#sys.exit()
import pygame, sys
class Player(object):
def __init__(self):
self.image = pygame.image.load("tipping over s0000.bmp").convert()
self.rect = self.image.get_rect()
self.name = " "
self.stats = [0, 0, 0, 0, 0] #str, int, dex, health, mana
self.admin = False # False = Member / True = Administrator
def draw(self, screen):
self.image = pygame.transform.scale(self.image, (75, 75))
screen.blit(self.image, (self.rect.x, self.rect.y))
def update(self, mlocX, mlocY, n_steps):
self.rect.x += (mlocX - self.rect.x) / n_steps
self.rect.y += (mlocY - self.rect.y) / n_steps
Your design has a few flaws.
You are calling clock.tick() only on the MOUSEBUTTONDOWN event. It should be called on every frame.
Your bliting and display.update should also be done in the loop, not in the event queue.
The player update should also be called in the loop.
You scale your image each time you call draw. I think you may want to do that in the init method only. Since draw should be called repeatedly in the loop.
As for the step counter, I suggest to have a player state, and a step counter there. Something like this:
# for easier state access
def enum(*sequential, **named):
enums = dict(zip(sequential, range(len(sequential))), **named)
return type('Enum', (), enums)
player_states = enum('STATIONARY', 'MOVING')
STEPS = 30
def __init__(self):
self.state = player_states.STATIONARY
self.steps = 0
self.dest = None
def move(self,mlocX,mlocY):
if self.state != player_states.MOVING:
self.state = player_state.MOVING
self.steps = STEPS
self.dest = (mlocX,mlocY)
def update(self):
if self.steps != 0:
self.rect.x += (self.dest[0] - self.rect.x) / STEPS
self.rect.y += (self.dest[1] - self.rect.y) / STEPS
self.steps -= 1
else:
self.state = player_states.STATIONARY
As for you question with steps, you can use physics and the famous distance = velocity * time equation.
The clock.tick() method returns the number of ms passed since the last call to tick().
If you pass this to the update method, you can than change the moving equations to:
def update(self,delta):
self.rect.x += PLAYER_SPEED * direction * delta
Then pick PLAYER_SPEED to something that will suit you.