Move in degrees pygame - python

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()

Related

How to rotate a Vector2 in pygame

I am trying to make something like a clock, but I am having trubles rotating the vector.
For what I can tell the vector is rotating in respect of the point (0, 0) of the screen, but I want it to rotate in respect of the 'center' vector.
Another problem I am having is that, even tho the fps are locked on 60, it seems like the vector is speeding up.
Here's the code:
import pygame, sys
from pygame import Vector2
pygame.init()
screen = pygame.display.set_mode((500, 500))
clock = pygame.time.Clock()
SCREEN_UPDATE = pygame.USEREVENT
pygame.time.set_timer(SCREEN_UPDATE, 100)
angle = 0
vector = Vector2(250, 100)
center = Vector2(250, 200)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == SCREEN_UPDATE:
vector.rotate_ip(angle)
angle += 1
screen.fill('black')
pygame.draw.line(screen, 'white', center, vector)
pygame.display.flip()
clock.tick(60)
I was expecting the vector to rotate with a constant speed and in respect to the 'center' vector.
rotate_ip rotates the vector itself. As you increase the angle, the vector rotates more and more with each iteration. You must rotate the vector in place by a constant angle at each iteration:
vector.rotate_ip(1)
The other option is to create a new vector from the original vector with increasing angle with the rotate function:
original_vector = Vector2(250, 100)
vector = Vector2(original_vector)
while True:
for event in pygame.event.get():
# [...]
if event.type == SCREEN_UPDATE:
vector = original_vector.rotate(angle)
angle += 1

how can i move a rectangle in py game without mouse and keys?

