Pygame object phases through collision object - python

Im making a simple game where u can jump, and theres gravity.
Gravity is represented using
def updatecollision(self):
global colision
colision = self.hitbox.colliderect(suelo.rect)
if not
collision:
self.rect.y += 40
As you can see, whenever player collides with suelo.rect, it stops falling. Problem is, theres a point where the self.rect.y applies a movement that phases through the suelo.rect, phasing it.
Hitbox rect actually is a rect that covers my PNG file, whereas Rect is the entire section of the png, including transparent pixels.
I thought about using regular rect to check when theres less than 20 pixels beetween the suelo.rect and hitbox.rect, so the change of position modifier makes up and doesnt phase.
A bigger section of my code:
def controlarkeys(self):
global jump
global contadorSalto
global saltoMax
if jump:
self.rect.y -= contadorSalto
if contadorSalto > -saltoMax:
contadorSalto -= 3
else:
jump = False
def updatecolisiones(self):
global colision
colision = self.hitbox.colliderect(suelo.rect)
def dibujar(self, pantalla):
if not colision:
self.rect.y *= 1.02
while True:
for evento in pygame.event.get():
if evento.type==pygame.QUIT:
sys.exit()
if evento.type == pygame.KEYDOWN:
if not jump and colision and evento.key == pygame.K_SPACE:
jump = True
contadorSalto = saltoMax
print(evento)
migue.updatecolisiones()
migue.controlarkeys()
pantalla.fill(color)
suelo.updt(pantalla)
migue.dibujar(pantalla)

Related

Enemies' and bullets' positions' value does not change - pygame

