Snake bodies not appending to snake correctly [duplicate] - python

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

Related

i have a problem: my enemies are stacked vertically, on top of my player which is weird, and they don't move to their predefined positions [duplicate]

This question already exists:
in my shooting game, my players are positioned on top of my head (player) which is very abnormal and also once the game starts, they start shooting [closed]
Closed 6 months ago.
i have earlier tried to format my question properly but i hope this one is better. am creating a shooter game from https://github.com/russs123/Shooter. however my problem is that my enemies are stacked vertically upwards in a straight line when i run my game and the enemies don't start off at their predefined location. below is the full code i wrote. perhaps if you can run it, it will be better understood.
import pygame, sys
from player import Player
import os
import random
import csv
pygame.init()
screen_width = 600
scroll_thresh = 200
screen_scroll = 0
bg_scroll = 0
screen_height = int(screen_width * 0.8)
screen = pygame.display.set_mode((screen_width, screen_height))
pygame.display.set_caption("Shooter Game")
clock = pygame.time.Clock()
fps = 60
# define game variables
GRAVITY = 0.75
level = 1
ROWS = 16
COLS = 150
TILE_SIZE = screen_height // ROWS
TILE_TYPES = 21
img_list = []
for x in range(TILE_TYPES):
img = pygame.image.load(f"img/tile/{x}.png")
img = pygame.transform.scale(img, (TILE_SIZE, TILE_SIZE))
img_list.append(img)
# color variables
bg = (144, 201, 120)
RED = (255, 0, 0)
WHITE = (255, 255, 255)
GREEN = (0, 255, 0)
BLACK = (0, 0, 0)
# define player action variables
moving_left = False
moving_right = False
shoot = False
grenade = False
grenade_thrown = False
# load up images
pine1_img = pygame.image.load("img/Background/pine1.png").convert_alpha()
pine2_img = pygame.image.load("img/Background/pine2.png").convert_alpha()
mountain_img = pygame.image.load("img/Background/mountain.png").convert_alpha()
sky_cloud_img = pygame.image.load("img/Background/sky_cloud.png").convert_alpha()
bullet_img = pygame.image.load("img/icons/bullet.png").convert_alpha()
grenade_img = pygame.image.load("img/icons/grenade.png").convert_alpha()
# pick up boxes
health_box_img = pygame.image.load("img/icons/health_box.png").convert_alpha()
ammo_box_img = pygame.image.load("img/icons/ammo_box.png").convert_alpha()
grenade_box_img = pygame.image.load("img/icons/grenade_box.png").convert_alpha()
# name_dt = type(name)
item_boxes = {
"Health": health_box_img,
"Ammo": ammo_box_img,
"Grenade": grenade_img,
}
# define fonts
font = pygame.font.SysFont("Futura", 30)
def draw_text(text, font, text_col, x, y):
img = font.render(text, True, text_col)
screen.blit(img, (x, y))
def draw_bg():
screen.fill(bg)
screen.blit(sky_cloud_img, (0, 0))
screen.blit(mountain_img, (0, screen_height - mountain_img.get_height() - 300))
screen.blit(pine1_img, (0, screen_height - pine1_img.get_height() - 150))
screen.blit(pine2_img, (0, screen_height - pine2_img.get_height()))
class Soldier(pygame.sprite.Sprite):
def __init__(self, char_type, x, y, scale, speed, ammo, grenades):
pygame.sprite.Sprite.__init__(self)
self.alive = True
self.char_type = char_type
self.speed = speed
self.ammo = ammo
self.start_ammo = ammo
self.shoot_cooldown = 0
self.grenades = grenades
self.health = 100
self.max_health = self.health
self.direction = 1
self.vel_y = 0
self.jump = False
self.in_air = True
self.flip = False
self.animation_list = []
self.frame_index = 0
self.action = 0
# ai specific variables
self.move_counter = 0
self.vision = pygame.Rect(0, 0, 150, 20)
self.idling = 0
self.idling_counter = 0
self.update_time = pygame.time.get_ticks()
# load all images for the players
animation_types = ["idle", "run", "jump", "death"]
for animation in animation_types:
temp_list = []
# reset temporary list of images
# count number of files in the folder
num_of_frames = len(os.listdir(f"img/{self.char_type}/{animation}"))
for i in range(num_of_frames):
img = pygame.image.load(f"img/{self.char_type}/{animation}/{i}.png").convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width() * scale), int(img.get_height() * scale)))
temp_list.append(img)
self.animation_list.append(temp_list)
self.image = self.animation_list[self.action][self.frame_index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.width = self.image.get_width()
self.height = self.image.get_height()
def update(self):
self.update_animation()
self.check_alive()
# update cooldown
if self.shoot_cooldown > 0:
self.shoot_cooldown -= 1
def move(self, moving_left, moving_right):
screen_scroll = 0
dx = 0
dy = 0
if moving_left:
dx = -self.speed
self.flip = True
self.direction = -1
if moving_right:
dx = self.speed
self.flip = False
self.direction = 1
if self.jump == True and self.in_air == False:
self.vel_y = -11
self.jump = False
self.in_air = True
# apply gravity
self.vel_y += GRAVITY
if self.vel_y > 10:
self.vel_y
dy += self.vel_y
# check for collision
for tile in world.obstacle_list:
# check collision in the x direction
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
dx = 0
# check for collision in the y direction
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
# check if below the ground, i.e jumping
if self.vel_y < 0:
self.vel_y = 0
dy = tile[1].bottom - self.rect.top
# check if above the ground, ie falling
elif self.vel_y >= 0:
self.vel_y = 0
self.in_air = False
dy = tile[1].top - self.rect.bottom
self.rect.x += dx
self.rect.y += dy
# update scroll based on player position
if self.char_type == "player":
if self.rect.right > screen_width - scroll_thresh or self.rect.left < scroll_thresh:
self.rect.x = -dx
screen_scroll = -dx
return screen_scroll
def shoot(self):
if self.shoot_cooldown == 0 and self.ammo > 0:
self.shoot_cooldown = 20
bullet = Bullet(self.rect.centerx + (0.75 * self.rect.size[0] * self.direction), self.rect.centery,
self.direction)
bullet_group.add(bullet)
# reduce ammo after each shot
self.ammo -= 1
def ai(self):
if self.alive and player.alive:
if self.idling == False and random.randint(1, 200) == 1:
self.update_action(0)
self.idling = True
self.idling_counter = 50
# check if ai is near the player
if self.vision.colliderect(player.rect):
# stop running and face the player
self.update_action(0)
self.shoot()
else:
if self.idling == False:
if self.direction == 1:
ai_moving_right = True
else:
ai_moving_right = False
ai_moving_left = not ai_moving_right
self.move(ai_moving_left, ai_moving_right)
self.update_action(1)
self.move_counter += 1
# update ai vision as the enemy moves
self.vision.center = (self.rect.centerx + 75 * self.direction, self.rect.centery)
if self.move_counter > TILE_SIZE:
self.direction *= -1
self.move_counter *= -1
else:
self.idling_counter -= 1
if self.idling_counter <= 0:
self.idling = False
# scroll
self.rect.x = screen_scroll
def update_animation(self):
ANIMATION_COOLDOWN = 100
# update image depending on current index
self.image = self.animation_list[self.action][self.frame_index]
# check if enough time has passed since the last update
if pygame.time.get_ticks() - self.update_time > ANIMATION_COOLDOWN:
self.update_time = pygame.time.get_ticks()
self.frame_index += 1
# if animation has run out then restart to the first
if self.frame_index >= len(self.animation_list[self.action]):
if self.action == 3:
self.frame_index = len(self.animation_list[self.action]) - 1
else:
self.frame_index = 0
def check_alive(self):
if self.health <= 0:
self.health = 0
self.speed = 0
self.alive = False
self.update_action(3)
def update_action(self, new_action):
# check if new action is different from the previous one
if new_action != self.action:
self.action = new_action
# update animation settings
self.frame_index = 0
self.update_time = pygame.time.get_ticks()
def draw(self):
screen.blit(pygame.transform.flip(self.image, self.flip, False), self.rect)
class HealthBar():
def __init__(self, health, x, y, max_health):
self.x = x
self.y = y
self.health = health
self.max_health = max_health
def draw(self, health):
self.health = health
pygame.draw.rect(screen, BLACK, (self.x - 2, self.y - 2, 154, 24))
pygame.draw.rect(screen, RED, (self.x, self.y, 150, 20))
ratio = self.health / self.max_health
pygame.draw.rect(screen, GREEN, (self.x, self.y, 150 * ratio, 20))
class World():
def __init__(self):
self.obstacle_list = []
def process_data(self, data):
for y, row in enumerate(data):
for x, tile in enumerate(row):
if tile >= 0:
img = img_list[tile]
img_rect = img.get_rect()
img_rect.x = x * TILE_SIZE
img_rect.y = y * TILE_SIZE
tile_data = (img, img_rect)
if 0 <= tile <= 8:
self.obstacle_list.append(tile_data)
elif 9 <= tile <= 10:
# water
water = Water(img, x * TILE_SIZE, y * TILE_SIZE, )
water_group.add(water)
pass
elif 11 <= tile <= 14:
# decoration
decoration = Decoration(img, x * TILE_SIZE, y * TILE_SIZE)
decoration_group.add(decoration)
elif tile == 15:
# create a player
player = Soldier("player", x * TILE_SIZE, y * TILE_SIZE, 1.65, 5, 20, 5)
health_bar = HealthBar(10, 10, player.health, player.health)
elif tile == 16:
# create enemy
enemy = Soldier("enemy", x * TILE_SIZE, y * TILE_SIZE, 1.65, 2, 20, 0)
enemy_group.add(enemy)
elif tile == 17:
# create ammo box
item_box = ItemBox("Ammo", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 18:
# create grenade box
item_box = ItemBox("Grenade", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 19:
# create health box
item_box = ItemBox("Health", x * TILE_SIZE, y * TILE_SIZE)
item_box_group.add(item_box)
elif tile == 20:
# create exit point
exit = Exit(img, x * TILE_SIZE, y * TILE_SIZE, )
exit_group.add(exit)
return player, health_bar
def draw(self):
for tile in self.obstacle_list:
tile[1][0] += screen_scroll
screen.blit(tile[0], tile[1])
class Decoration(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
self.rect.x += screen_scroll
class Water(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
self.rect.x += screen_scroll
class Exit(pygame.sprite.Sprite):
def __init__(self, img, x, y):
pygame.sprite.Sprite.__init__(self)
self.image = img
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
class ItemBox(pygame.sprite.Sprite):
def __init__(self, item_type, x, y):
pygame.sprite.Sprite.__init__(self)
self.item_type = item_type
self.image = item_boxes[self.item_type]
self.rect = self.image.get_rect()
self.rect.midtop = (x + TILE_SIZE // 2, y + (TILE_SIZE - self.image.get_height()))
def update(self):
# scroll
self.rect.x += screen_scroll
# check if player has picked up the box
if pygame.sprite.collide_rect(self, player):
# check what kind of box it was
if self.item_type == "Health":
player.health += 25
if player.health > player.max_health:
player.health = player.max_health
elif self.item_type == "Ammo":
player.ammo += 15
elif self.item_type == "Grenade":
player.grenades += 3
# delete the item box
self.kill()
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y, direction):
pygame.sprite.Sprite.__init__(self)
self.speed = 10
self.image = bullet_img
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.direction = direction
def update(self):
self.rect.x += self.direction * self.speed
# check if bullet has left the screen
if self.rect.right < 0 and self.rect.left > screen_width:
self.kill()
# check for collision with level
for tile in world.obstacle_list:
if tile[1].colliderect(self.rect):
self.kill()
# check collision with characters
if pygame.sprite.spritecollide(player, bullet_group, False):
if player.alive:
player.health -= 5
self.kill()
for enemy in enemy_group:
if pygame.sprite.spritecollide(enemy, bullet_group, False):
if enemy.alive:
enemy.health -= 25
self.kill()
class Grenade(pygame.sprite.Sprite):
def __init__(self, x, y, direction):
pygame.sprite.Sprite.__init__(self)
self.timer = 100
self.vel_y = -11
self.speed = 7
self.image = grenade_img
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.direction = direction
self.width = self.image.get_width()
self.height = self.image.get_height()
def update(self):
self.vel_y += GRAVITY
dx = self.direction * self.speed
dy = self.vel_y
# check for collision with level
for tile in world.obstacle_list:
# check if grenade has hit a wall
if tile[1].colliderect(self.rect.x + dx, self.rect.y, self.width, self.height):
self.direction *= -1
dx = self.direction * self.speed
if tile[1].colliderect(self.rect.x, self.rect.y + dy, self.width, self.height):
self.speed = 0
# check if below the ground, i.e thrown
if self.vel_y < 0:
self.y = 0
dy = tile[1].bottom - self.rect.top
# check if above the ground, ie falling
elif self.vel_y >= 0:
self.vel_y = 0
dy = tile[1].top - self.rect.bottom
# update grenade position
self.rect.x += dx
self.rect.y += dy
# explosion countdown
self.timer -= 1
if self.timer <= 0:
self.kill()
explosion = Explosion(self.rect.x, self.rect.y, 0.5)
explosion_group.add(explosion)
# do damage to anyone nearby
if abs(self.rect.centerx - player.rect.centerx) < TILE_SIZE * 2 and \
abs(self.rect.centery - player.rect.centery) < TILE_SIZE * 2:
player.health -= 50
for enemy in enemy_group:
if abs(self.rect.centerx - enemy.rect.centerx) < TILE_SIZE * 2 and \
abs(self.rect.centery - enemy.rect.centery) < TILE_SIZE * 2:
enemy.health -= 50
class Explosion(pygame.sprite.Sprite):
def __init__(self, x, y, scale):
pygame.sprite.Sprite.__init__(self)
self.images = []
for num in range(1, 6):
img = pygame.image.load(f"img/explosion/exp{num}.png").convert_alpha()
img = pygame.transform.scale(img, (int(img.get_width() * scale), (int(img.get_height() * scale))))
self.images.append(img)
self.frame_index = 0
self.image = self.images[self.frame_index]
self.rect = self.image.get_rect()
self.rect.center = (x, y)
self.counter = 0
def update(self):
EXPLOSION_SPEED = 4
# update explosion animation
self.counter += 1
if self.counter >= EXPLOSION_SPEED:
self.counter = 0
self.frame_index += 1
# if animation is complete, then delete the explosion
if self.frame_index >= len(self.images):
self.kill()
else:
self.image = self.images[self.frame_index]
# create sprite groups
enemy_group = pygame.sprite.Group()
bullet_group = pygame.sprite.Group()
grenade_group = pygame.sprite.Group()
explosion_group = pygame.sprite.Group()
item_box_group = pygame.sprite.Group()
decoration_group = pygame.sprite.Group()
water_group = pygame.sprite.Group()
exit_group = pygame.sprite.Group()
# create empty tile list
world_data = []
for row in range(ROWS):
r = [-1] * COLS
world_data.append(r)
# load in level data and create world
with open(f"level{level}_data.csv", newline="") as csvfile:
reader = csv.reader(csvfile, delimiter=",")
for x, row in enumerate(reader):
for y, tile in enumerate(row):
world_data[x][y] = int(tile)
print(world_data)
world = World()
player, health_bar = world.process_data(world_data)
run = True
while run:
clock.tick(fps)
draw_bg()
world.draw()
health_bar.draw(player.health)
# show ammo
draw_text("AMMO:", font, WHITE, 10, 35)
for x in range(player.ammo):
screen.blit(bullet_img, (90 + (x * 10), 40))
# show grenade
draw_text("GRENADES: ", font, WHITE, 10, 65)
for x in range(player.grenades):
screen.blit(grenade_img, (130 + (x * 15), 66))
player.update()
player.draw()
for enemy in enemy_group:
enemy.ai()
enemy.update()
enemy.draw()
# update and draw groups
bullet_group.update()
grenade_group.update()
explosion_group.update()
item_box_group.update()
decoration_group.update()
water_group.update()
exit_group.update()
bullet_group.draw(screen)
grenade_group.draw(screen)
explosion_group.draw(screen)
item_box_group.draw(screen)
decoration_group.draw(screen)
water_group.draw(screen)
exit_group.draw(screen)
# update player actions, 1 is run, 0 = idle, 2 = in air
if player.alive:
# shoot bullet
if shoot:
player.shoot()
elif grenade and grenade_thrown == False and player.grenades > 0:
grenade = Grenade(player.rect.centerx + (0.5 * player.rect.size[0] * player.direction),
player.rect.top, player.direction)
grenade_group.add(grenade)
# reduce grenades
player.grenades -= 1
grenade_thrown = True
if player.in_air:
player.update_action(2)
elif moving_left or moving_right:
player.update_action(1)
else:
player.update_action(0)
screen_scroll = player.move(moving_left, moving_right)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# keyboard presses
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_a:
moving_left = True
if event.key == pygame.K_d:
moving_right = True
if event.key == pygame.K_SPACE:
shoot = True
if event.key == pygame.K_q:
grenade = True
if event.key == pygame.K_w and player.alive:
player.jump = True
if event.key == pygame.K_ESCAPE:
run = False
# keyboard button released
if event.type == pygame.KEYUP:
if event.key == pygame.K_a:
moving_left = False
if event.key == pygame.K_d:
moving_right = False
if event.key == pygame.K_SPACE:
shoot = False
if event.key == pygame.K_q:
grenade = False
grenade_thrown = False
pygame.display.update()
pygame.quit()
i have looked at this code for days but cant seem to find out the problem exactly. so, the question once again is how to properly arrange the enemies on the screen at their various predefined positions and not directly on top of my player.

Python point curves

I would like to create a game in python but I need a thought provoking impulse, how I can draw a point, which draws a line behind him/a trail, so far, so good, I have no idea how to make my point not just moving in the 4 directions, I want him to move forward on his own and the user should steer left and right.
Missing is:
• The trail of my point (later I have to check, if another sprite touches it)
• The "curvy" moving
My current code:
import pygame
import os
pygame.init()
width, height = 970, 970
screen = pygame.display.set_mode((width, height))
h_center = ((height / 2) - 4)
w_center = ((width / 2) - 4)
class Point(pygame.sprite.Sprite):
def __init__(self):
self.image = pygame.image.load(os.path.join("assets", "point.png"))
self.x = (width / 2)
self.y = (height / 2)
self.speed = 5
self.direction = 3 # 1:north ; 2:east ; 3:south ; 4:west
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 1
if key[pygame.K_DOWN]:
self.direction = 3
elif key[pygame.K_UP]:
self.direction = 1
if key[pygame.K_RIGHT]:
self.direction = 2
elif key[pygame.K_LEFT]:
self.direction = 4
def move(self):
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
def draw(self, surface):
surface.blit(self.image, (self.x, self.y))
def main():
point = Point()
clock = pygame.time.Clock()
background = pygame.image.load('backgroundborder.png').convert()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
point.handle_keys()
point.move()
screen.fill((0, 0, 0))
screen.blit(background, (0, 0))
point.draw(screen)
pygame.display.update()
clock.tick(40)
if __name__ == '__main__':
main()
Please assist
Two things:
Use a list to store the trail. It's just a list of previous positions.
Use the arrow keys to adjust speed, not direction
I also added a few lines of code so the dot stays on the screen. The dot.png is just a black dot 20x20 pixels.
Here's the updated code:
import pygame
import os
pygame.init()
width, height = 970, 970
screen = pygame.display.set_mode((width, height))
h_center = ((height/2) - 4)
w_center = ((width/2) - 4)
trail=[None]*50 # trail has 50 dots
trailimage = pygame.image.load('dot.png')
class Point(pygame.sprite.Sprite):
def __init__(self):
self.image = pygame.image.load('dot.png')
self.x = (width/2)
self.y = (height/2)
self.speed = {'x':0, 'y':0}
self.direction = 3 # 1north ; 2east ; 3south ; 4west
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 1
if key[pygame.K_DOWN]:
self.speed['y']+=0.25
elif key[pygame.K_UP]:
self.speed['y']-=0.25
if key[pygame.K_RIGHT]:
self.speed['x']+=0.25
elif key[pygame.K_LEFT]:
self.speed['x']-=0.25
def move(self):
self.y += self.speed['y']
self.x += self.speed['x']
# wrap to other side of screen
if self.x > width: self.x = (self.x - width)
elif self.x < 0: self.x = (width + self.x)
if self.y > height: self.y = (self.y - height)
elif self.y < 0: self.y = (height + self.y)
def draw(self, surface):
surface.blit(self.image, (self.x, self.y))
def main():
point = Point()
clock = pygame.time.Clock()
#background = pygame.image.load('backgroundborder.png').convert()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
point.handle_keys()
point.move()
screen.fill((200, 200, 200))
#screen.blit(background, (0, 0))
for d in trail:
if d: screen.blit(trailimage, d)
del trail[0] # remove last point in trail
trail.append((point.x,point.y)) # append this position
point.draw(screen)
pygame.display.update()
clock.tick(40)
if __name__ == '__main__':
main()
To make the controls more like CurveFever, I updated the code so the left \ right keys adjust the travel direction (in degrees). The speed is constant.
Here is the updated code:
import pygame
import os
import math
pygame.init()
width, height = 970, 970
screen = pygame.display.set_mode((width, height))
h_center = ((height/2) - 4)
w_center = ((width/2) - 4)
trail=[None]*50 # trail has 50 dots
trailimage = pygame.image.load('dot.png')
speed = 8 # constant
class Point(pygame.sprite.Sprite):
def __init__(self):
self.image = pygame.image.load('dot.png')
self.x = (width/2)
self.y = (height/2)
self.speed = {'x':0, 'y':0}
self.deg = -90 # up, direction in degrees
def handle_keys(self):
key = pygame.key.get_pressed()
dist = 1
if key[pygame.K_RIGHT]:
self.deg+=2
elif key[pygame.K_LEFT]:
self.deg-=2
self.speed['x'] = speed*math.cos(math.radians(self.deg))
self.speed['y'] = speed*math.sin(math.radians(self.deg))
def move(self):
self.y += self.speed['y']
self.x += self.speed['x']
# wrap to other side of screen
if self.x > width: self.x = (self.x - width)
elif self.x < 0: self.x = (width + self.x)
if self.y > height: self.y = (self.y - height)
elif self.y < 0: self.y = (height + self.y)
def draw(self, surface):
surface.blit(self.image, (self.x, self.y))
TrailTrim = False # set True for constant trail length
def main():
point = Point()
clock = pygame.time.Clock()
#background = pygame.image.load('backgroundborder.png').convert()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
point.handle_keys()
point.move()
screen.fill((200, 200, 200)) # clear screen
#screen.blit(background, (0, 0))
for d in trail:
if d: screen.blit(trailimage, d)
if (TrailTrim): del trail[0] # delete trail end
trail.append((point.x,point.y)) # add current postiion
point.draw(screen) # draw current point
pygame.display.update()
clock.tick(40) # 40 FPS
if __name__ == '__main__':
main()

Using mask for collision detection pygame [duplicate]

This question already has an answer here:
Pygame collision with masks
(1 answer)
Closed 2 years ago.
I am trying to make a game where your supposed to land the player on a surface. I have named this surface as 'lander'. Here is my player class:
class player:
def __init__(self, x, y, height, width):
self.ufo = pygame.image.load(r"C:\Users\rahul\OneDrive\Documents\A level python codes\lander\ufo.png")
self.ufo = pygame.transform.scale(self.ufo, (height, width))
self.height = height
self.width = width
self.x = x
self.y = y
self.speed = 0
self.score = 0
def draw(self):
g.screen.blit(self.ufo, (self.x, self.y))
def fly_up(self):
self.speed = 5
p.y = p.y -4
self.y -= self.speed
def fly_right(self):
self.speed = 5
p.y = p.y -4
self.x += self.speed
def fly_left(self):
self.speed = 5
p.y = p.y -4
self.x -= self.speed
Here is my lander class:
class lander:
def __init__(self, x, y, height, width):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = (192, 192, 192)
def draw(self):
self.lander = pygame.draw.rect(g.screen, self.color, (self.x, self.y, self.width, self.height))
def move(self):
self.speed = 4
self.x -= self.speed
def lander_off_screen(self):
if self.x == 0 - p.height - 50:
self.x = 1200 + p.height + 50
self.y = random.randint(230, 590)
I want to detect if they are colliding using masks. However I am having a couple of problems.
Firstly I dont know how to convert self.lander to alpha. It gave me an error when I tried.
Even when I used an actual image instead of a rectange as lander, it didnt work like I
I wanted it to.
Thanks
My code so far for reference is here:
import pygame
import os
import random
import time
os.environ["SDL_VIDEO_CENTERED"] = "1"
pygame.init()
start = time.time()
def main():
class game:
def __init__(self, *args):
self.win = pygame.display
self.SH = 600
self.SW = 1200
self.screen = self.win.set_mode((self.SW, self.SH))
self.win.set_caption("UFO Lander")
self.white = (255, 255, 255)
self.red = (255, 0, 0)
self.green = (0, 255, 0)
self.transparent = (0, 0, 0, 0)
self.background = pygame.image.load(r"C:\Users\rahul\OneDrive\Documents\A level python codes\lander\background.png")
self.background = pygame.transform.scale(self.background, (self.SW, self.SH))
self.transparent = (0, 0, 0, 0)
clock = pygame.time.Clock()
self.FPS = 30
self.FPS = clock.tick(self.FPS)
self.gravity = 4
self.time_passed = pygame.time.get_ticks()/1000
def write(self, text, color, x , y, size):
font = pygame.font.SysFont("Aerial", size)
text = font.render(text, True, color)
textRect = text.get_rect()
self.screen.blit(text,(x , y))
def player_off_screen(self):
if p.x < 0:
p.x = 0
g.write("YOU CRASHED!!", self.red, 50, 250, 200)
g.win.update()
pygame.time.delay(1000)
main()
if p.x + p.height > 1200:
p.x = 1200 - p.height
g.write("YOU CRASHED!!", self.red, 50, 250, 200)
g.win.update()
pygame.time.delay(1000)
main()
if p.y < 0:
p.y = 0
g.write("YOU CRASHED!!", self.red, 50, 250, 200)
g.win.update()
pygame.time.delay(1000)
main()
if p.y + p.width > 600:
p.y = 600 - p.width
g.write("YOU CRASHED!!", self.red, 50, 250, 200)
g.win.update()
pygame.time.delay(1000)
main()
class thrust:
def __init__(self, x, y):
self.thrust = pygame.image.load(r"C:\Users\rahul\OneDrive\Documents\A level python codes\lander\thrust.png")
self.thrust = pygame.transform.scale(self.thrust, (100, 150))
self.x = x
self.y = y
def draw(self):
g.screen.blit(self.thrust, (self.x, self.y))
def update_x(self, variable):
self.x = p.x + variable
def update_y(self, variable):
self.y = p.y + variable
class player:
def __init__(self, x, y, height, width):
self.ufo = pygame.image.load(r"C:\Users\rahul\OneDrive\Documents\A level python codes\lander\ufo.png")
self.ufo = pygame.transform.scale(self.ufo, (height, width))
self.height = height
self.width = width
self.x = x
self.y = y
self.speed = 0
self.score = 0
def draw(self):
g.screen.blit(self.ufo, (self.x, self.y))
def fly_up(self):
self.speed = 5
p.y = p.y -4
self.y -= self.speed
def fly_right(self):
self.speed = 5
p.y = p.y -4
self.x += self.speed
def fly_left(self):
self.speed = 5
p.y = p.y -4
self.x -= self.speed
p = player(100, 100, 350, 150)
t = thrust(p.x, p.y)
t2 = thrust(p.x, p.y)
class obstacles:pass
class lander:
def __init__(self, x, y, height, width):
self.x = x
self.y = y
self.height = height
self.width = width
self.color = (192, 192, 192)
def draw(self):
self.lander = pygame.draw.rect(g.screen, self.color, (self.x, self.y, self.width, self.height))
def move(self):
self.speed = 4
self.x -= self.speed
def lander_off_screen(self):
if self.x == 0 - p.height - 50:
self.x = 1200 + p.height + 50
self.y = random.randint(230, 590)
class fuel:
def __init__(self):
self.x = 100
self.y = 60
self.height = 30
self.width = 300
def draw(self):
pygame.draw.rect(g.screen, g.white, (self.x, self.y, self.width, self.height))
def less_fuel(self):
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT or (pygame.K_UP) or (pygame.K_LEFT):
self.change = 0.7
self.width -= self.change
def run_out(self):
if self.width < 0:
g.write("FUEL EMPTY!!", g.red, 50, 250, 200)
g.win.update()
pygame.time.delay(1000)
main()
f = fuel()
class menu: pass
g = game()
run = True
t = thrust(p.x, p.y)
l = lander(1200 - p.height - 50, random.randint(230, 590), 20, p.height+ 50)
while run:
g.screen.blit(g.background, (0, 0))
l.draw()
l.move()
g.write("s", g.white, 350, 10, 50)
g.write("Fuel: ", g.white ,10, 60, 50)
g.write("Score: %d" %(p.score), g.white, 1000, 10, 50)
p.draw()
p.y += g.gravity
g.player_off_screen()
l.lander_off_screen()
t.update_x(50)
t.update_y(110)
t2.update_x(200)
t2.update_y(110)
f.draw()
f.run_out()
try:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
f.less_fuel()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
p.fly_right()
if event.key == pygame.K_LEFT:
p.fly_left()
if event.key == pygame.K_UP:
t.draw()
t2.draw()
p.fly_up()
end = time.time()
duration = end - start
g.write("Total game time: %d" %(duration), g.white ,10, 10, 50)
except NameError:
pass
pygame.display.flip()
if __name__ == "__main__":
main()
Create a pygame.Surface with the size of the rectangle rather than drawing a rectangle:
class lander:
def __init__(self, x, y, height, width):
# [...]
self.image = pygame.Surface((width, height))
self.image.fill((192, 192, 192))
def draw(self):
self.lander = self.image.get_rect(topleft = (self.x, self.y))
g.screen.blit(self.image, self.lander)
Use pygame.mask.from_surface() to create a pygame.mask.Mask object from a Surface:
class player:
def __init__(self, x, y, height, width):
# [...]
self.mask = pygame.mask.from_surface(self.ufo)
class lander:
def __init__(self, x, y, height, width):
# [...]
self.mask = pygame.mask.from_surface(self.image)
Use overlap() to detect the collision of masks. The offset parameter of the method overlap() is the relative position of the othermask in relation to the pygame.mask.Mask object.
In the following p is an instance of player and l is an instance of lander. So the offset is calculated by subtracting the coordinates of p from the coordinates of l:
offset = (l.x - p.x), (l.y - p.y)
if p.mask.overlap(l.mask, offset):
print("hit")
Minimal example: repl.it/#Rabbid76/PyGame-SurfaceMaskIntersect
See also: Mask and Pygame collision with masks is not working

Object to shoot a projectile

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

Pygame: Passing two player objects to class [duplicate]

This question already has answers here:
Created multiple instances of the same image using a loop, can I move each instance of the image independently?
(1 answer)
What is the difference between pygame sprite and surface?
(1 answer)
Closed 1 year ago.
I am a Pygame newbie and am learning as I go. I would like to become familiar with normal Pygame conventions. I would like you Pygame experts go over my code and let me know if there are any places I should modify my code to follow best practice.
The point of the game is to collect as many pills as possible. Yellows are worth 10, reds 20, blue 30 and black 40. First one to reach 15,000 wins. The ships are controlled using ‘wasd’ and ‘updownleftright’. The complete code is posted below.
Some areas of concern I am looking at are the following:
Where I created two separate classes to store the score information. I feel that I could do the same job with one class, but am a bit confused on how that would look since I am using a TextGroup and it needs to be passed both Ship objects on the call to TextGroup.update().
class Text(Entity):
def __init__(self, text, size, color, position, font=None):
Entity.__init__(self)
self.color = color
self.position = position
self.font = pygame.font.Font(font, size)
self.text = text
self.image = self.font.render(str(text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(position[0]-self.rect.width/2, position[1])
class Mass_Left(Text):
def __init__(self, text, size, color, position, font=None):
Text.__init__(self, text, size, color, position, font=None)
def update(self, ship_left, ship_right):
self.text = "mass: " + str(ship_left.density-169)
self.image = self.font.render(str(self.text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(self.position[0]-self.rect.width/2, self.position[1])
class Mass_Right(Text):
def __init__(self, text, size, color, position, font=None):
Text.__init__(self, text, size, color, position, font=None)
def update(self, ship_left, ship_right):
self.text = "mass: " + str(ship_right.density-169)
self.image = self.font.render(str(self.text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(self.position[0]-self.rect.width/2, self.position[1])
Also, here in the method ‘moveShip()’ where I am checking if self.player is ‘left’ or ‘right’. I feel that there should be a way to do this by passing the class a function when the Ship object is created that will take appropriate action and different action depending on whether it’s the right or left ship.
def moveShip(self):
key = pygame.key.get_pressed()
if self.player == 'left' and (key[pygame.K_w] or key[pygame.K_s] or key[pygame.K_a] or key[pygame.K_d]):
if key[pygame.K_w]:
self.rect.y -= self.speed
if key[pygame.K_s]:
self.rect.y += self.speed
if key[pygame.K_a]:
self.rect.x -= self.speed
if key[pygame.K_d]:
self.rect.x += self.speed
# Adjust Player 2 Speed
if self.player == 'right' and (key[pygame.K_UP] or key[pygame.K_DOWN] or key[pygame.K_LEFT] or key[pygame.K_RIGHT]):
if key[pygame.K_UP]:
self.rect.y -= self.speed
if key[pygame.K_DOWN]:
self.rect.y += self.speed
if key[pygame.K_LEFT]:
self.rect.x -= self.speed
if key[pygame.K_RIGHT]:
self.rect.x += self.speed
Same issue here with the method ‘moveInbounds()’
Same issue in the method ‘winGame()’.
The function ‘genRandom’ generates a tuple that contains a random x value for the Pills and a random density value between 1-4. I am using string concatenation, then doing a type conversion, but I’m sure there’s a more straight forward way to generate a random tuple.
def genRandom(size):
xval_density = []
for j in range(size):
length = str(random.randrange(0, (WIN_W/2) - PILL_WIDTH))
stup = '('
stup = stup + str(length)
stup = stup + ", "
stup = stup + random.choice('1111111111111111111122222334')
stup = stup + ')'
tup = literal_eval(stup)
xval_density.append(tup)
return xval_density
I’m also uncomfortable using so many global variables such as PILL_COUNT and TIMER. So if there is a best practice in that situation, I’d be happy to know about it.
Here's the complete code:
import sys, pygame, os, random, math, time
from ast import literal_eval
# Force static position of screen
os.environ['SDL_VIDEO_CENTERED'] = '1'
# Runs imported module
pygame.init()
# Constants
LEFT = 'left'
RIGHT = 'right'
YELLOW = (255, 255, 0)
RED = (255,0,0)
BLUE = (0,0,153)
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
SHIP_WIDTH = 13
SHIP_HEIGHT = 13
PILL_WIDTH = 7
PILL_HEIGHT = 25
PILL_MAX_SIZE = 3000
PILL_COUNT = 0
TIMER = 0
WIN_W = 1200
WIN_H = 670
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Text(Entity):
def __init__(self, text, size, color, position, font=None):
Entity.__init__(self)
self.color = color
self.position = position
self.font = pygame.font.Font(font, size)
self.text = text
self.image = self.font.render(str(text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(position[0]-self.rect.width/2, position[1])
class Mass_Left(Text):
def __init__(self, text, size, color, position, font=None):
Text.__init__(self, text, size, color, position, font=None)
def update(self, ship_left, ship_right):
self.text = "mass: " + str(ship_left.density-169)
self.image = self.font.render(str(self.text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(self.position[0]-self.rect.width/2, self.position[1])
class Mass_Right(Text):
def __init__(self, text, size, color, position, font=None):
Text.__init__(self, text, size, color, position, font=None)
def update(self, ship_left, ship_right):
self.text = "mass: " + str(ship_right.density-169)
self.image = self.font.render(str(self.text), 1, self.color)
self.rect = self.image.get_rect()
self.rect.move_ip(self.position[0]-self.rect.width/2, self.position[1])
class Ship(Entity):
def __init__(self, x, y, player):
Entity.__init__(self)
self.win = False
self.speed = 5
self.player = player
self.density = SHIP_WIDTH * SHIP_HEIGHT
self.old_density = 144
self.densityIncrease = False
self.image = pygame.Surface((SHIP_WIDTH, SHIP_HEIGHT)).convert()
self.rect = pygame.Rect(x, y, SHIP_WIDTH, SHIP_HEIGHT)
def moveShip(self):
key = pygame.key.get_pressed()
if self.player == 'left' and (key[pygame.K_w] or key[pygame.K_s] or key[pygame.K_a] or key[pygame.K_d]):
if key[pygame.K_w]:
self.rect.y -= self.speed
if key[pygame.K_s]:
self.rect.y += self.speed
if key[pygame.K_a]:
self.rect.x -= self.speed
if key[pygame.K_d]:
self.rect.x += self.speed
# Adjust Player 2 Speed
if self.player == 'right' and (key[pygame.K_UP] or key[pygame.K_DOWN] or key[pygame.K_LEFT] or key[pygame.K_RIGHT]):
if key[pygame.K_UP]:
self.rect.y -= self.speed
if key[pygame.K_DOWN]:
self.rect.y += self.speed
if key[pygame.K_LEFT]:
self.rect.x -= self.speed
if key[pygame.K_RIGHT]:
self.rect.x += self.speed
def moveInbounds(self):
# Keep Ship Movement Inbounds
if self.rect.y < WIN_H/15:
self.rect.y = WIN_H/15
if self.rect.y > WIN_H - self.rect.height:
self.rect.y = WIN_H - self.rect.height
if self.player == 'left':
if self.rect.x < 0:
self.rect.x = 0
if self.rect.x > WIN_W/2 - self.rect.width:
self.rect.x = WIN_W/2 - self.rect.width
elif self.player == 'right':
if self.rect.x < WIN_W/2:
self.rect.x = WIN_W/2
if self.rect.x > WIN_W - self.rect.width:
self.rect.x = WIN_W - self.rect.width
def checkCollisions(self, pillGroup):
collisions = pygame.sprite.spritecollide(self, pillGroup, True)
for key in collisions:
self.density += key.density
def grow(self):
if self.old_density < self.density:
self.old_density = self.density
self.rect.width = self.rect.height = math.sqrt(self.density)
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
def update(self, pillGroup):
# Ship Movement
self.moveShip()
self.moveInbounds()
self.checkCollisions(pillGroup)
self.grow()
def winGame(self):
if self.win:
if TIMER % 5 == 0:
self.rect.width += 20
self.rect.height += 10
if self.player == 'left':
self.rect.x -= 4
elif self.player == 'right':
self.rect.x -= 10
if self.player == 'left':
self.rect.y -= 5
elif self.player == 'right':
self.rect.y -= 5
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
self.density += 378
else:
if TIMER % 5 == 0:
if self.rect.width == 0:
pass
elif self.rect.width > 10:
self.rect.width -= 5
self.rect.height -= 5
if self.density >= 0:
self.density -= self.density/3
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
elif self.rect.width <= 10:
self.rect.width -= 1
self.rect.height -= 1
if self.density > 0:
self.density -= 2
self.image = pygame.transform.scale(self.image, (self.rect.width, self.rect.height))
if self.density - 169 < 0:
self.density = 169
def check_done(self):
if self.rect.height > WIN_H*1.5 and self.rect.width > WIN_W * 1.5:
return False
else:
return True
class Pill(Entity):
def __init__(self, xval, density):
Entity.__init__(self)
self.speed = 3
self.density = density
self.image = pygame.Surface((PILL_WIDTH, PILL_HEIGHT)).convert()
self.image.fill(self.setColor(density))
self.rect = self.image.get_rect()
self.rect = self.rect.move(xval, WIN_H/15)
def setColor(self, density):
if density == 50:
return YELLOW
elif density == 100:
return RED
elif density == 150:
return BLUE
elif density == 200:
return BLACK
def update(self):
if self.rect.y > WIN_H:
self.kill()
else:
self.rect = self.rect.move((0, self.speed))
def addPill(pillGroup, xvalue, density):
global PILL_COUNT, PILL_MAX_SIZE, TIMER
if PILL_COUNT + 1 < PILL_MAX_SIZE and TIMER % 10 == 0:
pill = Pill(100, density)
pill2 = Pill(100 + WIN_W/2, density)
pillGroup.add(pill, pill2)
PILL_COUNT += 1
def genRandom(size):
xval_density = []
for j in range(size):
length = str(random.randrange(0, (WIN_W/2) - PILL_WIDTH))
stup = '('
stup = stup + str(length)
stup = stup + ", "
stup = stup + random.choice('1111111111111111111122222334')
stup = stup + ')'
tup = literal_eval(stup)
xval_density.append(tup)
return xval_density
def loseGame(left, right):
if left.density > 1500 or right.density > 1500:
if left.density > 1500:
left.win = True
elif right.density > 1500:
right.win = True
return False
else:
return True
def main():
# Initialize variables
global TIMER, PILL_COUNT
fps = 60
pygame.display.set_caption('Pong')
screen = pygame.display.set_mode((WIN_W, WIN_H), pygame.SRCALPHA)
clock = pygame.time.Clock()
play = game_done = True
xval_density = genRandom(PILL_MAX_SIZE)
# Create Game Objects
ship_left = Ship((WIN_W/4) - (SHIP_WIDTH/2), WIN_H - (SHIP_HEIGHT * 4), 'left')
ship_right = Ship((WIN_W/1.3) - (SHIP_WIDTH/2), WIN_H - (SHIP_HEIGHT * 4), 'right')
score1 = Mass_Left("mass: " + str(ship_left.density-1), 40, BLACK, (WIN_W/5, 10))
score2 = Mass_Right("mass: " + str(ship_right.density-1), 40, BLACK, (WIN_W/1.25, 10))
vert_partition = pygame.Surface((1, WIN_H))
hori_partition = pygame.Surface((WIN_W, 1))
# Create Groups
shipGroup = pygame.sprite.Group()
shipGroup.add(ship_left, ship_right)
pillGroup = pygame.sprite.Group()
textGroup = pygame.sprite.Group()
textGroup.add(score1, score2)
# Gameplay
while play:
# Checks if window exit button pressed
for event in pygame.event.get():
if event.type == pygame.QUIT: sys.exit()
# Keypresses
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
sys.exit()
# Update Groups
shipGroup.update(pillGroup)
pillGroup.update()
textGroup.update(ship_left, ship_right)
# Adding Pills
addPill(pillGroup, xval_density[PILL_COUNT][0], xval_density[PILL_COUNT][1]*50)
# Print Groups
screen.fill(WHITE)
pillGroup.draw(screen)
shipGroup.draw(screen)
textGroup.draw(screen)
screen.blit(vert_partition, (WIN_W/2, WIN_H/15))
screen.blit(hori_partition, (0, WIN_H/15))
play = loseGame(ship_left, ship_right)
TIMER += 1
# Limits frames per iteration of while loop
clock.tick(fps)
# Writes to main surface
pygame.display.flip()
# Gameplay
while game_done:
ship_left.winGame()
ship_right.winGame()
# Updating
pillGroup.update()
textGroup.update(ship_left, ship_right)
# Adding Pills
addPill(pillGroup, xval_density[PILL_COUNT][0], xval_density[PILL_COUNT][1]*50)
# Print Groups
screen.fill(WHITE)
pillGroup.draw(screen)
shipGroup.draw(screen)
textGroup.draw(screen)
screen.blit(vert_partition, (WIN_W/2, WIN_H/15))
screen.blit(hori_partition, (0, WIN_H/15))
game_done = ship_left.check_done() and ship_right.check_done()
TIMER += 1
# Limits frames per iteration of while loop
clock.tick(fps)
# Writes to main surface
pygame.display.flip()
if __name__ == "__main__":
main()

Categories