Pygame circle and its associated rect for collision detection - python

I am having an issue with Pygame drawing a circle and its associated rect. When I draw a circle such as pygame.draw.circle(surface, color, center, radius) it creates a rect. I am attempting to do collision detection using pygame.collidepoint(event.pos) where event.pos is the position of a mouse click. The size of my window is 500x400 and has a black background and the drawn circle is blue. When I click outside of the circle (in the black background), the circle should increase in size by multiplying the radius by an int. Conversely, when a click is registered inside of the circle, the circle shrinks by dividing the radius by an int.
The problem I am having is that the rect is not actually the same size as the circle such that when you click on the background when the circle is large but not completely filling the window, it shrinks instead of getting bigger (i.e. it registers a click inside the circle). At a certain point, the rect associated with the circle becomes the same size as the window even if the circle is not completely filling it which results in registering a click inside.
My question is how can I use hit detection for the circle only and not the rect which is larger than the actual circle?
Here is the relevant code:
def handleMouseDown(self, position):
if self.circle.collidepoint(position):
self.radius = self.radius // 2
else:
self.radius = self.radius * 2
self.draw()
def draw(self):
self.surface.fill(self.black)
self.circle = pygame.draw.circle(self.surface, self.color, self.center, self.radius)

Just calculate the distance between the center of the circle and the position of the mouse click:
x1, y1 = position
x2, y2 = self.circle.center
distance = math.hypot(x1 - x2, y1 - y2)
Then check if this distance is smaller or equal the radius of the circle:
if distance <= self.radius:
self.radius = self.radius // 2
...

When a click is detected, have the handler determine if it is actually inside the circle. Otherwise ignore it.

Related

How to center an image in the middle of the window in pygame?

So I am Loading an Image in python and I need to Center the Image in Pygame. As Pygame starts the drawing from the upper left side of the screen so the x and y coordinates 0,0 doesn't work. Can anyone tell me the center x and y coordinates of the pygame window?
You can get the center of the window through the window rectangle (pygame.Surface.get_rect):
screen = pygame.display.set_mode((800, 600))
center = screen.get_rect().center
Use this to blit an image (pygame.Surface object) in the center of the screen:
screen.blit(image, image.get_rect(center = screen.get_rect().center))
pygame.Surface.get_rect() returns a rectangle with the size of the Surface object, that always starts at (0, 0). However, the position of the rectangle can be specified with a keyword argument. In this case, the center of the rectangle is set by the center of the screen.
When the 2nd argument of blit is a rectangle (pygame.Rect), the upper left corner of the rectangle will be used as the position for the blit.

How do I make a sprite "stand' on another sprite in pygame?

I've got two sprites that can detect collisions, but I'm not sure how I can make my character sprite "stand" on my platform. Nothing I've done seems to work.
If you have 2 pygame.Rect objects, you can move the 2nd rectangle on the 1st rectangle by setting the bottom of the 2nd rectangle with the top of the 1st rectangle. With the virtual attributes of pygame.Rect it is easy to do so:
rect2.bottom = rect1.top
Or if you have 2 pygame.sprite.Sprite objects:
sprite2.rect.bottom = sprote1.rect.top
If you have 2 pygame.Surface objects (surf1 and surf2) and the 2 corresponding positions (x1, y1) and (x2, y2), use get_rect() to create pygame.Rect objects from the Surfaces. Move the 2nd rectangle on the 1st rectangle and update the position of the 2nd rectangle:
rect1 = surf1.get_rect(topleft = (x1, y1))
rect2 = surf2.get_rect(topleft = (x2, y2))
rect2.bottom = rect1.top
y2 = rect2.y

Rotating the player about the center point in pygame

