When creating a vector only game in PyGame (movement and location use vectors) and attempting to make a planetary style centered gravity, the gravitation pull is inconsistent. Here is the code for the gravity function:
import math
import pygame
import time
class movementObj:
def __init__(self, posX = 0.0, posY = 0.0, currentVelocity = [0,0], maxVelocity = 20.0):
self.x = posX
self.y = posY
self.vel = currentVelocity
self.maxVel = maxVelocity
def gravity(self, pos, val):
pos = self.__subVec(pos,[self.x,self.y])
currentSpeed = math.sqrt(math.pow(pos[0]-self.x,2)+math.pow(pos[1]-self.y,2))
change = val / currentSpeed
pos = self.__mulVec(pos,change)
self.accelerate(pos)
return pos
Here is the whole code:
import math
import pygame
import time
class movementObj:
def __init__(self, posX = 0.0, posY = 0.0, currentVelocity = [0,0], maxVelocity = 20.0):
self.x = posX
self.y = posY
self.vel = currentVelocity
self.maxVel = maxVelocity
def __addVec(self, vectorOne, vectorTwo):
returnVec = [(vectorOne[i] + vectorTwo[i]) for i in range(len(vectorOne))]
return returnVec
def __mulVec(self,vectorOne,mulValue):
returnVec = [(i*mulValue) for i in vectorOne]
return returnVec
def resistance(self,val):
self.vel = self.__mulVec(self.vel,1-val)
def gravity(self, pos, val):
# pos = self.__subVec(pos,[self.x,self.y])
# spd = math.sqrt(math.pow(pos[0]-self.x,2)+math.pow(pos[1]-self.y,2))
# over = spd-val
# overPercent = 1-(over/spd)
# pos = self.__mulVec(pos,overPercent)
# # print(pos,end="\r")
# self.accelerate(pos)
# return overPercent
pos = self.__subVec(pos,[self.x,self.y])
currentSpeed = math.sqrt(math.pow(pos[0]-self.x,2)+math.pow(pos[1]-self.y,2))
change = val / currentSpeed
pos = self.__mulVec(pos,change)
self.accelerate(pos)
return pos
def __getSpeed(self):
return math.sqrt(math.pow(self.vel[0],2)+math.pow(self.vel[1],2))
def __subVec(self, vectorOne, vectorTwo):
returnVec = [(vectorOne[i] - vectorTwo[i]) for i in range(len(vectorOne))]
return returnVec
def updatePos(self):
self.x += self.vel[0]
self.y += self.vel[1]
self.normalizeVelocity()
# print(str(self.vel[0]-self.x) + "," + str(self.vel[1]-self.y), end="\r")
def accelerate(self, acc):
self.vel = self.__addVec(self.vel,acc)
def normalizeVelocity(self):
currentSpeed = self.__getSpeed()
if self.x > 400:
self.x = self.x-400
self.y = 400-self.y
if self.x < 0:
self.y = 400-self.y
self.x = self.x+400
if self.y > 400:
self.x = 400-self.x
self.y = self.y-400
if self.y < 0:
self.x = 400-self.x
self.y = self.y+400
if currentSpeed < self.maxVel:
return
over = currentSpeed-self.maxVel
overPercent = 1-(over/currentSpeed)
self.vel = self.__mulVec(self.vel,overPercent)
def start(self):
print(self.__getSpeed())
size = 400
obj = movementObj()
pygame.init()
screen = pygame.display.set_mode([size, size])
def gameloop():
counter = 0
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
keys=pygame.key.get_pressed()
if keys[pygame.K_DOWN]:
obj.accelerate([0,0.2])
if keys[pygame.K_UP]:
obj.accelerate([0,-0.2])
if keys[pygame.K_LEFT]:
obj.accelerate([-0.2,0])
if keys[pygame.K_RIGHT]:
obj.accelerate([0.2,0])
gline = obj.gravity([200,200],0.1)
# print(gline, end="\r")
# obj.resistance(0.015)
screen.fill((255, 255, 255))
pygame.draw.line(screen, (0,0,255), (obj.x,obj.y), (obj.x+(1000*gline[0]),obj.y+(1000*gline[1])), 2)
pygame.draw.line(screen, (0,255,0), (obj.x,obj.y), (obj.x+(10*obj.vel[0]),obj.y+(10*obj.vel[1])), 2)
pygame.draw.circle(screen, (0, 0, 0), (obj.x,obj.y), 1)
pygame.draw.circle(screen, (0, 0, 0), (200,200), 1)
pygame.display.flip()
obj.updatePos()
# obj.accelerate([0.08,-0.08])
time.sleep(50/1000)
counter += 1
import sys
gameloop()
sys.stdout.close()
pygame.quit()
When running it, the goal is to have consistent acceleration towards the center from any location on the plane, but instead it is inconsistent. Any ideas why?
(photos of the problem: green is the velocity, blue is acceleration)
I suggest to simplify the code and to use pygame.math.Vector2 for the calculations. Very handy is the scale_to_length method, which scales the vector to a certain length. This function can also be used to limit the length of a vector. Of course you can do the math your own, however I strongly suggest to introduce helper functions like normalize and scale_to_lenght.
import pygame
import sys
class MovementObj:
def __init__(self, posX = 0.0, posY = 0.0, currentVelocity = [0,0], maxVelocity = 5.0):
self.pos = pygame.math.Vector2(posX, posY)
self.vel = pygame.math.Vector2(currentVelocity)
self.maxVel = maxVelocity
def gravity(self, target, accVal):
acc = pygame.math.Vector2(target) - self.pos
if acc.length() > 0:
acc.scale_to_length(accVal)
self.accelerate(acc)
return acc
def updatePos(self):
self.pos += self.vel
def accelerate(self, acc):
self.vel += acc
if self.vel.length() > self.maxVel:
self.vel.scale_to_length(self.maxVel)
size = 400
pygame.init()
screen = pygame.display.set_mode([size, size])
def gameloop():
obj = MovementObj()
clock = pygame.time.Clock()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
keys=pygame.key.get_pressed()
ax = (keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]) * 0.2
ay = (keys[pygame.K_DOWN] - keys[pygame.K_UP]) * 0.2
obj.accelerate([ax, ay])
gline = obj.gravity([200,200], 0.1)
obj.updatePos()
screen.fill((255, 255, 255))
pygame.draw.circle(screen, (0, 0, 0), obj.pos, 5)
pygame.draw.circle(screen, (0, 0, 0), (200, 200), 5)
pygame.draw.line(screen, (0, 0, 255), obj.pos, obj.pos + gline * 200, 2)
pygame.draw.line(screen, (255, 0, 0), obj.pos, obj.pos + obj.vel * 5, 2)
pygame.display.flip()
clock.tick(20)
gameloop()
sys.stdout.close()
pygame.quit()
Related
I made a snake game with pygame and it works fine. However, it's sometimes not very responsive to user inputs. I'm using a clock and delay to set how fast the snake should move.
Movement code:
pygame.time.delay(1)
clock.tick(7)
player.move()
render_screen()
Render Screen:
screen.fill((0, 0, 0))
draw_grid()
player.draw()
food.draw()
screen.fill((0, 0, 0), (0, 513, 512, 50))
start.draw()
screen.blit(comic_font.render('Score: ' + str(score), False, (255, 100, 0)), (10, size + 5))
pygame.display.update()
Full Code:
import pygame
import random
pygame.init()
class Snake:
def __init__(self, x, y):
self.x = x
self.y = y
self.speed_x = 1
self.speed_y = 0
segments = []
for a in range(1, 4):
segments.append(Segment(self.x - a, self.y))
self.segments = segments
def move(self):
global score, game_mode
# handle key inputs
keys = pygame.key.get_pressed()
for key in keys:
if self.speed_x == 0:
if keys[pygame.K_a]:
self.speed_x = -1
self.speed_y = 0
if keys[pygame.K_d]:
self.speed_x = 1
self.speed_y = 0
elif self.speed_y == 0:
if keys[pygame.K_w]:
self.speed_x = 0
self.speed_y = -1
if keys[pygame.K_s]:
self.speed_x = 0
self.speed_y = 1
# check for wall collisions
if self.x + self.speed_x < 0 or self.x + self.speed_x > rows - 1 or self.y + self.speed_y > rows - 1 or \
self.y + self.speed_y < 0:
game_mode = "end"
start.text = "RETRY"
return None
# moves snake
self.segments.insert(0, Segment(self.x, self.y))
self.x += self.speed_x
self.y += self.speed_y
# checks for self collisions
for segment in self.segments:
if self.x == segment.x and self.y == segment.y and self.segments.index(segment)!=len(self.segments)-1:
game_mode = "end"
start.text = "RETRY"
break
# checks for eating food
if self.x == food.x and self.y == food.y:
food.x = random.randrange(0, 16)
food.y = random.randrange(0, 16)
score += 1
else:
self.segments.pop()
def draw(self):
for segment in self.segments:
segment.draw()
pygame.draw.rect(screen, (255, 0, 0), (self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class Segment:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, (0, 255 - player.segments.index(self), 0),
(self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
def draw(self):
pygame.draw.rect(screen, (255, 255, 0), (self.x * size / rows, self.y * size / rows, size / rows, size / rows))
class button:
def __init__(self, x, y, r, g, b, text, font, width, height):
self.x = x
self.y = y
self.r = r
self.b = b
self.g = g
self.text = text
self.font = font
self.width = width
self.height = height
def draw(self):
pygame.draw.rect(screen, (self.r, self.g, self.b), (self.x, self.y, self.width, self.height))
screen.blit(self.font.render(self.text, False, (0, 100, 0)), (self.x + 10, self.y))
def mouse_over(self, pos):
if self.x < pos[0] < self.x + self.width and self.y < pos[1] < self.y + self.height:
return True
def draw_grid():
margin = size / rows
x = 0
y = 0
for line in range(rows):
x = x + margin
y = y + margin
pygame.draw.line(screen, (255, 255, 255), (x, 0), (x, size))
pygame.draw.line(screen, (255, 255, 255), (0, y), (size, y))
def render_screen():
screen.fill((0, 0, 0))
draw_grid()
player.draw()
food.draw()
screen.fill((0, 0, 0), (0, 513, 512, 50))
start.draw()
screen.blit(comic_font.render('Score: ' + str(score), False, (255, 100, 0)), (10, size + 5))
pygame.display.update()
size = 512
rows = 16
screen = pygame.display.set_mode((size, size + 50))
pygame.display.set_caption("Snake")
comic_font = pygame.font.SysFont("Comic Sans MS", 30)
clock = pygame.time.Clock()
running = True
game_mode = "wait"
player = Snake(8, 8)
food = Food(random.randrange(0, 16), random.randrange(0, 16))
start = button(size - 155, size + 5, 0, 255, 0, "PLAY", comic_font, 150, 40)
score = 0
render_screen()
while running:
for event in pygame.event.get():
pos = pygame.mouse.get_pos()
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if start.mouse_over(pos):
if game_mode == "wait":
game_mode = "play"
start.text = "PAUSE"
elif game_mode == "play":
game_mode = "wait"
start.text = "RESUME"
elif game_mode == "end":
game_mode = "play"
start.text = "PAUSE"
score = 0
running = True
game_mode = "wait"
player = Snake(8, 8)
food = Food(random.randrange(0, 16), random.randrange(0, 16))
render_screen()
if game_mode == "play":
pygame.time.delay(1)
clock.tick(7)
player.move()
render_screen()
The full code is above to reproduce the problem.
After experimenting with this a bit, it seems that when the pygame.time.delay(1) is smaller, there will be less delay.
I think that the user gets the input read only between the tics so they have to input exactly on the spot.
I think maybe this is why the input is less behind when the delay is smaller as the delay will affect how long between each input.
Does anyone know why the game doesn't receive inputs well at certain times?
pygame.key.get_pressed() returns a list, with the states of all keyboard buttons. It makes no sense to iterate through this list, because you just want to evaluate the current state of the keys wasd.
Set the speed dependent on the pressed keys:
class Snake:
# [...]
def move(self):
# [...]
# handle key inputs
keys = pygame.key.get_pressed()
if self.speed_x == 0:
if keys[pygame.K_a]:
self.speed_x, self.speed_y = -1, 0
elif keys[pygame.K_d]:
self.speed_x, self.speed_y = 1, 0
elif self.speed_y == 0:
if keys[pygame.K_w]:
self.speed_x, self.speed_y = 0, -1
elif keys[pygame.K_s]:
self.speed_x, self.speed_y = 0, 1
# [...]
pygame.time.Clock.tick delays to keep the game running slower than the given ticks per second. The states are returned by pygame.key.get_pressed() are evaluated, when the events are handled (pygame.event.get()).
Delay the game, after the events have been handled, the snake has been moved and the display was updated:
while running:
for event in pygame.event.get():
# [...]
if game_mode == "play":
player.move()
render_screen()
clock.tick(7) # <---
This question already has an answer here:
How do I get the snake to grow and chain the movement of the snake's body?
(1 answer)
Closed 2 years ago.
I finally figured out how to add body parts to my snake, but they add on in an unusual way. I have been struggling on this for a while, and finally made it so they append. But they don't do it correctly. It seems like they append 1 pixel behind instead of a full bodies length. Does anyone know why?
# Constants
WIN_WIDTH = 500
WIN_HEIGHT = 600
HALF_WIN_WIDTH = WIN_WIDTH / 2
HALF_WIN_HEIGHT = WIN_HEIGHT / 2
FPS = 10
# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
DARK_GREEN = (0, 100, 0)
YELLOW = (255, 255, 0)
# Variables
screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
pygame.display.set_caption("Snake")
clock = pygame.time.Clock()
running = True
class Text:
def __init__(self, x, y, size, font, color, text):
self.x = x
self.y = y
self.size = size
self.font = font
self.color = color
self.text = text
def draw(self):
self.my_font = pygame.font.SysFont(self.font, self.size)
self.text_surface = self.my_font.render(self.text, True, self.color)
screen.blit(self.text_surface, (self.x, self.y))
class Food:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
def draw(self):
self.rect = (self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, BLUE, self.rect)
def events(self):
pass
def update(self):
pass
class Body:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
def draw(self):
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, YELLOW, self.rect)
# Snake class
class Snake:
def __init__(self, x, y):
self.x = x
self.y = y
self.width = 25
self.height = 25
self.direction = 1
self.kill = False
self.collide = False
self.speed = 3
self.score = 0
self.bodies = []
def draw(self):
self.rect = pygame.Rect(self.x, self.y, self.width, self.height)
pygame.draw.rect(screen, BLACK, self.rect)
def events(self):
# change direction on key press
self.keys = pygame.key.get_pressed()
if self.keys[pygame.K_UP] and self.direction != 3:
self.direction = 1
if self.keys[pygame.K_DOWN] and self.direction != 1:
self.direction = 3
if self.keys[pygame.K_LEFT] and self.direction != 2:
self.direction = 4
if self.keys[pygame.K_RIGHT] and self.direction != 4:
self.direction = 2
if self.rect.colliderect(food.rect):
self.speed += 0.5
food.x = random.randint(0, WIN_WIDTH)
food.y = random.randint(0, WIN_HEIGHT)
self.score += 5
self.colliide = False
self.bodies.append(Body(0, 0))
# Move the end bodies first in reverse order
for i in range(len(self.bodies)-1, 0, -1):
x = snake.bodies[i-1].x
y = snake.bodies[i-1].y
snake.bodies[i].x = x
snake.bodies[i].y = y
snake.bodies[i].draw()
# Move body 0 to where the head is
if len(snake.bodies) > 0:
x = snake.x
y = snake.y
snake.bodies[0].x = x
snake.bodies[0].y = y
snake.bodies[0].draw()
def update(self):
# move
if self.direction == 1:
self.y -= self.speed
if self.direction == 2:
self.x += self.speed
if self.direction == 3:
self.y += self.speed
if self.direction == 4:
self.x -= self.speed
# if on edge of screen
if self.rect.right > WIN_WIDTH:
self.kill = True
if self.x < 0:
self.kill = True
if self.y < 0:
self.kill = True
if self.rect.bottom > WIN_HEIGHT:
self.kill = True
# Create the snake object
snake = Snake(HALF_WIN_WIDTH, HALF_WIN_HEIGHT)
food = Food(random.randint(0, WIN_WIDTH), random.randint(0, WIN_HEIGHT))
# Main Loop
while running:
score_text = Text(220, 5, 40, 'arial', WHITE, f'Score: {snake.score}')
# Draw
screen.fill(DARK_GREEN)
snake.draw()
food.draw()
score_text.draw()
# Event handling
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if snake.kill:
running = False
snake.events()
# Update
snake.update()
food.update()
clock.tick(60)
pygame.display.update()
Thank you very much!
You have to track the positions which have been met by the snake. Add a list attribute self.position to the class Snake:
class Snake:
def __init__(self, x, y):
# [...]
self.positions = [(self.x, self.y)]
# [...]
Add the new position to the list when the snake moves:
class Snake:
# [...]
def update(self):
# move
if self.direction == 1:
self.y -= self.speed
if self.direction == 2:
self.x += self.speed
if self.direction == 3:
self.y += self.speed
if self.direction == 4:
self.x -= self.speed
# add ne position
if self.x != self.positions[0][0] or self.y != self.positions[0][1]:
self.positions.insert(0, (self.x, self.y))
Update the x and y coordinate of the body along the stored positions in events. Define a distance between the parts of the body (e.g. 35). And use a method getPos to get the position of a part, by its index:
class Snake:
# [...]
def events(self):
# change direction on key press
self.keys = pygame.key.get_pressed()
if self.keys[pygame.K_UP] and self.direction != 3:
self.direction = 1
if self.keys[pygame.K_DOWN] and self.direction != 1:
self.direction = 3
if self.keys[pygame.K_LEFT] and self.direction != 2:
self.direction = 4
if self.keys[pygame.K_RIGHT] and self.direction != 4:
self.direction = 2
if self.rect.colliderect(food.rect):
self.speed += 0.5
food.x = random.randint(100, WIN_WIDTH - 125)
food.y = random.randint(150, WIN_HEIGHT - 175)
self.score += 5
self.colliide = False
self.bodies.append(Body(0, 0))
# Move the end bodies first in reverse order
for i in range(len(self.bodies)):
pos = self.getPos(i+1, 35, i == len(self.bodies)-1)
snake.bodies[i].x = pos[0]
snake.bodies[i].y = pos[1]
snake.bodies[i].draw()
The arguments to method getPos are the index of the body part, the distance between the parts and delToEnd. delToEnd becomes true, when the last part of the body is get and indicates, that the positions at the end of the list, which are "behind" the last part of the snake can be deleted:
class Snake:
# [...]
def getPos(self, i, dist, delToEnd):
lenToI = i * dist
lenAct = 0
px, py = self.positions[-1]
for j in range(len(self.positions)-1):
px, py = self.positions[j]
pnx, pny = self.positions[j+1]
delta = math.sqrt((px-pnx)*(px-pnx) + (py-pny)*(py-pny))
lenAct += delta
if lenAct >= lenToI:
w = (lenAct - lenToI) / delta
px = pnx - (pnx-px) * w
py = pny - (pny-py) * w
if delToEnd:
del self.positions[j:]
break
return (round(px), round(py))
I have a problem with collisions in my game, spaceship's mask doesn't collide properly with a background and I believe that offset is a problem, however I'm not sure.
I've tried multiple collision techniques and checked a lot of answers to my problem, but none of them helped me.
import pygame as pg
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (2, 29)
pg.init()
def gameLoop():
display_width = 1800
display_height = 1000
pg.display.set_caption("Kerbal Space Landing Simulator")
clock = pg.time.Clock()
display = pg.display.set_mode((display_width, display_height))
sprsp = pg.image.load('C:/Users/PC/PycharmProjects/untitled/sprspaceship.png').convert_alpha()
cosbg = pg.image.load('C:/Users/PC/PycharmProjects/untitled/cosmos bg.png').convert_alpha()
done = False
class Spaceship:
def __init__(self, x, y, mass):
self.x = x
self.y = y
self.width = 139
self.height = 106
self.velx = 0
self.vely = 0
self.mass = mass
self.color = (255, 255, 255)
self.spr = sprsp
self.fuel = 500
self.mask = pg.mask.from_surface(self.spr)
self.angle = 0
self.changerot = 0
def check_controls(self):
if keys[pg.K_SPACE] and self.fuel > 0:
if self.angle > 0:
self.vely += 0.005 * (self.angle - 90)
self.velx += -0.005 * self.angle
else:
self.vely += -0.005 * (self.angle + 90)
self.velx += -0.005 * self.angle
self.fuel += -3
if keys[pg.K_LEFT] and self.angle < 90:
self.angle += 2
if keys[pg.K_RIGHT] and self.angle > -90:
self.angle += -2
def update_pos(self):
self.vely += 0.01
self.x += self.velx
self.y += self.vely
self.mask = pg.mask.from_surface(self.spr)
def update_rotation(self):
self.rspr = pg.transform.rotate(self.spr, self.angle)
self.changerot -= self.angle
def draw(self):
if self.fuel > 0:
pg.draw.rect(display, (255, 255, 255), (display_width - 100, 100 + 500 - self.fuel, 10, self.fuel), 0)
display.blit(self.rspr, (int(self.x), int(self.y)))
self.changerot = 0
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.mask = pg.mask.from_threshold(display, (160, 160, 160))
self.hitbox = pg.Rect(self.x, self.y, display_width, 500)
self.ox = display_width // 2 - self.x // 2
self.oy = display_height // 2 - self.y // 2
def draw(self):
pg.draw.rect(display, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
spaceship = (Spaceship(500, 100, 1))
terrain = (Terrain(0, 800))
def redrawGameWindow():
display.blit(cosbg, (0, 0))
spaceship.draw()
terrain.draw()
pg.display.update()
def check_for_collisions():
offset = (int(spaceship.x - terrain.ox), int(spaceship.y - terrain.oy))
print(offset)
print(spaceship.mask.overlap(terrain.mask, offset))
return spaceship.mask.overlap(terrain.mask, offset)
# return spaceship.hitbox.colliderect(terrain.hitbox)
# return pg.sprite.spritecollide(spaceship.spr, terrain.mask, False, pg.sprite.collide_mask)
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
keys = pg.key.get_pressed()
mouse_pressed = pg.mouse.get_pressed()
x, y = pg.mouse.get_pos()
spaceship.check_controls()
spaceship.update_pos()
spaceship.update_rotation()
if check_for_collisions() is not None:
print('Hit! You\'ve hit the ground with the speed:', spaceship.vely)
exit()
redrawGameWindow()
clock.tick(60)
gameLoop()
Spaceship doesn't collide with the surface. I know how to fix it using simpler code, but in future I want to use randomly generated terrain. Could you help me with these collisions?
The mask for the Terrain is never set. Crate a proper Terrain mask:
class Terrain(object):
def __init__(self, x, y):
self.x = x
self.y = y
maskSurf = pg.Surface((display_width, display_height)).convert_alpha()
maskSurf.fill(0)
pg.draw.rect(maskSurf, (160, 160, 160), (self.x, self.y, display_width, 500), 0)
self.mask = pg.mask.from_surface(maskSurf)
print(self.mask.count())
# [...]
When using pygame.mask.Mask.overlap(), then you've to check the overlapping of the Spaceship and the Terrain, rather than the Terrain and the Spaceship.
Since the Terrain mask is a mask of the entire screen, the offset for the overlap() test is the position of the Spaceship:
def check_for_collisions():
offset = (int(spaceship.x), int(spaceship.y))
collide = terrain.mask.overlap(spaceship.mask, offset)
print(offset, collide)
return collide
Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect
See also: Mask
I am making a game in Python pygame and have made a moving background, a jumping sprite as a player and spawning obstacles. Now I am working on the collision between the player and obstacles and have worked out some code but eventually an error occurs:
collided = pygame.sprite.spritecollide(player, obsticles, True)
File "C:\Users\Kasutaja\AppData\Roaming\Python\Python27\site
packages\pygame\sprite.py", line 1514, in spritecollide
for s in group.sprites():
AttributeError: 'Obsticles' object has no attribute 'sprites'
I have no idea, why that class is not a sprite and how to make it a sprite. I hope after fixing this error the collision works.
player image 1
obsticles image 2
background image 3
import pygame, random
pygame.init()
W, H = 800,600
HW, HH = W/2,H/2
AREA = W * H
bg = pygame.image.load('Linn.png')
bg = pygame.transform.scale(bg, (800, 600))
DS = pygame.display.set_mode((W,L))
clock = pygame.time.Clock()
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, py, paat, veerg, rida):
super(Player,self).__init__()
self.x = x
self.y = y
self.jumping = False
self.platform_y = py
self.velocity_index = 0
self.paat = pygame.image.load('STlaev.png').convert_alpha()
self.paat = pygame.transform.scale(self.paat,(64,64))
self.rect = self.paat.get_rect()
self.veerg = veerg
self.rida = rida
self.kokku = veerg * rida
self.rect = self.paat.get_rect()
W = self.veergL = self.rect.width/veerg
H = self.weegK = self.rect.height/rida
HW,HH = self.veergKeskel = (W/2,H/2)
self.veerg = list([(index % veerg * W, int(index/veerg) * H,W,H )for index in range(self.kokku)])
self.handle = list([ #pildi paigutamise voimalikud positsioonid
(0, 0), (-HW, 0), (-W, 0),
(0, -HH), (-HW, -HH), (-W, -HH),
(0, -W), (-HW, -H), (-W, -H),])
self.mask = pygame.mask.from_surface(self.paat)
def do_jumpt(self):
global velocity
if self.jumping:
self.y += velocity[self.velocity_index]
self.velocity_index += 1
if self.velocity_index >= len(velocity) - 1:
self.velocity_index = len(velocity) - 1
if self.y > self.platform_y:
self.y = self.platform_y
self.jumping = False
self.velocity_index = 0
def draw(self, DS,veergindex,x,y,handle=0):
DS.blit(self.paat,(self.x+self.handle[handle][0], self.y + self.handle[handle][1]),self.veerg[veergindex])
def do(self):
self.do_jumpt()
p.draw(DS,index%p.kokku,300,300,0)
def update(self):
self.rect.topleft = self.x, self.y
p = Player(310, 200, 200, 'STlaev.png', 4, 1) #Mangija algkordinaadid, huppe
korgus, pilt, sprite valik
velocity = list([(i/ 2.0)-14 for i in range (0,50)]) #Huppe ulatus
index = 3
def keys(player):
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE] or keys[pygame.K_UP] and player.jumping == False:
player.jumping = True
class Obsticles(pygame.sprite.Sprite):
def __init__(self, x, y, img, width, height):
super(Obsticles,self).__init__()
self.img = pygame.image.load('box.png').convert()
self.img = pygame.transform.scale(self.img, (64,64))
self.rect = self.img.get_rect()
self.x = x
self.y = y
self.width = width
self.height = height
self.hitbox = (x,y,width,height)
self.mask = pygame.mask.from_surface(self.img)
def draw(self, DS):
DS.blit(self.img, (self.x, self.y))
def update(self):
self.rect.topleft = self.x, self.y
def redrawWindow():
for i in objects:
i.draw(DS)
pygame.time.set_timer(pygame.USEREVENT+2, random.choice([2000, 3000, 1000, 4000,0]))
objects = []
'''Sprites'''
sprites = pygame.sprite.Group()
player = Player(310, 200, 200, 'STlaev.png', 4, 1)
obsticles = Obsticles(832,300,'box.png',64,64)
all_sprites = pygame.sprite.Group(player,obsticles)
ob = pygame.sprite.Group(obsticles)
x=0
while True:
'''Game loop'''
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.USEREVENT+2:
r = random.randrange(0,2)
if r == 0:
objects.append(Obsticles(832,300,64,64))
'''Obsticle speed and deleting'''
for i in objects:
i.x -= 5 #the speed of the obsticle
if i.x < -64: #deleting obsticle from the window
objects.pop(objects.index(i))
'''Background movement'''
back_x = x % bg.get_rect().width
DS.blit(bg, (back_x - bg.get_rect().width, 0))
if back_x < W:
DS.blit(bg, (back_x, 0))
x -= 1
'''Sprites'''
all_sprites.update()
collided = pygame.sprite.spritecollide(player,obsticles ,True)
for i in collided:
print('Collision.')
'''Fuctions'''
keys(p)
p.do()
index+=1
redrawWindow()
pygame.display.update()
clock.tick(60)
pygame.quit()
quit()
collided = pygame.sprite.spritecollide(player, obsticles, True)
Its the fact you are using the object obsticles, not the sprite group ob. So to fix it you do this:
collided = pygame.sprite.spritecollide(player, ob, True)
I'm trying to make my tanks shoot, and I did all the code I think I should have done but I don't know why the tanks aren't shooting anything.
import pygame, assetloader
from pygame.locals import *
import random, time, math
import pygame
GRAD = math.pi/180
blue = (0, 0, 255)
wallRects = []
bullets = []
maze = [[] for i in range(25)]
assetloader.set_asset_path("assets/")
I defined the Bullet Class here:
def calculate_dir_with_angle(angle):
direction = [0, 0]
if (angle > 0 and angle < 180) or (angle > -360 and angle < -180):
direction[0] = -1
elif (angle > -180 and angle < 0) or (angle > 180 and angle < 360):
direction[0] = 1
elif (angle > -90 and angle < 90) or (angle > 270 and anlge < 360):
direction[1] = -1
elif (angle > 90 and angle < 270) or (angle > -270 and angle < -90):
direction[1] = 1
return direction
class Bullet:
def __init__(self, pos, r, angle):
self.x = pos[0]
self.y = pos[1]
self.r = r
self.counter = 50
direction = calculate_dir_with_angle(angle)
self.vel = [direction[0] * 2, direction[1] * 2]
def draw(self, screen):
self.x = int(self.x)
self.y = int(self.y)
pygame.draw.circle(screen, (25, 25, 25), (self.x, self.y), (self.r))
def move(self):
self.x += self.vel[0]
self.y += self.vel[1]
self.rect = pygame.Rect(self.x-self.r, self.y - self.r, 2 * self.r, 2 * self.r)
for wr in wallRects:
if self.rect.centery >= wr.top and self.rect.centery <= wr.bottom:
if self.rect.left <= wr.right and self.rect.left > wr.left:
self.vel[0] = -self.vel[0]
self.x = wr.right + self.r + 1
self.rect.x = wr.right + 1
elif self.rect.right >= wr.left and self.rect.right < wr.right:
self.vel[0] = -self.vel[0]
self.x = wr.left + self.r - 1
self.rect.x = wr.left - 2 * self.r - 1
if self.rect.centerx >= wr.left and self.rect.centerx <= wr.right:
if self.rect.top <= wr.bottom and self.rect.top > wr.top:
self.vel[1] = -self.vel[1]
self.y = wr.bottom + self.r + 1
self.rect.y = wr.bottom + 1
elif self.rect.bottom >= wr.top and self.rect.bottom < wr.bottom:
self.vel[1] = -self.vel[1]
self.y = wr.top - self.r - 1
self.rect.y = wr.top - 2 * self.r - 1
if self.counter > 0:
self.counter -= 1
def generateRandomPosition():
row = random.randint(1, 23)
col = random.randint(1, 23)
while maze[row][col-1] != 0 or maze[row][col] != 0 or maze[row][col+1] != 0:
row = random.randint(1, 23)
col = random.randint(1, 23)
return row, col
Player 1:
class Player(pygame.sprite.Sprite):
def __init__(self, x, y, pos):
pygame.sprite.Sprite.__init__(self)
self.image, self.rect = assetloader.load_image("Tank.png", -1)
self.rect.x = x
self.rect.y = y
self.rect.clamp_ip(screen.get_rect())
self.rows = pos[0]
self.cols = pos[1]
self.x = self.cols * gsize
self.y = self.rows * gsize
self.orig_image, self.orig_rect = assetloader.load_image("Tank.png", -1)
self.orig_rect.x = self.x
self.orig_rect.y = self.y
self.orig_gun_pos = self.orig_rect.midtop
self.ammo = 5
def checkCollisions(self):
for b in bullets:
if b.counter <= 0:
if b.rect.colliderect(self.orig_rect):
self.dead = True
def calculate_gun_pos(self):
self.orig_gun_pos = self.orig_rect.midtop
new_y = self.orig_gun_pos[1] - self.orig_rect.centery
new_x = self.orig_gun_pos[0] - self.orig_rect.centerx
rads = self.dir * GRAD
gun_x = (new_y * math.sin(rads)) + (new_x * math.cos(rads)) + (self.orig_rect.centerx)
gun_y = (new_y * math.cos(rads)) - (new_x * math.sin(rads)) + (self.orig_rect.centery)
self.gun_pos = (gun_x, gun_y)
def shoot(self):
if self.ammo > 0:
self.calculate_gun_pos()
b = Bullet(self.gun_pos, 3, self.dir)
bullets.append(b)
self.ammo -= 1
def draw(self, screen):
image = pygame.transform.rotate(self.image, self.dir)
screen.blit(image, self.rect)
def update(self):
oldCenter = self.rect.center
self.rect = self.image.get_rect()
self.rect.center = oldCenter
screen_rect = screen.get_rect()
keys = pygame.key.get_pressed()
if keys[K_m]:
p.shoot()
if not screen_rect.contains(self.rect):
self.rect.clamp_ip(screen_rect)
Calling the functions:
size = width, height = 500, 400
gsize = 25
start_x, start_y = 0, 0
bgColor = 255, 255, 255
pygame.init()
screen = pygame.display.set_mode(size)#, pygame.FULLSCREEN)
pygame.display.set_caption("Sample Sprite")
clock = pygame.time.Clock()
p = Player(width/2, height/4, (3,4))
coll_font = pygame.font.Font(None, 30)
going = True
while going:
clock.tick(60)
for event in pygame.event.get():
if event.type == QUIT:
going = False
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
going = False
elif event.type == KEYDOWN:
if event.key == K_m:
p.shoot()
for b in bullets:
b.move()
p.update()
screen.fill(bgColor)
p.draw(screen)
pygame.display.flip()
pygame.quit()
How would I call the bullet to actually appear and fire because I have the Bullet class which gets called within the Player class in def shoot(self) so does anyone have an idea why the bullets aren't appearing?
I usually add bullets in this way: I pass the group that contains all sprites and the bullet group to the player instance and add new bullets to these group in the player's handle_event method.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((640, 480))
screen_rect = screen.get_rect()
FONT = pg.font.Font(None, 24)
BG_COLOR = pg.Color('gray12')
BULLET_IMAGE = pg.Surface((20, 11), pg.SRCALPHA)
pg.draw.polygon(
BULLET_IMAGE, pg.Color('aquamarine1'), [(0, 0), (20, 5), (0, 11)])
PLAYER_IMAGE = pg.Surface((50, 30), pg.SRCALPHA)
pg.draw.polygon(
PLAYER_IMAGE, pg.Color('dodgerblue1'), [(0, 0), (50, 15), (0, 30)])
class Player(pg.sprite.Sprite):
def __init__(self, pos, all_sprites, bullet_group):
super().__init__()
self.image = PLAYER_IMAGE
self.orig_image = self.image # Needed to preserve image quality.
self.rect = self.image.get_rect(center=(pos))
self.pos = Vector2(pos)
self.vel = Vector2(1, 0)
self.angle = 0
self.angle_speed = 0
self.all_sprites = all_sprites
self.bullet_group = bullet_group
def handle_event(self, event):
if event.type == pg.MOUSEBUTTONDOWN:
# Left button fires a bullet from cannon center with
# current angle. Add the bullet to the bullet_group.
if event.button == 1:
bullet = Bullet(self.pos, self.angle)
self.bullet_group.add(bullet)
self.all_sprites.add(bullet)
elif event.type == pg.KEYDOWN:
# Rotate self by setting the .angle_speed.
if event.key in (pg.K_a, pg.K_LEFT):
self.angle_speed = -3
elif event.key in (pg.K_d, pg.K_RIGHT):
self.angle_speed = 3
elif event.type == pg.KEYUP:
if event.key in (pg.K_a, pg.K_LEFT):
self.angle_speed = 0
elif event.key in (pg.K_d, pg.K_RIGHT):
self.angle_speed = 0
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if self.angle_speed != 0:
self.rotate()
def rotate(self):
# Update the angle and the velocity vector.
self.angle += self.angle_speed
self.vel.rotate_ip(self.angle_speed)
# Rotate the image and get a new rect with the previous center.
self.image = pg.transform.rotozoom(self.orig_image, -self.angle, 1)
self.rect = self.image.get_rect(center=self.rect.center)
class Bullet(pg.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pg.transform.rotate(BULLET_IMAGE, -angle)
self.rect = self.image.get_rect(center=pos)
# To apply an offset (40 pixels) to the start position,
# create another vector and rotate it as well.
offset = Vector2(40, 0).rotate(angle)
# Add the offset vector to the position vector (the center).
self.pos = Vector2(pos) + offset
# Rotate the start velocity vector (9, 0) by the angle.
self.vel = Vector2(9, 0).rotate(angle)
def update(self):
# Add the velocity to the pos to move the sprite.
self.pos += self.vel
self.rect.center = self.pos # Update the rect as well.
# Remove bullets outside of the screen area.
if not screen_rect.contains(self.rect):
self.kill()
def main():
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
# Bullets will be added to this group.
bullet_group = pg.sprite.Group()
# Pass the bullet group to the player.
player = Player((300, 200), all_sprites, bullet_group)
all_sprites.add(player)
playing = True
while playing:
for event in pg.event.get():
if event.type == pg.QUIT:
playing = False
# Pass events to the player instance.
player.handle_event(event)
all_sprites.update()
screen.fill(BG_COLOR)
all_sprites.draw(screen)
pg.display.update()
clock.tick(30)
if __name__ == '__main__':
main()
pg.quit()