Pygame - making a sprite move in the direction it is facing - python

I'm making a top down car racing game and I want to make the car rotate when you press the left and right keys (I’ve already done that part), the sprite's rotation is stored in a variable as degrees. I'd like to be able to make it move according to acceleration in the direction it is facing. I can figure out the acceleration part myself, it's just figuring out what pixel exactly is in that direction. Can anyone give me some simple code to help with this?
Here are the contents of the class that are relevant:
def __init__(self, groups):
super(Car, self).__init__(groups)
self.originalImage = pygame.image.load(os.path.join("Data", "Images", "Car.png")) #TODO Make dynamic
self.originalImage.set_colorkey((0,255,0))
self.image = self.originalImage.copy() # The variable that is changed whenever the car is rotated.
self.originalRect = self.originalImage.get_rect() # This rect is ONLY for width and height, the x and y NEVER change from 0!
self.rect = self.originalRect.copy() # This is the rect used to represent the actual rect of the image, it is used for the x and y of the image that is blitted.
self.velocity = 0 # Current velocity in pixels per second
self.acceleration = 1 # Pixels per second (Also applies as so called deceleration AKA friction)
self.topSpeed = 30 # Max speed in pixels per second
self.rotation = 0 # In degrees
self.turnRate = 5 # In degrees per second
self.moving = 0 # If 1: moving forward, if 0: stopping, if -1: moving backward
self.centerRect = None
def update(self, lastFrame):
if self.rotation >= 360: self.rotation = 0
elif self.rotation < 0: self.rotation += 360
if self.rotation > 0:
self.image = pygame.transform.rotate(self.originalImage.copy(), self.rotation)
self.rect.size = self.image.get_rect().size
self.center() # Attempt to center on the last used rect
if self.moving == 1:
self.velocity += self.acceleration #TODO make time based
if self.velocity > self.topSpeed: self.velocity = self.topSpeed # Cap the velocity

Trigonometry: The formula to get your coordinate is:
# cos and sin require radians
x = cos(radians) * offset
y = sin(radians) * offset
You use velocity for offset. (This means a negative velocity will drive backwards).
so:
def rad_to_offset(radians, offset): # insert better func name.
x = cos(radians) * offset
y = sin(radians) * offset
return [x, y]
loop_update is something like:
# vel += accel
# pos += rad_to_offset( self.rotation, vel )
math.cos, math.sin: uses radians, so
storing rotations as radians is simpler. If you want to define speed / etc as degrees, you still can.
# store radians, but define as degrees
car.rotation_accel = radians(45)
car.rotation_max_accel = radians(90)

I can't really do better than pointing you to this tutorial (*). In particular, the first part explains how to do rotation and make the sprites move in certain directions.
(*) Shameless plug :-) but very relevant to the question.

Related

How can i make my randomly moving object move in a specific angle in pygame

Full Code
self.VEL = 3
self.RAND = numpy.linspace(-self.VEL, +self.VEL, 100) # calculating value between -2 and 2, a total of 100 values
-----------------------------------------
if self.new_line:
self.deltax = random.choice(self.RAND)
self.deltay = random.choice(self.RAND)
self.new_line = False
self.move(self.deltax, self.deltay)
-----------------------------------------
def move(self, deltax, deltay):
self.x += deltax
self.y += deltay
I'm currently building an ant simulator in python with pygame. The ants start in the middle and move on from there randomly. The movement works like this. Every few milliseconds self.new_line is set to True. Then a new self.deltax and self.deltay are calculated. Now, as long as self.new_line is False my ant will move with self.x += deltax and self.y += deltay per iteration. When self.new_line is True again the ant will start a new line with new values for self.deltax and self.deltay. The values for deltax and deltay are between -2 and +2 meaning the ant could move in every angle.
Visualization
Questions:
However i want my ants only to move in a specific angle. Like This
How can i do this?
If you run the program with on ant you will see that the speed of the ant changes with new lines. How can make my ant to have one consistent speed?
How can i make my ant bounce of walls in a specific angle?
Thanks for your help.
These are multiple questions. The intent is to ask only one question. I will only answer 2 of them.
Use pygame.math.Vector2 and rotate() to generate a direction vector with constant length and angle in a specified range:
if self.new_line:
angle = random.randint(-45, 45)
direction_vector = pygame.math.Vector2(0, -self.VEL).rotate(angle)
self.deltax = direction_vector.x
self.deltay = direction_vector.y