I am making a game where there are two players and they can shoot each other. Their movement will be defined by a rotation around a fixed point, the point will be(600, 300), which is the center of our screen. The player will keep rotating around the point as long as they are pressing a certain button(which is keep providing force to our player) else they will fall(due to gravity). I think it would help to think of it as a ball attached to a point using a string. The string is attached as long as a button is pressed and gets unattached as soon as the button is released and the ball flies off. Here is my player class
class Player:
def __init__(self):
self.pos = [500, 200]
self.width = 30
self.height = 30
self.player = pygame.image.load("player.png").convert_alpha()
self.player = pygame.transform.scale(self.player, (self.width, self.height))
self.rect = self.player.get_rect()
self.rotated_player = None
self.anguler_vel = 0
self.Fg = 0.05
self.Fp = 0
self.arm_length = 0
Fp is the force perpendicular to the force of gravityFg. Fg is the force which is pulling it down on our player. Fp is defined by math.sin(theta) * Fg. I am keeping track of Fp because i want the player to keep moving in the direction of rotation after its unattatched from the string. arm_length is the length of the string.
I have a Point class, which is the point about which our player will rotate. Here's the point class.
class Point:
def __init__(self,x, y):
self.pos = [x, y]
dx = self.pos[0] - player.pos[0]
dy = self.pos[1] - player.pos[1]
self.angle = math.atan2(dy, dx)
Now, i need help with the actual rotation itself. I am aware that adding a certain value to the angle every single frame would make it go around. But how would i make it go around a certain point that i specify and how would the arm length tie into this?. I find that it is really difficult to implement all of this because the y-axis is flipped and all the positional values have to be scaled down when using them in calculations because of the FPS rate. Any help on how this is done would be appreciated as well. Thanks
When you use pygame.transform.rotate the size of the new rotated image is increased compared to the size of the original image. You must make sure that the rotated image is placed so that its center remains in the center of the non-rotated image. To do this, get the rectangle of the original image and set the position. Get the rectangle of the rotated image and set the center position through the center of the original rectangle. e.g.:
def rotate_center(image, rect, angle):
rotated_image = pygame.transform.rotate(image, angle)
new_rect = rotated_image.get_rect(center = rect.center)
return rotated_image, new_rect
screen.blit(*rotate_center(image, image_rect, angle))
Alos see How do I rotate an image around its center using PyGame? and How to rotate an image(player) to the mouse direction?

Finding the center of an image to define a hitbox

I'm trying to select the center of my image to make a hitbox some pixels around it.
I was able to inflate() the hitbox to make it the right size, but it always uses the top left corner of the image. This is fine when i'm moving left, but when i turn right it goes away from the character (in the image the character is dragging a sword, so it goes way off center).
I've been reading about Vector2, pos and offset, but i can't get it to work.
In conclusion i need to learn a way to find the center of my image in order to place it's hitbox a few pixels to each side. Either that or how to "shift" the corner the hitbox uses so it's always in the front of the char.
Use a pygame.Rect object. Get the rectangle from the pygame.Surface object (image) and set the top left corner position (x, y) of the rectangle. e.g.:
rect = image.get_rect()
rect.topleft = (x, y)
respectively
rect = image.get_rect(topleft = (x, y))
pygame.Rect provides a lot of virtual attributes, which can be used to retrieve the corners, borders, center and size of the rectangle. .center returns the center of the rectangle:
center_x, center_y = rect.center

PyGame Circular Collision Issues - Circle Interior

