Python 2.7 - Pygame - move_ip() too granular - python

I'm practicing making an android app in pygame, and decided to do your classic bubble shooter. I have run into a problem though where the projectile launched by the player isn't accurate enough.
def move (self, time_passed):
x = (math.sin(self.angle) * self.speed) * time_passed
y = (math.cos(self.angle) * self.speed) * time_passed
c = self.rect.center
self.rect.center = (c[0]+x, c[1]+y)
def update(self, screen, time_passed):
if self.launched:
if not self.mpos:
self.mpos = pygame.mouse.get_pos()
diffx, diffy = (self.mpos[0]-self.rect.center[0],
self.mpos[1]-self.rect.center[1])
self.angle = math.atan2(diffx, diffy)
self.move(time_passed)
The screen and time_passed arguments are passed from the main loop and are the display and returned value of the Clock class's tick(60)/1000.0, for the sake of clarity.
If I print the values of x and y from move() it's very accurate: x: 1.0017224084 y: -21.9771825359, or something similar depending on where the mouse is. However, it seems pygame or move_ip only work with integers. I can click between what would be 1.00000001 and 1.9999999 and the ball will shoot in the exact spot, being 1 (in reference to the x coordinate). This is bad for my game. Is there a solution to this so I can get my objects to move very precisely?
EDIT: I realized I pasted in the wrong code. self.rect.center = (c[0]+x, c[1]+y) is where self.rect.move_ip(x, y) should be. Both methods yield the same result, however.

Keep track of your own positions using precise float values, and then instead of incrementally moving your Rects from frame to frame, just place them at their proper (integer-ized) positions each frame. That way you don't suffer from the sum of each frame's rounding error, but instead only have a single rounding involved at any given time.

Related

Im currently making a game with pygame and I need an explanation on some vector code

So I'm making a game and I've got some help from another post to make bullets fly towards the mouse cursor. The original person who showed me this did explain it to me and I have a rough idea what it does but I didn't quite understand it. So I'm posting here for further explanation.
def Shoot(self):
pos = self.rect.centerx, self.rect.centery
mpos = py.mouse.get_pos()
direction = py.math.Vector2(mpos[0] - pos[0], mpos[1] - pos[1])
direction.scale_to_length(10)
return Bullet(pos[0], pos[1], round(direction[0]), round(direction[1]))
Edit: well I know what it does I just don't how I do it. I know It allows for projectiles to a fly towards the mouse even on diagonals but I don't know how it does it.
Whats happening is your getting the position of the cube/player with pos.
mpos is the mouse position on the screen
direction gets the direction between the player and the mouse. for example it the direction could be 10 pixels down and 100 pixels to the right.
The next line scales the direction down to 10, so instead of moving 100 pixels right and 10 down, its close to about 1 down and 10 right (not exactly but pretty close)
The last line creates the bullet with the x position, y position, x speed, y speed. rounding the speed as i said above, its not exactly 1 down and 10 right, it will be some decimal so to make it a nice number, you round it
I've tried to explain that in the answer to your previous question (Im currently making a game with pygame and Ive run into an Issue.), but I'll try it again.
The instruction
direction = py.math.Vector2(mpos[0] - pos[0], mpos[1] - pos[1])
Computes the distance from the point pos (A) to the point mpos (B) along the x-axis and y-axis. Such a tuple of axis aligned distances is called Vector:
At this point the Euclidean distance from point A to point B is unknown.
In the following the vector is scaled to a length of 10, by the operation pygame.math.Vector2.scale_to_length:
direction.scale_to_length(10)
That means that the x and y component of the vector is changed in that way (xd, yd), that the Euclidean length of the vector is 10 (d = 10):
If the components of the vector are added to the components of the point A, once per frame, then the point A steps towards the point B (A1, A2, ...):

Speed up AI obstacle detection in python pygame

