My spawning system doesn't work and I dont know why - python

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)>
...

Related

pygame having trouble spawning enemies

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))

Python, variable importing between files

Preamble: I feel I probably have wasted so much time for a simple situation...
Now, I am making a game with pygame, and at some point, I wanted to split files into two, namely main.py and configurations.py in order to make it more readable.
Everything was going good, until I ran into this problem.
I will share whole code at the bottom, but I want to summarize first:
Now first of all, in main.py, I am importing by,
from configurations import *
now, the game loop on the main.py depends on the variable running by
while running:
.......
.......
.......
And the variable running is initialized in configurations.py by,
# initialize some variables
running = True
So, the main.py has to be getting variable running because it does not give any error and uses it in while running statement.
In the main loop, there is a section where I check for events as follows,
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
This part works just as expected, it alters variable running and program gets out of the while loop.
Now, here comes the problematic part.
In one of the classes(Player class) there is a method as decrease_HP,
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
However, the point I could not figure out is that, it is not changing running variable properly, and game never stops(gets out of while loop). Here is one example output which shows the problem.
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
-1 HP Current HP: 2
-1 HP Current HP: 1
-1 HP Current HP: 0
-1 HP Current HP: -1
-1 HP Current HP: -2
-1 HP Current HP: -3
-1 HP Current HP: -4
-1 HP Current HP: -5
-1 HP Current HP: -6
So, I hope I could make it clear. I probably have a misunderstanding about importing variables or variable scopes.
By the way, I have tried adding global running above the running = False statement in Player.decrease_HP function.
Thanks in advance.
Exact codes in the files
main.py
# Pygame template - skeleton for a new pygame project
from configurations import *
# initiate some variables
max_bullet = 10
# initialize pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("CORONA RACE")
clock = pygame.time.Clock()
player = Player()
all_sprites.add(player)
# initialize some variables
running = True
# have to use this because, otherwise, for the first SPACE key pressing, the newest_bullet is not defined yet.
newest_bullet = Bullet(0, 0)
# Game loop
while running:
# keep loop running at the right speed
clock.tick(FPS)
# Process input (events)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
else:
pass
while len(mobs) != 5:
m = Mob()
all_sprites.add(m)
mobs.add(m)
keystate = pygame.key.get_pressed()
player.speedx = 0
if keystate[pygame.K_RIGHT]:
player.speedx += player.SPEED
if keystate[pygame.K_LEFT]:
player.speedx -= player.SPEED
if keystate[pygame.K_SPACE] and player.rect.top - newest_bullet.rect.bottom > BULLET_H + MARGIN and not len(bullets) >= max_bullet:
newest_bullet = player.shoot()
# BULLET_H refers to height of the bullet and margin refers to the minimum allowable margin between two consequent b
# If there are more than 10 bullets at a time on the screen, then no more new bullets can be fired.
if keystate[pygame.K_ESCAPE]:
running = False
if random.randint(0, 14530) > 14470:
power_up = PowerUp()
all_sprites.add(power_up)
powerups.add(power_up)
hits = pygame.sprite.spritecollide(player, powerups, True)
for pu in hits:
power_up_funcs[pu.type](player)
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for m in hits:
pass
hits = pygame.sprite.spritecollide(player, mobs, True)
if hits:
player.decrease_HP()
# print(player.HP)
# Update
all_sprites.update()
# Draw / render
screen.fill(WHITE)
all_sprites.draw(screen)
# *after* drawing everything, flip the display
pygame.display.flip()
pygame.quit()
raise SystemExit # to exit python
configurations.py
import pygame
import random
# define constants
WIDTH = 600
HEIGHT = 960
FPS = 30
BULLET_H = 24
BULLET_W = 8
POWERUP_H = 30
POWERUP_W = 30
MOB_W = 50
MOB_H = 80
MARGIN = 10
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
CYAN = (0, 255, 255)
# create sprite groups
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
mobs = pygame.sprite.Group()
# initialize some variables
running = True
# player sprite
class Player(pygame.sprite.Sprite):
SPEED = 15
def __init__(self):
super().__init__()
# pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((100, 150))
self.image.fill(CYAN)
pygame.draw.circle(self.image, RED, (50, 75), 15, 5)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 5
self.speedx = 0
self.HP = 3
def update(self):
self.rect.x += self.speedx
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
return bullet # I need this to set the margin in continious fire.
def change_color(self):
pass
def increase_HP(self):
if self.HP <= 2:
self.HP += 1
print("+1 HP", "Current HP:", self.HP)
else:
print("HP IS ALREADY FULL", "Current HP:", self.HP)
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
class Mob(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((MOB_W, MOB_H))
self.image.fill(MAGENTA)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
self.rect.bottom = random.randint(-2 * MOB_H, 0)
def update(self):
self.rect.y += 6
if self.rect.top > HEIGHT:
self.kill()
# Bullet sprite
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((BULLET_W, BULLET_H))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.bottom = y
self.speedx = 0
self.speedy = -20
def update(self):
self.rect.y += self.speedy
# kill it if it moves away from the screen
if self.rect.bottom < 0:
self.kill() # built in method of pygame.sprite
# powerup sprite
power_up_funcs = [Player.increase_HP, print] # container for to-do functs.
class PowerUp(pygame.sprite.Sprite):
SPEEDY = 8
def __init__(self):
super().__init__()
self.type = random.randint(0, 1) # [0,1] integer
if self.type == 0: # HP power up
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx #debug
self.rect.bottom = 0
elif self.type == 1: # shield
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx # debug
self.rect.bottom = 0
else:
pass
def update(self):
self.rect.y += self.SPEEDY
if self.rect.top > HEIGHT:
self.kill()
TLDR: Use import configuration and fully qualified names, e.g. configuration.running.
If a function inside configuration needs to modify a top-level value, it must use global.
def decrease_HP(self):
global running
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
Using from configurations import running (or equivalent via ... import *) in main binds the value of configurations.running to a new name main.running. While these names initially share the same value, re-assigning either breaks this equivalency. This is exactly the same as rebinding other names.
>>> a = 1
>>> b = a # a and b point to same value
>>> a == b
True
>>> b = 2 # rebind only b
>>> a == b
False
To make changes visible across the application, one should use an object an modify its value. A common example are containers, such as lists.
>>> a = [1]
>>> b = a # a and b point to same value
>>> a == b
True
>>> b[0] = 2 # modify content of value of b
>>> a == b
True
>>> a[0] == b[0] # content is the same
True
Since modules are objects, it is possible to use them directly to store state.
>>> import configuration
>>> b = configuration # configuration and b point to same value
>>> configuration == b
True
>>> b.running = False # modify content of value of b
>>> configuration == b
True
>>> configuration.running == b.running # content is the same
True
Functions have local scope. Any assignment to a name inside a function implicitly declares the target as local to the function.
>>> running = True
>>> def stop():
... running = False
...
>>> stop() # changes only running inside the function
>>> running
True
This can be made visible by accessing a local name before it has a value.
>>> running = True
>>> def stop():
... print(running)
... running = False
...
>>> stop()
UnboundLocalError: local variable 'running' referenced before assignment
By declaring the name global, no local name is created. The global value can be directly read and written.
>>> running = True
>>> def stop():
... global running
... print(running)
... running = False
...
>>> stop() # will print the global value before the change
True
>>> running # global value was changed
False

Sprites won't show up

Cannot figure out why my code won't show sprites when I run it. The code is from a how-to book I found at my library, I have reread the code multiple times and haven't found anything different than what the book says to do. This is my first time coding on python and I haven't seen anything to help me solve my problem yet. This is all the code I've written for the game so far.
import pygame
from pygame import *
from random import randint
pygame.init()
WINDOW_WIDTH = 1100
WINDOW_HEIGHT = 600
WINDOW_RES = (WINDOW_WIDTH, WINDOW_HEIGHT)
WIDTH = 100
HEIGHT = 100
WHITE = (255, 255, 255)
SPAWN_RATE = 360
GAME_WINDOW = display.set_mode(WINDOW_RES)
display.set_caption('Attack of the vampire Pizzas!')
pizza_img = image.load('vampire.png')
pizza_surf = Surface.convert_alpha(pizza_img)
VAMPIRE_PIZZA = transform.scale(pizza_surf, (WIDTH, HEIGHT))
background_img = image.load('restaurant.jpg')
background_surf = Surface.convert_alpha(background_img)
BACKGROUND = transform.scale(background_surf, WINDOW_RES)
class VampireSprite(sprite.Sprite):
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
all_vampires.add(self)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self, game_window):
game_window.blit(self.image, (self.rect.x, self.rect.y))
all_vampires = sprite.Group()
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(BACKGROUND, tile_color, (WIDTH * column, HEIGHT * row, WIDTH, HEIGHT), 1)
GAME_WINDOW.blit(BACKGROUND, (0,0))
----------------------------------------------
#Start Main Game Loop
game_running = True
#Game Loop
while game_running:
for event in pygame.event.get():
#Exit loop on quit
if event.type == QUIT:
game_running = False
if randint(1, SPAWN_RATE) == 1:
VampireSprite()
for vampire in all_vampires:
vampire.update(GAME_WINDOW)
display.update()
pygame.quit()
The Code seems to have all the correct components, except that it's a bit mixed up.
Generally when you make a sprite, it has the __init__() function - obviously for initialisation, and additionally an update() function. The update() function usually does not draw the object to the display/surface, but adjusts the position (i.e.: the sprite.rect) or changes the "look" (image) used for the sprite, for drawing later.
Sprites are usually grouped into an aptly-named Sprite Group. Once sprites are in a group, a single call to group.update() will call the update() function on every sprite contained. It's really convenient, and works well.
So tweaking your Vampire Pizza Sprite:
class VampireSprite(sprite.Sprite):
def __init__(self):
super().__init__()
self.speed = 2
self.lane = randint(0, 4)
self.image = VAMPIRE_PIZZA.copy()
y = 50 + self.lane * 100
self.rect = self.image.get_rect(center = (1100, y))
def update(self):
# TODO - do Vampire Pizzas Move?
# if so, update the self.rect
pass
And that's all that's needed. I removed the painting code, this will be handled by a sprite group.
So later on:
# Make a group of vampire sprites
all_vampires = sprite.Group()
all_vampires.add( VampireSprite() ) # Start with a single vampire
# Game Loop
game_running = True
while game_running:
# handle events
for event in pygame.event.get():
#Exit loop on quit
if event.type == QUIT:
game_running = False
# spawn some vampires, maybe
if randint(1, SPAWN_RATE) == 1:
all_vampires.add( VampireSprite() ) # add to the group
# Update/move all vampire sprites
all_vampires.update() # updates every sprite in group
# repaint the screen
GAME_WINDOW.blit(BACKGROUND, (0,0))
# draw some columns(?)
tile_color = WHITE
for row in range(6):
for column in range(11):
draw.rect(GAME_WINDOW, tile_color, (WIDTH * column, HEIGHT * row, WIDTH, HEIGHT), 1)
all_vampires.draw() # paints every sprite in group
display.update()
pygame.quit()
There's two calls from the all_vampires sprite group - all_vampires.update() and all_vampires.draw()? With just these two calls, all sprites in the group are moved (adjusted/whatever), and painted to the screen.