How do I have circle bounce back up after gravity takes it down?

I have a ball and once it hits the floor, I want it to bounce back up to say half the original bounce, until it can't bounce anymore. I added a pass where the ball just stops at the bottom. How do I make it bounce back up?
kickoff_location = [WIDTH/2 - ball_size[0] / 2, 210]
gravity = 2
ball_bounce = False
if not(ball_bounce):
if kickoff_location[1] < player_one_pos[1] + (player_width / 2) + ball_size[0]:
kickoff_location[1] += gravity
elif kickoff_location[1] == player_one_pos[1] + (player_width / 2) + ball_size[0]:
kickoff_location[1] -= gravity / 2
pass
You need a speed vector for your ball, ie. vel = [0, 0] # [dX, dY]
You need simple gravity acceleration constant, ie. grav = 0.1
You need enviroment resistance constant, ie. resist = 0.8
You need mentioned elasticity factor, ie. elast = 0.5
Now, in every update cycle you:
update Y part of velocity by gravity: vel[1] += grav
update both velocity parts by resistance: vel[0] *= resist ; vel[1] *= resist
check for collisions, vertical and horizontal separately. If collision is vertical (floor/ceiling) reverse Y speed and modify it by elasticity: vel[1] = -vel[1] * elast. The same for horizontal collisions, but for X part of velocity.
update ball position by speed vector: ball_loc[0] += vel[0] ; ball_loc[1] += vel[1]
And tinker with constants to get realistic ball movement...

Pygame making an object chase the cursor

Been at this for the past few hours, trying to make a small program where an image chases the cursor around. So far I've managed to make it so that the image is directly on top of the cursor and follows it around that way. However what I need is for the image to actually "chase" the cursor, so it would need to initially be away from it then run after it until it's then on top of the mouse.
Basically hit a wall with whats going wrong and what to fix up, here's what I've gotten so far:
from __future__ import division
import pygame
import sys
import math
from pygame.locals import *
class Cat(object):
def __init__(self):
self.image = pygame.image.load('ball.png')
self.x = 1
self.y = 1
def draw(self, surface):
mosx = 0
mosy = 0
x,y = pygame.mouse.get_pos()
mosx = (x - self.x)
mosy = (y - self.y)
self.x = 0.9*self.x + mosx
self.y = 0.9*self.y + mosy
surface.blit(self.image, (self.x, self.y))
pygame.display.update()
pygame.init()
screen = pygame.display.set_mode((800,600))
cat = Cat()
Clock = pygame.time.Clock()
running = True
while running:
screen.fill((255,255,255))
cat.draw(screen)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
pygame.display.update()
Clock.tick(40)
Probably not in the best shape of coding, been messing with this for just over 5 hours now. Any help is much appreciated! Thanks :)
Assuming you want the cat to move at a fixed speed, like X pixels per tick, you need to pick a new position X pixels toward the mouse cursor. (If you instead want the cat to move slower the closer it gets, you'd instead pick a position a certain % of the way between the current position and the mouse cursor. If you want it to move faster the closer it gets, you need to divide instead of multiply. And so on. But let's stick with the simple one first.)
Now, how do you move X pixels toward the mouse cursor? The usual way of describing this is: You find the unit vector in the direction from the current position to the cursor, then multiply it by X, and that gives you the steps to add. And you can reduce that to nothing fancier than a square root:
# Vector from me to cursor
dx = cursor_x - me_x
dy = cursor_y - me_y
# Unit vector in the same direction
distance = math.sqrt(dx*dx + dy*dy)
dx /= distance
dy /= distance
# speed-pixel vector in the same direction
dx *= speed
dy *= speed
# And now we move:
me_x += dx
me_y += dy
Note that me_x and me_y are going to be floating-point numbers, not integers. That's a good thing; when you move 2 pixels northeast per step, that's 1.414 pixels north and 1.414 pixels east. If you round that down to 1 pixel each step, you're going to end up moving 41% slower when going diagonally than when going vertically, which would look kind of silly.

