Pygame wall collision detection seems "bugged" - python

Here is my code
import pygame, sys
pygame.init() #load pygame modules
size = width, height = 800, 600 #size of window
speed = [25,25] #speed and direction
x= 100
y= 100
screen = pygame.display.set_mode(size) #make window
s=pygame.Surface((50,50)) #create surface 50px by 50px
s.fill((33,66,99)) #color the surface blue
r=s.get_rect() #get the rectangle bounds for the surface
r[0] = x #changes initial x position
r[1] = y #changes initial y position
clock=pygame.time.Clock() #make a clock
while 1: #infinite loop
clock.tick(30) #limit framerate to 30 FPS
for event in pygame.event.get(): #if something clicked
if event.type == pygame.QUIT:#if EXIT clicked
pygame.quit()
sys.exit() #close cleanly
r=r.move(speed) #move the box by the "speed" coordinates
#if we hit a wall, change direction
if r.left <= 0 or r.right >= width:
speed[0] = -(speed[0])*0.9 #reduce x axis "speed" by 10% after hitting
if r.top <= 0 or r.bottom >= height:
speed[1] = -speed[1]*0.9 #reduce y axis "speed" by 10% after hitting
screen.fill((0,0,0)) #make redraw background black
screen.blit(s,r) #render the surface into the rectangle
pygame.display.flip() #update the screen
It's a simple window that shows a square moving, hitting the edges and bouncing back. However, in this particular example (speed set to 25 on both axis) and speed reduction set to 0.9 after bouncing back (less 10%), my square seems to get stuck on the left side of the window (I suggest you to copy and paste it and see for yourself)
If I change the speed to a lower value or set no speed reduction whatsoever after bouncing everything works fine.
Any reason on why this is happening?

Let's step through this code one by one:
speed = [25,25] #speed and direction
if r.left <= 0 or r.right >= width:
speed[0] = -(speed[0])*0.9
Let's just look at what happens at the x axis.
Let's assume the position before this check is 1. In the next frame, the value of position is 1-25 = -24. Since the condition is now fulfilled, the speed becomes 25 * 0.9 = 22.5.
The rectangle moves to position -1.5 and we are still on the wrong side of the wall. Since you change the direction of speed on each frame, the rectangle will be stuck there.
There are 2 solutions to the problem, the first one is already described by Alex.
The second one, is to move the rectangle first, and if the rectangle moves out of bounds, return it to before the wall.

Right! To make the square move and bounce freely without getting potentially stuck in an edge what you need to do is to reverse the speed (and decrease it by 10%) before you actually move the ball! This is my simple suggestion
if r.left + speed[0] <= 0 or r.right + speed[0] >= width:
speed[0] = - (speed[0])*0.9
if r.top + speed[1] <= 0 or r.bottom + speed[1] >= height:
speed[1] = -(speed[1])*0.9
What the above modifications manages, is that it essentialy does not allow the square to go out of bounds at any time! As far as to what caused your above issue, after doing some debugging, it is apparent that the square manages to move outside the screen! (i.e. negative x,negative y etc), While it might seem harmless, this behaviour especially at lower speeds can cause the square to rapidly reverse and at the same time lower it's speed by 10%!
For example if at any point the square is at position x = - 1 with an xspeed of 1. Due to this condition: if r.left + speed[0] <= 0 or r.right + speed[0] >= width: it's speed will reverse multiple times back and forth and decrease at the same time not allowing the square to escape this edge!
Phew! Sorry for the long answer and I hope I helped!
Cheers!,
Alex

Related

Graphics.py circle won't bounce off walls

I am trying to get a ball to bounce off of the walls of my bounds. Currently, my circle is supposed to hit the wall and then change velocities and move in the opposite direction but this is not happening. I would appreciate some help :) Thank you
from graphics import*
from random import*
from time import*
win = GraphWin('My Program', 600, 600)
win.setBackground('pink')
my_circle = Circle(Point(200,300),30)
my_circle.setFill('blue')
my_circle.setOutline('darkorchid1')
my_circle.draw(win)
key = win.checkKey()
while key == '':
vel_x = randint(-30,30)
vel_y = randint(-30,30)
my_circle.move(vel_x, vel_y)
sleep(0.1)
for bounce in range(600):
find_center = my_circle.getCenter()
center_x = find_center.getX()
center_y = find_center.getY()
if center_x == 600 or center_y == 600:
vel_x = -randint(30,-30)
vel_y = -randint(30,-30)
my_circle.move(vel_x, vel_y)
sleep(0.1)
key = win.checkKey()
Several things that may affect the problem.
You should set your velocities once, as you're doing in the first lines in the while loop. Whenever it bounces you should move in the opposite direction using vel_x = -vel_x and vel_y = -vel_y.
Right now you're updating both of velocities which might lead to some wierd bounces when only one of the centeres hits a wall.
It might be more logical to check wether the distance from the center to the wall is less than the radius of the circle. This will prevent the issue when the ball moves from x=599 to x=601 in one iteration, skipping the if statement. (This would also make it so that the circle bounces when its edge hits the wall instead of the center)
Currently you're only checking 2 walls, unless you meant to do this you should add the if statements for the other walls aswell.
Small other thing, the second time you draw the random velocities you draw from the range 30 to -30, which is invalid.

