How do I move a character based on it's rotation? - python

I have a character in PyGame which I want to move forward depending on its rotation. Currently it's very strange and wonky.
Here's all my code: https://www.toptal.com/developers/hastebin/enapuravet.lua
Here's the relevent code:
def rotate(self, amount):
self.rotation += amount
if self.rotation == 181:
self.rotation = -180
elif self.rotation == -181:
self.rotation = 180
print(self.rotation)
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = self.rect.center)
def move_forward(self, speed):
print(self.rotation)
new_coordinates = calculate_new_xy((self.x, self.y), 2, self.rotation*(math.pi/180))
self.x = new_coordinates[0]
self.y = new_coordinates[1]
self.rect = self.capybara.get_rect(center = (self.x, self.y))
def calculate_new_xy(old_xy,speed,angle_in_radians):
print(angle_in_radians)
new_x = old_xy[0] + (speed*math.cos(angle_in_radians))
new_y = old_xy[1] + (speed*math.sin(angle_in_radians))
return new_x, new_y
Here's what it does:
https://gyazo.com/3b7bf1c5b2e760a53913b6d3acae6e67
I also tried some other x_y calculating functions like this:
move_vec = pygame.math.Vector2()
move_vec.from_polar((speed, angle_in_degrees))
return old_xy + move_vec
but they had the same/very similar results.
This is the capybara:

Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .center) of the rectangle.
The unit of the angle of math.sin and math.cos is Randian. You needs to convert the angle from degrees to radians with math.radians:
class Capybara:
def __init__(self, size_multiplier):
# [...]
self.x, self.y = 300, 300
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def rotate(self, amount):
self.rotation += amount
ifv self.rotation > 360:
self.rotation -= 360
elif self.rotation < 0:
self.rotation += 360
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def move(self, move):
self.x += move * math.cos(math.radians(self.rotation + 90))
self.y -= move * math.sin(math.radians(self.rotation + 90))
See also Move Character with Vector, Image rotation while moving or How to turn the sprite in pygame while moving with the keys.
Minimal example:
import pygame, math
pygame.init()
size = width, height = 600, 600
green = 50, 168, 82
screen = pygame.display.set_mode(size)
class Capybara:
def __init__(self, size_multiplier):
self.capybara = pygame.image.load('capybara.png')
self.o_size = 92, 206
self.new_size = (self.o_size[0] * size_multiplier,self.o_size[1] * size_multiplier)
self.capybara = pygame.transform.scale(self.capybara, self.new_size)
self.base_capybara = self.capybara
self.rotation = 0
self.x, self.y = 300, 300
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def rotate(self, amount):
self.rotation += amount
if self.rotation > 360:
self.rotation -= 360
elif self.rotation < 0:
self.rotation += 360
self.capybara = pygame.transform.rotate(self.base_capybara, self.rotation)
self.rect = self.capybara.get_rect(center = (round(self.x), round(self.y)))
def move(self, move):
self.x += move * math.cos(math.radians(self.rotation + 90))
self.y -= move * math.sin(math.radians(self.rotation + 90))
capybara = Capybara(0.8)
fpsClock = pygame.time.Clock()
rotate_l = False
rotate_r = False
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
keys = pygame.key.get_pressed()
rotate = keys[pygame.K_RIGHT] - keys[pygame.K_LEFT]
move = keys[pygame.K_DOWN] - keys[pygame.K_UP]
capybara.move(-move * 5)
capybara.rotate(-rotate)
screen.fill(green)
screen.blit(capybara.capybara, capybara.rect)
pygame.display.update()
fpsClock.tick(60)

Related

Why is my enemybullets shooting from wrong area in Pygame

