Creating a platformer in pygame and im using vectors for the gravity and movement. Right now im stuck on the collisions with the platforms because the plat will hit the platform and sink almost entirely in the platform like this:
Player sunk into platform block
(EDIT) People wanted more code info so...
Main Class:
import pygame as pg
import sys
from sprites import *
class Game:
def __init__(self):
pg.init()
pg.mixer.init()
pg.display.set_caption(TITLE)
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
self.clock = pg.time.Clock()
self.running = True
self.load_data()
self.new_game()
def load_data(self):
# Eventually will load data
pass
def new_game(self):
# Creates new instances of all of the sprites
# groups and re-renders sprites at starting locations
self.all_sprites = pg.sprite.Group()
self.platform_sprites = pg.sprite.Group()
self.player = Player(self, 60, 60)
# Creates some test platforms
for x in range(10):
Platform(self, x, 6)
def run(self):
# Basic game loop
while self.running:
self.dt = self.clock.tick(FPS) # Get time passed since last update / Set FPS
self.events()
self.render()
self.update()
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.running = False
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
self.running = False
# Check for quit event and close window
def render(self):
self.screen.fill(WHITE)
self.all_sprites.draw(self.screen) # Draws all sprites to screen
pg.display.flip() # updates screen
def update(self):
self.all_sprites.update() # calls the update function of all of the sprites
if __name__ == '__main__':
g = Game()
while g.running:
g.run()
pg.quit()
sys.exit()
.... Player and platform sprite code:
import pygame as pg
from constants import *
vec = pg.math.Vector2
class Player(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites
self.game = game
super().__init__(self.groups)
self.image = pg.Surface((40, 70))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
pg.draw.rect(self.image, BLACK, (0, 0, self.rect.width, self.rect.height), 3)
self.pos = vec(x, y)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def move(self):
self.acc = vec(0, 0)
keypress = pg.key.get_pressed()
if keypress[pg.K_a]:
self.acc.x += -PLAYER_MOVE_SPEED
if keypress[pg.K_d]:
self.acc.x += PLAYER_MOVE_SPEED
self.acc += self.vel * PLAYER_FRICTION
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
def collisions(self, axis):
if axis == 'y':
for plat in self.game.platform_sprites:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.pos.y = plat.rect.top
def update(self):
self.move()
self.collisions('y')
class Platform(pg.sprite.Sprite):
def __init__(self, game, x, y):
self.groups = game.all_sprites, game.platform_sprites
self.game = game
super().__init__(self.groups)
self.image = pg.Surface((TILESIZE, TILESIZE))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.x = x * TILESIZE
self.y = y * TILESIZE
def update(self):
self.rect.x = self.x
self.rect.y = self.y
The "Constants" file only has stuff like the screen size, colors, and player speed defined. IF any of that's needed I'll add it.
In the Player.collisions:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.pos.y = plat.rect.top
The main cause of your issue is the player's velocity:
You're resetting the vertical acceleration (acc.y) and position (pos.y), but it seems like you're forgetting about the vertical speed (vel.y). The player's sinking because it still has downward velocity, which you can remedy with self.vel.y = 0.
.
Another more minor issue might be, as #LarryMcMuffin correctly guessed, that there is an ordering issue between your moving/drawing:
You may be updating Player.pos.y correctly so the player stands perfectly on top of the platform, but you also need to update self.rect.midbottom if you want your character to be drawn at its new position -- which you do, but in Player.move, which is called before. You could reorder the move/collision functions, but my suggestion is to simply move self.rect.midbottom = self.pos from Player.move to the very end of Player.update.
End result:
def collisions(self, axis):
if axis == 'y':
for plat in self.game.platform_sprites:
if self.acc.y > 0:
if pg.Rect.colliderect(self.rect, plat.rect):
self.acc.y = 0
self.vel.y = 0
self.pos.y = plat.rect.top
def update(self):
self.move()
self.collisions('y')
self.rect.midbottom = self.pos
Another thing I'd like to point out, even though it's probably not related to the issue you described: Use the delta-time.
See, you have two values that influence your character's position:
velocity (vel): expresses a change of position over time
acceleration (acc): expresses a change of velocity over time
The over time is key here -- since the time elapsed between two calls of move is variable, this matters: as more/less time passed, the speed should act more/less upon the position. Fortunately, you're saving the time elapsed between two frames/updates in Game.dt. Pass it to your player and use it more or less like so:
self.acc += self.vel * PLAYER_FRICTION
self.vel += self.acc * dt
self.pos += (self.vel + 0.5 * self.acc) * dt
It won't fix it completely so that two calls with dt = .5 produce the same effect as one call with dt = 1, but it will "fill the gap". I don't think you can achieve it without either updating the physics on a fixed-interval basis, or resorting to expensive and complex calculations (using the power function).
Related
I'm making a basic dodger game where you play as a fish and need to avoid getting hit by obstacles. I want the obstacles to come from the right side of the screen at a random velocity (from a certain range, let's just say 4 to 6). I already have the fish/underwater gravity mechanics working. But I have no idea where to start with the obstacles.
This is my main python script:
import pygame
from assets.player import Bob
# Screen properties
pygame.init()
# Important Variables
run = True
game_speed = 0
SCREEN_WIDTH, SCREEN_HEIGHT = 900, 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
clock = pygame.time.Clock()
FPS = 60
# Properties
bg_game_surface = pygame.image.load('images/game-background.png')
player = Bob(game_speed)
#############
# Functions #
#############
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
screen.blit(bg_game_surface, (0, 0))
player.update(screen)
pygame.display.update()
clock.tick(FPS)
pygame.quit()
And this is the class for my player:
import pygame
class Bob:
def __init__(self, game_speed):
self.img = pygame.image.load('images/player/bob.png').convert_alpha()
self.img_up = pygame.image.load('images/player/bob_up.png').convert_alpha()
self.rect = self.img.get_rect()
self.game_speed = game_speed
def update(self, screen):
key = pygame.key.get_pressed()
### Collision
if self.rect.top <= 0:
self.rect.centery = (screen.get_height() - self.img.get_height() * 2) + 10
elif self.rect.top >= screen.get_height() - self.img.get_height() * 2:
self.rect.centery = 20
### Movement
if key[pygame.K_w] or key[pygame.K_UP]:
self.rect.centery -= 6
self.img = pygame.image.load('images/player/bob_up.png').convert_alpha()
### Gravity
else:
self.img = pygame.image.load('images/player/bob.png').convert_alpha()
self.rect.centery += 4
screen.blit(self.img, self.rect)
My folders are sorted like this:
Right now, I'd like that the trash.py is also a class with a function called "update". When that function is executed, obstacles (preferably in the form of an image) come out of the right side of the display, doing what I said above.
Any help is appreciated. Thanks in advance :)
You should make a sprite class group, but first you need to import the random module:
import random
Now you can create the Obstacle Sprite class, this is just an example:
class Obstacle(pygame.sprite.Sprite):
def __init__(self, pos): #pos is the starting position
super().__init__()
self.image = pygame.Surface((10,10)) # You can change this with pygame.image.load('...')
self.rect = self.image.get_rect(topleft = pos)
self.vel = random.randint(4,6) #random speed of the obstacle
def collisions(self, player): #collisions function
if self.rect.colliderect(player.rect):
print('collision!') #write what you want to happen
def movement(self): #the blocks are moving left
if self.rect.x > -100: #just putting some limits
self.rect.x -= self.vel
def update(self, player): #try to put all the other functions in the update one
self.collisions(player)
self.movement()
Once you created the class, you only need to create some objects of that class (every obstacle).
First we create a sprite group before of the while loop:
obstacles = pygame.sprite.Group()
Then, inside your main loop, you need a condition that generates the obstacles.
I've done something like this, but you really should change it:
if x < 10: #I created x before the while (x=0)
posx = SCREEN_WIDTH + 50 #the starting x will be 50 pixels right of the screen
posy = random.randint(0, SCREEN_HEIGHT) #Im setting this to random
obs = Obstacle((posx, posy))
obstacles.add(obs)
x +=1
This basically creates 9 obstacles when we start the game, with different random x speed and y location, and draw them on the screen. If you collide with one of the blocks, the code just prints "collision!" (see the Obstacle class). Hope it was helpful and I didn't miss something.
I am building a 2d game using pygame,how do i make the ground collide with the player
here is my game code, I am new to pygame and python
class Player(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.surf = pygame.image.load('img/PlayerIdle1.png')
self.surf = pygame.transform.smoothscale(self.surf,(44,44))
self.rect = self.surf.get_rect(center = (10, 420))
self.pos = vec((10, 360))
self.vel = vec(0,0)
self.acc = vec(0,0)
def gravity(self):
self.acc = vec(0,0.5)
self.acc.x += self.vel.x * FRIC
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.midbottom = self.pos
this update method for collision is not working
def update(self):
hits = pygame.sprite.spritecollide(p1 ,Gro_und, False)
if p1.vel.y > 0:
if hits:
self.vel.y = 0
self.pos.y = hits[0].rect.top + 1
print('got hit')
def render(self):
screen.blit(self.surf, self.rect)
screen.blit(self.surf, self.rect)
class Ground(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.cimage = pygame.image.load('img/gnd.png')
self.cimage = pygame.transform.smoothscale(self.cimage,(800,200))
self.rectCimg = self.cimage.get_rect()
self.surf = self.cimage
self.rect = self.rectCimg
self.cY1 = 400
self.cX1 = 0
self.cY2 = 400
self.cX2 = self.rectCimg.width
self.scroll_speed = 20
def update(self):
self.cX1 -= self.scroll_speed
self.cX2 -= self.scroll_speed
if self.cX1 <= -self.rectCimg.width:
self.cX1 = self.rectCimg.width
if self.cX2 <= -self.rectCimg.width:
self.cX2 = self.rectCimg.width
def render(self):
screen.blit(self.cimage, (self.cX1, self.cY1))
screen.blit(self.cimage, (self.cX2, self.cY2))
def gravity(self):
pass
p1 = Player()
back_ground = Background()
Gro_und = Ground()
all_sprites = pygame.sprite.Group()
all_sprites.add(p1)
all_sprites.add(Gro_und)
run = True
while run:
clock.tick(fps)
#draw and scroll the ground
back_ground.update()
back_ground.render()
Gro_und.update()
Gro_und.render()
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for entity in all_sprites:
entity.update()
entity.render()
entity.gravity()
pygame.display.update()
pygame.quit()
I am getting ground object not iterable error while running the update function in player class for collision
what should i do to to solve this
If I'm not mistaken, the reason you're getting a Ground object not iterable error is because the pygame.sprite.spritecollide function is supposed to receive the sprite you want to check for collision with (in your case, p1), but the second parameter should be an instance of pygame.sprite.Group, which is iterable (unlike a normal sprite, which your Ground object is based upon.
I see that you have already made a sprite group, all_sprites, but you should create another one for just the ground (for example, ground_sprites), and then change the function call in your update function to be like so:
def update(self):
hits = pygame.sprite.spritecollide(p1 ,ground_sprites, False)
if p1.vel.y > 0:
if hits:
self.vel.y = 0
self.pos.y = hits[0].rect.top + 1
print('got hit')
I am making a game and I need the player to be constantly moving in one direction until the player decides to move in a different also constantly. In the code I have supplied the player has to hold down a key to move in that direction.
Here is my code for the player and the function for moving:
class player:
def __init__(self, x, y, width, height, colour):
self.width = width # dimensions of player
self.height = height # dimensions of player
self.x = x # position on the screen
self.y = y # position on the screen
self.colour = colour # players colour
self.rect = (x, y, width, height) # all the players properties in one
self.vel = 2 # how far/fast you move with each key press
self.path = []
def draw(self, win):
pygame.draw.rect(win, self.colour, self.rect)
def move(self):
keys = pygame.key.get_pressed() # dictionary of keys - values of 0/1
if keys[pygame.K_LEFT]: # move left: minus from x position value
if self.x <= 5:
pass
else:
self.x -= self.vel
self.y = self.y
elif keys[pygame.K_RIGHT]: # move right: add to x position value
if self.x == 785:
pass
else:
self.x += self.vel
elif keys[pygame.K_UP]: # move up: minus from y position value
if self.y <= 105:
pass
else:
self.y -= self.vel
elif keys[pygame.K_DOWN]: # move down from
if self.y >= 785:
pass
else:
self.y += self.vel
self.update()
def update(self):
self.rect = (self.x, self.y, self.width, self.height) # redefine where the player is
Here is my code for the main function:
def main(): # asking server for updates, checking for events
run = True
n = Network()
startPos = read_pos(n.getPos())
pygame.mixer.music.load("Heroic_Intrusion.ogg")
pygame.mixer.music.set_volume(0.5)
pygame.mixer.music.play(-1)
p = player(startPos[0], startPos[1], 10, 10, (255, 0, 0)) # connect, get starting position
p2 = player(0, 0, 10, 10, (0, 0, 255))
clock = pygame.time.Clock()
while run:
clock.tick(60)
p2Pos = read_pos(n.send(make_pos((p.x, p.y))))
p2.x = p2Pos[0]
p2.y = p2Pos[1]
p2.update()
for event in pygame.event.get():
if event.type == pygame.QUIT: # quitting condition
run = False
pygame.quit()
p.move() # move character based off what keys are being pressed
redrawWindow(win, p, p2)
One approach is to just set a movement amount in the player class. Each frame of the game, the player moves by a little in the set speed. Separate speeds are maintained for each of the direction-components of the movement. This allows the player to move diagonal.
class Player():
def __init__( self ):
...
self.x_vel = 0
self.y_vel = 0
def move(self):
keys = pygame.key.get_pressed() # dictionary of keys - values of 0/1
if keys[pygame.K_LEFT]: # move left: minus from x position value
self.x_vel = -1
if keys[pygame.K_RIGHT]:
self.x_vel = 1 # move right: plus from x position value
# etc. for up/down
def update( self ):
self.x += self.x_vel
self.y += self.y_vel
In the main loop, simply call the player.update() function.
A downside to this approach is that the on-screen speed is relative to the frame-rate. In this case the code could use the pygame millisecond clock to use an actual real-time speed in pixels/second. Or calculate the speed based on the actual FPS, and set the player.x_vel (etc.) accordingly.
import pygame
import os
import random
from pygame.locals import * # Constants
import math
import sys
import random
pygame.init()
screen=pygame.display.set_mode((1280,700)) #(length,height)
screen_rect=screen.get_rect()
background = pygame.Surface(screen.get_size())
background.fill((255,255,255)) # fill the background white
background = pygame.image.load('stage.png').convert()
Black=(0,0,0)
class Player(pygame.sprite.Sprite):
x = 20
y = 615
def __init__(self):
super().__init__() # calls the parent class allowing sprite to initalize
self.image = pygame.Surface((50,25)) # this is used to create a blank image with the size inputted
self.image.fill((0,0,128)) # fills the blank image with colour
self.rect = self.image.get_rect(topleft =(20,615)) # This place the player at the given position
self.dist = 10
def update(self): # code to make the character move when the arrow keys are pressed
if self.rect.right > 100: # These are to make the player so move constantly
self.rect.y += 1
self.rect.x += 2
if self.rect.bottom == 700:
pygame.quit()
keys = pygame.key.get_pressed()
if keys[K_LEFT]:
self.rect.move_ip(-1,0)
elif keys[K_RIGHT]:
self.rect.move_ip(0.5,0)
elif keys[K_UP]:
self.rect.move_ip(0,-0.5)
elif keys[K_DOWN]:
self.rect.move_ip(0,1)
self.rect.clamp_ip(screen_rect)
#while self.rect == (20,615):
if keys [K_SPACE]:
self.rect = self.image.get_rect(topleft =(100,100))
class Enemy(pygame.sprite.Sprite): # the enemy class which works fine
def __init__(self):
super().__init__()
#y = random.randint(300,1200)
x = random.randint(50,450)
self.image = pygame.Surface((50,25))
self.image.fill((128,0,0))
self.rect = self.image.get_rect(topleft=(300, 50))
def update(self):
if self.rect.bottom <= 400:
self.rect.y += 1
if self.rect.bottom >= 400:
self.rect.y -= 300
On this part i got so that the enemy class moves downwards and when it reaches 400 it teleport 300 upwards but i wanted it so that it constantly moves upwards and then downwards again.
I thought that i either cancel the movement downwards when it reaches the position but i don't think you can do that.
clock = pygame.time.Clock() # A clock to limit the frame rate.
player = Player()
enemy = Enemy()
enemy_list = pygame.sprite.Group(enemy)
#enemy_list.add(enemy)
sprites = pygame.sprite.Group(player, enemy)
def main(): #my main loop
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
sprites.update()
screen.blit(background, (0, 0))
sprites.draw(screen)
clock.tick(100) # Limit the frame rate to 60 FPS.
pygame.display.flip() #updates the whole screen
#Collison check
player_hit_list = pygame.sprite.spritecollide(player, enemy_list, True)
for enemy in player_hit_list:
pygame.quit()
if __name__ == '__main__':
main()
The Enemy class needs an additional attribute that keeps track of the direction it's moving: up or down.
So a simple solution could look like this:
class Enemy(pygame.sprite.Sprite): # the enemy class which works fine
def __init__(self):
super().__init__()
x = random.randint(50,450)
self.image = pygame.Surface((50,25))
self.image.fill((128,0,0))
self.rect = self.image.get_rect(topleft=(300, 50))
self.direction = 'DOWN'
def update(self):
self.rect.y += 1 if self.direction == 'DOWN' else -1
if self.rect.bottom >= 400:
self.direction = 'UP'
if self.rect.top <= 50:
self.direction = 'DOWN'
I am making a flappy bird replica and I can't get a collision mechanism. At the moment i'm trying to use .coliderect() but i'm not 100% sure how to. Here is the two classes. I'd like the program to do something, or just print('x') when the bird and the pipe collide, but when they collide at the moment the program does not do or output anything.
import pygame
vec = pygame.math.Vector2
BLACK = (0,0,0)
WIDTH = 500
HEIGHT = 400
pygame.init()
window = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption('Flappy Bird')
clock = pygame.time.Clock()
class Bird():
def __init__(self):
self.skin = pygame.image.load('bird2.png')
self.rect = self.skin.get_rect()
self.rect.center = (WIDTH / 2, HEIGHT / 2)
self.vx = 0
self.vy = 0
self.pos = vec(WIDTH / 2, HEIGHT / 2)
self.vel = vec(0, 0)
self.acc = vec(0, 0)
def update(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
window.fill(BLACK)
self.acc = vec(0, 0.7) #having 0.5 adds gravity
self.vy = 0
keys = pygame.key.get_pressed()
if keys[pygame.K_SPACE]:
self.vel.y = -7
if keys[pygame.K_LEFT]:
self.acc.x = -0.5 #change to y to add vertical motion
if keys[pygame.K_RIGHT]:
self.acc.x = 0.5 #change to y to add vertical motion
#applys friction
self.acc.x += self.vel.x * -0.08 #FRICTION
#motion equations
self.vel += self.acc
self.pos += self.vel + 0.5 * self.acc
self.rect.center = self.pos
window.blit(self.skin, self.pos)
class Pipe():
def __init__(self,x,y):
self.image = pygame.Surface((50,60))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.pos = vec(x,y)
def blit_pipe(self):
window.blit(self.image, self.pos)
def border_check():
if (flappy.pos.y)+32 > HEIGHT: #this is the very top of the flappy
print("You are under\n")
pygame.quit()
quit()
if flappy.pos.y < 0:
print("You are over\n") #this is the very top of the flappy
pygame.quit()
quit()
pipey = Pipe(300,200)
pipey.blit_pipe()
pipey2 = Pipe(100,200)
pipey2.blit_pipe()
flappy = Bird()
window.blit(flappy.skin, flappy.pos)
while True:
border_check()
flappy.update()
pipey.blit_pipe()
pipey2.blit_pipe()
if flappy.rect.colliderect(pipey.rect):
print('x')
clock.tick(30)
pygame.display.update()
You never set the position of the rects of the Pipe instances so they are still positioned at the default coords (0, 0). There are several ways to set the coords, for example you can pass the coords as the topleft argument to get_rect.
self.rect = self.image.get_rect(topleft=(x, y))
If something is wrong with the collision detection, print the rects to see the actual positions.