Handling sprite collisions against an angled rectangular obstacle that is static

So I have this obstacle that I want my sprite to collide with and it is at a particular angle.In this case, we are measuring from the positive x-axis to the top of the rectangle and in this instance it is 333.02 degrees with respect to the positive x-axis or 63.02 degrees with respect to the negative y-axis. So my issue is that how do I set up my pygame sprite to properly collide with the angle rectangle obstacle? Pygame sprite rectangles have no rotation attribute (to my knowledge) and I can't just say, "Hey when the right corner of my sprite collides with top, etc" because of this lack of rotation. My collisions work great for horizontal and even vertical surfaces but I am stuck on how to collide with angled obstacles.
Here is my collision code right now. It uses vectors and checks both x and y independently to see if anything is there. And below is a picture of the object I want to collide with created in the Tile Map Editor. It is at an angle of 333.02 degrees like I mentioned before. I also included a rough sketch of the axis in case that is relevant.
def update(self):
self.animate()
self.acc = vec(0, PLAYER_MASS * GRAVITY)
self.move()
# Equations of Motion
self.acc.x += self.vel.x * PLAYER_FRICTION
self.vel += self.acc
# Collision check in all 4 directions
self.pos.x += (
self.vel.x + 0.5 * self.acc.x * self.game.dt
) # Update x component (Frame-independent motion)
if abs(self.vel.x) < PLAYER_VELX_EPSILON:
self.vel.x = 0
self.rect.x = self.pos.x
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
for hit in hits: # Horizontal collision
if self.vel.x > 0: # Rightward motion
self.rect.right = hit.rect.left
elif self.vel.y < 0: # Leftward motion
self.rect.left = hit.rect.right
self.pos.x = self.rect.x # Update true postion
self.pos.y += self.vel.y + 0.5 * self.acc.y * self.game.dt # Update y component
self.rect.y = self.pos.y + 5
# This prevents double jumping
if self.vel.y > 0:
self.onGnd = False
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
for hit in hits: # Vertical Collision
if self.vel.y > 0: # Downward motion
self.rect.bottom = hit.rect.top
self.vel.y = 0
self.onGnd = True
elif self.vel.y < 0: # Upward motion
self.rect.top = hit.rect.bottom
self.vel.y = 0
self.pos.y = self.rect.y # Update true postion
# Limit Player's movement
if self.rect.bottom > HEIGHT:
self.vel.y = 0
self.rect.bottom = HEIGHT
self.pos.y = self.rect.y
Any help on this problem would be greatly appreciated!
The answer is coordinate translation. Imagine that the rotated object had its own coordinate system, where x runs along the bottom of the rectangle, and y up the side on the left. Then, if you could find the position of your sprite in that coordinate system, you could check for collisions the way you normally would with an unrotated rectangle, i.e., if x >=0 and x <= width and y >=0 and y <= height then there's a collision.
But how do you get the translated coordinates? The answer is matrices. You can use 2d transformation matrices to rotate, scale and translate vectors and coordinates. Unfortunately my experience with these types of transformations is in C#, not python, but this page for instance provides examples and explanations in python using numpy.
Note that this is quite simply the way 2d (and 3d) games work - matrix transformations are everywhere, and are the way to do collision detection of rotated, scaled and translated objects. They are also how sprites are moved, rotated etc: the pygame transform module is a matrix transformation module. So if the code and explanations looks scary at first glance, it is worth investing the time to understand it, since it's hard to write games without it beyond a certain point.
I'm aware this is not a full answer to your question, since I haven't given you the code, but it's too long for a comment, and hopefully points you in the right direction. There's also this answer on SO, which provides some simple code.
EDIT: just to add some further information, a full collision detection routine would check each collidable pixel's position against the object. This may be the required approach in your game, depending on how accurate you need the collision detection to be. That's what I do in my game Magnetar (https://www.youtube.com/watch?v=mbgr2XiIR7w), which collides multiple irregularly shaped sprites at arbitrary positions, scales and rotations.
I note however that in your game there's a way you could possibly "cheat" if all you need is collision detection with some angled slopes. That is you could have a data structure which records the 'corners' of the ground (points it change angle), and then use simple geometry to determine if an x,y point is below or above ground. That is, you would take the x value of a point and check which segment of the ground it is over. If it is over the sloped ground, work out how far along the x axis of the sloped ground it is, then use the sin of the angle times this value to work out the y value of the slope at that position, and if that is greater than the y value of the point you are checking, you have a collision.
The answer from seesharper may be the right way to go. I have no experience with that but it looks interesting and I will have to go read the links and learn something about this approach. I will still provide my immediate reaction to the question before I saw his answer though.
You could use pygames mask and mask collision detection routines. Create a mask that was the shape of the angled rectangle/platform and use the methods to detect collisions with that.
If you add a self.mask attribute to your Sprite subclass, the sprites will automatically use that mask in the collision detection.

Pygame sideways scrolling: platforms continue moving in opposite direction towards position 0

I fail at implementing a functioning sideways scrolling for a platformer using pygame: Whenever the player is not centered horizontally, I want the world (i.e. platforms) to scroll.
I believe the relevant code part in the main loop is:
if self.player.rect.center[0] != WIDTH/2:
self.player.pos.x -= self.player.vel.x
for pl in self.platforms:
pl.rect.x -= self.player.vel.x
However, what happens is that
when player moves to left, all platforms with x position < 0 move at a constant speed to the right but only until x = 0;
when player moves to right, all platforms with x position > 0 move at a constant speed to the left but only until x = 0.
Sometimes the platforms do not move all the way until x = 0. I believe they stop when the player velocity is practically 0.
What did I miss? I really can't see why the platforms behave so weirdly.
I am pretty sure the reason for this is that you used the (+= velocity) method on both the self.player.pos.x and p1.rect.x. This causes multiple problems as the player is only able to move when its not in the center(meaning once its in the center it isn't getting out), and that the platforms are moving at the same speed and in the same direction as the player should be. This is what is causing those weird effects. (you need to divide the vel in half btw otherwise you will move twice as fast!!!)
self.player.pos.x -= self.player.vel.x/2
if self.player.rect.center[0] != WIDTH/2:
for plat in self.platforms:
plat.rect.x += self.player.vel.x/2

Pygame Boundary Not Working

I'm a kid in middle school and hope to be a programmer when I grow up.I'm going to a summer school coding class and learning python and pygame.I already knew enough python but just got my hands wet in pygame.I was adding trying to add a boundary for my game but it's able to block the left and top of the screen here is my code:
import pygame,sys
from pygame.locals import *
pygame.init()
WIDTH = 400
HEIGHT = 400
pg = "player.gif"
bg = "y.gif"
screen=pygame.display.set_mode((WIDTH,HEIGHT))
background = pygame.image.load(bg)
player = pygame.image.load(pg)
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit
x,y = pygame.mouse.get_pos()
screen.blit(background,[0,0])
screen.blit(player,(x,y))
pygame.display.update()
if x <= WIDTH:
x = 0
if y <= HEIGHT:
y = 0
if x <= WIDTH:
x = 0
if y <= HEIGHT:
y = 0
Is this really what you want to do? Set x and y to zero whenever the mouse is positioned within the boundaries? OR do you want to limit x and y to only existing within the range from 0 to WIDTH or HEIGHT respectively?
x = min(max(x, 0), WIDTH)
y = min(max(y, 0), HEIGHT)
Furthermore, note that your player sprite has a width and height of its own. The x,y coordinate represents the location of the top-left corner of the sprite. If you want to restrict the sprite's position such that the entire thing is always on the screen, you first need to get the size of the sprite
spriteWidth, spriteHeight = player.get_rect().size
And then factor that size into your boundary calculation
x = min(max(x, 0), WIDTH - spriteWidth)
y = min(max(y, 0), HEIGHT - spriteHeight)
Additionally, you need to make sure you do this before you call screen.blit(player, (x, y)), or else the sprite will be drawn with the original, unbounded coordinates.

In pygame how to make an object controlled with arrow keys not move of the edge of the screen

I have made an object controlled with arrow keys. When I move it to the edge of the pygame screen, the object moves off the screen. I was wondering how to keep the object on the screen. Any suggestions?
On each handle of the input, check if the object's target x position plus its width exceeds the width of the canvas or if it is less than 0. Deny the movement if so.
Repeat for the y coordinate and the height.
Do something like this:
if player.x == #edge of screen:
player.x -= 0
if player.y == #edge of screen:
player.y -= 0
player.x being the players current x position and player.y being the players current y position or you can do the same thing but when the player goes of the screen it automatically goes to the other side of the screen it will probably take some tweaking to get it to look perfect
last = player.rect.copy()
player.update()
if not screen.get_rect().contains(player.rect):
player.rect = last

Categories