I have code to move a rectangle in pygame from left to right, up and down.
But I want to move my rectangle around the screen that I created..
can someone help me please?
import pygame
from pygame.locals import *
pygame.init()
FPS = 70
fpsclock = pygame.time.Clock()
SIZE = (1000, 700)
form1 = pygame.display.set_mode(SIZE)
WHITE=(255, 255, 255)
x = 0
y = 0
w = 50
h = 60
direction = 'right'
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
pygame.draw.rect(form1, (255, 0, 0), (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
if x,y==0,0:
direction='right'
if x,y==1200-50,0:
direction='down'
if x,y==1200-50,700-60:
direction='left'
if x,y==0,1200-50:
direction='right'
So the first thing you have to look at is the spacing. Even though your code works (after proper indentation) the square goes out of bounds.
The same thing applies to y as well if the square should go up and down.
If you want the square to go around you just need to go left, right, up, or down at the correct time. So if you want to start at the left upper corner and go around you just need to check if the square is in a corner and then change the direction accordingly.
Keep in mind that going down actually increases and going up decreases y.
EDIT:
Here you can see the result of my proposed concept
EDIT 2:
I've copied your code and refactored and completed it. I tried to explain why I did what I did.
import pygame
# Only import the things you need it makes debugging and documenting
# alot easier if you know where everything is coming from
from pygame.locals import QUIT
FPS = 70
# Use variables to define width and height in case you want to
# change it later
width = 200
height = 200
# I would group variables by usage, for example, I would group width,
# height of the screen and w, h of the box so I can easily manipulate
# the numbers if want to.
w = 50
h = 60
# Using an offset variable reduces the hardcoded numbers even more
# if its 0 it will just move along the border but if it >0 it will move
# the square to the centre
offset = 20
# You can declare it as a variable if you need the SIZE tuple somewhere
# else, if you don't need it you can just set it as
# pygame.display.set_mode((width, height))
SIZE = (width, height)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
x = offset
y = offset
direction = 'right'
# This is just my preference but I like to have the variables that are
# changeable at the top for easy access. I think this way the code is
# cleaner.
pygame.init()
fpsclock = pygame.time.Clock()
form1 = pygame.display.set_mode(SIZE)
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
form1.fill(WHITE)
# Try to avoid hardcoded numbers as much as possible, hardcoded
# numbers are hard to change later on when the code gets to certain
# size and complexity.
pygame.draw.rect(form1, RED, (x, y, w, h), 1)
pygame.display.update()
fpsclock.tick(FPS)
# Don't harcode conditions, use variables so you can easily change
# them later
if x == offset and y == offset:
direction='right'
if x == width - w - offset and y == offset:
direction='down'
if x == width - w - offset and y == height - h - offset:
direction='left'
if x == offset and y == height - h - offset:
direction='up'
if direction == 'right':
x += 5
elif direction == 'down':
#Keep in mind going down actually means to increment y
y += 5
elif direction == 'left':
x -= 5
elif direction == 'up':
y -= 5

Pygame lags with pygame rectangles added

I'm running a python game with approximately 2k rectangles being drawn on screen each frame.
The problem I have is that it runs on 12 fps and I have no idea how to fix that. When I remove all the rectangles it shifts to 100 fps. I'm not rendering all of them at once, but only the ones camera can see currently. How to fix this lag spike issue, is it because I'm using pygame rectangles or because I'm using them wrong?
here's the code
import pygame
black = (0,0,0)
pygame.init()
gameDisplay = pygame.display.set_mode((0,0),pygame.FULLSCREEN)
gameDisplay.fill(black)
gameDisplay.convert()
clock = pygame.time.Clock()
display_width = 1920
display_height = 1080
from opensimplex import OpenSimplex
tmp = OpenSimplex()
dimensions = [100,100]
size = 40
def mapping(x):
y = (x + 1) * 10 + 40
return y
class GroundCell:
def __init__(self,x,y,dim):
self.x = x
self.y = y
self.dim = dim
tempcells = []
allCells = []
for a in range(0,dimensions[0]):
tempcells = []
for b in range(0,dimensions[1]):
tempcells.append(GroundCell(a*size,b*size,mapping(tmp.noise2d(a*0.11,b*0.11))))
allCells.append(tempcells)
font = pygame.font.Font("freesansbold.ttf", 20)
while True:
for event in pygame.event.get():
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
pygame.quit()
quit()
for a in allCells:
for b in a:
if b.x in range(0,display_width) \
and b.y in range(0,display_height) or\
b.x+size in range(0,display_width) \
and b.y+size in range(0,display_height) :
pygame.draw.rect(gameDisplay,(b.dim,b.dim,b.dim),(b.x,b.y,size,size))
fps = font.render('FPS: ' + str(int(clock.get_fps())), 1, (0, 0, 0))
gameDisplay.blit(fps, (20, 20))
pygame.display.update()
clock.tick(120)
gameDisplay.fill(black)
Instead of drawing all those rects to the screen every tick, create a Surface with the noise once, and reuse it.
Some more notes:
Text rendering is expensive. If you use a lot of text rendering, better cache the surfaces created by Font.render. Note there's also the new freetype module in pygame, which is better than the font module in every way.
Your check if a rect is inside the screen is very strange. You could just use something like if 0 < b.x < display_width ... or even use pygame's Rect class, which offers nice methods like contains().
A good and fast way to create a Surface from arbitary data is to use numpy and pygame's surfarray module. Don't get intimidated, it isn't that hard to use.
Here's a running example based on your code:
import pygame
import numpy as np
black = (0,0,0)
pygame.init()
display_width = 1000
display_height = 1000
gameDisplay = pygame.display.set_mode((display_width, display_height))
gameDisplay.fill(black)
clock = pygame.time.Clock()
from opensimplex import OpenSimplex
tmp = OpenSimplex()
dimensions = [100,100]
size = 16
def mapping(x):
y = (x + 1) * 10 + 40
return y
# create an 2d array from the noise
def get_array():
rgbarray = np.zeros((dimensions[0], dimensions[1]))
for x in range(dimensions[0]):
for y in range(dimensions[1]):
c = int(mapping(tmp.noise2d(x*0.11, y*0.11)))
# simple way to convert the color value to all three (r,g,b) channels
rgbarray[x, y] = c | c << 8 | c << 16
return rgbarray
# create the array and copy it into a Surface
data = get_array()
surface = pygame.Surface((dimensions[0], dimensions[1]))
pygame.surfarray.blit_array(surface, data)
# scale the Surface to the desired size
scaled = pygame.transform.scale(surface, (dimensions[0]*size, dimensions[1]*size))
# simple way to cache font rendering
font = pygame.font.Font("freesansbold.ttf", 20)
cache = {}
def render(text):
if not text in cache:
cache[text] = font.render(text, 1, (0, 0, 0))
return cache[text]
x = 0
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
x-=1
if x < -1000:
x = 0
gameDisplay.blit(scaled, (x, 0))
fps = render('FPS: ' + str(int(clock.get_fps())))
gameDisplay.blit(fps, (20, 20))
pygame.display.update()
clock.tick(120)
As #sloth explained, there are better ways to do the same, but if you want to know what the actual problems are:
You're not drawing 2000 rectangles, you're drawing 10.000 of them, because your dimension is 100x100
You're doing a check if the rectangle is visible in the worst possible way in terms of performance. Just check what happens if you leave the check and don't draw the rectangles. You'll see that the performance is improved but it's still far from 120fps. That's because for every rectangle you generate a list of numbers from 0 to the width of the screen and another list from zero to the height of the screen. You also do that twice. That means, on a 1920x1080 screen: (1920 * 10000) + (1920 * 10000) + (1080 * 10000) + (1080*10000) = 60000000. So, 60 million checks. If you have 120fps, that means 60 million * 120 = 7.2 billion checks per second.
Just changing the check to something similar to if b.x+size < display_width and b.y+size < display_height and b.x > 0 and b.y > 0: will already improve the performance.
That said, it's still 10000 rectangles, and it's still 120fps, which means 1200000 rectangles per second, done with basically no HW acceleration and with a high level language. Don't expect top performance.

White spots when drawing mutliple lines close to each other

How can I get rid of white spots when drawing multiple circles close to each other in pygame?
Here is my code:
import pygame
from pygame import gfxdraw
from math import pow, atan2
def getColor(r, col1, col2, fun="lin"):
if fun =="pol2":
r = pow(r,2)
col1_r = tuple([r*x for x in col1])
col2_r = tuple([(1-r)*x for x in col2])
final_col = tuple(sum(i) for i in zip(col1_r, col2_r))
return final_col
def draw(sizeX, sizeY):
# Initialize the game engine
pygame.init()
screen = pygame.display.set_mode([sizeX, sizeY])
#Loop until the user clicks the close button.
done = False
clock = pygame.time.Clock()
while not done:
# This limits the while loop to a max of 10 times per second.
# Leave this out and we will use all CPU we can.
clock.tick(10)
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
screen.fill(WHITE)
for y in range(200,500):
for x in range(0,10):
gfxdraw.arc(screen, 400, 400, y, x*15, (x+1)*15, getColor(x/10,(0,0,(y-200)/2),(255,255,(y-200)/2), fun="lin"))
pygame.display.flip()
pygame.quit()
This phenomenon is called aliasing and happens when you take a continuous signal and samples it. In your case, gfx.draw() uses continuous functions (the trigonometric functions) to calculate which pixel to draw the color onto. Since theses calculations are in floats and have to be rounded to integers, it may happen that some pixels are missed.
To fix this you need an anti-aliasing filter. There are many different types such as low pass (blurring), oversampling etc.
Since these holes almost always are one pixel I'd create a function that identifies these holes and fills them with the average of it's neighbours colors. The problem is that Pygame is not very good at manually manipulating pixels, so it can be slow depending on the size of the image. Although, Pygame has a module called surfarray that's built on numpy which allows you to access pixels easier and faster, so that will speed it up some. Of course, it'll require you to install numpy.
I couldn't get your program to work, so next time make sure you really have a Minimal, Complete, and Verifiable example. The following code is just based on the image you provided.
import numpy as np
import pygame
pygame.init()
RADIUS = 1080 // 2
FPS = 30
screen = pygame.display.set_mode((RADIUS * 2, RADIUS * 2))
clock = pygame.time.Clock()
circle_size = (RADIUS * 2, RADIUS * 2)
circle = pygame.Surface(circle_size)
background_color = (255, 255, 255)
circle_color = (255, 0, 0)
pygame.draw.circle(circle, circle_color, (RADIUS, RADIUS), RADIUS, RADIUS // 2)
def remove_holes(surface, background=(0, 0, 0)):
"""
Removes holes caused by aliasing.
The function locates pixels of color 'background' that are surrounded by pixels of different colors and set them to
the average of their neighbours. Won't fix pixels with 2 or less adjacent pixels.
Args:
surface (pygame.Surface): the pygame.Surface to anti-aliasing.
background (3 element list or tuple): the color of the holes.
Returns:
anti-aliased pygame.Surface.
"""
width, height = surface.get_size()
array = pygame.surfarray.array3d(surface)
contains_background = (array == background).all(axis=2)
neighbours = (0, 1), (0, -1), (1, 0), (-1, 0)
for row in range(1, height-1):
for col in range(1, width-1):
if contains_background[row, col]:
average = np.zeros(shape=(1, 3), dtype=np.uint16)
elements = 0
for y, x in neighbours:
if not contains_background[row+y, col+x]:
elements += 1
average += array[row+y, col+x]
if elements > 2: # Only apply average if more than 2 neighbours is not of background color.
array[row, col] = average // elements
return pygame.surfarray.make_surface(array)
def main():
running = True
image = pygame.image.load('test.png').convert()
# image = circle
pos = image.get_rect(center=(RADIUS, RADIUS))
while running:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
running = False
elif event.key == pygame.K_1:
print('Reset circle.')
image = circle
elif event.key == pygame.K_2:
print('Starting removing holes.')
time = pygame.time.get_ticks()
image = remove_holes(image, background=(255, 255, 255))
time = pygame.time.get_ticks() - time
print('Finished removing holes in {:.4E} s.'.format(time / 1000))
screen.fill(background_color)
screen.blit(image, pos)
pygame.display.update()
if __name__ == '__main__':
main()
Result
Before
After
Time
As I said before, it's not a very fast operation. Here are some benchmarks based on the circle in the example:
Surface size: (100, 100) | Time: 1.1521E-02 s
Surface size: (200, 200) | Time: 4.3365E-02 s
Surface size: (300, 300) | Time: 9.7489E-02 s
Surface size: (400, 400) | Time: 1.7257E-01 s
Surface size: (500, 500) | Time: 2.6911E-01 s
Surface size: (600, 600) | Time: 3.8759E-01 s
Surface size: (700, 700) | Time: 5.2999E-01 s
Surface size: (800, 800) | Time: 6.9134E-01 s
Surface size: (900, 900) | Time: 9.1454E-01 s
And with your image:
Time: 1.6557E-01 s

Calculating the x and y coordinate changes since last tick

I'm using PyGame to create automatically moving units.
These units can be given a destination of type tuple with two elements (x, y) and the units have a static speed.
I'm calling units.update(dt) where dt is the time since last update (in milliseconds). I need to calculate how much the unit has moved since last update. Here's my Unit class:
class Unit(pygame.sprite.Sprite):
def __init__(self, image, speed):
self.image = pygame.image.load(image)
self.rect = self.image.get_rect() # This rect contains x and y for the Unit
self.speed = speed
self.destination = None
def update(self, dt):
if self.destination is not None and self.speed > 0:
dist = self.speed * dt
This way I can get the hypotenusa (dist), but I need the individual directions for self.rect.x and self.rect.y. How do I get the dx and dy from dist?
Also, here's my main.py:
import unit
import pygame
pygame.init()
display = pygame.display.set_mode((960, 720))
units = pygame.sprite.Group()
my_unit = unit.Unit('my_image.png', 3)
units.add(my_unit)
my_unit.destination = (150, 150)
clock = pygame.time.Clock()
running = True
while running:
dt = clock.tick(30)
units.update(dt)
display.fill((0, 0, 0))
units.draw(display)
pygame.display.update()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
You could add a variable inside the Unit class that stores the current position as a tuple, just like the direction. I guess it would be initialized at (0,0)?
Every update, calculate the angle from the current position towards the destination. Some basic trigonometry would give you the dx/dy, as as you said you already have the hypotenuse.
I can't help you more on this part because it depends how your unit can move (in diagonal), what the speed represents,...
Don't forget to update the current position afterwards.
You will get a rectangle as big as your image. This rectangle will always start at 0, 0 with a width. and height the same size as the image. So with this information you can calculate on your own what dx and dy are. Unfortunatly there is not a function like get_rect_x().

Categories