Related
I was just getting some help to figure out how to get my player fire bullets when I realized that they only go (kinda expected this but however as only had y value for movement). I don't know how I'll make the bullets fire off in the direction the player is facing.
I have some idea of what to but I just don't know how to do it... I thought I could somehow use the cursor and player tracking that's in this game for the visuals but I don't know how to make that a one-time thing instead of a constant. For diagonal movement of the bullet, I have no clue.
Code below (split into two parts/file Main.py and PlayerSprite.py):
Main:
py.init()
py.mixer.init()
screen = py.display.set_mode((WIDTH, HEIGHT))
py.display.set_caption("Dimensional Drifter")
clock = py.time.Clock()
all_sprites = py.sprite.Group()
NPCs = py.sprite.Group()
bullets = py.sprite.Group()
player = Player()
all_sprites.add(player)
for i in range(14):
n = NPC(player)
all_sprites.add(n)
NPCs.add(n)
# Game loop
running = True
while running:
# keep loop running at the right speed
clock.tick(FPS)
for event in py.event.get():
# check for closing window
if event.type == py.QUIT:
running = False
elif event.type == py.KEYDOWN:
if event.key == py.K_SPACE:
New_bullet = player.Shoot()
all_sprites.add(New_bullet)
bullets.add(New_bullet)
# Update
all_sprites.update()
# # check if there a collision between the bullet and NPC
hits = py.sprite.groupcollide(NPCs, bullets, True, True)
# check if there a collision between the player and NPC
hits = py.sprite.spritecollide(player, NPCs, True)
if hits:
running = False
# updates the position of of mouse and rotates it towards the mouse position
mouse_x, mouse_y = py.mouse.get_pos()
player.rotate(mouse_x, mouse_y)
# render
screen.fill(BLACK)
all_sprites.draw(screen)
# flip the display
py.display.flip()
py.quit()
PlayerSprite
import pygame as py
import math
import random
WIDTH = 800
HEIGHT = 600
FPS = 60
# 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)
class Player(py.sprite.Sprite):
def __init__(self):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((40, 40), py.SRCALPHA)
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT / 2
self.Yspeed = 0
self.rotatableimage = self.image
def update(self):
self.Xspeed = 0
self.Yspeed = 0
# line below allow for key press to equate to move of sprite
keypreesed = py.key.get_pressed()
if keypreesed[py.K_a]:
self.Xspeed = - 11
if keypreesed[py.K_d]:
self.Xspeed = 11
if keypreesed[py.K_w]:
self.Yspeed = - 11
if keypreesed[py.K_s]:
self.Yspeed = 11
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# line below allow the sprite to wrap around the screen
if self.rect.left > WIDTH:
self.rect.right = 0
if self.rect.right < 0:
self.rect.left = WIDTH
if self.rect.top > HEIGHT:
self.rect.top = 0
if self.rect.bottom < 0:
self.rect.bottom = HEIGHT
def rotate(self, mouse_x, mouse_y):
rel_x = mouse_x - self.rect.x
rel_y = mouse_y - self.rect.y
angle = (180 / math.pi) * -math.atan2(rel_y, rel_x)
self.image = py.transform.rotate(self.rotatableimage, int(angle))
self.rect = self.image.get_rect(center=(self.rect.centerx, self.rect.centery))
return
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top)
class NPC(py.sprite.Sprite):
def __init__(self, player):
py.sprite.Sprite.__init__(self)
self.player = player
self.image = py.Surface((30, 30)).convert_alpha()
self.image.fill(RED)
self.originalimage = self.image
self.rect = self.image.get_rect()
self.spawn()
# allows of spawning from all four side of the screen and set the x, y speed and spawn position
def spawn(self):
self.direction = random.randrange(4)
if self.direction == 0:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(-100, -40)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = random.randrange(4, 8)
elif self.direction == 1:
self.rect.x = random.randrange(WIDTH - self.rect.width)
self.rect.y = random.randrange(HEIGHT, HEIGHT + 60)
self.Xspeed = random.randrange(-2, 2)
self.Yspeed = -random.randrange(4, 8)
elif self.direction == 2:
self.rect.x = random.randrange(-100, -40)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
elif self.direction == 3:
self.rect.x = random.randrange(WIDTH, WIDTH + 60)
self.rect.y = random.randrange(HEIGHT - self.rect.height)
self.Xspeed = -random.randrange(4, 8)
self.Yspeed = random.randrange(-2, 2)
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# makes it so that NPC point to wards the player as it passes from side to side
dir_x, dir_y = self.player.rect.x - self.rect.x, self.player.rect.y - self.rect.y
self.rot = (180 / math.pi) * math.atan2(-dir_x, -dir_y)
self.image = py.transform.rotate(self.originalimage, self.rot)
# Respawns the NPC when they hit an side
if self.direction == 0:
if self.rect.top > HEIGHT + 10:
self.spawn()
elif self.direction == 1:
if self.rect.bottom < -10:
self.spawn()
elif self.direction == 2:
if self.rect.left > WIDTH + 10:
self.spawn()
elif self.direction == 3:
if self.rect.right < -10:
self.spawn()
class Bullet(py.sprite.Sprite):
def __init__(self, x, y):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Yspeed = -10
def update(self):
self.rect.y += self.Yspeed
# kill if moved of screen
if self.rect.bottom > HEIGHT or self.rect.top < 0:
self.kill()
if self.rect.right > WIDTH or self.rect.left < 0:
self.kill()
Add 2 attributes self.lastX and self.lastY to the class Player and change the attributes when the player changes the direction:
class Player(py.sprite.Sprite):
def __init__(self):
# [...]
self.lastX = 0
self.lastY = -10
def update(self):
# [...]
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
if self.Xspeed != 0 or self.Yspeed != 0:
self.lastX = self.Xspeed
self.lastY = self.Yspeed
Add an argument Xspeed ans Yspeed to the class Bullet
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Xspeed, Yspeed):
py.sprite.Sprite.__init__(self)
self.image = py.Surface((5, 5))
self.image.fill(YELLOW)
self.rect = self.image.get_rect()
self.rect.bottom = y
self.rect.centerx = x
self.Xspeed = Xspeed
self.Yspeed = Yspeed
def update(self):
self.rect.x += self.Xspeed
self.rect.y += self.Yspeed
# [...]
Set the attributes when the bullet spawns
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.centery, self.lastX, self.lastY)
Alternatively it is also possible to set the speed dependent on the direction to the mouse cursor.
Get the position of the player and the mouse cursor and compute the x and y distance (Vector ):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx = mpos[0] - pos[0]
vy = mpos[1] - pos[1]
If the mouse position and the bullet position are equal, that does not make any sense, thus the bullet is skipped
if vx == 0 and vy == 0:
return None
Of course this vector is far to long, if you would use it for the direction (Xspeed, Yspeed) directly, then the bullet would step to the mouse cursor in one turn.
In the following I use pygame.math.Vector2, because it provides the handy method scale_to_length, that scales a vector to a specified Euclidean length:
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
Now the x and y component of the vector contain the x and y component of the speed. Since the components are floating point values, they are round to integral values:
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Method Shoot:
class Player(py.sprite.Sprite):
# [...]
def Shoot(self):
pos = self.rect.center
mpos = py.mouse.get_pos()
vx, vy = mpos[0] - pos[0], mpos[1] - pos[1]
if vx == 0 and vy == 0:
return None
direction = py.math.Vector2(vx, vy)
direction.scale_to_length(10)
return Bullet(pos[0], pos[1], round(direction.x), round(direction.y))
Note, if you set the bullet dependent on the direction to the mouse cursor, then it may be useful to spawn the bullet by a mouse click:
while running:
# [...]
for event in py.event.get():
if event.type == py.QUIT:
# [...]
elif event.type == py.MOUSEBUTTONDOWN:
if event.button == 1:
New_bullet = player.Shoot()
if New_bullet:
all_sprites.add(New_bullet)
bullets.add(New_bullet)
You can use pygames Vector2 to move in any direction. You calculate the angle of the player you can use that.
class Player(py.sprite.Sprite):
def __init__(self):
...
self.angle = 0
def rotate(self, mouse_x, mouse_y):
...
self.angle = -angle #make negative otherwise it will be going away from mouse
def Shoot(self):
return Bullet(self.rect.centerx, self.rect.top, py.math.Vector2(1,0).rotate(self.angle))
then in your bullet class, get the direction and add to its position
class Bullet(py.sprite.Sprite):
def __init__(self, x, y, Dir):
...
self.Dir = Dir
def update(self):
self.rect.y += self.Dir[1] * self.speed
self.rect.x += self.Dir[0] * self.speed
...
As of currently the enemy sprites just spawn on the axis, and "move" with the player which is actually a scrolling background, and I'd like for the enemies to move only along the X axis so it doesn't destroy the immersion.
I'd also like for the sprites to spawn "Off the map" and whenever the map scrolls towards them they move towards the players set X axis? I think that would keep things simple, but isn't really the question at hand right now.
The current code I was trying to get working for the movement was :
def move_towards_player(self, player):
# Find direction vector (dx, dy) between enemy and player.
dx, dy = player.rect.x - self.rect.x, player.rect.y - self.rect.y
dist = math.hypot (dx, dy)
dx, dy = dx / dist, dy / dist # Normalize
# Move along this normalized vector towards the player
self.rect.x += dx * self.speed
self.rect.y += dy * self.speed
But it wouldn't work, this is with importing the math module.
(I know I don't need the y movements just wanted to get it working first)
Here is the rest of the code --
Zombie.py:
import pygame
from pygame.locals import *
import random
import math
class ZombieEnemy(pygame.sprite.Sprite):
def __init__(self, x=300, y=360):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('images/zombie.png')
self.rect = self.image.get_rect()
self.rect.center = (x, y)
all_zombies = pygame.sprite.Group()
for i in range( 50 ):
new_x = random.randrange( 0, 10000) # random x-position
# new_y = random.randrange( 0, ) # random y-position
all_zombies.add(ZombieEnemy(new_x)) # create, and add to group
I wasn't sure if I should create a whole new class for enemy movement, or what my partner is currently working on the Player, and getting the sprite animations and movement working right.
But here is the main code I have as of now:
import pygame
from Zombie import *
import math
from pygame.locals import *
pygame.init()
win = pygame.display.set_mode((900,567))
pygame.display.set_caption("Power Rangers ZOMBIES")
walkRight = [pygame.image.load('images/walk1.png'), pygame.image.load('images/walk2.png'), pygame.image.load('images/walk3.png'), pygame.image.load('images/walk4.png'), pygame.image.load('images/walk5.png'), pygame.image.load('images/walk6.png')]
walkLeft = [pygame.image.load('images/leftwalk2.png'), pygame.image.load('images/leftwalk3.png'), pygame.image.load('images/leftwalk4.png'), pygame.image.load('images/leftwalk5.png'), pygame.image.load('images/leftwalk6.png'), pygame.image.load('images/leftwalk7.png')]
bg = pygame.image.load('images/background.png')
char = pygame.image.load('images/standingstill.png')
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self,x,y,width,height):
self.x = x
self.y = y
self.width = width
self.height = height
self.vel = 0
self.isJump = False
self.left = False
self.right = False
self.walkCount = 0
self.jumpCount = 10
def draw(self, win):
if self.walkCount + 1 >= 18:
self.walkCount = 0
if self.left:
win.blit(walkLeft[self.walkCount//3], (self.x,self.y))
self.walkCount += 1
elif self.right:
win.blit(walkRight[self.walkCount//3], (self.x,self.y))
self.walkCount +=1
else:
win.blit(char, (self.x,self.y))
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location):
pygame.sprite.Sprite.__init__(self) #call Sprite initializer
self.image = pygame.image.load('images/background.png')
self.rect = self.image.get_rect()
self.rect.left, self.rect.top = location
BackGround = Background('images/background.png', [0,0])
# def redrawGameWindow():
# win.blit(bg, (0,0))
# man.draw(win)
# pygame.display.update()
#mainloop
man = Player(100, 340, 40, 60)
run = True
while run:
clock.tick(27)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] and man.x > man.vel:
BackGround.rect.left = BackGround.rect.left + int(10)
man.x -= man.vel
man.left = True
man.right = False
elif keys[pygame.K_RIGHT]: #and man.x < 500 - man.width - man.vel:
BackGround.rect.left = BackGround.rect.left - int(10)
man.x += man.vel
man.right = True
man.left = False
else:
man.right = False
man.left = False
man.walkCount = 0
if not(man.isJump):
if keys[pygame.K_SPACE]:
man.isJump = True
man.right = False
man.left = False
man.walkCount = 0
else:
if man.jumpCount >= -10:
neg = 1
if man.jumpCount < 0:
neg = -1
man.y -= (man.jumpCount ** 2) * 0.5 * neg
man.jumpCount -= 1
else:
man.isJump = False
man.jumpCount = 10
# redrawGameWindow()
for zombie in all_zombies:
zombie.move_towards_player(Player)
all_zombie.update()
win.blit(BackGround.image, BackGround.rect)
man.draw(win)
pygame.display.flip()
all_zombies.draw(screen)
pygame.quit()
I can't run code but I see two problems
1 - move_towards_player has to be inside class ZombieEnemy
# --- classes ---
class ZombieEnemy(pygame.sprite.Sprite):
def __init__(self, x=300, y=360):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('images/zombie.png')
self.rect = self.image.get_rect()
self.rect.center = (x, y)
def move_towards_player(self, player):
# Find direction vector (dx, dy) between enemy and player.
dx, dy = player.rect.x - self.rect.x, player.rect.y - self.rect.y
dist = math.hypot (dx, dy)
dx, dy = dx / dist, dy / dist # Normalize
# Move along this normalized vector towards the player
self.rect.x += dx * self.speed
self.rect.y += dy * self.speed
# --- other ---
all_zombies = pygame.sprite.Group()
2 - you have to use it in main loop
for zombie in all_zombies:
zombie.move_towards_player(player)
in main loop
while running:
# --- events ---
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
# If keystroke is pressed check right, left.
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
#playerX_change = -2.0
BackGround.rect.left = BackGround.rect.left + 2.5
if event.key == pygame.K_RIGHT:
#playerX_change = 2.0
BackGround.rect.left = BackGround.rect.left - 2.5
# if event.type == pygame.KEYUP:
# if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT:
# BackGround.rect.left = 0
# --- updates/moves ---
playerX += playerX_change
for zombie in all_zombies:
zombie.move_towards_player(player)
all_zombies.update()
# --- draws ---
screen.blit(BackGround.image, BackGround.rect)
player(playerX,playerY)
all_zombies.draw(screen)
pygame.display.flip()
But here I see other problem - your function expects player as class instance with self.rect inside (similar to ZombieEnemy) but you keep player as separated variables playerImg, playerX, playerY, playerX_change
So you have to create class Player or you have to use playerx, playery in move_towards_player instead of player.rect.x, player.rect.y
This question already has an answer here:
How can one continuously generate and track several random objects with a time delay in pygame? [duplicate]
(1 answer)
Closed 2 years ago.
I'm trying to design my first game with Python and Pygame to improve my coding skills.
Basically, the player has 4 circles and the goal is to prevent the Cube (or enemies) from reaching the middle of the screen, by killing them with the keyboard arrows.
The game works fine : circles move perfectly, the cubes are going faster as the levels go up, the scoring system is ok, etc...
The last step I can't figure out is to make the Cubes drop a bit more randomly, otherwise it's too easy. Currently, when a cube is killed, it reappear instantly on one of the 4 edges of the screen. I figured out how to position the cube randomly, but the timing doesn't work.
I tried Python sleep, but it freezes the screen. I tried adding a line to specify a delay with "delay = random.random()", and adding "delay <0.1" if condition in an if statement. My last idea was to use a time counter, equals to clock.tick(), and adding a condition : timecounter >3000 in an if statement. All these attempts don't work for me.
Any idea ? Here's my code so far:
# ---------- Packages and Inits ----------
import pygame, random, math, sys
pygame.init()
# ---------- Settings ----------
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 600
FPS = 60
SPEED = 1
CIRCLE_RADIUS = 50
ENEMY_SIZE = 40
# Colors
RED = (255,000,000)
BLUE = (000,000,255)
YELLOW = (255,255,000)
GREEN = (000,128,000)
BLACK = (000,000,000)
# ---------- Classes ----------
class Enemies:
def __init__( self, x, y, size=ENEMY_SIZE, thick=5, color=BLUE, speed=1, position="top"):
self.rect = pygame.Rect(0, 0, size, size)
if ( x == 0 and y == 0 ):
self.randomise()
self.rect.centerx = x
self.rect.centery = y
self.size = size
self.thick = thick
self.color = color
self.speed = speed
self.calcDirection()
self.position = position
def calcDirection( self ):
self.x_float = 1.0 * self.rect.centerx
self.y_float = 1.0 * self.rect.centery
# Determine direction vector from (x,y) to the centre of the screen
self.position_vector = pygame.math.Vector2( self.x_float, self.y_float )
self.velocity_vector = pygame.math.Vector2( SCREEN_WIDTH/2 - self.x_float, SCREEN_HEIGHT/2 - self.y_float )
self.velocity_vector = self.velocity_vector.normalize()
def update( self ):
x_delta = self.speed * self.velocity_vector[0]
y_delta = self.speed * self.velocity_vector[1]
self.x_float += x_delta
self.y_float += y_delta
self.rect.centerx = int( self.x_float )
self.rect.centery = int( self.y_float )
def draw( self, screen):
pygame.draw.rect(screen, self.color, self.rect )
def reachedPoint( self, x, y ):
return self.rect.collidepoint( x, y )
def randomise( self ):
self.rect.centerx = SCREEN_WIDTH//2
self.rect.centery = SCREEN_HEIGHT//2
side = random.randint( 0, 4 )
if ( side == 0 ):
self.rect.centery = SCREEN_HEIGHT
self.color = GREEN
self.position= "bot"
elif ( side == 1 ):
self.rect.centery = 0
self.color = YELLOW
self.position= "top"
elif ( side == 2 ):
self.rect.centerx = 0
self.color = BLUE
self.position= "left"
else:
self.rect.centerx = SCREEN_WIDTH
self.color = RED
self.position= "right"
self.calcDirection()
def set_speed( self, score):
if score < 25 : self.speed = 0.5
elif score < 50 : self.speed = 0.75
elif score < 100: self.speed = 1
elif score < 250: self.speed = 1.25
elif score < 500: self.speed = 1.5
else: self.speed = 2
return self.speed
class Circle:
def __init__(self, x, y, radius=CIRCLE_RADIUS, thick=5, color=BLUE, speed=SPEED, position="top"):
self.rect = pygame.Rect(0, 0, 2*radius, 2*radius)
self.rect.centerx = x
self.rect.centery = y
self.radius = radius
self.thick = thick
self.color = color
self.speed = speed
self.position = position
if speed >= 0:
self.directionX = 'right'
self.direction = 'up'
else:
self.directionX = 'left'
self.direction = 'down'
def draw(self, screen):
pygame.draw.circle(screen, self.color, self.rect.center, self.radius, self.thick)
def swing(self):
if self.position == "top":
self.rect.y -= self.speed
if self.rect.top <= 0 and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom > int(SCREEN_HEIGHT/2) - self.radius and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "bot":
self.rect.y -= self.speed
if self.rect.top < int(SCREEN_HEIGHT/2) + self.radius and self.direction == 'up':
self.direction = 'down'
self.speed = -self.speed
elif self.rect.bottom >= SCREEN_HEIGHT and self.direction == 'down':
self.direction = 'up'
self.speed = -self.speed
if self.position == "left":
self.rect.x -= self.speed
if self.rect.right > int(SCREEN_WIDTH/2) - self.radius and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
elif self.rect.left <= 0 and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
if self.position == "right":
self.rect.x -= self.speed
if self.rect.left < int(SCREEN_WIDTH/2) + self.radius and self.directionX == 'right':
self.directionX = 'left'
self.speed = -self.speed
elif self.rect.right >= SCREEN_WIDTH and self.directionX == 'left':
self.directionX = 'right'
self.speed = -self.speed
def isCollision(self, enemyX, enemyY, circleX, circleY):
distance = math.sqrt((math.pow(enemyX-circleX,2))+(math.pow(enemyY-circleY,2)))
if distance < 65:
return True
else:
return False
# ---------- Other Functions ----------
def set_number_of_enemies(score):
if score < 25 : number_of_enemies = 3
elif score < 50 : number_of_enemies = 4
elif score < 100: number_of_enemies = 5
elif score < 250: number_of_enemies = 6
elif score < 500: number_of_enemies = 7
else: number_of_enemies = 8
return number_of_enemies
# ---------- Main ----------
def main():
# Settings
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
screen_rect = screen.get_rect()
myFont = pygame.font.SysFont("monospace", 25)
clock = pygame.time.Clock()
game_over = False
# Variables
lifes = 5
score = 0
number_of_enemies = 3
# We create an empty list of enemies, as we want them to drop randomly
all_enemies = []
# Start with 4 circles
all_circles = [
Circle ( screen_rect.centerx , screen_rect.centery - 2*CIRCLE_RADIUS , position = "top" ),
Circle ( screen_rect.centerx , screen_rect.centery + 2*CIRCLE_RADIUS , position = "bot" ),
Circle ( screen_rect.centerx + 2*CIRCLE_RADIUS , screen_rect.centery , position = "right"),
Circle ( screen_rect.centerx - 2*CIRCLE_RADIUS , screen_rect.centery , position = "left" )]
while not game_over:
screen.fill(BLACK) # This has to be inside the while not game_over loop
time_counter = clock.tick()
print(time_counter)
# Circles
for c in all_circles:
c.draw(screen) # Place circles on the screen
c.swing() # Move circles from center to edges, and from edges to center, back and forth
# Set number of enemies
number_of_enemies = set_number_of_enemies(score) # Get the number of enemies we want, based on the score
if len(all_enemies) < number_of_enemies and time_counter > 3000: # Add if necessary
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top")) # We add a top enemy, but we randomise it right after
for e in all_enemies:
e.randomise()
time_counter = 0
# Enemies
for e in all_enemies:
e.draw(screen) # Place enemies on the screen
e.set_speed(score) # Set speed difficulty for enemies
e.update() # Move enemies from the edges of the screen towards the center
if ( e.reachedPoint( SCREEN_WIDTH//2, SCREEN_HEIGHT//2 ) ): # If the enemy reaches the middle, you lose a lifepoint and a new enemy is generated
lifes -=1
e.randomise()
# Scoring and lifepoints systems
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
for c in all_circles:
if event.type == pygame.KEYDOWN:
# LEFT
if event.key == pygame.K_LEFT and c.position == "left":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# RIGHT
if event.key == pygame.K_RIGHT and c.position == "right":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# TOP
if event.key == pygame.K_UP and c.position == "top":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# BOT
if event.key == pygame.K_DOWN and c.position == "bot":
hits = [e for e in all_enemies if c.isCollision(e.rect.centerx,e.rect.centery,c.rect.centerx,c.rect.centery)]
if not hits:
lifes -=1
for e in hits:
if len(hits) == 1:
score +=1
if len(hits) == 2:
score +=5/len(hits)
if len(hits) == 3:
score +=10/len(hits)
e.randomise()
# Game Over condition
if lifes == 0:
game_over = True
# Score / Lifes / Number of Enemies
print_lifes = myFont.render("Lifes:" + str(round(lifes)), 1, RED)
screen.blit(print_lifes, (10, SCREEN_HEIGHT-50))
print_score = myFont.render("Score:" + str(round(score)), 1, RED)
screen.blit(print_score, (10, 10))
print_enemies = myFont.render("# of Enemies:" + str(round(number_of_enemies)), 1, RED)
screen.blit(print_enemies, (10, 60))
pygame.display.update()
clock.tick(FPS)
main()
pygame.quit()
I recommend to use a timer event. Use pygame.time.set_timer() to repeatedly create an USEREVENT. e.g.:
milliseconds_delay = 3000
enemy_spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(enemy_spawn_event, milliseconds_delay)
Note, in pygame customer events can be defined. Each event needs a unique id. The ids for the user events have to start at pygame.USEREVENT. In this case pygame.USEREVENT+1 is the event id for the timer event, which spawns the enemies.
Create a new enemy when the event occurs in the event loop:
milliseconds_delay = 3000
enemy_spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(enemy_spawn_event, milliseconds_delay)
while not game_over:
# [...]
# Scoring and lifepoints systems
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
if event.type == enemy_spawn_event and len(all_enemies) < number_of_enemies:
all_enemies.append(Enemies(int(SCREEN_WIDTH/2), 0, color = YELLOW, position = "top"))
for e in all_enemies:
e.randomise()
time_counter = 0
I got the bullets to shoot but the player rect is not aligned with the player itself, so the bullets doesn't come from the player but rather from the rect that is offset.
The 3 main Classes:
(bullet, camera and player)
def RelRect(char, camera):
return Rect(char.rect.x - camera.rect.x, char.rect.y - camera.rect.y, char.rect.w, char.rect.h)
class Camera(object):
'''Class for center screen on the player'''
def __init__(self, screen, player, levelWidth, levelHeight):
self.player = player
self.rect = screen.get_rect()
self.rect.center = self.player.center
self.worldRect = Rect(0, 0, levelWidth, levelHeight)
def update(self):
if self.player.centerx > self.rect.centerx:
self.rect.centerx = self.player.centerx
if self.player.centerx < self.rect.centerx:
self.rect.centerx = self.player.centerx
if self.player.centery > self.rect.centery:
self.rect.centery = self.player.centery
if self.player.centery < self.rect.centery:
self.rect.centery = self.player.centery
def draw_sprites(self, surface, sprites):
for sprite in sprites:
if sprite.rect.colliderect(self.rect):
surface.blit(sprite.image, RelRect(sprite, self))
class Bullet():
def __init__(self, x, y, targetX, targetY):
self.image = ''
self.origX = x
self.origY = y
self.x = x
self.y = y
self.targetX = targetX
self.targetY = targetY
self.image = image.load('res/attack/attack.png')
self.vel = 20
# rnge is the range of the bullet, in frames
self.rnge = 50
# prog is the progress of the bullet, in frames
self.prog = 0
# dmg is the damage that the bullet will do upon impact
self.dmg = 1
self.dmg_mult = 1
# deathtick is the timer for enemy death
self.deathTick = 0
# rect is the hitbox of the bullet
self.w, self.h = self.image.get_width(), self.image.get_height()
self.rect = Rect(self.x, self.y, self.w, self.h)
def update(self):
# Increases Progress of the bullet
if not (sqrt((self.targetX - self.origX) ** 2 + (self.targetY - self.origY) ** 2)) == 0:
self.x += int((self.vel) * (self.targetX - self.origX) /
(sqrt((self.targetX - self.origX) ** 2 +
(self.targetY - self.origY) ** 2)))
self.y += int((self.vel) * (self.targetY - self.origY) /
(sqrt((self.targetX - self.origX) ** 2 +
(self.targetY - self.origY) ** 2)))
self.rect.center = [self.x, self.y]
def check(self, enemies):
# Checks if the bullet is out of range, then deletes it, if it is
if self.prog >= self.rnge:
bullets.remove(self)
#checks if bullets are out of bounds
elif not 0 < self.x < WIDTH - self.w or not 0 < self.y < HEIGHT - self.h:
bullets.remove(self)
else:
#checks if bullet hits target hitbox, if so, starts a timer that kills the bullet after 1 frame
for e in enemies:
if self.rect.colliderect(e.hitbox):
self.deathTick += 1
if self.deathTick > 1:
bullets.remove(self)
#draws each bullet
def draw(self):
screen.blit(self.image, self.rect)
#draws bullet hitboxes
def debug(self):
draw.rect(screen, (0,0,0), self.rect, 2)
draw.line(screen, (255,255,255), (self.x, self.y), (self.targetX, self.targetY), 4)
class Player(sprite.Sprite):
'''class for player and collision'''
def __init__(self, x, y):
sprite.Sprite.__init__(self)
self.moveUnitsY = 0
self.moveUnitsX = 0
self.x = x
self.y = y
self.ground = False
self.jump = False
self.image = image.load("res/move/Ridle.png").convert()
self.rect = self.image.get_rect()
self.Lrun = ["res/move/L1.png",
"res/move/L2.png",
"res/move/L3.png",
"res/move/L4.png",
"res/move/L5.png",
"res/move/L6.png"]
self.Rrun = ["res/move/R1.png",
"res/move/R2.png",
"res/move/R3.png",
"res/move/R4.png",
"res/move/R5.png",
"res/move/R6.png"]
self.direction = "right"
self.rect.topleft = [x, y]
self.frame = 0
def update(self, up, down, left, right):
if up:
if self.ground:
if self.direction == "right":
self.image = image.load("res/move/Ridle.png")
self.jump = True
self.moveUnitsY -= 20
if down:
if self.ground and self.direction == "right":
self.image = image.load("res/move/Ridle.png").convert_alpha()
if self.ground and self.direction == "left":
self.image = image.load("res/move/Lidle.png").convert_alpha()
if not down and self.direction == "right":
self.image = image.load("res/move/Ridle.png").convert_alpha()
if not down and self.direction == "left":
self.image = image.load("res/move/Lidle.png").convert_alpha()
if left:
self.direction = "left"
self.moveUnitsX = -vel
if self.ground:
self.frame += 1
self.image = image.load(self.Lrun[self.frame]).convert_alpha()
if self.frame == 4: self.frame = 0
else:
self.image = self.image = image.load("res/move/Lidle.png").convert_alpha()
if right:
self.direction = "right"
self.moveUnitsX = +vel
if self.ground:
self.frame += 1
self.image = image.load(self.Rrun[self.frame]).convert_alpha()
if self.frame == 4: self.frame = 0
else:
self.image = self.image = image.load("res/move/Ridle.png").convert_alpha()
if not (left or right):
self.moveUnitsX = 0
self.rect.right += self.moveUnitsX
self.collide(self.moveUnitsX, 0, world)
if not self.ground:
self.moveUnitsY += 0.3
if self.moveUnitsY > 10:
self.moveUnitsY = 10
self.rect.top += self.moveUnitsY
if self.jump:
self.moveUnitsY += 2
self.rect.top += self.moveUnitsY
if self.ground == True:
self.jump = False
self.ground = False
self.collide(0, self.moveUnitsY, world)
def collide(self, moveUnitsX, moveUnitsY, world):
self.ground = False
for pos in world:
if self.rect.colliderect(pos):
if moveUnitsX > 0:
self.rect.right = pos.rect.left
if moveUnitsX < 0:
self.rect.left = pos.rect.right
if moveUnitsY > 0:
self.rect.bottom = pos.rect.top
self.moveUnitsY = 0
self.ground = True
if moveUnitsY < 0:
self.rect.top = pos.rect.bottom
self.moveUnitsY = 0
and then the running loop:
while running:
for evnt in event.get():
if evnt.type == QUIT or evnt.type == KEYDOWN and evnt.key == K_ESCAPE:
running = False
if evnt.type == KEYDOWN and evnt.key == K_UP:
up = True
if evnt.type == KEYDOWN and evnt.key == K_DOWN:
down = True
if evnt.type == KEYDOWN and evnt.key == K_LEFT:
left = True
if evnt.type == KEYDOWN and evnt.key == K_RIGHT:
right = True
if evnt .type == MOUSEBUTTONDOWN:
# checks if any mouse button is down, if so sets clicking to true
button = evnt.button
#startTicks = time.get_ticks()
if evnt.type == MOUSEBUTTONUP:
# checks if any mouse button is down, if so sets clicking to true
button = 0
if evnt.type == MOUSEMOTION:
# sets mx and my to mouse x backgand y if mouse is moving
mx, my = evnt.pos
if evnt.type == KEYUP and evnt.key == K_UP:
up = False
if evnt.type == KEYUP and evnt.key == K_DOWN:
down = False
if evnt.type == KEYUP and evnt.key == K_LEFT:
left = False
if evnt.type == KEYUP and evnt.key == K_RIGHT:
right = False
if button == 1:
bullets.append(Bullet(player.rect[0]+ player.rect[2]//2, player.rect[1] + player.rect[3]//2, mx, my))
asize = ((screen_rect.w // background_rect.w + 1) * background_rect.w, (screen_rect.h // background_rect.h + 1) * background_rect.h)
bg = Surface(asize)
for x in range(0, asize[0], background_rect.w):
for y in range(0, asize[1], background_rect.h):
screen.blit(background, (x, y))
for b in bullets:
b.update()
b.draw()
b.check(enemies)
time_spent = sec(clock, FPS)
camera.draw_sprites(screen, all_sprite)
draw.rect(screen, (255,0,0), player.rect, 4)
player.update(up, down, left, right)
camera.update()
display.flip()
if you run the program itself, you can see that the red rectangle (4th last line) that represents the player rect is not to where the character is suppose to appear....
How can I make it so that the player rect will be at the position of the character? So that the bullets come from the player.
Thanks :)
Full Code Here:
https://pastebin.com/z1LwxYYt
The problem with your code is that neither your red rectangle nor the bullets are drawn to the screen in relation to the camera.
The Bullet class should subclass Sprite, too, so you can add them to the all_sprite-group, like you do with the obstacles and the player.
Then let the Camera-class handle the drawing of the bullets.
As for the red rectangle, I suggest removing the RelRect function and move it into the Camera class itself, like this:
class Camera(object):
...
def translate(self, rect):
return Rect(rect.x - self.rect.x, rect.y - self.rect.y, rect.w, rect.h)
def draw_sprites(self, surface, sprites):
for sprite in sprites:
if sprite.rect.colliderect(self.rect):
surface.blit(sprite.image, self.translate(sprite.rect, self))
which would allow you to draw the rect like this:
draw.rect(screen, (255,0,0), camera.translate(player.rect), 4)
I'm working in a game, and in this game an object falls from the top of the game screen and the player at the bottom of the screen has to hit the object falling. When the player hits the falling object, the player's width and height needs to increase. When I tested the code, the collision worked when the player hit the falling object from the side, but the collision didn't work when the player hit the falling object in the middle. Can someone help me?
PYTHON
# IMPORTS
import pygame, random
# GLOBALS
global screen, displayW, displayH
global clock, FPS
global end, food, player
# SETGLOBALVALUES
def setGlobalValues():
global screen, displayW, displayH
global clock, FPS
global end, food, player
displayW = 800
displayH = 600
screen = pygame.display.set_mode((displayW, displayH))
clock = pygame.time.Clock()
FPS = 60
end = False
food = Food()
player = Player()
# MAIN
def main():
pygame.init()
setGlobalValues()
setup()
gameLoop()
quitGame()
# GAMELOOP
def gameLoop():
global end, player
while not end:
for event in pygame.event.get():
# ONCLICK QUIT
if event.type == pygame.QUIT:
end = True;
# KEYDOWN
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
player.velX -= 10
if event.key == pygame.K_RIGHT:
player.velX += 10
# KEYUP
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT:
player.velX = 0
if event.key == pygame.K_RIGHT:
player.velX = 0
draw()
animate()
collision()
setFPS()
# DRAW
def draw():
global screen, food, player
# fill background
screen.fill((255, 255, 255))
player.draw()
food.draw()
# update
pygame.display.update()
# ANIMATE
def animate():
global food, player
food.animate()
player.animate()
# COLLISION
def collision():
global player, food;
player.collision()
food.collision();
# SETFPS
def setFPS():
global clock, FPS
clock.tick(FPS);
# CLASSES
class Food():
def __init__(self, img="", x=0, h=0, w=0, velY=0, color=()):
global displayW
self.img = pygame.image.load("assets/img/rsz_burger.png")
self.w = 30
self.h = 30
self.x = random.randrange(0, displayW - self.w)
self.y = -100
self.velY = 3
self.color = (255, 0, 0)
def draw(self):
global screen
screen.blit(self.img, (self.x, self.y))
def animate(self):
self.y += self.velY
if self.y >= displayW:
self.reset()
def collision(self):
global displayW, displayH
# collision with player
if self.y >= player.y and self.y <= player.y + player.h and self.x >= player.x and self.x <= player.x + player.w:
player.w += player.increase
player.h += player.increase
player.y - player.increase
print(player.w)
self.reset()
def reset(self):
self.y = -100
self.x = random.randrange(0, displayW - self.w)
self.velY += 1
screen.blit(self.img, (self.x, self.y))
class Player():
def __init__(self, x=0, y=0, velX=0, velY=0, w=0, h=0, increase=0, color=()):
global displayW, displayH
self.w = 20
self.h = 20
self.x = displayW / 2 - self.w / 2
self.y = displayH - 100
self.velX = 0
self.velY = 0
self.increase = 2
self.color = (0, 0, 0)
def draw(self):
global screen
pygame.draw.ellipse(screen, self.color, (self.x, self.y, self.w, self.h))
def animate(self):
self.x += self.velX
self.y += self.velY
def collision(self):
global displayW
# collision to walls
if self.x <= 0:
self.velX = 0
elif self.x + self.h >= displayW:
self.velX = 0
# SETUP
def setup():
pygame.display.set_caption("Food Catcher")
# QUIT GAME
def quitGame():
pygame.quit()
quit()
# CALL MAIN
if __name__ == "__main__":
main()
The problem was that had to minus the player's width
Before:
if self.y >= player.y and self.y <= player.y + player.h and self.x >= player.x and self.x <= player.x + player.w:
player.w += player.increase
player.h += player.increase
player.y - player.increase
After:
if self.y >= player.y - player.h and self.y <= player.y + player.h and self.x >= player.x - player.w and self.x <= player.x + player.w:
player.w += player.increase
player.h += player.increase
player.y - player.increase
I forgot that the objects x and y start at the top left.