I am teaching myself pygame and am looking at making my character able to rotate and then move in the direction they are facing.
I can do the rotation but cannot get the character to move in the direction the image is then facing.
The code is on Trinket HERE
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
super().__init__()
self.image = pygame.Surface([width , height])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
#Loading the image for the character
self.img = pygame.image.load("char.jfif")
#creating a copy of the image
self.img_orig = self.img.copy()
#defining the starting angle of the character image
self.angle = 0
self.velocity = 5
self.rect = self.img_orig.get_rect()
def moveLeft(self):
self.angle += 1
self.img = pygame.transform.rotate(self.img_orig, self.angle)
def moveRight(self):
self.rect.x += self.velocity
if self.rect.x > 485:
self.rect.x = 485
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
pSprite.moveForward()
if keys[pygame.K_DOWN]:
pSprite.moveDown()
if keys[pygame.K_LEFT]:
pSprite.moveLeft()
if keys[pygame.K_RIGHT]:
pSprite.moveRight()
#---- Game Logic Here
#--- Drawing Code Here
#Reset the screen to blank
screen.fill(BLUE)
#Draw Play Area
#Draw Sprites
screen.blit(pSprite.img,(pSprite.rect.x, pSprite.rect.y))
You can use pygame's Vector2 class instead of calculating the position of your sprite yourself.
I also suggest to let the sprite itself handle its movement instead of doing so in the main loop and using a clock for constant framerates and easy control of the speed of your sprites.
You also probably want to use an image format with alpha channel (like PNG).
Here's a simple example:
import pygame
class Actor(pygame.sprite.Sprite):
def __init__(self, pos, *grps):
super().__init__(*grps)
self.image = pygame.image.load('char.png').convert_alpha()
self.image_org = self.image.copy()
self.rect = self.image.get_rect(center=pos)
self.pos = pygame.Vector2(pos)
self.direction = pygame.Vector2((0, -1))
def update(self, events, dt):
pressed = pygame.key.get_pressed()
# if a is pressed, rotate left with 360 degress per second
if pressed[pygame.K_a]: self.direction.rotate_ip(dt * -360)
# if d is pressed, rotate right with 360 degress per second
if pressed[pygame.K_d]: self.direction.rotate_ip(dt * 360)
# check if should move forward or backward
movement = 0
if pressed[pygame.K_w]: movement = 1
if pressed[pygame.K_s]: movement = -1
movement_v = self.direction * movement
if movement_v.length() > 0:
movement_v.normalize_ip()
# move 100px per second in the direction we're facing
self.pos += movement_v * dt * 100
# rotate the image
self.image = pygame.transform.rotate(self.image_org, self.direction.angle_to((0, -1)))
self.rect = self.image.get_rect(center=self.pos)
def main():
pygame.init()
screen = pygame.display.set_mode((600, 480))
sprites = pygame.sprite.Group()
Actor((100, 100), sprites)
clock, dt = pygame.time.Clock(), 0
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill('grey')
sprites.draw(screen)
sprites.update(events, dt)
pygame.display.flip()
dt = clock.tick(60) / 1000
main()
char.png
Rotate the player around its center (see How do I rotate an image around its center using PyGame?):
self.angle += 1
self.img = pygame.transform.rotate(self.img_orig, self.angle)
self.rect = self.img.get_rect(center = self.rect.center)
Use an attribute x and y to store the position of the player with floating point accuracy.
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
# [...]
self.x, self.y = self.rect.center
Compute the direction of the player dependent on the angle with the trgonometric function math.sin and math.cos. Change the position attributes and update the rect attribute:
self.x += self.velocity * math.cos(math.radians(self.angle + 90))
self.y -= self.velocity * math.sin(math.radians(self.angle + 90))
self.rect.center = round(self.x), round(self.y)
The y-axis needs to be reversed (-dy) as the y-axis is generally pointing up, but in the PyGame coordinate system the y-axis is pointing down. In addition, a correction angle must be deducted (+ 90). Since the "angle" is 0 ° when the sprite is looking up, you need to add 90 ° to the angle for the calculation of the direction vector.
See also te in pygame while moving with the keys](How to turn the sprite in pygame while moving with the keys.
Class Bob:
import pygame
import math
BLACK = (0,0,0)
class Bob(pygame.sprite.Sprite):
def __init__(self, color , height , width):
super().__init__()
self.image = pygame.Surface([width , height])
self.image.fill(BLACK)
self.image.set_colorkey(BLACK)
#Loading the image for the character
self.img = pygame.image.load("char.jfif")
#creating a copy of the image
self.img_orig = self.img.copy()
#defining the starting angle of the character image
self.angle = 0
self.velocity = 5
self.rect = self.img_orig.get_rect()
self.x, self.y = self.rect.center
def rotate(self, change_angle):
self.angle += change_angle
self.img = pygame.transform.rotate(self.img_orig, self.angle)
self.rect = self.img.get_rect(center = self.rect.center)
def move(self, distance):
self.x += distance * math.cos(math.radians(self.angle + 90))
self.y -= distance * math.sin(math.radians(self.angle + 90))
self.rect.center = round(self.x), round(self.y)
def moveLeft(self):
self.rotate(1)
def moveRight(self):
self.rotate(-1)
def moveForward(self):
self.move(self.velocity)
def moveDown(self):
self.move(-self.velocity)
When setting the starting position of the player, you need to set the x, y and rect attribute:
pSprite = Bob(WHITE , 25,25)
pSprite.rect.x = 50
pSprite.rect.y = 50
pSprite.x, pSprite.y = pSprite.rect.center
Related
So, I'm trying to make a bullet curve in a spiral shape.
However, I end up with the bullets freezing in place and moving in a circle instead of forward.
Here is my bullet class code:
class Bullet(pygame.sprite.Sprite): #bullet class
def __init__(self, pos, img, angle=0, speed=10, acceleration=0, curve=10):
super().__init__()
#import image.
self.dir = angle
self.angle = (180 / math.pi) * -angle - 90
#turns towards the mouse
self.image = pygame.transform.rotate(img, self.angle)
self.rect = self.image.get_rect()
self.pos = list(pos)
self.spd = speed
self.acl = acceleration
self.curve = curve
self.wave = 0
#start lifetime of bullet
self.born = pygame.time.get_ticks()
def update(self):
#destroys itself if more than 10 secs have past from birth
self.live = pygame.time.get_ticks()
if self.live - self.born > 10000:
bullets.remove(self)
all_sprites_list.remove(self)
#update stats of bullet
self.dir += self.curve
self.spd += self.acl
#move the bullet
self.pos[0] += math.cos(self.dir) * self.spd
self.pos[1]+= math.sin(self.dir) * self.spd
self.rect.center = self.pos
Here is an example that creates spiral pathing bullets when right-clicking. Left-click generated bullets travel horizontally.
I have commented out the command to fill the frame so that you can see the path the bullet takes.
import pygame
import math
import random
width = 800
height = 600
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
class Bullet(pygame.sprite.Sprite):
"""Horizontally travelling bullet"""
def __init__(self, pos, color="grey16"):
super().__init__()
self.image = pygame.Surface((12, 5))
self.image.fill(color)
self.rect = self.image.get_rect(center=pos)
def move(self):
"""Move the bullet."""
self.rect.x += 5
def update(self):
"""Move and check for offscreen disposal"""
self.move()
# Remove the bullet if it flies up off the screen
if not 0 < self.rect.x < width + 12:
# print(f"Destroying {self} {self.rect.center} X")
self.kill() # Remove the sprite from all sprite groups.
elif not 0 < self.rect.y < height:
# print(f"Destroying {self} {self.rect.center} Y")
self.kill()
class SpiralBullet(Bullet):
"""Bullet sprite that moves in a spiral"""
def __init__(self, pos):
super().__init__(pos, "violetred")
# need to track the age to determine spiral position
self.start_tick = pygame.time.get_ticks()
self.start_pos = pos
self.angv = 0.1 # Angular Velocity
self.v = 2.0 # Velocity
def move(self):
"""Move in a spiral pattern"""
dt = pygame.time.get_ticks() - self.start_tick
if dt > 0:
dt = dt / 100 # slow it down
self.rect.x = (self.v * dt) * math.cos(self.angv * dt) + self.start_pos[0]
self.rect.y = (self.v * dt) * math.sin(self.angv * dt) + self.start_pos[1]
class Target(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((30, 50))
self.image.fill("orange")
self.rect = self.image.get_rect()
# create at the right of the screen
self.rect.x = width // 1.2
self.rect.y = random.randint(50, height - 50)
self.lives = 3
def update(self):
if self.lives <= 0:
self.kill() # Remove the sprite from all sprite groups.
# Create Groups for tracking sprites
all_sprites_list = pygame.sprite.Group()
bullets = pygame.sprite.Group()
targets = pygame.sprite.Group()
score = 0
screen.fill("turquoise") # initial fill incase we disable per frame fill
done = False
while not done:
## Handle Events
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# Click a mouse button to instantiate a bullet.
if event.button == 3: # right-click for SpiralBullet
bullet = SpiralBullet(event.pos)
all_sprites_list.add(bullet)
bullets.add(bullet)
else:
bullet = Bullet(event.pos)
all_sprites_list.add(bullet)
bullets.add(bullet)
## Game State update
# Create a target if there aren't any
if not targets:
target = Target()
all_sprites_list.add(target)
targets.add(target)
# Call the update() method on all the sprites.
all_sprites_list.update()
# Check which bullets have collided with the Target.
collided_bullets = pygame.sprite.groupcollide(bullets, targets, True, False)
for bullet in collided_bullets:
score += 1 # Increment the score.
target.lives -= 1 # Decrement the lives.
## Draw a frame
# screen.fill("turquoise") # comment to see path
all_sprites_list.draw(screen)
## Update the display
pygame.display.flip()
pygame.display.set_caption(f"Score: {score}")
clock.tick(60)
pygame.quit()
This will show something like:
Here the lower orange target was created first and was hit by two spiral bullets and a horizontal bullet causing the higher target to spawn.
I am creating an Asteroids type game using Pygame and am having trouble firing projectiles. I have a sprite group for the projectile object and some code to add the projectile.
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
Problem is when I press space in game to fire the projectile I get the error "add() argument after * must be an iterable, not int".
I have a very similar statement for adding asteroids when the game first starts without any issues so I'm not really sure what the problem is. I'll leave the rest of the code below. The add statement giving issues is in the main function near the bottom. Any help is appreciated.
#Import Modules
import pygame
import math
import random
#Movement Function
def calculate_new_xy(old_xy,speed,direction):
new_x = old_xy[0] + (speed*math.cos(direction))
new_y = old_xy[1] + (speed*math.sin(direction))
return new_x, new_y
#Collision Function
def isColliding(x, y, xTo, yTo, size):
if x > xTo - size and x < xTo + size and y > yTo - size and y < yTo + size:
return True
return False
#Draw Text Function
def drawText(msg, color, x, y, s, center=True):
screen_text = pygame.font.SysFont("Impact", s).render(msg, True, color)
if center:
rect = screen_text.get_rect()
rect.center = (x, y-50)
else:
rect = (x, y)
display.blit(screen_text, rect)
#Initialize Variables
#Colors
white = (255, 255, 255)
black = (0, 0, 0)
#Display Height/Width
display_width = 800
display_height = 600
#Asteroid Class
class Asteroid(pygame.sprite.Sprite):
#Initialize values
def __init__(self, pos=(0, 0)):
#Initialize sprite class
pygame.sprite.Sprite.__init__(self)
#Asteroid sprite
self.asteroid = pygame.image.load("asteroid.png").convert()
self.image = self.asteroid
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize random starting angle
self.angle = random.randint(0, 360)
#Asteroid random Speed
self.speed = random.randint(2, 3)
#Asteroid random direction
self.direction = math.radians(random.randrange(0, 360, 3))
#Update asteroid object
def update(self):
#Constantly rotate asteroid
self.angle -= 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.asteroid, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Move Asteroid
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the asteroid to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -20
elif self.rect.x < -20:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -20
elif self.rect.y < -20:
self.rect.y = display_height
#Projectile Class
class Projectile(pygame.sprite.Sprite):
#Initialize values
def _init_(self,x,y,direction):
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
#Update projectile object
def update(self):
#Changing direction
self.x += projectilespd * math.cos(self.direction)
self.y += projectilespd * math.sin(self.direction)
#Draw projectile
pygame.draw.circle(display, white, (self.x,self.y),1)
#Screen Border
if self.x > display_width:
self.x = 0
elif self.x < 0:
self.x = display_width
elif self.y > display_height:
self.y = 0
elif self.y < 0:
self.y = display_height
self.ttl -= 1
#Player Class
class Player(pygame.sprite.Sprite):
#Initialize ship sprite, angle lines, and rectangle
def __init__(self, pos=(0, 0), size=(200, 200)):
#Player sprite
self.ship = pygame.image.load("ship.png").convert()
self.image = self.ship
#Rectangle
self.rect = self.image.get_rect()
self.rect.center = pos
#Initialize angle
self.angle = 0
#Initialize direction
self.direction = 0
#Update player object
def update(self):
#Rotation
pressed = pygame.key.get_pressed()
if pressed[pygame.K_LEFT]: self.angle -= 3 % 360
if pressed[pygame.K_RIGHT]: self.angle += 3 % 360
#Get image angle and position
self.image = pygame.transform.rotate(self.ship, self.angle*-1)
#Use rectangle to get center of image
#Save ship's current center.
x, y = self.rect.center
#Replace old rect with new rect.
self.rect = self.image.get_rect()
#Put the new rect's center at old center.
self.rect.center = (x, y)
#Convert angle to radians
self.direction = math.radians(self.angle-90)
#Increase speed if Up is pressed
if pressed[pygame.K_UP]: self.speed = 5
else: self.speed = 0
#Move Ship
self.rect.center = calculate_new_xy(self.rect.center,self.speed,self.direction)
#Screen Border
#Moves the player to the opposite side of the screen if they go outside the border
if self.rect.x > display_width:
self.rect.x = -50
elif self.rect.x < -50:
self.rect.x = display_width
elif self.rect.y > display_height:
self.rect.y = -50
elif self.rect.y < -50:
self.rect.y = display_height
#Main Function
def main(gameState):
#Player starting position
player = Player(pos=(400, 300))
#Asteroid group
asteroids = pygame.sprite.Group()
#Projectile group
projectiles = pygame.sprite.Group()
#Create asteroids
for x in range(8):
asteroids.add(Asteroid(pos=(100 + (x*120), 100 + (x*20))))
while True:
for event in pygame.event.get():
#closes game
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
#Game Menu
while gameState == "Menu":
#Fill background
display.fill((0,0,0))
#Display menu text
drawText("ASTEROIDS", white, display_width / 2, display_height / 2, 150)
drawText("Press any key to START", white, display_width / 2, display_height / 2 + 120, 40)
#Check game start or end
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
gameState = "Playing"
pygame.display.update()
#Low frame rate for menu
clock.tick(5)
#Get key inputs
pressed = pygame.key.get_pressed()
#Fill background
display.fill(black)
#Check for player collision with asteroid
for asteroid in asteroids:
if gameState != "Game Over":
if isColliding(player.rect.x, player.rect.y, asteroid.rect.x, asteroid.rect.y, 30):
gameState = "Game Over"
#Update and draw player if not game over
if gameState != "Game Over":
#Update player
player.update()
#Draw player
display.blit(player.image, player.rect)
#Update asteroids
asteroids.update()
#Draw asteroids
asteroids.draw(display)
#Fire Projectiles
if pressed[pygame.K_SPACE]:
projectiles.add(Projectile(player.rect.x, player.rect.y, player.direction))
#Update projectiles
projectiles.update()
#Draw projectiles
projectiles.draw(display)
#Display Game Over and restart option
if gameState == "Game Over":
drawText("GAME OVER", white, display_width / 2, display_height / 2, 150)
drawText("Press R to restart", white, display_width / 2, display_height / 2 + 120, 40)
if pressed[pygame.K_r]:
main(gameState = "Playing")
#Makes updates to the game screen
pygame.display.update()
#Frame rate
clock.tick(60)
#Initialize Game
if __name__ == '__main__':
#initialize pygame
pygame.init()
#initialize display settings
display = pygame.display.set_mode((display_width,display_height))
pygame.display.set_caption('Asteroids')
#initialize game clock
clock = pygame.time.Clock()
#start main function
main(gameState = "Menu")
The issue is not the pygame.sprite.Group.add operation, but the obejct you want to add is not a pygame.sprite.Sprite object, because the object is not constructed at all.
You missed to the super call in the constructor of Projectile. Furthermore the name of the constructor has to be __init__ rather _init_:
class Projectile(pygame.sprite.Sprite):
#Initialize values
def __init__(self,x,y,direction):
super.__init__()
self.x = x
self.y = y
self.dir = direction
self.ttl = 30
The code below moves two balls on the screen. The first one is moved with an angle of 10 degree at a low speed with a good drawing quality. The second ball is moved with an angle of 1 degree and in order for the angle to be respected, the speed must be much higher and the drawing is unsatisfactory with a lot of blinking. Is there a way to slow down the drawing of the second ball and avoid the excessive blinking ?
import pygame, sys, math
from pygame.locals import *
pygame.init()
DISPLAYSURF = pygame.display.set_mode((1000, 600))
pygame.display.set_caption('Bouncing Ball with position and angle')
# Set our color constants
BLACK = (0, 0, 0)
YELLOW = (255, 255, 0)
# Create the ball class
class Ball():
def __init__(self,
screen,
color,
radius,
startX,
startY,
speed,
angle=45):
super().__init__()
self.screen = screen
self.color = color
rectSize = radius * 2
self.rect = pygame.Rect(startX, startY, rectSize, rectSize)
self.speed = speed
self.angle = math.radians(angle)
def update(self):
delta_x = self.speed * math.cos(self.angle)
delta_y = self.speed * math.sin(self.angle)
self.rect = self.rect.move(delta_x, delta_y)
if self.rect.right >= self.screen.get_width() or self.rect.left <= 0:
self.angle = math.pi - self.angle
if self.rect.top <= 0 or self.rect.bottom >= self.screen.get_height():
self.angle = -self.angle
def draw(self):
'''
Draw our ball to the screen with position information.
'''
pygame.draw.circle(self.screen, self.color, self.rect.center, int(self.rect.width / 2))
# Create a new Ball instance named 'myball'
myball = Ball(screen=DISPLAYSURF, color=YELLOW, startX=100, startY=100, radius=150, speed=8, angle=10)
mySmaLlAngleball = Ball(screen=DISPLAYSURF, color=YELLOW, startX=100, startY=500, radius=150, speed=58, angle=-1)
run = True
clock = pygame.time.Clock()
# Display loop
while run:
# Handle events
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
# Update ball position
myball.update()
mySmaLlAngleball.update()
# Draw screen
DISPLAYSURF.fill(BLACK)
myball.draw()
mySmaLlAngleball.draw()
pygame.display.update()
clock.tick(30)
The issue is caused, because pygame.Rect stores integral coordinates:
The coordinates for Rect objects are all integers. [...]
The fraction component of delta_x and delta_y is lost at self.rect.move(delta_x, delta_y).
You have to use floating point numbers for the computation. Add an attribute self.pos, which is a tupe with 2 components an stores the center point of a ball:
self.pos = self.rect.center
Compute the position with the maximum floating point accuracy:
delta_x = self.speed * math.cos(self.angle)
delta_y = self.speed * math.sin(self.angle)
self.pos = (self.pos[0] + delta_x, self.pos[1] + delta_y)
Update the self.rect.center by the round()ed position.
self.rect.center = round(self.pos[0]), round(self.pos[1])
self.pos is the "internal" position and responsible for the exact computation of the position. self.rect.center is the integral position and responsible to draw the ball. self.pos slightly changes in each frame. self.rect.center changes only if the a component of the coordinate has changed by 1.
Class Ball:
class Ball():
def __init__(self,
screen,
color,
radius,
startX,
startY,
speed,
angle=45):
super().__init__()
self.screen = screen
self.color = color
rectSize = radius * 2
self.rect = pygame.Rect(startX, startY, rectSize, rectSize)
self.speed = speed
self.angle = math.radians(angle)
self.pos = self.rect.center
def update(self):
delta_x = self.speed * math.cos(self.angle)
delta_y = self.speed * math.sin(self.angle)
self.pos = (self.pos[0] + delta_x, self.pos[1] + delta_y)
self.rect.center = round(self.pos[0]), round(self.pos[1])
if self.rect.right >= self.screen.get_width() or self.rect.left <= 0:
self.angle = math.pi - self.angle
if self.rect.top <= 0 or self.rect.bottom >= self.screen.get_height():
self.angle = -self.angle
def draw(self):
'''
Draw our ball to the screen with position information.
'''
pygame.draw.circle(self.screen, self.color, self.rect.center, int(self.rect.width / 2))
With this solution you can scale up the flops per second (clock.tick()) and scale down the speed by the same scale. This leads to a smooth movement without blinking.
Hey i am trying to create a breakout clone with pygame, and i used
self.course(180 - self.course) % 360
To bounce the ball of the paddle, however i was looking into the vector 2 class, but i got no idea how to convert my Ball class using this. If anyone could guide me in the right direction.
here is my code that i want to convert using vector2.
import pygame
import math
class Ball(pygame.sprite.Sprite):
course = 130
def __init__(self):
# Calling the parent class (Sprite)
pygame.sprite.Sprite.__init__(self)
# Creating the ball and load the ball image
self.image = pygame.image.load("ball.png").convert()
self.rect = self.image.get_rect()
self.rect.x = 0
self.rect.y = 270
# Creating a bounce function to make the ball bounce of surfaces.
def bounce(self, diff):
self.course = (180 - self.course) % 360
self.course -= diff
# Create the function that will update the ball.
def update(self):
course_radianse = math.radians(self.course)
self.rect.x += 10 * math.sin(course_radianse)
self.rect.y -= 10 * math.cos(course_radianse)
self.rect.x = self.rect.x
self.rect.y = self.rect.y
# Check if ball goes past top
if self.rect.y <= 0:
self.bounce(0)
self.rect.y = 1
# Check if ball goes past left side
if self.rect.x <= 0:
self.course = (360 - self.course) % 360
self.rect.x = 1
# Check if ball goes past right side
if self.rect.x >= 800:
self.course = (360 - self.course) % 360
self.rect.x = 800 - 1
if self.rect.y > 600:
return True
else:
return False
A vector defines a direction and an amount. You have to add the vector to the location of the ball. Sadly pygame.Rect stores integral numbers only, so the location of the object has to be stored in a pygame.math.Vector2, too. You need 1 vector for the location of the object and a 2nd one for the direction. Every time when the location changes, then the .rect attribute has to be set by the rounded location.
If the object hits a surface then the Ball is reflected (.reflect()) by the Normal vector to the surface.
Minimal example: repl.it/#Rabbid76/PyGame-BallBounceOffFrame
import pygame
import random
class Ball(pygame.sprite.Sprite):
def __init__(self, startpos, velocity, startdir):
super().__init__()
self.pos = pygame.math.Vector2(startpos)
self.velocity = velocity
self.dir = pygame.math.Vector2(startdir).normalize()
self.image = pygame.image.load("ball.png").convert_alpha()
self.rect = self.image.get_rect(center = (round(self.pos.x), round(self.pos.y)))
def reflect(self, NV):
self.dir = self.dir.reflect(pygame.math.Vector2(NV))
def update(self):
self.pos += self.dir * self.velocity
self.rect.center = round(self.pos.x), round(self.pos.y)
pygame.init()
window = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
all_groups = pygame.sprite.Group()
start, velocity, direction = (250, 250), 5, (random.random(), random.random())
ball = Ball(start, velocity, direction)
all_groups.add(ball)
run = True
while run:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
all_groups.update()
if ball.rect.left <= 100:
ball.reflect((1, 0))
if ball.rect.right >= 400:
ball.reflect((-1, 0))
if ball.rect.top <= 100:
ball.reflect((0, 1))
if ball.rect.bottom >= 400:
ball.reflect((0, -1))
window.fill(0)
pygame.draw.rect(window, (255, 0, 0), (100, 100, 300, 300), 1)
all_groups.draw(window)
pygame.display.flip()
Lets assume you have a group of blocks:
block_group = pygame.sprite.Group()
Detect the collision of the ball and the block_group. Once a collision (pygame.sprite.spritecollide()) is detected, reflect the ball on the block:
block_hit = pygame.sprite.spritecollide(ball, block_group, False)
if block_hit:
bl = block_hit[0].rect.left - ball.rect.width/4
br = block_hit[0].rect.right + ball.rect.width/4
nv = (0, 1) if bl < ball.rect.centerx < br else (1, 0)
ball.reflect(nv)
I am new to pygame and I am trying to make a game where the player has to bypass some enemy's to get to a point where you can go to the next level. I need the enemy's to walk back and forward on a predetermined path but I can't figure out how to do it. So I was wondering if there is an easy way to do this?
This is my code.
import pygame
import random
import os
import time
from random import choices
from random import randint
pygame.init()
a = 0
b = 0
width = 1280
height = 720
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Game")
done = False
n = 0
x = 0
y = 0
x_wall = 0
y_wall = 0
clock = pygame.time.Clock()
WHITE = (255,255,255)
RED = (255,0,0)
change_x = 0
change_y = 0
HW = width / 2
HH = height / 2
background = pygame.image.load('mountains.png')
#player class
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("character.png")
self.rect = self.image.get_rect()
self.rect.x = width / 2
self.rect.y = height / 2
#enemy class
class Enemy(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = width / 3
self.rect.y = height / 3
#wall class
class Wall(pygame.sprite.Sprite):
def __init__(self, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("wall.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
#wall movement
def update(self):
self.vx = 0
self.vy = 0
key = pygame.key.get_pressed()
if key[pygame.K_LEFT]:
self.vx = 5
self.vy = 0
elif key[pygame.K_RIGHT]:
self.vx = -5
self.vy = 0
if key[pygame.K_UP]:
self.vy = 5
self.vx = 0
elif key[pygame.K_DOWN]:
self.vy = -5
self.vx = 0
self.rect.x = self.rect.x + self.vx
self.rect.y = self.rect.y + self.vy
#player sprite group
sprites = pygame.sprite.Group()
player = Player()
sprites.add(player)
#enemy sprite group
enemys = pygame.sprite.Group()
enemy = Enemy()
enemy2 = Enemy()
enemys.add(enemy, enemy2)
#all the wall sprites
wall_list = pygame.sprite.Group()
wall = Wall(x_wall, y_wall)
wall2 = Wall((x_wall + 50), y_wall)
wall3 = Wall((x_wall + 100), y_wall)
wall4 = Wall((x_wall + 150), y_wall)
wall5 = Wall((x_wall + 200), y_wall)
wall6 = Wall((x_wall + 250), y_wall)
#add all the walls to the list to draw them later
wall_list.add(wall, wall2, wall3, wall4, wall5, wall6)
#add all the walls here to fix the collision
all_walls = (wall, wall2, wall3, wall4, wall5, wall6)
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
sprites.update()
wall_list.update()
enemys.update()
#collision between player and and walls
if player.rect.collidelist(all_walls) >= 0:
print("Collision !!")
player.rect.x = player.rect.x - player.vx
player.rect.y = player.rect.y - player.vx
#fill the screen
screen.fill((0, 0, 0))
#screen.blit(background,(x,y))
#draw the sprites
sprites.draw(screen)
wall_list.draw(screen)
enemys.draw(screen)
pygame.display.flip()
clock.tick(60)
pygame.quit()
Here is the download link with the images if you want to run it:
https://geordyd.stackstorage.com/s/hZZ1RWcjal6ecZM
I'd give the sprite a list of points (self.waypoints) and assign the first one to a self.target attribute.
In the update method I subtract the self.pos from the self.target position to get a vector (heading) that points to the target and has a length equal to the distance. Scale this vector to the desired speed and use it as the velocity (which gets added to the self.pos vector each frame) and the entity will move towards the target.
When the target is reached, I just increment the waypoint index and assign the next waypoint in the list to self.target. It's a good idea to slow down when you're getting near the target, otherwise the sprite could get stuck and moves back and forth if it can't reach the target point exactly. Therefore I also check if the sprite is closer than the self.target_radius and decrease the velocity to a fraction of the maximum speed.
import pygame as pg
from pygame.math import Vector2
class Entity(pg.sprite.Sprite):
def __init__(self, pos, waypoints):
super().__init__()
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(0, 0)
self.max_speed = 3
self.pos = Vector2(pos)
self.waypoints = waypoints
self.waypoint_index = 0
self.target = self.waypoints[self.waypoint_index]
self.target_radius = 50
def update(self):
# A vector pointing from self to the target.
heading = self.target - self.pos
distance = heading.length() # Distance to the target.
heading.normalize_ip()
if distance <= 2: # We're closer than 2 pixels.
# Increment the waypoint index to swtich the target.
# The modulo sets the index back to 0 if it's equal to the length.
self.waypoint_index = (self.waypoint_index + 1) % len(self.waypoints)
self.target = self.waypoints[self.waypoint_index]
if distance <= self.target_radius:
# If we're approaching the target, we slow down.
self.vel = heading * (distance / self.target_radius * self.max_speed)
else: # Otherwise move with max_speed.
self.vel = heading * self.max_speed
self.pos += self.vel
self.rect.center = self.pos
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
waypoints = [(200, 100), (500, 400), (100, 300)]
all_sprites = pg.sprite.Group(Entity((100, 300), waypoints))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
for point in waypoints:
pg.draw.rect(screen, (90, 200, 40), (point, (4, 4)))
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
Instead of the waypoints list and index I'd actually prefer to use itertools.cycle and just call next to switch to the next point:
# In the `__init__` method.
self.waypoints = itertools.cycle(waypoints)
self.target = next(self.waypoints)
# In the `update` method.
if distance <= 2:
self.target = next(self.waypoints)
Use a list to have him walk back and forth.
class Enemy(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("enemy.png")
self.image = pygame.transform.scale(self.image, (int(50), int(50)))
self.rect = self.image.get_rect()
self.rect.x = width / 3
self.rect.y = height / 3
#x or y coordinates
self.list=[1,2,3,4,5]
self.index=0
def update(self):
# patrol up and down or left and right depending on x or y
if self.index==4:
#reverse order of list
self.list.reverse()
self.index=0
#set the x position of the enemy according to the list
self.rect.x=self.list[self.index]
self.index+=1