I'm making a puzzle game that requires the user to 'draw' circles onto a background to get a ball to the exit. They create circles by holding their mouse button, the circle grows; when it is big enough, they let go and it is 'punched' into the physical space and balls then react to it.
I have a problem, however, that when two circles are intersecting (so a ball should pass through), if the intersection is not larger than the diameter of the ball the ball collides with the interior of the circle as usual.
This may be a little hard to comprehend, so here's a link to the screencast showing the problem (You can't embed videos on Stack Overflow): http://www.youtube.com/watch?v=3dKyPzqTDhs
Hopefully that made my problem clear. Here is the Python / PyGame code for the Ball and Circle classes:
class Ball():
def __init__(self, (x,y), size, colourID):
"""Setting up the new instance"""
self.x = x
self.y = y
self.size = size
self.exited = False
self.colour = setColour(colourID)
self.thickness = 0
self.speed = 0.01
self.angle = math.pi/2
def display(self, surface):
"""Draw the ball"""
# pygame.gfxdraw.aacircle(screen,cx,cy,new_dist,settings['MINIMAP_RINGS'])
if self.exited != True:
pygame.draw.circle(surface, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
"""Move the ball according to angle and speed"""
self.x += math.sin(self.angle) * self.speed
self.y -= math.cos(self.angle) * self.speed
(self.angle, self.speed) = module_physicsEngine.addVectors((self.angle, self.speed), gravity)
self.speed *= drag
And the Circle class:
class Circle():
def __init__(self, (x,y), size, colourID):
"""Set up the new instance of the Circle class"""
self.x = x
self.y = y
self.size = size
self.colour = setColour(colourID)
self.thickness = 2
self.angle = 0 # Needed for collision...
self.speed = 0 # detection against balls
def display(self, surface):
"""Draw the circle"""
pygame.draw.circle(surface, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
Within the main loop of the game (while running == True: etc.), this code is used to perform actions on each ball:
for b in balls:
b.move()
for i, ball in enumerate(balls):
for ball2 in balls[i+1:]:
collideBalls(ball, ball2)
collideCircle(b) # <---------------- This is the important line
collideExit(b)
b.display(screen)
And finally, the collideCircle(b) function, which is called once per ball to check for collisions with the interior of a circle, and also to check if the circles are intersecting.
def collideCircle(ball):
"""Check for collision between a ball and a circle"""
hit = False
closestDist = 0
for c in circles:
# Code cannot be replaced with physicsEngine.collideTest because it
# is slightly differnt, testing if ball [ball] inside a circle [c]
dx = c.x - ball.x
dy = c.y - ball.y
distance = math.hypot(dx, dy)
if distance <= c.size - ball.size:
# If BALL inside any CIRCLE
hit = False
break
else:
# If we're outside of a circle.
if closestDist < c.size - (distance - ball.size):
hit = c
closestDist = (c.size - (distance - ball.size))
if hit:
module_physicsEngine.circleBounce(hit, ball)
Ok, so I know that this has been a bit of a long and talky question, but I think you have all the information needed. Is the solution to make the balls interact correctly something to do with the line if distance <= c.size - ball.size:?
Anyway, thanks in advance!
Nathan out.
TL;DR - Watch the youtube video, and let me know why it's not working.
The problem is with unintended hits rather than missed ones. What you really want to check is if all parts of the ball are covered by some circle, while the check you're doing is if any circle only partially overlaps - but an override if any circle fully covers the ball.
I figure for any potential hit point, i.e. closest inner wall of a circle, let that point "walk" along the wall by checking its distance from all other circles. Should it then leave the ball, it was a false hit.
First you find the list of circles that touch the ball at all. As before, if any of them cover it, you can skip the rest of the checks. Also find the closest wall point to the ball for the circles. For each of those closest wall points, if it overlaps another circle, move it to the intersection point which is closest to the ball but further away than the current point. Discard it if it's outside the ball. Repeat the procedure for all circles, since more than two may overlap. Also note that the moving of the point may cause it to enter new circles.
You could precompute the intersection points and discard any that are a ball radius inside of any other circle.
This can surely be improved on, but it's a start, I think. I suspect a bug involving the case when both intersection points of a pair of circles overlap the ball, but a walk chain leads one of them outside the ball. Perhaps the initial collision points should be replaced only by both intersection points, not the closest.
I watched the video and I like the game principle. :)
Maybe the problem is that you break out of the loop as soon as you encounter a circle that encloses the ball. I'm referring to the snippet
if distance <= c.size - ball.size:
# If BALL inside any CIRCLE
hit = False
break
Why would you not check all of the other circles, in that case? There might be another circle yet unchecked that causes a hit.
Btw, I wouldn't say if condition == True:, that's unpythonic. Just say if condition:.

Categories

Resources