pygame - problem with killing a sprite in a group

I am trying to create a game using pygame for a school project. I would like obstacles(in this case boxes) which get in the way of the player. The player is able to destroy the boxes which would result in another box to be spawned in a random location at the same height.
I've split the code into 3 seperate modules seperating the sprites, the main code and the settings(game variables).
main:
import pygame as pg
import random
from sprites import *
from settings import *
import os
import sys
import time
class Game:
def __init__(init):#initialising the games properties(window,sound,speed,etc).
pg.init()
pg.mixer.init()
init.clock = pg.time.Clock()
init.screen = pg.display.set_mode((WIDTH,HEIGHT))
pg.display.set_caption(TITLE)
init.clock = pg.time.Clock()
init.running = True
init.font_name = pg.font.match_font(FONT_NAME)
init.data()
def data(load):
load.dir = os.path.dirname(__file__)
def new(new):#starts the game again.
new.score = 0
new.obstacles = pg.sprite.Group()
new.platforms = pg.sprite.Group()
new.bullets = pg.sprite.Group()
new.all_sprites = pg.sprite.Group()
new.player = Player(new)
new.all_sprites.add(new.player)
for plat in PLATFORM_LIST:
p = Platform(*plat)
new.all_sprites.add(p)
new.platforms.add(p)
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
new.run()
def run(run):
run.playing = True
while run.playing:
run.cooldown = 0
run.clock.tick(FPS)
run.events()
run.update()
run.draw()
def update(update):
bullet = Bullet
#game update.
update.all_sprites.update()
#spawning obstacles lower half
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
#spawning obstacles randomly throughout the middle half
#spawning obstacles randomly throughout the map upper half
#check if bullet collides with an obstacles.
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
#check if player hits the sides of an obstacle.
if update.player.velocity.x >0:#when moving right.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery+20:#if the player is above the platform.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
update.player.acceleration.y = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits side of platforms
if update.player.velocity.x >0 and (update.player.velocity.y < 0):#when moving right.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y < collide[0].rect.centery+50:#if the player is below the obstacle.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y > collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits a platform while ascending:
if update.player.velocity.y <0:#only when moving up.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y > collide[0].rect.bottom:
update.player.pos.y = collide[0].rect.bottom + (PLAYER_HEIGHT/2) + PLAYER_JUMP
update.player.velocity.y = 0
#check if a player hits a platform while falling.
if update.player.velocity.y >0:#only while falling will this apply.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)#false allows you to avoid deleting the object you jump into.
if collide:
if update.player.pos.y < collide[0].rect.centery:#if the player is above the center of the platform.
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y < collide[0].rect.centery:
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
#spawning obstacles randomly throughout the map upper half
#spawning obstacles randomly throughout the middle half
def events(events):
events.cooldown += events.clock.get_time()
#processes inputs.
for event in pg.event.get():
#check for window closing.
if event.type == pg.QUIT:#if the 'x' button is clicked
if events.playing:
events.playing = False#stop the game loop.
events.running = False#stop the main loop.
if event.type == pg.KEYDOWN:
if event.key == pg.K_UP:
events.player.jump()
if event.key == pg.K_SPACE:
events.player.bullet_list.append(events.player.shoot())
#print(len(events.player.bullet_list))
def draw(draw):
draw.screen.fill(GREY)# creates a black screen.
draw.draw_text(str(draw.player.PLAYER_HEALTH),24,BLACK,WIDTH/32,HEIGHT /32)
draw.all_sprites.draw(draw.screen)#draws the sprites in the group all_sprites.
#after drawing the screen is flipped.
pg.display.flip()
def start_screen(start):#screen displayed when the game is started.
start.screen.fill(BGCOLOR)
start.draw_text(TITLE,48,WHITE,WIDTH/2,HEIGHT /4)
start.draw_text("Arrows to move,UP to jump", 22,WHITE,WIDTH/2,HEIGHT/2)
start.draw_text("Press a key to play",22,WHITE,WIDTH/2,HEIGHT*3/4)
pg.display.flip()
start.any_key()#temporary key to start system.
def any_key(wait):
waiting = True
while waiting:#a loop is used for the start screen until an action is done.
wait.clock.tick(FPS)#allows animations to
for event in pg.event.get():
if event.type == pg.QUIT:#if the 'x' button is pressed during the start screen.
waiting = False
wait.running = False#stops the main loop.
if event.type == pg.KEYUP:#if any key is released.
waiting = False
def over_screen(over):#displayed when the game ends.
if not over.running:
return#skips the over screen when 'x' button is pressed.
over.screen.fill(BGCOLOR)
over.draw_text('GAME OVER',48,WHITE,WIDTH/2,HEIGHT /4)
def draw_text(self, text, size, color, x, y):
font = pg.font.Font(self.font_name, size)#selects the chosen font.
text_surface = font.render(text, True, color)#creates the text with anti aliasing and the color chosen.
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)#position of text.
self.screen.blit(text_surface, text_rect)#renders text on screen.
g = Game()
g.start_screen()
while g.running:#the main loop.
g.new()
g.over_screen()
pg.quit()#closes the window.
sprites:
#Sprite class
import random
import pygame as pg
from settings import *
vec = pg.math.Vector2 #creates a 2D Vector which stores the x an y cordinates for the sprites.
class Player(pg.sprite.Sprite):
def __init__(self, game):#create initialise the properties of the sprite.
self.game = game#reference to variable in game class.
pg.sprite.Sprite.__init__(self)#provides functions for the sprite in other functions.
self.image = pg.Surface((PLAYER_WIDTH,PLAYER_HEIGHT))#creates a square for the Player to be used as a hitbox.
self.image.fill(GREEN)#place holder for the player.
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2),(HEIGHT/4)# allows you to move the character.
self.pos = vec(WIDTH/2,HEIGHT/2)#the center of the sprite.
self.velocity = vec(0,0)#the speed of the player.
self.acceleration = vec(0,0)#allows for the change in speed.
self.facing = 0 #direction the player is looking.
self.current = 0#current direction facing.
self.PLAYER_HEALTH = 1000
self.bullet_list = []
def jump(self):
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
if hits:#only able to jump when colliding with platform.
self.velocity.y += -PLAYER_JUMP
collide = hits = pg.sprite.spritecollide(self, self.game.obstacles, False)
if collide:
self.velocity.y += -PLAYER_JUMP
def shoot(self):
#if game.cooldown > 400:
#cooldown = 0
self.bullet = Bullet(self,self.current,self.rect.centerx, self.rect.top)
self.bullet_list.append(self.bullet)
for bullet in self.bullet_list:
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
self.game.all_sprites.add(self.bullet)
self.game.bullets.add(self.bullet)
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
#self.game.all_sprites.add(self.bullet)
#self.game.bullets.add(self.bullet)
def update(self):
self.acceleration = vec(0,PLAYER_GRAV)#resets the position of player when not moving.
keys = pg.key.get_pressed()#inputs a pressed key.
if keys[pg.K_LEFT]:
self.acceleration.x = -PLAYER_ACC
self.facing = -1
self.current = self.facing
if keys[pg.K_RIGHT]:
self.acceleration.x = PLAYER_ACC
self.facing = 1
self.current = self.facing
if self.acceleration.x == 0:#if standing, the previous direction is saved
self.facing = self.current
#print(self.current)
#friction.
self.acceleration.x += self.velocity.x * PLAYER_FRICTION
#equation for displacment.
self.velocity += self.acceleration
self.pos += self.velocity + 0.5 * self.acceleration#moves thes players position to the new x,y co-ordinate.
#boundaries of screen.
if self.rect.right > WIDTH:
self.pos.x = WIDTH -(PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
self.velocity.x = 0 #avoids player sticking to walls by capping speed at boundaries.
self.acceleration.x = 0 #reduces the amount of 'jitter' when trying to move past boundaries.
if self.rect.left < 0 :
self.pos.x = (PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
# have to add half the player width to avoid player going into walls.
self.velocity.x = 0 #avoids player sticking to walls by stopping player at boundaries.
self.rect.midbottom = self.pos#tracks the position of the players center.
class Platform(pg.sprite.Sprite,):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
class Bullet(pg.sprite.Sprite):
damage = 25
def __init__(self,player,current, x, y):
self.player = player#allows the bullet class to use player variables.
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((20,10))
self.image.fill(LBLUE)
self.rect = self.image.get_rect()
self.rect.right = x
self.rect.centery = y + (PLAYER_HEIGHT/2)
#self.damage = 25
self.velocity = vec(0,0)
if current == -1:#when looking left.
self.velocity = vec(-10,0)
if current == 1:#when looking right.
self.velocity = vec(10,0)
def update(self):
self.rect.right += self.velocity.x
#remove when moves of off screen.
if self.rect.right > WIDTH:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
if self.rect.left <0:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
#print(self.rect.x)
settings:
#settings
TITLE = "downpour"
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'Ariel'
#platforms
PLATFORM_LIST = [(0, HEIGHT - 50, WIDTH, 50),
(WIDTH -225 ,HEIGHT * 3/4 -50,200, 40),#(x,y,width,height)of the platforms.
(0 +25 ,HEIGHT * 3/4 -50,200, 40),
(0 +350,HEIGHT * 3/4 -150,200, 40)]
OBSTACLE_LIST = [(WIDTH/2,HEIGHT -50-50,100,50),(WIDTH/3,HEIGHT -50-50,100,50),(WIDTH/2,HEIGHT -50-50,100,50)]
#player properties
PLAYER_WIDTH = 50
PLAYER_HEIGHT = 50
PLAYER_ACC = 0.55
PLAYER_FRICTION = -0.05
PLAYER_GRAV = 0.8
PLAYER_JUMP = 15
#colors defines
WHITE = (255,255,255)
BLACK = (0,0,0)
GREY = (211,211,211)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
LBLUE = (132,112,255)
BGCOLOR = LBLUE
The problem I have encountered is with spawning a new box after destroying one of the multiple boxes. A box can be destroyed by depleting its health through shooting at it.
Lets say I have 3 boxes: A,B and C. when I try to destroy B or C, box A is the one that is destroyed and respawned.
I feel like it's an obvious answer...
code relating to the obstacle:
creating the class:
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
adding it to a Sprite group:
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
collisions:
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
spawning a new obstacle:
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
First of all, for all instance methods, it would help the reader if you used the name self instead of all the custom names you're using such as new or update for the first argument.
After that rewrite, you code will look like follows:
collide = pg.sprite.groupcollide(self.bullets,self.obstacles,True,False)
if collide:
self.obstacle.obs_health = self.obstacle.obs_health - bullet.damage
if self.obstacle.obs_health == 0:
self.obstacle.kill()
Now ask yourself, why does the program know that the self.obstacle is the one that collided? Should self.obstacle even exist? It looks like self.obstacle was just used a temporary local variable upon creation of the Game class to add Obstacle's to self.obstacles.
If so just use a local variable as follows:
for obs in OBSTACLE_LIST:
obstacle = Obstacle(*obs)
self.all_sprites.add(obstacle)
self.obstacles.add(obstacle)
At this point, hopefully the error message will make it clear that referencing self.obstacle isn't going to work. pg.sprite.groupcollide returns you a Sprite_dict, so you need to extract the obstacle from collide to figure out what has collided.
#KentShikama Thanks for pointing that out.
I have fixed the issue by using the dictionary.
for obstacles, bullets in collide.items():
obstacles.obs_health = obstacles.obs_health - bullet.damage
if obstacles.obs_health == 0:
obstacles.kill()

Pygame Move Image Speed

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.

Categories