I am currently creating a top-down shooter in Pygame and currently need my enemies to shoot bullets at my player. The problem is that when I move my character the position where the bullets are shooting from moves as well when they are meant to be shot from the enemies at all times. I have watched many different tutorials but non of which have proven helpful. Hoping someone can help.
import pygame
import sys
import math
import random
import time
import multiprocessing
from pygame import mixer
pygame.init()
displayWidth = 100
displayHeight = 200
enemytime = time.time() + 10
enemyshoottime = time.time() + 1
#Enemy
class Enemy1(object):
def __init__(self, x, y):
self.x = x
self.y = y
self.hit_box = (self.x-10, self.y -10, 70, 70)
self.animation_images = [pygame.image.load("Enemy1_animation_0.png"), pygame.image.load("Enemy1_animation_1.png"),
pygame.image.load("Enemy1_animation_2.png"), pygame.image.load("Enemy1_animation_3.png")]
self.animation_count = 0
self.reset_offset = 0
self.offset_x = random.randrange(-150, 150)
self.offset_y = random.randrange(-150, 150)
self.health = 4
def main(self, display):
if self.animation_count + 1 == 16:
self.animation_count = 0
self.animation_count += 1
if self.reset_offset == 0:
self.offset_x = random.randrange(-150, 150)
self.offset_y = random.randrange(-150, 150)
self.reset_offset = random.randrange(120, 150)
else:
self.reset_offset -= 1
if player.x + self.offset_x > self.x-display_scroll[0]:
self.x += 1
elif player.x + self.offset_x < self.x-display_scroll[0]:
self.x -= 1
if player.y + self.offset_y > self.y-display_scroll[1]:
self.y += 1
elif player.y + self.offset_y < self.y-display_scroll[1]:
self.y -= 1
display.blit(pygame.transform.scale(self.animation_images[self.animation_count//4], (50, 50)), (self.x-display_scroll[0], self.y-display_scroll[1]))
self.hit_box = (self.x-display_scroll[0]-10, self.y-display_scroll[1]-10, 70, 70)
pygame.draw.rect(display, (255, 0, 0), self.hit_box, 2)
#Enemy Bullet
class EnemyBullet:
def __init__(self, x, y, playerx, playery):
self.x = x
self.y = y
self.playerx = 300
self.playery = 300
self.speed = 7
self.angle = math.atan2(y-playerx, x-playery)
self.x_vel = math.cos(self.angle) * self.speed
self.y_vel = math.sin(self.angle) * self.speed
def main(self, display):
self.x -= int(self.x_vel)
self.y -= int(self.y_vel)
EnemyBulletRect = pygame.draw.circle(display, (255,0,0), (self.x, self.y), 5)
#list's
enemies = [ Enemy2(800, -200), Enemy3(-300, 500), Enemy4(1000, 400)]
enemy = Enemy1(600, 400)
enemy_bullets = []
sounds = ['explosion1.mp3', 'explosion2.mp3', 'explosion3.mp3']
player = Player(400, 300, 32, 32)
display_scroll = [0,0]
player_bullets = []
while True:
display.fill((0, 0, 0))
display.blit(displayImage, (0, 0))
#display.blit(ImageBackground, (0, 0))
display.blit(Earth, (700, 100))
show_score(textX, textY)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
pygame.quit()
#Enemy shoots
if time.time() >= enemyshoottime:
enemy_bullets.append(EnemyBullet(enemy.x, enemy.y, playerx, playery))
from playsound import playsound
playsound('lazer.mp3', block=False)
enemyshoottime = time.time() + 1
for bullets in enemy_bullets:
bullets.main(display)
#spawn enemies
if time.time() >= enemytime:
# Time to spawn a new enemy.
enemies.append( Enemy3( 100, 500 ) )
enemies.append( Enemy3( 600, 400 ) )
# Reset the alarm.
enemytime = time.time() + 10
The motion vector of the bullet can be calculated from the normalized direction vector (Unit vector) multiplied by the velocity. The unit vector is obtained by dividing the components of the vector by the Euclidean length of the vector.
Do not round the velocity before adding it to the position, but round the position when using it to draw the bullet. If you round the component vectors before adding them or round the position attributes themselves, there will be an inaccuracy that accumulates over time. Round only the values that are used in pygame.draw.circle Use round instead of int.
class EnemyBullet:
def __init__(self, x, y, playerx, playery):
self.x = x
self.y = y
self.speed = 7
dx = playerx - x
dy = playery - y
d = math.sqrt(dx*dx, dy*dy) # or d = math.hypot(dx, dy)
self.x_vel = self.speed * dx / d
self.y_vel = self.speed * dy / d
def main(self, display):
self.x += self.x_vel
self.y += self.y_vel
EnemyBulletRect = pygame.draw.circle(
display, (255,0,0), (round(self.x), round(self.y)), 5)

Rotate a rectangle over waves

I am trying to move a rectangle over the waves,trying to simulate a boat sailing.
For that, I rotate the rectangle using the height of the drawn lines and calculating the angle they form with the rectangle.
However, for some reason, in some points of the waves the rectangle is flickering.
The code shows the problem.
import sys
import math
import pygame
from pygame.locals import *
pygame.init()
SCREEN_WIDTH = 900
SCREEN_HEIGHT = 900
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
BLUE = (0, 103, 247)
RED = (255,0,0)
watter_levels = [0 for _ in range(SCREEN_WIDTH)]
def draw_water(surface, dy):
amplitude = 35
global watter_levels
for x in range(SCREEN_WIDTH):
y = int(math.sin((x)*(0.01)) * amplitude + dy)
watter_levels[x] = y
pygame.draw.aaline(surface,BLUE,(x,y),(x,y))
def get_water_level(index):
if index <= 0:
return watter_levels[0]
if index >= SCREEN_WIDTH:
return watter_levels[-1]
return watter_levels[index]
font = pygame.font.Font(None,30)
def debug(info,x=10,y=10):
display_surf = pygame.display.get_surface()
debug_surface = font.render(str(info),True,'White')
debug_rect = debug_surface.get_rect(topleft=(x,y))
pygame.draw.rect(display_surf,'Black',debug_rect)
display_surf.blit(debug_surface,debug_rect)
class Ship(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((80,80),pygame.SRCALPHA)
self.image.fill('red')
self.rect = self.image.get_rect(topleft=(0,0))
self.copy_img = self.image.copy()
self.move = pygame.math.Vector2(0,0)
self.copy_img = self.image.copy()
self.velocity = 8
def rotate(self, angle):
self.image = pygame.transform.rotate(self.copy_img, int(angle))
self.rect = self.image.get_rect(center=(self.rect.center))
def update(self):
self.get_input()
self.rect.bottom = get_water_level(self.rect.centerx)
left_y = get_water_level(self.rect.left)
right_y = get_water_level(self.rect.right)
angle = 180*math.atan2(left_y-right_y,self.image.get_width())/math.pi
debug("angle: "+str(int(angle)))
print(self.rect.left,self.rect.right)
self.rotate(angle)
def replicate(self):
if self.rect.left == 363:
return
self.rect.x += 1
def get_input(self):
self.replicate()
# keys = pygame.key.get_pressed()
# if keys[K_LEFT]:
# self.move.x = -self.velocity
# elif keys[K_RIGHT]:
# self.move.x = self.velocity
# else:
# self.move.x = 0
# if keys[K_UP]:
# self.move.y = -self.velocity
# elif keys[K_DOWN]:
# self.move.y = self.velocity
# else:
# self.move.y = 0
# self.rect.x += self.move.x
# self.rect.y += self.move.y
# if self.rect.left <= 0:
# self.rect.left = 0
# if self.rect.right >= SCREEN_WIDTH:
# self.rect.right = SCREEN_WIDTH
ship_sprite = pygame.sprite.GroupSingle()
ship_sprite.add(Ship())
while True:
screen.fill((200,210,255,0))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
ship_sprite.update()
ship_sprite.draw(screen)
draw_water(screen,SCREEN_HEIGHT-300)
clock.tick(60)
pygame.display.update()
It's about accuracy. You can improve the result by storing the water level with floating point precision:
def draw_water(surface, dy):
amplitude = 35
global watter_levels
for x in range(SCREEN_WIDTH):
y = math.sin(x * 0.01) * amplitude + dy
watter_levels[x] = y
pygame.draw.aaline(surface, BLUE, (x, round(y)), (x, round(y)))
Furthermore you have to get the left and right from the "unrotated" rectangle:
class Ship(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((80,80),pygame.SRCALPHA)
self.image.fill('red')
self.rect = self.image.get_rect(topleft = (0, 0))
self.copy_img = self.image.copy()
self.copy_rect = pygame.Rect(self.rect)
self.move = pygame.math.Vector2(0, 0)
self.velocity = 8
def rotate(self, angle):
self.image = pygame.transform.rotate(self.copy_img, round(angle))
self.rect = self.image.get_rect(center = (self.copy_rect.center))
def update(self):
self.get_input()
self.copy_rect.bottom = round(get_water_level(self.copy_rect.centerx))
left_y = get_water_level(self.copy_rect.left)
right_y = get_water_level(self.copy_rect.right)
angle = math.degrees(math.atan2(left_y-right_y, self.copy_img.get_width()))
debug("angle: " + str(round(angle)))
print(self.copy_rect.left, self.copy_rect.right)
self.rotate(angle)
def replicate(self):
if self.copy_rect.left == 363:
return
self.copy_rect.x += 1

How to move the player across a one background image?

Apologies for asking so many questions recently. I'm just starting to get into pygame.
With my previous questions I don't think I've been wording it properly for what I am trying to do.
Here is a quick picture I did to try demonstrate
This is a single background image or map that I would like the player to move across. The red X is just a starting place for the character.
I'm try to make it so when I move the player the background will also follow as if the player is moving through the map. I've already limited the player from not being able to go off the borders of the actual screen. Just having a bit of trouble now trying to make it so the single image will move along the player and if the player reaches the end of the map picture movement stops. I have seen people use scrolling and duplicating the image when the player moves. I just want to see it to the single image that the player will move across. I don't want to worry about collisions just be able to get the movement working.
This is the code I am currently using:
from pygame.locals import *
from math import sin
pygame.display.set_caption("TEST")
clock = pygame.time.Clock()
time_passed = 0
class Player():
def __init__(self,x,y):
self.Image = pygame.image.load("myAvatar.png").convert()
self.x = 200
self.y = 200
def getX(self):
return self.rect.x
def getY(self):
return self.rect.y
def handle_keys(self,screenHeight,screenWidth):
key = pygame.key.get_pressed()
dist = 2
if key[K_LEFT] and self.x > 0:
self.x -= 500 * time_passed
if key[K_RIGHT] and self.x < screenWidth -20:
self.x += 500 * time_passed
if key[K_UP] and self.y > 0:
self.y -= 500 * time_passed
if key[K_DOWN] and self.y < screenHeight -20:
self.y += 500 * time_passed
def draw(self, game_window):
self.Image = pygame.transform.scale(self.Image,(20,20))
game_window.blit(self.Image, (int(self.x), int(self.y)))
class Map():
def __init__(self):
self.Image = pygame.image.load("test2.png").convert()
self.rect = self.Image.get_rect()
self.x = 0
self.y = 0
def draw(self, game_window,screenHeight,screenWidth):
self.x = min(max(self.x, player.x - 2 * screenWidth / 2), player.x - screenWidth / 2)
self.y = min(max(self.y, player.y -2 * screenHeight / 2), player.y - screenHeight / 2)
game_window.blit(self.Image,(-self.x,-self.y))
class Enemy():
def __init__ (self,x,y):
self.Image = pygame.image.load("WC.jpg").convert()
self.rect = self.Image.get_rect(topleft = (x,y))
def draw(self, game_window):
self.Image = pygame.transform.scale(self.Image,(20,20))
game_window.blit(self.Image, (self.rect.x, self.rect.y))
pygame.init()
clock = pygame.time.Clock()
screenWidth = 400
screenHeight = 400
game_window = pygame.display.set_mode((screenWidth,screenHeight))
player = Player(200,200)
map = Map()
enemy = Enemy(250,250)
leave = False
while not leave:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
running = False
player.handle_keys(screenHeight,screenWidth)
game_window.fill((0,0,0))
map.draw(game_window,screenHeight,screenWidth)
#enemy.draw(game_window)
player.draw(game_window)
pygame.display.update()
pygame.display.flip()
time_passed = clock.tick() / 1000
pygame.quit()
quit()
Thanks
Shay
The player's movement depends on the size of the map. The x and y attributes of the "player" store the position on the map and are limited to the size of the map (map_size). The player is always drawn in the center of the screen, except it is near the boarders of the map:
class Player():
def __init__(self, x, y):
self.Image = pygame.image.load("myAvatar.png").convert()
self.x = 200
self.y = 200
def handle_keys(self, map_size):
key = pygame.key.get_pressed()
self.x += (key[K_RIGHT] - key[K_LEFT]) * 500 * time_passed
self.y += (key[K_DOWN] - key[K_UP]) * 500 * time_passed
self.x = max(0, min(map_size[0]-20, self.x))
self.y = max(0, min(map_size[1]-20, self.y))
def draw(self, game_window, map_size):
window_size = game_window.get_size()
center = window_size[0] // 2, window_size[0] // 2
pos = [self.x, self.y]
for i in range(2):
if center[i] < pos[i] <= map_size[i]-center[i]:
pos[i] = center[i]
elif pos[i] > map_size[i] - center[i]:
pos[i] = window_size[i] - map_size[i] + pos[i]
game_window.blit(self.Image, (int(pos[0]), int(pos[1])))
The player's position on the map is centered in the window:
class Map():
def __init__(self):
self.Image = pygame.image.load("test2.png").convert()
def draw(self, game_window):
window_size = game_window.get_size()
map_size = self.Image.get_size()
x = max(0, min(map_size[0] - window_size[0], player.x - 200))
y = max(0, min(map_size[1] - window_size[1], player.y - 200))
game_window.blit(self.Image, (-x, -y))
Application loop:
leave = False
while not leave:
for event in pygame.event.get():
if event.type == pygame.QUIT:
leave = True
player.handle_keys(map.Image.get_size())
game_window.fill((0,0,0))
map.draw(game_window)
#enemy.draw(game_window)
player.draw(game_window, map.Image.get_size())
pygame.display.update()
pygame.display.flip()
time_passed = clock.tick() / 1000
pygame.quit()

Bouncing pygame mask object on walls

I am rewriting a simple game. Before I just used e.g. pygame.draw.circle() to create and interact with objects.
However, there was a need to use masks for more complex tasks (like collision with polygons).
This is how I let the ball bounce on walls before:
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
But now with masks implemented, it only stops when it hits the wall but doesn't bounce off anymore.
Full code:
import pygame
from pygame.math import Vector2
import math
width = 1150
height = 800
# Colors
GOLD = (255, 215, 0)
drag = 0.999 # Between 0 and 1
elasticity = 0.75 # Between 0 and 1
pygame.init()
screen = pygame.display.set_mode((width, height))
clock = pygame.time.Clock()
# Define game object class
class Circle:
def __init__(self, coordinates, velocity, angle, radius, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.radius = radius
self.objectColor = objectColor
self.initSurface = pygame.Surface((self.radius*2, self.radius*2), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.circle(self.initSurface, self.objectColor, [self.radius, self.radius], self.radius)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.velocity = Vector2(self.velocity) * drag
self.rectangle.center = Vector2(self.x, self.y)
def bounce(self):
# Right boundary
if self.x > width - self.radius:
self.x = 2 * (width - self.radius) - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Left boundary
elif self.x < self.radius:
self.x = 2 * self.radius - self.x
self.angle = - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Top boundary
if self.y > height - self.radius:
self.y = 2 * (height - self.radius) - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
# Bottom boundary
elif self.y < self.radius:
self.y = 2 * self.radius - self.y
self.angle = math.pi - self.angle
self.velocity = Vector2(self.velocity) * elasticity
class Polygon:
def __init__(self, coordinates, velocity, angle, pointList, objectColor):
self.x = coordinates[0]
self.y = coordinates[1]
self.velocity = velocity
self.angle = angle
self.pointList = pointList
self.objectColor = objectColor
self.initSurface = pygame.Surface((max(self.pointList, key=lambda item: item[0])[0],
max(self.pointList, key=lambda item: item[1])[1]), pygame.SRCALPHA)
self.rectangle = self.initSurface.get_rect(center=Vector2(self.x, self.y))
self.surface = self.initSurface
self.mask = None
self.draw()
def draw(self):
pygame.draw.polygon(self.initSurface, self.objectColor, self.pointList)
self.mask = pygame.mask.from_surface(self.initSurface)
def move(self):
self.x += self.velocity[0]
self.y += self.velocity[1]
self.rectangle.center = Vector2(self.x, self.y)
def rotate(self, angle):
self.angle += angle
self.velocity.rotate_ip(-angle)
surface = pygame.transform.rotate(self.initSurface, self.angle)
self.rectangle = surface.get_rect(center=self.rectangle.center)
# We need a new mask after the rotation.
self.mask = pygame.mask.from_surface(surface)
self.surface = surface
# Colliding game object particles
def collide(p1, p2):
offset = p1.rectangle[0] - p2.rectangle[0], p1.rectangle[1] - p2.rectangle[1]
overlap = myBall.mask.overlap(p1.mask, offset)
if overlap:
p2.velocity = Vector2(p1.velocity) * 1.4
# Images
BG_IMG = pygame.Surface((1150, 800))
BG_IMG.fill((30, 120, 30))
# Init Ball and car (input)
myBall = Circle(Vector2(575, 400), Vector2(0, 0), 0, 60, GOLD)
myInput = Polygon(Vector2(470, 370), Vector2(3, 0), 0, [(0, 0), (50, 10), (50, 20), (0, 30)], GOLD)
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
myInput.rotate(5)
elif keys[pygame.K_RIGHT]:
myInput.rotate(-5)
# Move the car
myInput.move()
# Move the ball
myBall.velocity *= .99 # Friction
myBall.move()
myBall.bounce()
# Car collision.
collide(myInput, myBall)
# Drawing
screen.blit(BG_IMG, (0, 0))
screen.blit(myBall.surface, myBall.rectangle)
screen.blit(myInput.surface, myInput.rectangle)
pygame.display.flip()
clock.tick(60)
pygame.quit()
What am I missing here? I can't see through anymore.
In the bounce method you make the the velocity smaller, you never flip it so, just add a minus sign and it should bounce back instead of moving slower and slower into the boundry
self.velocity = Vector2(-self.velocity) * elasticity

Drawing images onto Pygame's background or screen

I am writing a game with pygame which involves two player-controlled tanks which go around a brick map with their own health bars and shoot each other with one kind of bullet. From what I can tell, everything else is working, but I am having trouble determining which surface to draw the bricks, tanks, and bullets on: the "background" or the "screen" (the bricks do not change during the game, only the tanks and bullets). I tried drawing everything but the health bars on the background, but that just resulted in a black screen which only displayed the health bars. I then drew everything directly onto the screen and that displays everything correctly at first, but when the tanks move around, the screen doesn't refresh (I get many many tank images overlapping each other as the tank moves) and the bullets do not fire properly as a result. The code is below but the main() method is at the very bottom and that is what is causing errors. The "effects.py" file that is imported at the very top contains just the bullets and "booms" and "bombs" classes which are just special effects but aren't used so far in the main() method so they can be ignored.
from __future__ import print_function, division
import pygame, os, sys
from pygame.locals import *
import random
import effects
from effects import *
import math
GRAD = math.pi / 180
class Brick(pygame.sprite.Sprite):
def __init__(self, pos, image, top, bottom, right, left, bricks):
pygame.sprite.Sprite.__init__(self)
self.rect = image.get_rect(topleft = pos)
self.image = image
self.pos = pos
self.top = top
self.bottom = bottom
self.right = right
self.left = left
self.health = 30
bricks.add(self)
class City(object):
def __init__(self, bricks, level):
self.level = level
self.city = self.level.convert_alpha()
self.brick = pygame.image.load("brick.png").convert_alpha()
self.bricks = bricks
self.x = self.y = 0
collidable = (255, 0, 0, 255)
self.height = self.city.get_height()
self.width = self.city.get_width()
self.vehicle_pos = (0,0)
while self.y < self.height:
color = self.city.get_at((self.x, self.y))
collidable = (255, 0, 0, 255), (0,0,0,255)
top = False
bottom = False
right = False
left = False
if color in collidable:
self.bricks.add(Brick((self.x*30, self.y*30), self.brick, top, bottom, right, left, self.bricks))
print ("brick added!")
print (self.x, self.y)
self.x += 1
if self.x >= self.width:
self.x = 0
self.y += 1
def get_size(self):
return [self.city.get_size()[0]*30, self.city.get_size()[1]*30]
class Tank(pygame.sprite.Sprite):
book = {} # a book of tanks to store all tanks
number = 0 # each tank gets his own number
firekey = (pygame.K_SPACE, pygame.K_RETURN)
forwardkey = (pygame.K_w, pygame.K_i)
backwardkey = (pygame.K_s, pygame.K_k)
tankLeftkey = (pygame.K_a, pygame.K_j)
tankRightkey = (pygame.K_d, pygame.K_l)
color = ((200,200,0), (0,0,200))
def __init__(self, pos, angle, health):
self.number = Tank.number
Tank.number += 1
Tank.book[self.number] = self
pygame.sprite.Sprite.__init__(self)
self.tank_pic = pygame.image.load("tank.png").convert_alpha()
self.image = self.tank_pic
self.image_type = self.tank_pic
self.tank1_pic = pygame.image.load("tank1.png").convert_alpha()
self._image = self.image
self.rect = self.image.get_rect()
self.rect = self.rect.move(pos)
self.tankAngle = angle # tank facing
#---handles controls---#
self.firekey = Tank.firekey[self.number] # main gun
self.forwardkey = Tank.forwardkey[self.number] # move tank
self.backwardkey = Tank.backwardkey[self.number] # reverse tank
self.tankLeftkey = Tank.tankLeftkey[self.number] # rotate tank
self.tankRightkey = Tank.tankRightkey[self.number] # rotat tank
self.health = health
self.alive = True
self.speed = 5
self.angle = angle
self.timer = 3
self.timerstart = 0
self.x, self.y = self.rect.center
self.bullet_s = pygame.mixer.Sound("bullet.wav")
self.bullet_s.set_volume(.25)
def rotate(self):
center = self.rect.center
self.image = pygame.transform.rotozoom(self._image, self.angle, 1.0)
self.rect = self.image.get_rect(center = center)
def update(self, keys, bricks, bullets, booms, bombs):
self.bricks = bricks
self.t = True
self._rect = Rect(self.rect)
self._rect.center = self.x, self.y
self.rotate()
turn_speed = 3
pressedkeys = pygame.key.get_pressed()
if pressedkeys[self.forwardkey]:
self.x += sin(radians(self.angle))*-self.speed
self.y += cos(radians(self.angle))*-self.speed
if pressedkeys[self.backwardkey]:
self.x += sin(radians(self.angle))*self.speed
self.y += cos(radians(self.angle))*self.speed
if pressedkeys[self.tankLeftkey]:
self.angle += turn_speed
if pressedkeys[self.tankRightkey]:
self.angle -= turn_speed
if keys[self.firekey]:
if self.timer >= 3:
self.timer = self.timerstart
self.b_size = "small"
bullets.add(Bullet(self.rect.center, self.angle, self.b_size, "vehicle"))
self.bullet_s.play()
if self.timer < 3:
self.timer += 1
if self.angle > 360:
self.angle = self.angle-360
if self.angle <0:
self.angle = self.angle+360
self.rect.center = self.x, self.y
x = self.rect.centerx
y = self.rect.centery
_x = self._rect.centerx
_y = self._rect.centery
for b in bricks:
if self.rect.colliderect(b.rect):
if _x+21 <= b.rect.left and x+21 > b.rect.left:
if b.left == True:
self.x = b.rect.left-21
if _x-21 >= b.rect.right and x-21 < b.rect.right:
if b.right == True:
self.x = b.rect.right+21
if _y+21 <= b.rect.top and y+21 > b.rect.top:
if b.top == True:
self.y = b.rect.top-21
if _y-21 >= b.rect.bottom and y-21 < b.rect.bottom:
if b.bottom == True:
self.y = b.rect.bottom+21
for b in bullets:
if self.rect.colliderect(b.rect):
b_size = b.get_size()
pygame.sprite.Sprite.kill(b)
if b_size == "small":
booms.add(Boom(b.rect.center, "small"))
self.health -= 1
if b_size == "big":
booms.add(Boom(b.rect.center, "big"))
self.health -=5
for b in bombs:
if self.rect.colliderect(b.rect) and b.timer == 20:
self.health -=5
if self.health <= 0:
booms.add(Boom(self.rect.center, "huge"))
self.alive = False
self.health = 0
'''if self.image_type == self.tank_pic:
self.image = self.tank1_pic
self.image_type = self.tank1_pic
print "switch 1"
if self.image_type == self.tank1_pic:
self.image = self.tank_pic
self.image_type = self.tank_pic
print "switch 2"'''
class Turret(pygame.sprite.Sprite):
def __init__(self, pos, angle, follow):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("turret.png").convert_alpha()
self.timer = 40
self.timer_start = self.timer
self.size = "big"
self.bang_s = pygame.mixer.Sound("bang.wav")
self.speed = 3
self.bang_s.set_volume(1.0)
self._image = self.image
self.rect = self.image.get_rect()
self.rect = self.rect.move(pos)
self.angle = angle
self.timer = 40
self.wait_timer = 5
self.timer_restart = 0
self.x, self.y = self.rect.center
self.follow = follow
def rotate(self):
center = self._rect.center
self.image = pygame.transform.rotozoom(self._image, self.angle, 1.0)
self.rect = self.image.get_rect(center = center)
def update(self, pos, mx, my, keys, booms, tank_angle, bricks, background, city):
self.background = background
self.city_size = city
self.t = True
self.bricks = bricks
self.end = None
self._rect = Rect(self.rect)
self._rect.center = pos
self.tank_angle = tank_angle
t_x, t_y = self.rect.center
if keys[K_m]:
if self.wait_timer >= 5:
self.follow = not self.follow
self.wait_timer = self.timer_restart
if self.follow:
self.mouse_angle = math.atan2(xd, yd)*(180/math.pi)+180 #used atan2(x,y) instead of atan2(y,x). Sprite was origanly drawn along the y axis, gave better results
if self.angle < self.mouse_angle:
if math.fabs(self.angle - self.mouse_angle) < 180:
self.angle +=self.speed
else:
self.angle -=self.speed
else:
if math.fabs(self.angle - self.mouse_angle) < 180:
self.angle -=self.speed
else:
self.angle +=self.speed
if math.fabs(self.angle - self.mouse_angle) < self.speed+.5:
self.angle = self.mouse_angle
if not self.follow:
if self.angle != self.tank_angle:
if self.angle < self.tank_angle:
if math.fabs(self.angle - self.tank_angle) < 180:
self.angle +=self.speed
else:
self.angle -=self.speed
else:
if math.fabs(self.angle - self.tank_angle) < 180:
self.angle -=self.speed
else:
self.angle +=self.speed
if math.fabs(self.angle - self.tank_angle) < self.speed+.5:
self.angle = self.tank_angle
else:
self.angle = self.tank_angle
self.rotate()
if self.angle > 360:
self.angle = self.angle-360
if self.angle <0:
self.angle = self.angle+360
if self.wait_timer < 5:
self.wait_timer += 1
# ---------- END OF CLASSES ---------- #
def main():
pygame.init()
version = "Tank Wars 2.0"
screen = pygame.display.set_mode((1170, 510),0,32)
n = 1
size = screen.get_size()
pygame.mouse.set_visible(False)
map_ = pygame.image.load("c2.png")
health = 40
health_full = health
bricks= pygame.sprite.Group()
bricks_des = pygame.sprite.Group()
bricks_non = pygame.sprite.Group()
bullets = pygame.sprite.Group()
booms = pygame.sprite.Group()
bombers = pygame.sprite.Group()
bombs = pygame.sprite.Group()
tanks = pygame.sprite.Group()
allgroup = pygame.sprite.LayeredUpdates()
#assign default groups to each sprite class
#Tank.groups = tanks, allgroup
#Turret.groups = allgroup
#Bullet.groups = bullets, allgroup
city = City(bricks, map_)
city_size = city.get_size()
clock = pygame.time.Clock()
timer = 0
chance = None
score = 0
player1 = Tank((150, 250), 360, 40)
tanks.add(player1)
player2 = Tank((1100, 250), 360, 40)
tanks.add(player2)
player1_turret = Turret((150, 250), 360, False)
player2_turret = Turret((1100, 250), 360, False)
background = pygame.Surface((city_size), 0, 32)
city.bricks.draw(screen)
font4 = pygame.font.Font("7theb.ttf", 13)
font5 = pygame.font.SysFont("Courier New", 16, bold=True)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
return
clock.tick(24)
time_passed = clock.tick()
keys = pygame.key.get_pressed()
m_x, m_y = pygame.mouse.get_pos()
background.fill((87, 87, 87))
if player1.alive == True:
player1.update(keys, bricks, bullets, booms, bombs)
player1_turret.update(player1.rect.center, m_x, m_y, keys, booms, 360, bricks, background, city_size)
screen.blit(player1.image, player1.rect)
screen.blit(player1_turret.image, player1_turret.rect)
if player2.alive == True:
player2.update(keys, bricks, bullets, booms, bombs)
player2_turret.update(player2.rect.center, m_x, m_y, keys, booms, 360, bricks, background, city_size)
screen.blit(player2.image, player2.rect)
screen.blit(player2_turret.image, player2_turret.rect)
bullets.update(bricks, booms)
bombs.update(booms, player1)
bombs.update(booms, player2)
booms.update(background)
bombs.draw(screen)
bullets.draw(screen)
bombers.draw(screen)
healthshow = font4.render('Health ', False, (255,255,255))
#---Player 1 Healthbar---#
pygame.draw.ellipse(screen, (255, ((player1.health*255)/health_full),0), (90, 20, 10, 13))
pygame.draw.ellipse(screen, (255, ((player1.health*255)/health_full),0), (92+(100*(float(player1.health)/float(health_full))), 20, 10, 13))
screen.fill((255,((player1.health*255)/health_full),0),(96,20,(100*(float(player1.health)/float(health_full))), 13))
screen.blit(healthshow, (5, 20))
#---Player 2 Healthbar---#
pygame.draw.ellipse(screen, (255, ((player2.health*255)/health_full),0), (90, 20, 10, 13))
pygame.draw.ellipse(screen, (255, ((player2.health*255)/health_full),0), (92+(100*(float(player2.health)/float(health_full))), 20, 10, 13))
screen.fill((255,((player2.health*255)/health_full),0),(96,20,(100*(float(player2.health)/float(health_full))), 13))
screen.blit(healthshow, (500, 20))
allgroup.clear(screen, background)
pygame.display.flip()
if __name__ == "__main__":
main()
My suggestion is to make a global screen variable (instead of being wrapped inside a function, and generate a single city image, with the bricks added to it. Since those are all the components that do not change, this can be blitted to the screen every time the frame is refreshed.
After that, on each frame, the tank (with health bar and other stuff) and bullets can be blitted directly to the screen variable.
It is probably simplest to just do pygame.display.flip() after all of the blitting is done, but keeping an array of pygame.Rects representing blit positions and then keeping the old blit Rects and then doing something like:
old_rects = current_rects
current_rects = []
#make blits, collect Rects in current_rects
pygame.display.update(current_rects + old_rects)
This way, pygame.display.update() can be used (since it is more efficient), and there won't be problems with no refreshing.
I hope this works and that you can implement something like this.

Categories

Resources