I am trying to train a NEAT algorithm to play a simple game called 'curvefever'.
I was able to create a pygame version of curvefever and now I want to train the AI to play it.
Therefore, the AI has to learn to avoid obstacles: borders surrounding the game and tracks that each player leaves behind, like in Snake.
At the moment I am doing this in the following way:
Each player has a set of 'sensors' reaching forward that detect if and how far away an obstacle is.
Each 'sensor' is a straight line consisting of several pygame rectangles.
For each sensor it will detect if a collision with one of the obstacle rectangles occurred and calculate the distance of the collision to the player.
Which sensor detected the collision and the distance of the collision is the information that goes to the neural network.
The problem is that this is very slow! Running 'python -m cProfile -s cumtime ai.py' I figured that it is the detection of obstacles that is slowing the script down, taking up about 50% of the total runtime.
Please see some code below how I create the lines of sight:
posx = x-position of player
posy = y-position of player
dir = direction the player is going
dangle = is the degree-spacing between lines of sight
angle = total range (in degrees) of lines of sight
def create_lines_of_sight(posx, posy, dir, dangle, angle, length):
dirs = [xdir for xdir in np.ceil(np.arange(dir-angle,dir+angle,dangle))]
d_posx = np.cos(np.deg2rad(dir))
d_posy = np.sin(np.deg2rad(dir))
return list(map(functools.partial(f_lrects,posx,posy,length), dirs))
def create_rects(posx, posy, d_posx, d_posy, i):
return f_rect(posx+i*d_posx,posy+i*d_posy,1,1,0,curvefever.WHITE)
f_create_rect = create_rects
def create_line_of_rects(posx, posy, length,dir):
l = pygame.sprite.Group()
ladd = l.add
d_posx = np.cos(np.deg2rad(dir))
d_posy = np.sin(np.deg2rad(dir))
i = [i for i in range(2,length,8)]
ladd(map(functools.partial(f_create_rect,posx,posy,d_posx,d_posy),i))
return l
f_lrects = create_line_of_rects
All obstacles are rectangles defined as:
class Rect(pygame.sprite.Sprite):
def __init__(self,x,y,width,height,dir,color):
super().__init__()
self.image = pygame.Surface([width, height])
self.image.fill(color)
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.centery = y
and are saved in a sprite group.
What I tried
I tried adding a map command to get rid of the for loop, that did not speed it up much.
I tried adding the function names to remove the function lookup, I read this makes it faster, but it didn't.
I tried detecting an obstacle using 'Bresenham's Line Algorithm' and checking if an obstacle (x,y) position overlaps with the line of sight. Although this was a faster it did not work as it often missed obstacles. This happened because the line of sight did not exactly match the obstacle centre (rectx,recty) although it did overlap with the rectangle itself.
What do other people use to detect obstacles (maybe in pygame)? Any tips on how I can make this faster or more efficient are very welcome.
Thank you very much for your help!
I've been working on a similar project.
In the end I've used the pygame.Rect.clipline() and pygame.Vector2.distance_to() methods of pygame:
def intersect_rect(self,other) -> tuple[float,float]:
cl=other.clipline(self.a.x,self.a.y,self.b.x,self.b.y)
if cl:
return cl[0] if self.a.distance_to(cl[0]) <self.a.distance_to(cl[1]) else cl[1]
else:
return
self and other are both of a class, that inherited form the pygame.Rect class. self.a and self.b are two pygame.Vector2 objects. Where self.a is in the origin of the player and self.b the LoS.
This resulted in a speedup of 100x, compared to a pure python function.

Python Pygame forces