I'm writing a pygame shooting game where you have to use space station to shoot enemies flying from the top of the screen to the bottom. However, I noticed that the Y positions of enemy(enemy.pos_y) and bullet(bullet.pos_y) is just the position where they spawn, but this position doesn't change even though the enemy moves in the window of the game (because of the move() function in class Enemy) and also bullet moves (also because of the move() function but in Bullet class) and that's obvious because the Y values are constant (enemy.pos_y=-30 and bullet.pos_y=STATION_HEIGHT - 5. So is there any solution to make those positions' values change (I'm saying value because on screen enemies' and bullets' positions change) and still spawn them in those given positions? This issue stops my further steps, because for example making collision between enemies and bullets is impossible, because it's like they never move, and so they never meet. So how to make detect those objects' position's value's change?
In this picture you can see that enemies' positions' value remains the same: enter image description here
Enemies' class:
import pygame
import math
class Enemy:
def __init__(
self, pos_x: float, pos_y: float, texture: pygame.Surface, speed: float
):
self.pos_x = pos_x
self.pos_y = pos_y
self.texture = texture
self.speed = speed
def move(self):
self.pos_y += self.speed
Bullets' class:
import pygame
class Bullet:
def __init__(
self, pos_x: float, pos_y: float, texture: pygame.Surface, speed: float
):
self.pos_x = pos_x
self.pos_y = pos_y
self.texture = texture
self.speed = speed
def move(self):
self.pos_y -= self.speed
Here I display enemies and bullets (in main loop):
running = True
while running:
all_event = pygame.event.get()
for event in all_event:
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
shot_sound.play()
# Bullets
bullet = Bullet(
bulletX + station.pos_x,
STATION_HEIGHT + 10,
bullet_texture,
BULLET_SPEED,
)
bullets.append(bullet)
print(f'position X={bullet.pos_x}')
# Enemies move
for enemy in enemies:
for i in range(3):
enemy.move()
for i in range(3):
enemy = Enemy(random.randrange(67, 520), -30, enemy_texture, ENEMY_SPEED)
start_time+=1
if start_time > 200:
enemies.append(enemy)
start_time = 0
The whole code you can find on my Github: enter link description here
ps. I'm sorry if this question and problem's explanation isn't well-written, but this is my first question here on stackoverflow so I'm not really experienced, I'm waiting for questions if something's not understandable.
You actually just print the position of a newly created bullet. If you want to see the position of a moving bullet, you need to move the print statement in the loop that moves the bullets:
while running:
# [...]
for enemy in enemies:
enemy.move()
print(f'enemy position Y={enemy.pos_y}')
# [...]
for bullet in bullets:
bullet.move()
print(f'bullet position Y={bullet.pos_y}')
# [...]
or
while running:
# [...]
for i, enemy in enumerate(enemies):
enemy.move()
print(f'position of enemy {i} Y={enemy.pos_y}')
# [...]
for i, bullet in enumerate(bullets):
bullet.move()
print(f'position of bullet {i} Y={bullet.pos_y}')
# [...]

Pygame: my bullet class does nothing apart from being drawn

So im trying to make a local multiplayer game, i have created the player class which works fine so far.
Now im trying to do a bullet class to create different types of shots for the player's spaceships, however upon calling my bullet, it only gets drawn to the screen where the player was at the start of the game and it doesnt move (so far just trying to program it to move)
Here is the code:
import pygame
pygame.init()
#Display screen
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Arena")
background = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\background.png').convert_alpha()
playerImg = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player.png').convert_alpha()
player2Img = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player2.png').convert_alpha()
regular_bullet = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\bullet.png').convert_alpha()
class Player:
def __init__(self, image, x, y, isPlayer1, isPlayer2):
self.image = image
self.x = x
self.y = y
self.isPlayer1 = isPlayer1
self.isPlayer2 = isPlayer2
def Move(self):
key_states = pygame.key.get_pressed()
x_change = 0.2
y_change = 0.2
if self.isPlayer1:
if key_states[pygame.K_a]:
self.x += -x_change
if key_states[pygame.K_d]:
self.x += x_change
if key_states[pygame.K_w]:
self.y += -y_change
if key_states[pygame.K_s]:
self.y += y_change
elif self.isPlayer2:
if key_states[pygame.K_LEFT]:
self.x += -x_change
if key_states[pygame.K_RIGHT]:
self.x += x_change
if key_states[pygame.K_UP]:
self.y += -y_change
if key_states[pygame.K_DOWN]:
self.y += y_change
def draw(self, screen):
screen.blit(self.image,(self.x,self.y))
def ColorPlayer(self ,image):
if self.isPlayer1:
self.image.fill((190,0,0,100), special_flags = pygame.BLEND_ADD)
if self.isPlayer2:
self.image.fill((0,0,190,100), special_flags = pygame.BLEND_ADD)
class Bullet():
def __init__(self, image, bulletx, bullety):
self.image = image
self.bulletx = bulletx
self.bullety = bullety
self.bullet_state = "ready"
def draw(self, screen):
if self.bullet_state == "fire":
screen.blit(self.image,(self.bulletx,self.bullety))
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
if self.bulletx > 800:
self.bullet_state = "ready"
def redrawWindow(screen, player, player2, background, player_bullet):
screen.fill((255,255,255))
screen.blit(background,(0,0))
player.draw(screen)
player2.draw(screen)
player_bullet.draw(screen)
pygame.display.update()
def Main():
done = False
player = Player(playerImg,200,200,True,False)
player2 = Player(player2Img,600,200,False,True)
player1_bullet = Bullet(regular_bullet, player.x, player.y)
while not done:
player.Move()
player2.Move()
player.ColorPlayer(playerImg)
player2.ColorPlayer(player2Img)
redrawWindow(screen, player, player2, background, player1_bullet)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.shoot()
Main()
It looks like shoot is effectively the "update" function for your bullet, and the code inside would need to be called every frame (like with player.Move()) for the bullet to keep moving after it's fired.
The only code you have that changes the bullet position is this bit inside the shoot method.
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
Nothing else moves it. You need to have a move function that adjusts it position and call that each frame. In fact your shoot function is a bit odd it looks more like what the move function would look like. Perhaps rename it to move() and call it each frame?
That function is a bit unusual for a shoot() function, since it moves an existing bullet instead of firing a new one. Normally a shoot method would be expected to be a method in the player and to create a new bullet. It does not really make sense for a bullet instance function to shoot and to create a new bullet. Also you seem to have coded it so that there is a single bullet that goes to 800 and then can be re-fired, but you do not have anything that resets the bullets position back to where the player is.
It might make more sense for the 'ready' and 'fire' state to be a state of the player since the player is the one that can or cannot fire. Also the shoot() method is more commonly in the player class and when it fires (the player is in state 'ready') it creates a new Bullet instance at the players position.
The bullet move() method would be called each frame just like the player move() functions are. Normally there would be more than one bullet allowed at a time and so you would keep a list of the and have to iterate through the list of bullets moving them along (and doing collision checking to see if they hit anything). If a bullet goes off the screen or hits something then you remove the bullet from the bullet list.
You have to change 2 things.
The method Bullet.shoot() has to be continuously invoked in the main application loop. Note, this method updates the bullet dependent on its state. If the state of the bullet is "fire" then the bullet moves. Else it stands still.
Update the position of the bullet when it is fired. The bullet always has to start at the position of the player:
def Main():
# [...]
while not done:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.bulletx = player.x
player1_bullet.bullety = player.y
player1_bullet.shoot()

Blit function not working, is loop going to fast?

Hey I'm working on a gravity function for my Super Mario bross. I would like a smooth movement of gravity.But my player is like teleporting from top to the ground.
I though it was the loop that was going too fast and pygame couldn't blit the image but i've tried to slow the loop with time.sleep() or pygame.time.wait()
It is not working.
At the start it's like this :
Image : Before
Image : One sec later
Thanks for helping !
def moove(self,keys):
if(self.gravity()):
if keys[pygame.K_LEFT]:
self.orientation = "Left"
if not(self.x - vel<0) and not self.collision_with_walls():
map.draw()
self.rect.x -= vel
camera.update(player)
self.draw_player()
def gravity(self):
refresh = 0
self.collision_with_ground = False
while not self.collision_with_ground:
self.rect.y += 1
blocks_hit_list = pygame.sprite.spritecollide(self,sol_sprites,False)
if not(blocks_hit_list == []):
self.collision_with_ground = True
self.rect.y -= 1
map.draw()
player.draw_player()
return True
else:
map.draw()
player.draw_player()
pygame.time.wait(10)
In your line: while not self.collision_with_ground: you are ensuring that you will not leave this loop until your player has reached the ground. You will never blit (which is outside this loop) until this loop has been left. Try if instead of while and move your other functions outside that loop (you should probably take them out of this function):
def gravity(self):
refresh = 0
self.collision_with_ground = False
if not self.collision_with_ground:
self.rect.y += 1
blocks_hit_list = pygame.sprite.spritecollide(self,sol_sprites,False)
if not(blocks_hit_list == []):
self.collision_with_ground = True
self.rect.y -= 1
map.draw()
player.draw_player()
return True
map.draw()
player.draw_player()
pygame.time.wait(10)

How to find perfect collision of a moving object into an obstacle

I have two sprites: a robot sprite and an obstacle sprite. I am using mask.overlap to determine if there is an overlap to prevent the robot from moving into the area of the obstacle (it functions as a blocking obstacle). Below is a portion of the movement evaluation code. It tests to see if the movement will cause a collision:
if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
# adjust the speed to make the objects perfectly collide
This code works. If the movement would cause a collision, then it prevents the robot from moving.
ISSUE
For high speeds, the code prevents movement like it should, but it leaves a visual gap between the robot and the obstacle.
For example: if the speed is 30 and the two obstacles are 20 pixels away, the code will prevent the movement because a collision would be caused. But leaves a 20 pixel gap.
GOAL
If there were to be a collision, adjust the speed to the remaining pixel distance (20px like in the example) so that the robot and the obstacle perfectly collide. The robot can't move 30, but he can move 20. How can I calculate that remaining distance?
Here's what I described in the comment. Check if the sprites are colliding (I use spritecollide and the pygame.sprite.collide_mask functions here), and then use the normalized negative velocity vector to move the player backwards until it doesn't collide with the obstacle anymore.
import pygame as pg
from pygame.math import Vector2
pg.init()
screen = pg.display.set_mode((800, 600))
GRAY = pg.Color('gray12')
CIRCLE_BLUE = pg.Surface((70, 70), pg.SRCALPHA)
pg.draw.circle(CIRCLE_BLUE, (0, 0, 230), (35, 35), 35)
CIRCLE_RED = pg.Surface((170, 170), pg.SRCALPHA)
pg.draw.circle(CIRCLE_RED, (230, 0, 0), (85, 85), 85)
class Player(pg.sprite.Sprite):
def __init__(self, pos, key_left, key_right, key_up, key_down):
super().__init__()
self.image = CIRCLE_BLUE
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)
self.vel = Vector2(0, 0)
self.pos = Vector2(self.rect.topleft)
self.dt = 0.03
self.key_left = key_left
self.key_right = key_right
self.key_up = key_up
self.key_down = key_down
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == self.key_left:
self.vel.x = -230
elif event.key == self.key_right:
self.vel.x = 230
elif event.key == self.key_up:
self.vel.y = -230
elif event.key == self.key_down:
self.vel.y = 230
elif event.type == pg.KEYUP:
if event.key == self.key_left and self.vel.x < 0:
self.vel.x = 0
elif event.key == self.key_right and self.vel.x > 0:
self.vel.x = 0
elif event.key == self.key_down and self.vel.y > 0:
self.vel.y = 0
elif event.key == self.key_up and self.vel.y < 0:
self.vel.y = 0
def update(self, dt):
self.pos += self.vel * dt
self.rect.center = self.pos
class Obstacle(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.image = CIRCLE_RED
self.mask = pg.mask.from_surface(self.image)
self.rect = self.image.get_rect(topleft=pos)
class Game:
def __init__(self):
self.done = False
self.clock = pg.time.Clock()
self.screen = screen
self.player = Player((100, 50), pg.K_a, pg.K_d, pg.K_w, pg.K_s)
obstacle = Obstacle((300, 240))
self.all_sprites = pg.sprite.Group(self.player, obstacle)
self.obstacles = pg.sprite.Group(obstacle)
def run(self):
while not self.done:
self.dt = self.clock.tick(60) / 1000
self.handle_events()
self.run_logic()
self.draw()
pg.quit()
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
elif event.type == pg.MOUSEBUTTONDOWN:
if event.button == 2:
print(BACKGROUND.get_at(event.pos))
self.player.handle_event(event)
def run_logic(self):
self.all_sprites.update(self.dt)
collided_sprites = pg.sprite.spritecollide(
self.player, self.obstacles, False, pg.sprite.collide_mask)
for obstacle in collided_sprites:
# The length of the velocity vector tells us how many steps we need.
for _ in range(int(self.player.vel.length())+1):
# Move back. Use the normalized velocity vector.
self.player.pos -= self.player.vel.normalize()
self.player.rect.center = self.player.pos
# Break out of the loop when the masks aren't touching anymore.
if not pg.sprite.collide_mask(self.player, obstacle):
break
def draw(self):
self.screen.fill(GRAY)
self.all_sprites.draw(self.screen)
pg.display.flip()
if __name__ == '__main__':
Game().run()
You can pretty easily get a precise (if not exact) solution via a bisection search: once the collision is detected at the end of the full step, try half a step, and then either one or three quarters, and so on. This is treating the collision test as a boolean-valued function of the movement distance and looking for a “zero” (really the transition from miss to hit).
Note that this does nothing to resolve the issue of clipping through a thin wall or corner (where the initial collision test fails to detect the obstacle) and with complicated obstacles will find an arbitrary edge (not necessarily the first) to stop at.
I decided to go with the approach suggested by skrx in his comment: to basically back up by 1px until there is no longer a collision.
if pressed_keys[pygame.K_s]:
temp_position = [robot.rect.x, robot.rect.y]
temp_position[1] += speed
offset_x = temp_position[0] - obstacle.rect.x
offset_y = temp_position[1] - obstacle.rect.y
overlap = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap is None:
robot.rect.y += speed
else:
for step_speed in range(1, speed - 1):
collision[1] -= 1
offset_x = collision[0] - obstacle.rect.x
offset_y = collision[1] - obstacle.rect.y
overlap_adj = obstacle.mask.overlap(robot.mask, (offset_x, offset_y))
if overlap_adj is None:
robot.rect.y += (speed - step_speed)
break
This is a bit of a clumsy approach, but it will satisfy what i need for now and keep vector math at bay. For those who are looking for the proper way to approach this using normalized vectors and such, i would recommend using the answer skrx provided. I will likely come back to this and update it in the future. But for now, this will give users a couple options on how to proceed with perfect collision.

sprite collision detection and removing one sprite from group in pygame

so I need to remove one sprite from the group every time I press the - (minus) key. However, I am confused as to how to do it. Another issue I've run into is when I press the + key it is adding more than one sprite at a time. I'm guessing I need to also check for KEYUP ? I also have a bug where some of the sprites will be created on top of each and will stay stuck bouncing off one another. How can I check for existing sprites where the new will be generated? Here is my code
dragon.py
import pygame
from pygame.locals import *
class Dragon(pygame.sprite.Sprite):
def __init__(self, x,y, vx, vy):
super().__init__();
self.image = pygame.image.load("images/dragon.png").convert()
self.image.set_colorkey(pygame.Color(255,0,255))
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.vx = vx
self.vy = vy
def draw(self, SCREEN):
SCREEN.blit(self.image, (self.rect.x, self.rect.y))
def move(self, SCREEN):
r_collide = self.rect.x + self.image.get_width() + self.vx >= SCREEN.get_width()
l_collide = self.rect.x + self.vx <= 0
t_collide = self.rect.y + self.vy <= 0
b_collide = self.rect.y + self.image.get_height() + self.vy >= SCREEN.get_height()
# Check collision on right and left sides of screen
if l_collide or r_collide:
self.vx *= -1
# Check collision on top and bottom sides of screen
if t_collide or b_collide:
self.vy *= -1
self.rect.x += self.vx
self.rect.y += self.vy
def bounce(self, SCREEN):
self.vy *= -1
self.vx *= -1
dragon_animation.py
import pygame
import sys
from random import *
from pygame.locals import *
from flying_dragon.dragon import Dragon
def main():
pygame.init()
FPS = 30
FPS_CLOCK = pygame.time.Clock()
''' COLOR LIST '''
WHITE = pygame.Color(255,255,255)
''' create initial window '''
window_size = (500, 500)
SCREEN = pygame.display.set_mode(window_size)
''' set the title '''
pygame.display.set_caption("Flying Dragons")
''' fill background color in white '''
SCREEN.fill(WHITE)
''' group to store dragons '''
dragons = pygame.sprite.Group()
d1 = Dragon(0,0,3,2)
d1.draw(SCREEN)
dragons.add(d1)
''' main game loop '''
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
SCREEN.fill(WHITE)
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_PLUS or pygame.K_KP_PLUS:
d = Dragon(randint(1, SCREEN.get_width() - dragon.rect.x), randint(1, SCREEN.get_height()- dragon.rect.y), randint(1, 5), randint(1, 5))
dragons.add(d)
for dragon in dragons:
dragons.remove(dragon)
collisions = pygame.sprite.spritecollide(dragon, dragons, False)
dragons.add(dragon)
for dragon in collisions:
dragon.bounce(SCREEN)
for dragon in dragons:
dragon.move(SCREEN)
dragons.update(SCREEN)
dragons.draw(SCREEN)
pygame.display.update()
FPS_CLOCK.tick(FPS)
if __name__ == "__main__":
main()
if event.key == pygame.K_MINUS or pygame.K_KP_MINUS:
dragons.remove(dragons[0])
Should work for removing a sprite. Sprites aren't ordered in the group so will just delete a random one.
As another pointer, you should look at redefining your Dragon.move() function as dragon.update() then the Group.update() call will move all your dragons.
you don't need to draw the dragon immediately after it's creation before the while loop. The draw inside the while loop is sufficient.
When you press a key the event list will hold which key you are holding down and as long as this is down a dragon will be created. As the loop runs pretty fast several dragons will be created before you remove your finger from the keyboard.
also good to do
pygame.event.pump()
before the for event in pygame.event.get(): so that you clear the event list beofre the next run.
read about the pygame event pump here.
Use key = pygame.key.get_pressed() instead of event.get as it will read a key press ONCE.
can't run your code because of this error.
ImportError: No module named flying_dragon.dragon

Categories