Everywhere online says to use atan2(pointA.y-pointB.y, pointA.x, pointB.x) but it just doesn't work.
import pygame
import sys
from math import *
WIDTH, HEIGHT = 1024, 576
screen = pygame.display.set_mode((WIDTH, HEIGHT))
def rot(surface, angle):
rotated_surface = pygame.transform.rotozoom(surface, angle, 1)
rotated_rect = rotated_surface.get_rect()
return rotated_surface, rotated_rect
surf = pygame.Surface((64, 32), flags=pygame.SRCALPHA)
surf.fill((255, 0, 0))
def map_range(value, leftMin, leftMax, rightMin, rightMax):
# Figure out how 'wide' each range is
leftSpan = leftMax - leftMin
rightSpan = rightMax - rightMin
# Convert the left range into a 0-1 range (float)
valueScaled = float(value - leftMin) / float(leftSpan)
# Convert the 0-1 range into a value in the right range.
return rightMin + (valueScaled * rightSpan)
while 1:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
mouse_pos = pygame.Vector2(pygame.mouse.get_pos())
center = pygame.Vector2(surf.get_rect().center)
angle = degrees(atan2(mouse_pos.y - center.y, mouse_pos.x - center.x))
rotated_surface = rot(surf, angle)
rotated_surface[1].center = (WIDTH/3, HEIGHT/3)
screen.fill((0,0,0))
screen.blit(rotated_surface[0], rotated_surface[1])
pygame.display.update()
I converted it in degrees and all, but it just doesn't rotate properly towards the cursor. Am I missing something?
You're using the center of surf in your calculation. The center of surf is in the top-left of your screen. Use the center of your rotated_surface.
Negate the angle.
2 Mistakes:
The center of the object is (WIDTH/3, HEIGHT/3)
The y-axis of the pygame coordinate system points down. So you need to invert the y-axis before calculating the angle.
center = pygame.Vector2(WIDTH/3, HEIGHT/3)
angle = degrees(atan2(-(mouse_pos.y - center.y), mouse_pos.x - center.x))
rotated_surface = rot(surf, angle)
rotated_surface[1].center = center.x, center.y
See also How to know the angle between two vectors? and How to rotate an image(player) to the mouse direction?.
Related
I am coding for a fourier transform simulation. Here I have to draw many epicycles. I have a some values of radius which are less than 1 like: 7x10^-14, 0 etc. So, when I draw the circles and assign border width to 1 , I get a value error: width greater than radius. If I put the border width to zero then the circle becomes filled up in color and seems very ugly. So, please show me a way to how I can draw a cricle with a border and radius values less than 1. Here is the code:
radius_list = [0.0, 8.539660890638339e-15, 66.66666666666669, 3.3275832379191784e-14, `
1.1234667099445444e-14, 2.534379764899661e-14, 33.333333333333336, 1.018719954857117e-14,
2.0236265985141534e-14, 2.4825216024150285e-14, 66.66666666666674, 1.5953403096630258e-13`]
run = False
while not run:
clock.tick(fps)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
x = x_pos
y = y_pos
for i in range(iteration):
prevx = x
prevy = y
frequency = freq_list[i]
radius = radius_list[i]
phase = phase_list[i]
print(radius)
x+= int(radius*math.cos((frequency*time) + phase + math.pi/2))
y+= int(radius*math.sin((frequency*time) + phase + math.pi/2))
**pygame.draw.circle(screen, white, (prevx, prevy), int(radius),1)**
[...] please show me a way to how I can draw a circle with a border and radius values less than 1
You can't. The unit of drawing in PyGame is pixels. PyGame cannot draw half a pixel. It makes no sens to draw something with a size less than 1, because PyGame can't do it:
Ensure that the minimum radius is 1:
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)
All you can do is to skip the circles with a radius less than 0.5:
if radius >= 0.5;
pygame.draw.circle(screen, white, (prevx, prevy), min(1, round(radius)), 1)
I need to rotate a triangle (Not an image) around at the center of the screen. I have seen other people answer this question, but the triangle could not point upwards.
I have tried to use other peoples functions, but they see to only work partly, like the function I mentioned above.
import pygame
disp=pygame.display.set_mode((200,200))
import math
def rotate_triange(mouse_pos,triangle_pos):
#The code here
import time
while True:
time.sleep(1)
pygame.Surface.fill(disp,(255,255,255))
center = (100,100)
radius = 10
mouse_position = pygame.mouse.get_pos()
for event in pygame.event.get():
pass
points = rotate_triangle((100,100),mouse_position)
pygame.draw.polygon(disp,(0,0,0),points)
pygame.display.update()
In pygame 2 dimensional vector arithmetic is implemented in pygame.math.Vector2.
Define a Vector2 object for the mouse position and the center of the triangle. Calculate the angle of vector form the center point to the mouse position (.angle_to()):
vMouse = pygame.math.Vector2(mouse_pos)
vCenter = pygame.math.Vector2(center)
angle = pygame.math.Vector2().angle_to(vMouse - vCenter)
Define the 3 points of the triangle around the (0, 0) and rotate them by the angle (.rotate())
points = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
rotated_point = [pygame.math.Vector2(p).rotate(angle) for p in points]
To calculate the final points, the points have to b scaled and translated by the center of the triangle:
triangle_points = [(vCenter + p*scale) for p in rotated_point]
See the example:
import pygame
import math
def rotate_triangle(center, scale, mouse_pos):
vMouse = pygame.math.Vector2(mouse_pos)
vCenter = pygame.math.Vector2(center)
angle = pygame.math.Vector2().angle_to(vMouse - vCenter)
points = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
rotated_point = [pygame.math.Vector2(p).rotate(angle) for p in points]
triangle_points = [(vCenter + p*scale) for p in rotated_point]
return triangle_points
disp=pygame.display.set_mode((200,200))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
mouse_position = pygame.mouse.get_pos()
points = rotate_triangle((100, 100), 10, mouse_position)
pygame.Surface.fill(disp, (255,255,255))
pygame.draw.polygon(disp, (0,0,0), points)
pygame.display.update()
A version of the algorithm, without the use of pygame.math.Vector2, looks as follows:
def rotate_triangle(center, scale, mouse_pos):
dx = mouse_pos[0] - center[0]
dy = mouse_pos[1] - center[1]
len = math.sqrt(dx*dx + dy*dy)
dx, dy = (dx*scale/len, dy*scale/len) if len > 0 else (1, 0)
pts = [(-0.5, -0.866), (-0.5, 0.866), (2.0, 0.0)]
pts = [(center[0] + p[0]*dx + p[1]*dy, center[1] + p[0]*dy - p[1]*dx) for p in pts]
return pts
Note this version is probably faster. It needs a math.sqrt operation, in compare to math.atan2 which is probably used by .angle_to() and math.sin respectively math.cos which is probably used by .rotate(), of the former algorithm.
The result coordinates are the same.
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 have problem with this code, cause I want 4 points, (which are circles) to make vertex of square, but I don't know what the difference should be between those vertexes (variable "change"). I left this variable empty, please, can you give me value I should insert there and explain why?
Here's the code:
import pygame
from math import sin, cos
pygame.init()
screen = pygame.display.set_mode((800,600))
BLACK = (0,0,0)
WHITE = (255,255,255)
BLUE = (0,0,255)
GRAY = (175,175,175)
clock = pygame.time.Clock()
Font = pygame.font.SysFont(None, 50)
angle = 0
angle_c = 0
ex = False
a = (0,0)
b = (0,0)
c = (0,0)
d = (0,0)
change =
size = 95
x_c = 400
y_c = 200
while not ex:
for event in pygame.event.get():
if event.type == pygame.QUIT:
ex = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT:
angle_c = 0.05
if event.key == pygame.K_RIGHT:
angle_c = -0.05
if event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT or event.key == pygame.K_LEFT:
angle_c = 0
angle += angle_c
a = (round(sin(angle)*size+x_c), round(cos(angle)*size+y_c))
b = (round(sin(angle+change)*size+x_c), round(cos(angle+change)*size+y_c))
c = (round(sin(angle+change*2)*size+x_c), round(cos(angle+change*2)*size+y_c))
d = (round(sin(angle+change*3)*size+x_c), round(cos(angle+change*3)*size+y_c))
screen.fill(WHITE)
pygame.draw.circle(screen, BLUE, (400,200), round(sin(360)*100), 3)
pygame.draw.circle(screen, BLUE, a, 10)
pygame.draw.circle(screen, WHITE, a, 8)
pygame.draw.circle(screen, BLUE, b, 10)
pygame.draw.circle(screen, WHITE, b, 8)
pygame.draw.circle(screen, BLUE, c, 10)
pygame.draw.circle(screen, WHITE, c, 8)
pygame.draw.circle(screen, BLUE, d, 10)
pygame.draw.circle(screen, WHITE, d, 8)
pygame.display.update()
clock.tick(50)
pygame.quit()
The angles from the center of a square to the square's vertices differ by 90 degrees, or pi / 2 radians (which is the unit expected by Python's sin and cos functions).
So you could set your change variable to could be pi / 2 (after adding pi to the list of names to import from the math module), and your code would probably work.
But it's even easier to calculate the coordinates than that, as rotations by 90 degrees change sine and cosine values in a predictable way:
sin(a + pi/2) is cos(a)
cos(a + pi/2) is -sin(a).
Applying this transformation repeatedly lets you figure out what the sines and cosines should be after further rotations.
You only need to call sin and cos once each and then you can use the values to find all the coordinates.
y = round(sin(angle) * size)
x = round(cos(angle) * size)
a = ( x + x_c, y + y_c)
b = (-y + x_c, x + y_c)
c = (-x + x_c, -y + y_c)
d = ( y + x_c, -x + y_c)
Note that in the code above I'm following the mathematical convention that angles start at zero along the positive x-axis and increase as you initially start rotating towards the positive y-axis (which will be clockwise in pygame's coordinate system). Your previous code seemed to measure angles from the positive y-axis and increase in the opposite direction. If that's what you really want, you can simply reverse x and y in the initial assignments (assign the cosine to y and the sine to x).
I am making a utility for myself to easily translate degrees to x and y cordinates in my games and I got stuck on a problem; trying to move the player in degrees across the screen. I found multiple formulas that didn't work and I need some help. Here is my code that I found:
def move(degrees, offset):
x = math.cos(degrees * 57.2957795) * offset # 57.2957795 Was supposed to be the
y = math.sin(degrees * 57.2957795) * offset # magic number but it won't work.
return [x, y]
I then ran this:
move(0, 300)
Output:
[300.0, 0.0]
and it worked just fine, but when I did this:
move(90, 300)
it outputted this:
[-89.8549554331319, -286.22733444608303]
Your approach is almost correct. You should use radians for sin/cos functions. Here is a method I commonly use in C++ (ported to python) for 2D movement.
import math
def move(degrees, offset)
rads = math.radians(degrees)
x = math.cos(rads) * offset
y = math.sin(rads) * offset
return x, y
The number is correct, but the operation is wrong. In order to convert degrees to radians you need to divide by 180 degrees per half-circle and then multiply by pi radians per half-circle. This is equivalent to dividing by the constant you have.
You can use the from_polar method of the pygame.math.Vector2 class to set the polar coordinates of a vector. Then you can use this vector to adjust the position of a sprite or rect.
import pygame as pg
from pygame.math import Vector2
def move(offset, degrees):
vec = Vector2() # Create a zero vector.
vec.from_polar((offset, degrees)) # Set its polar coordinates.
return vec
pg.init()
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
BLUE = pg.Color('dodgerblue1')
rect = pg.Rect(300, 200, 30, 20)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
# Use the vector that `move` returns to move the rect.
rect.move_ip(move(50, 90))
screen.fill(BG_COLOR)
pg.draw.rect(screen, BLUE, rect)
pg.display.flip()
clock.tick(30)
pg.quit()