I've been wanting to create a physics engine for my game, and decided I should first cover forces. I'm using pygame on python to accomplish this.
For example, I want to be able to program a force of a certain magnitude, that acts on an object. I haven't been able to think of any way to do this. After I get the basic strategy down, it should be easy to subtract magnitudes of forces and add all kinds of different properties. Here is my source code now:
import pygame
pygame.init()
display_width = 800
display_height = 600
gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption('Physics Engine')
clock = pygame.time.Clock()
x = display_width / 2
y = display_height / 2
gameExit = False
def force():
# force function
# Code here
y_change = 0
x_change = 0
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN: # check for key presses
if event.key == pygame.K_LEFT: # left arrow turns left
x_change = -10
y_change = 0
elif event.key == pygame.K_RIGHT: # right arrow turns right
x_change = 10
y_change = 0
y += y_change
x += x_change
print(x, y)
gameDisplay.fill((255, 255, 255))
pygame.draw.rect(gameDisplay, (255, 0, 0), [x, y, 30, 30])
pygame.display.update()
clock.tick(25)
pygame.quit()
quit()
As of now, it simply is a red square moving left and right. I want it to be that if I hold down the arrow key it moves, and when I release it stops. I've tried using a boolean in the event, but it doesn't seem to want to work.
I would like to implement a simple gravity factor.
Thanks so much for reading!
There are a few points I can see. Firstly, if you want an effect to occur while you hold a key down then you should query if that key is down, rather than just pressed. Ignore KEYDOWN events and use http://www.pygame.org/docs/ref/key.html#pygame.key.get_pressed
instead. Secondly, you should define or use library classes for positions, velocities, etc... which will give you a concrete set of operations.
From there the basic idea is to query the keys, set this update's acceleration from that and gravity, set velocity by updating old velocity with acceleration, set position by updating old position with velocity, and repeat forever.
When a force is applied, it adds an acceleration to the object in terms of the mass in the direction of the force.
First of all, I would suggest creating variables for your object's mass, x and y velocity and its x and y resultant force components. When a force is added to the object, it would be easier to add it in terms of its x and y components ( if you wanted the force to be defined with an angle and magnitude you could use opposite = sin(angle) * hypotenuse and adjacent = cos(angle) * hypotenuse to split the force into components. Careful though -- the math module uses radians, and pygame uses degrees ) .
To apply the force on the screen, you can use force = mass * acceleration to find the x and y accelerations on the object, and apply those changes to the x and y velocities of the object (that's your x_change and y_change). Then apply the x and y velocities to the position of the object.
If you want it to work in terms of meters and seconds, instead of pixels and frames, you'll need to work out conversions for that as well: just use your clock.tick value to work out frames per second, and you can make up a value for pixels per meter.
As for gravity, the force applied = mass * gravitational field strength (the gravitational field strength on earth is 9.81 Newtons / kilogram ). Using this formula, you can add gravity to your object like any other force. The acceleration due to gravity is always the same as the gravitational field strength though, which might be useful if no other vertical forces are being applied.

Frame-independent movement issue in PyGame with Rect class

I'm writing a simple game with PyGame, and I've run into a problem getting things moving properly. I'm not experienced with game programming, so I'm not sure if my approach is even correct..
I have a class Ball, which extends PyGame's Sprite class. Each frame, the Ball object is supposed to move around the screen. I'm using frame-independent(?)/time-based movement with the time delta returned by PyGame's Clock object each time around. Such as,
class Ball(pygame.sprite.Sprite):
# . . .
def update(self, delta):
self.rect.x += delta * self.speed # speed is pixels per second
self.rect.y += delta * self.speed # ... or supposed to be, anyway
... and ...
class Game(object):
# . . .
def play(self):
self.clock = pygame.time.Clock()
while not self.done:
delta = self.clock.tick(self.fps) / 1000.0 # time is seconds
self.ball.update(delta)
PyGame's Sprite class is backed by a Rect object (rect) that tracks the sprite's position. And Rect's coordinates are automatically converted to integers whenever a value is updated. So, my problem is that each time update() is called, any extra fraction-of-a-pixel movement is lost, instead of being accumulated over time as it should. Speed in "pixels per second" isn't accurate this way. Even worse, if the amount of movement per frame is less than one pixel, the ball doesn't move at all for those frames, because it's always rounded down to zero. (i.e., no movement if pps < fps)
I'm not sure how to deal with this. I tried adding separate x and y values to Ball which aren't forced to be integers and updating the rect every time those change. That way the x and y values accumulate the fractions of a pixel as normal. As in,
class Ball(pygame.sprite.Sprite):
# . . .
def update(self, delta):
self.x += delta * self.speed
self.y += delta * self.speed
def getx(self):
return self._x
def setx(self, val):
self._x = val # this can be a float
self.rect.x = val # this is converted to int
def gety(self):
return self._y
def sety(self, val):
self._y = val # this can be a float
self.rect.y = val # this is converted to int
x = property(getx,setx)
y = property(gety,sety)
But that ends up being messy: it's easy to update the rect object when x and y change, but not the other way around. Not to mention the Rect object has lots of other useful coordinates that can be manipulated (like centerx, top, bottomleft, etc. -- which is why one would still want to move the rect directly). I'd more or less end up having to re-implement the whole Rect class in Ball, to store floats before it passes them down to the rect object, or else do everything based on just x and y, sacrificing some of the convenience of the Rect class either way.
Is there a smarter way to handle this?
I don't know if you still need the answer, but I figured this out a bit ago and thought I'd give this question an answer since I came across it in trying to figure out what was going on. (Thanks for helping me confirm the issue, by the way.)
You can make use of Python's round function. Pretty much, just throw what you want into that function, and it'll spit out a properly rounded number.
The first way I got it working was like this:
x = box['rect'].x
if leftDown:
x -= 300 * deltaTime
if rightDown:
x += 300 * deltaTime
box['rect'].x = round(x)
(Where my deltaTime is a float, being a fraction of a second.)
As long as you're putting the float value you get through the round function before applying it, that should do the trick. It doesn't have to use separate variables or anything.
It may be worth noting that you cannot draw in fractions of a pixel. Ever. If you're familiar with a Lite-Brite, pixels work in that way, being a single lit up light. That's why the number you use in the end has to be an integer.

Python - Pygame - Handling angles and speeds of animations

I'm learning Pygame, and like most people (I think) am writing a little game to get a handle on it. That being said, feel free to answer my questions as well as critique anything else if it sucks.
So, the issue is with my "boss" object. It's supposed to drop into the game from above, and then start firing a random number of shots in a 360deg circle. It works sometimes, but I am noticing a few things going on that I didn't expect. 1) The bullets should be moving at a constant speed, but they seem to slow down over time, and move faster along the Y plane than the X. 2) Despite there being minimum 8 shots, I often only see 3-4 (pretty sure some are overlapping as I often see one shot that looks a little bigger), and some will shoot down and to the right and one will shoot in the opposite direction. It shouldn't do that. So, here's the code. I'll post the shooting calculations, and then the bullet (Fireball) instance. The rest is pretty typical from what I've seen. I update and draw by calling the sprite group they're in, which are called at the bottom of my main loop.
def shoot (self, shots, time_passed):
angle = (math.pi*2)/shots
for i in xrange(shots):
bullet = Fireball("fireball.png", self.direction, 100)
bullet.angle = angle
bullet.pos = ((math.sin(bullet.angle) * bullet.speed) * time_passed,
(math.cos(bullet.angle) * bullet.speed) * time_passed)
Fireball.container.remove(bullet)
EnemyFireball.container.add(bullet)
bullet.rect.center = self.rect.center
angle += angle
And this is the bullet:
class Fireball (pygame.sprite.Sprite):
container = pygame.sprite.Group()
def __init__ (self, image, direction, speed):
pygame.sprite.Sprite.__init__ (self, self.container)
self.image = pygame.image.load(image).convert_alpha()
self.rect = self.image.get_rect()
self.radias = ((self.rect.width/2 + self.rect.height/2)/2)
self.speed = speed
self.direction = direction
self.angle = -1
def update (self, time_passed):
if self.angle != -1:
self.rect.move_ip(self.pos)
else:
self.rect.move_ip(0, (self.speed*time_passed) * self.direction)
if self.rect.bottom < 0: self.kill()
if self.rect.top > 2000: self.kill()
If I may draw a picture... 0 <-- boss, and lines for bullets.
What I expect:
\|/
-0-
/|\
What I am seeing:
\
0-
|\
... and the bullet down straight down always looks larger, so I think there's some overlapping, but I can't see why. Time_passed is just the time calculated between Clock.tick(60), and the shots argument is a randint between 8-16.
I hope that all makes sense. If not, let me know. I'll try to clarify. Thanks in advance for the help.
Here's a link to the the source if more context is needed. Don't worry, there's not much. http://code.google.com/p/scroller-practice/source/browse/
you need to keep angle in two variables.
The right way to do it would be
curr_angle = 0
angle_step = (math.pi*2)/shots
and then at the end of each loop
curr_angle += angle_step
the way you do it, you end up with angles angle, then 2*angle, then 4*angle and so forth.
if you take away all the other parts and just have a loop that is
angle = (math.pi*2)/shots
for i in xrange(shots):
angle += angle
it should be clear that you're doubling angle each time instead of incrementing it.

Categories

Resources