I am making a little game with matplotlib (I know, terrible tool for the job but its a meme). I have a polygon that I am trying to translate around the screen, and also let you rotate it. However, I think there is a mix up between pixel coordinates and data coordinates, because once I move the polygon, the center of rotation is very wrong. Here is the code I have so far:
TLDR: Graph is 40x40 in size. I initialize my Polygon at 20,20. Before I move, rotation works fine, once I thrust, the rotation center point is no longer correct and I can see the polygon rotating around some other point
def thrust():
dx = math.cos(self.angle)
dy = math.sin(self.angle)
self.vx += self.acceleration * dx
self.vy += self.acceleration * dy
def update(ax):
self.cx += self.vx # update data coordinates
self.cy += self.vy
ts = ax.transData
coords = ts.transform([self.cx, self.cy])
t = mpl.transforms.Affine2D().translate(self.cx, self.cy)
r = mpl.transforms.Affine2D().rotate_deg_around(coords[0], coords[1], self.angle)
te = t + r
self.shape.set_transform(ts + te)
return self.shape
Related
I'm new to OpenGL and I try to use the following code (GL_POLYGON) to draw a circle using python. But it seems like it only draws it when the points are added in a clockwise manner, otherwise, it just draws nothing
This successfully draws a circle
p = self.pos + Y_VEC * self.height # center of the circle
dv = self.dir_vec * self.radius # vector point forward
rv = self.right_vec * self.radius # vector point to right
sides = 20 # sides of the circle (circle is in fact polygon)
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle -= inc
glEnd()
Nothing rendered (only change is "-=" to "+=")
angle = 0
inc = 2 * math.pi / sides
glColor3f(0, 1, 0)
glPointSize(10.0)
glBegin(GL_POLYGON) # GL_POLYGON drawn but not shown in top view? GL_LINE_LOOP works
# glVertex3f(*p) # used for TRIANGLE_FAN
for i in range(sides+1):
pc = p + dv * math.cos(angle) + rv * math.sin(angle)
glVertex3f(*pc)
angle += inc # change here
glEnd()
Is this normal? What am I doing wrong?
Make sure face culling is disabled with glDisable(GL_CULL_FACE).
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.
I am trying to make my circle bounce off of my rectangle using Zelle graphics.py. Once the circle bounces off of the rectangle I wanted it to keep moving randomly. Here is my code so far, and it's working!
Also I know that each circle graphics technically can use the points of the smallest possible square that would fit around the circle to do the collision but I'm having trouble with doing that.
from graphics import *
import random
def delay(d):
for i in range(d):
for i in range(50):
pass
#-------------------------------------------------
def main():
win=GraphWin("Moving Circle",500,400)
win.setBackground('white')
pt= Point(100,200)
cir=Circle(pt,30)
#changes the color of the circle for each game
r = random.randrange(256)
b = random.randrange(256)
g = random.randrange(256)
color = color_rgb(r, g, b)
cir.setFill(color)
cir.draw(win)
#rectangle
rec = Rectangle(Point(450,450), Point(275, 425))
rec.draw(win)
rec.setFill('black')
#-------------------------------------------------
pt5 = Point(250,30)
instruct1=Text(pt5, "click multiple times to start(rectangle can take multiple clicks to move)")
instruct1.setTextColor('black')
instruct1.draw(win)
#-------------------------------------------------
p=cir.getCenter()
p2=win.getMouse()
dx=1
dy=1
keepGoing=True
while keepGoing:
d = 100
delay(d)
cir.move(dx,dy)
p=cir.getCenter()
p2=win.checkMouse()
instruct1.setText("")
#rectanlge
isClicked= win.checkMouse()
if isClicked:
rp = isClicked
rc = rec.getCenter()
rdx = rp.getX() - rc.getX()
rdy = rp.getY() - rc.getY()
rec.move(rdx,rdy)
#circle
if((p.getX()-30)<=0.0) or ((p.getX()+30)>=500):
dx= -dx
if((p.getY()-30)<=0.0) or ((p.getY()+30)>=400):
dy=-dy
p3=win.checkMouse()
main()
I know that each circle graphics technically can use the points of the
smallest possible square that would fir around the circle to do the
collision
I'm playing with an alternate idea -- we could consider a circle around the rectangle instead of a square around the circle. The issue for me is that we not only need to detect collision, but come out with a sense of which way to move away from the other object. It's not just True and False but rather a (dx, dy) type of result.
Obviously, a circle around the rectangle is too crude, but suppose it were lots of smaller circles making up the rectangle and we measure circle center to center distance to detect a hit:
A hit on just a central (green) rectangle circle means reverse the vertical direction of the big circle. A hit on just the end (red) circle means reverse the horizontal direction of the big circle. And we can detect both kinds of hits and reverse the big circle completely.
Here's my rework of your code with the above in mind -- I also fixed your multiple clicking issue and made lots of style changes:
from random import randrange
from graphics import *
WIDTH, HEIGHT = 500, 400
RADIUS = 30
def delay(d):
for _ in range(d):
for _ in range(50):
pass
def distance(p1, p2):
return ((p2.getX() - p1.getX()) ** 2 + (p2.getY() - p1.getY()) ** 2) ** 0.5
def intersects(circle, rectangle):
dx, dy = 1, 1 # no change
center = circle.getCenter()
rectangle_radius = (rectangle.p2.getY() - rectangle.p1.getY()) / 2
rectangle_width = rectangle.p2.getX() - rectangle.p1.getX()
y = rectangle.getCenter().getY()
for x in range(int(rectangle_radius * 2), int(rectangle_width - rectangle_radius * 2) + 1, int(rectangle_radius)):
if distance(center, Point(rectangle.p1.getX() + x, y)) <= rectangle_radius + RADIUS:
dy = -dy # reverse vertical
break
if distance(center, Point(rectangle.p1.getX() + rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
elif distance(center, Point(rectangle.p2.getX() - rectangle_radius, y)) <= rectangle_radius + RADIUS:
dx = -dx # reverse horizontal
return (dx, dy)
def main():
win = GraphWin("Moving Circle", WIDTH, HEIGHT)
circle = Circle(Point(WIDTH / 5, HEIGHT / 2), RADIUS)
# change the color of the circle for each game
color = color_rgb(randrange(256), randrange(256), randrange(256))
circle.setFill(color)
circle.draw(win)
# rectangle
rectangle = Rectangle(Point(275, 425), Point(450, 450)) # off screen
rectangle.setFill('black')
rectangle.draw(win)
dx, dy = 1, 1
while True:
delay(100)
circle.move(dx, dy)
# rectangle
isClicked = win.checkMouse()
if isClicked:
point = isClicked
center = rectangle.getCenter()
rectangle.move(point.getX() - center.getX(), point.getY() - center.getY())
# circle
center = circle.getCenter()
if (center.getX() - RADIUS) <= 0.0 or (center.getX() + RADIUS) >= WIDTH:
dx = -dx
if (center.getY() - RADIUS) <= 0.0 or (center.getY() + RADIUS) >= HEIGHT:
dy = -dy
# collision bounce
x, y = intersects(circle, rectangle)
dx *= x
dy *= y
main()
Not perfect, but something to play around with, possibly plugging in a better intersects() implementation.
I'm making a basic pong game (paddle is a rectangle on the bottom of the screen and the ball drops from the top of the screen). I want the ball to bounce back up ONLY when it hits the paddle. So far, I've written code that will make the ball bounce off the top and bottom screen, but I'm having trouble with getting the ball to bounce off the paddle.
I have to modify the parameters that are passed to my test_collide_ball method. If it’s current x values are within the range of the paddle, then it bounces back up.
I've been trying to think of a solution for this, and what I'm thinking is that if the ball hits the paddle's y coordinate (the height), then it bounces back up. But it also has to be within the range of x coordinates that make up the paddle (so the width of the paddle).
But when I do this, the ball just gets stuck in place. Any feedback is appreciated! Thanks in advance.
Here is my code for the ball class/methods:
import pygame
class Ball:
def __init__(self, x, y, radius, color, dx, dy):
self.x = x
self.y = y
self.radius = radius
self.color = color
self.dx = dx
self.dy = dy
def draw_ball(self, screen):
pygame.draw.ellipse(screen, self.color,
pygame.Rect(self.x, self.y, self.radius, self.radius))
def update_ball(self):
self.x += self.dx
self.y += self.dy
def test_collide_top_ball(self, top_height):
if (self.y <= top_height):
self.dy *= -1
def test_collide_bottom_ball(self, paddle):
if (self.y == paddle.y) and (self.x >= paddle.x) and (self.x <= paddle.x + paddle.width):
self.dy *= -1
What appears to be happening is your ball enters the collision zone and reverses it's direction. The ball is still in the collision zone, however, and it reverses it's direction again.
What you should look into is a debounce check. Put simply, this is code that prevents something from happening twice or more times (de-bouncing it).
From your code example, the ball's momentum is reversed when it enters the paddle zone. What you might add is a boolean flag to see if you have already detected that the ball entered the zone. When it is first detected, set the flag to true. When the ball moves outside of the zone, set the flag back to false. Only reverse the ball's momentum if the flag is false.
So, (excusing my rusty Python)
def test_collide_bottom_ball(self, paddle):
if (self.y == paddle.y) and (self.x >= paddle.x) and (self.x <= paddle.x + paddle.width) and (!self.hitPaddle):
self.dy *= -1
self.hitPaddle = true
else
self.hitPaddle = false
And in your entity:
self.hitPaddle = false
Just like #MrDoomBringer is saying, you need to prevent it from getting stuck within the pad.
One easy method to solve that is to check whether self.dy is positive - the ball is moving downwards. This way you could also add the same "within" check for the Y-pos as you did with the X-pos. Otherwise, having a collision with an exact Y-coordinate is pretty hard unless you're using the right speed etc.
Another thing - if you have a ball, you most likely want to add it's size to the equation. Then you might want to use some more fancy collision-techniques, such as this: http://www.migapro.com/circle-and-rotated-rectangle-collision-detection/
I'm making a game in which balls bounce around the inside of a much larger circle. The larger circle doesn't move.
Here's the code that I'm currently using for these collisions:
def collideCircle(circle, ball):
"""Check for collision between a ball and a circle"""
dx = circle.x - ball.x
dy = circle.y - ball.y
distance = math.hypot(dx, dy)
if distance >= circle.size + ball.size:
# We don't need to change anything about the circle, just the ball
tangent = math.atan2(dy, dx)
ball.angle = 2 * tangent - ball.angle
ball.speed *= elasticity + 0.251
angle = 0.5 * math.pi + tangent
ball.x -= math.sin(angle)
ball.y += math.cos(angle)
It is based on the wonderful tutorial by Peter Collingridge over here.
The circle and ball objects are both classes, with (x,y), radius, angle and speed.
I am having two problems with this method, however:
The ball bounces from (what I suspect) is its "anchor point", which seems to be in the top right corner of the circle.
When colliding with the bottom 5% of the circle, is fails to bounce high enough and therefore "sinks" out of the screen. I am guessing that this is because the bounce is not high enough to move the ball above its (incorrectly placed) "anchor point"?
Having looked at possible solutions already on here, notably "Fast circle collision detection" [Link deleted due to spam link limit], which, although in Java is using the same method, these all deal with external collisions, whereas I am looking at bouncing a ball around the interior of a circle.
Here is also the class definitions of the Ball() and the Circle():
class Ball():
def __init__(self, (x,y), size):
"""Setting up the new instance"""
self.x = x
self.y = y
self.size = size
self.colour = (0,128,255)
self.thickness = 0
self.speed = 0.01
self.angle = math.pi/2
def display(self):
"""Draw the ball"""
pygame.draw.circle(screen, 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) = addVectors((self.angle, self.speed), gravity)
self.speed *= drag
class Circle():
def __init__(self, (x,y), size):
"""Set up the new instance of the Circle class"""
self.x = x
self.y = y
self.size = size
self.colour = (236, 236, 236)
self.thickness = 0
self.angle = 0 # Needed for collision...
self.speed = 0 # detection against balls
def display(self):
"""Draw the circle"""
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness
Thanks in advance, Nathan
Without answering your question, I'd like to comment on your implementation strategy and recommend a new approach. You represent the velocity of the ball in polar coordinate form, as ball.angle and ball.speed.
I think that this is going to be generally inconvenient for you. For example, in your collision code you end up calling atan2 to turn the vector (dx, dy) into an angle, and then you call sin and cos to turn the angle back into a vector again. (Also, should you ever try to generalize your code to three dimensions, you will find yourself in a world of pain.) So, unless you have particular requirements that necessitate polar coordinates, I recommend that you do what everyone else does, namely represent the velocity of the ball in Cartesian coordinates as the vector (vx, vy).
I also recommend changing your physics approach from a static one ("is object A currently colliding with object B?") to a dynamic one ("will object A collide with object B during its next movement step?"). In a static physics system you often end up with objects intersecting each other at the end of a movement step, and then you have to figure out the best way to get them to separate again, which is hard to get right.
If you do both of these, it is straightforward to bounce the ball without any trigonometry.
Step 1. Transform circle/circle collision into point/circle collision using Minkowski addition:
Step 2. Consider a time segment in which the ball starts at p = (px,py) and moves by v = (vx,vy). Does it intersect with the circle? You can use a standard line segment/circle test for this except that the sense of the test is reversed.
Step 3. Find the point of collision c = (cx,cy). The ball bounces off the circle in the same way as it would bounce off the line t tangent to the circle at this point. For a circle centred at the origin, the tangent vector is just (−cy,cx) and I'm sure you can work out how to compute it for other circles.
See this answer for how to calculate the new path of the ball based on coefficients of friction and restitution.
Step 4. Don't forget that the ball may still have some distance to move along the new vector w. If the time step is large enough or the velocity high enough it may collide again during the same time segment.
I'm glad you liked my tutorial. I like your variation, it should actually be simpler.
First, I think you need change the test for collision to:
if distance >= circle.size - ball.size:
Because the larger the ball size, the smaller the distance between its centre and the centre of the circle can be. This should make the balls bounce at the right place (inside the circle).
Then I think you just need to swap the signs for the x and y and everything should work.
ball.x += math.sin(angle)
ball.y -= math.cos(angle)
To move the ball by the correct distance you can calculate the overlap:
overlap = math.hypot(dx, dy) - (circle.size - ball.size)
if overlap >= 0:
tangent = math.atan2(dy, dx)
ball.angle = 2 * tangent - ball.angle
ball.speed *= elasticity
angle = 0.5 * math.pi + tangent
ball.x += math.sin(angle)*overlap
ball.y -= math.cos(angle)*overlap
Good luck
Most graphics packages use upper-left as start for drawing code. You most likely want 2 sets of coordinates, the one's you collide/move/etc with and the one's for drawing (x-radius, y-radius).
Also, without having thought about it too much, should the check for intersection be distance + ball.size >= circle.size? The balls distance from the center plus its radius should be less than the circle's radius, if I understood the setup correctly.