Working around a pygame trigonometry error

I'm making a pygame game designed so that bullets will shoot in the direction of the mouse. I'm using
a Class to define the bullets in a list like this:
class Bullet:
def __init__(self,pos,speed,size):
self.pos = pos
self.speed = speed
self.size = size
def move(self):
self.pos[0] = int(self.pos[0] + self.speed[0])
self.pos[1] = int(self.pos[1] + self.speed[1])
I'm using this trigonometry function to get the vector of the angle in which I'm going to be shooting bullets.
def getUnitVector(x1, y1, x2, y2):
delx = x2 - x1
dely = y2 - y1
m = math.sqrt(delx * delx + dely * dely)
unit = (delx / m, dely / m)
return unit
level = [
I'm not using angles because I have to work around a pygame rounding error.
these are the variables I'm plugging into the function.
mousex, mousey = pygame.mouse.get_pos()
startx = 50
starty = 400
aim = getUnitVector(startx, starty, mousex, mouse
This how i'm handling the aim and making the bullets shoot from the start x,y
if pygame.mouse.get_pressed()[0]:
if reload>10:
bx = BULLETSPEED * aim[0]
by = BULLETSPEED * aim[1]
bullet = Bullet([startx,starty], [bx,by],10)
bullets.append(bullet)
reload=0
reload = reload + 1
I just want to let you know. I'm working on a school assignment and I will be learning more in depth about vectors and trig next unit so I don't really want to spend too much time learning this stuff right now :L . Also if you know any active python forums that might be more helpful in answer this question please comment. I cant find any.
Thank you for your time.
I might just build a work-around by only allowing it to shoot if the mouse is within 20 pixels or something so the error is minimized.
The bullet class should have a rectangle attribute so that the bullet's direction of travel runs through the bullet's center.
Doing this would first require reading the Pygame documentation here: Pygame.Rect Docs
Centering the bullet rectangle on the direction of travel could be implemented in a movement method as such:
def move(self):
self.dir = self.get_direction(self.target) # get direction
if self.dir: # if there is a direction to move
self.trueX += (self.dir[0] * self.speed) # calculate speed from direction to move and speed constant
self.trueY += (self.dir[1] * self.speed)
self.rect.center = (round(self.trueX),round(self.trueY)) # apply values to bullet.rect.center
More information can be found in this helpful sprite movement example:Pygame Sprite Movement

Simple 2D gravity with pygame; Value Guidelines?

I am trying to simulate gravity in a simple 2D window using pygame. It's very simple stuff (a dot rising and falling again) and I understand the mechanics, i.e. speed as a vector and the y portion being consistently diminished by a value g representing gravity during each run through the mainloop and the subsequent update of the position of the dot.
It's all working fine, but I am having trouble choosing the correct values to be inserted. It is all trial and error at the moment. Is there a good rule of thumb on how to determine which numbers to use, to achieve a semi-realistic looking trajectory?
I inserted a minimal example below, with the following values important to my question:
window = (640, 480) # pixels
initial_speed = 20 # pixels per update along the y axis
gravity = 0.4 # deduction on initial_speed per update
Now, why do these numbers happen to make the illusion work? I tried first to use formulae I learnt in physics class years and years ago, but, with or without unit conversions, the simulation was just not right. Most of the time, I didn't even see the ball, yet the above values, found through trial and error work.
Thanks for all your help in advance. If you need more information, just post a comment and I will try to oblige.
Here is the minimal example. Note the vector2D library was conveniently borrowed from the pygame website (follow this link)
#!/usr/bin/env python
import pygame
from pygame.locals import *
from vector2D import Vec2d
pygame.init()
GRAVITY = 0.4
class Dot(pygame.sprite.Sprite):
def __init__(self, screen, img_file, init_position, init_direction, speed):
pygame.sprite.Sprite.__init__(self)
self.screen = screen
self.speed = Vec2d(speed)
self.base_image = pygame.image.load(img_file).convert_alpha()
self.image = self.base_image
# A vector specifying the Dot's position on the screen
self.pos = Vec2d(init_position)
# The direction is a normalized vector
self.direction = Vec2d(init_direction).normalized()
def blitme(self):
""" Blit the Dot onto the screen that was provided in
the constructor.
"""
self.screen.blit(self.image, self.pos)
def update(self):
self.speed.y -= GRAVITY
displacement = Vec2d(
self.direction.x * self.speed.x,
self.direction.y * self.speed.y
)
self.pos += displacement
def main():
DIMENSION = SCREEN_WIDTH, SCREEN_HEIGHT = 640, 480
BG_COLOUR = 0,0,0
# Creating the screen
window = screen = pygame.display.set_mode(
(SCREEN_WIDTH, SCREEN_HEIGHT), 0, 32)
screen = pygame.display.get_surface()
clock = pygame.time.Clock()
dot = Dot(screen, "my/path/to/dot.jpg", (180, SCREEN_HEIGHT),
(0, -1), (0, 20))
mainloop = True
while mainloop:
# Limit frame speed to 50 FPS
time_passed = clock.tick(50)
for event in pygame.event.get():
if event.type == pygame.QUIT:
mainloop = False
# Redraw the background
screen.fill(BG_COLOUR)
dot.update()
dot.blitme()
pygame.display.flip()
if __name__ == '__main__':
main()
def update(self):
self.speed.y -= GRAVITY
displacement = Vec2d(
self.direction.x * self.speed.x,
self.direction.y * self.speed.y
)
self.pos += displacement
You call Dot.update() 50 times every second. The equation you're using,
delta v_y = - g #* (1 second)
represents the change in velocity every second, but it gets called 50 times every second. This means your velocities will lose 50*GRAVITY m/s ever second and this is why you are forced to make your gravity so weak.
So, I would recommend adding time_passed as an argument for Dot.update, and change the speed changing statement to
def update(self, time_passed):
self.speed.y -= GRAVITY * time_passed/1000. #time converted from millisecond to seconds
This will make the units more intuitive. Switch GRAVITY back to 10 and use realistic velocities.
EDIT:
Additionally, the displacement vector also needs to contain time, or else the displacement will depend on FPS. If you have x frames per second, then each update occurs after t = 1000/x milliseconds. This is the amount of time Dot spends "moving" in each update. Think about how we approximate Dot's motion; it gets an update, calculates its new velocity and travels at that speed for t seconds. However, in your implementation, the displacement vector is independent of time. Change the update method to be something like:
def update(self, time):
time /= 1000.
self.speed.y -= GRAVITY * time
displacement = Vec2d(
self.direction.x * self.speed.x * time,
self.direction.y * self.speed.y * time
)
self.pos += displacement
Now, according to kinematics, we'll get something like
max height = v0**2 / 2g
so initial velocities of 10-20m/s will only produce max heights of 5-20m. If you don't want to work with velocities of 50-200m/s, then you could add a scaling factor in the displacement vector, something like
displacement = Vec2d(
self.direction.x * self.speed.x * time * scaling_factor,
self.direction.y * self.speed.y * time * scaling_factor
)

Categories

Resources