Pygame making an object chase the cursor - python

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.

Related

How to make a cue ball follow an equation in a pool game

I am writing a python project for a pool game where rather than using a cue stick to hit a ball straight, the user can input a math equation (This will allow the use of exponential absolute and trigonometry etc. For example: y = x, y = exp(x), y = sin(x) etc) which the cue ball will follow. However, I am not sure how to move the cue ball object along the equation of a line.
The cue ball will have a fixed velocity and friction. I have thought about using a graph and centering the origins of the graph to the balls x and y coordinates when the velocity of the cue ball is equal to 0.
The game is written mostly in Pygame, apart form the equation input box where a new window has been created in Tkinter.
If anyone has any knowledge of useful modules for using equations (Rather than just representing data or equations on a graph), that will help.
import pygame
from config import *
class Cueball:
def __init__(self, x, y):
self.x = x
self.y = y
self.velocity = pygame.math.Vector2(0,0)
self.image = pygame.image.load(path.join(CUEBALL_FOLDER, "Cueball.png"))
Here is an example where you can type in your equation and the ball position is updated. It uses the Python built-in eval() function. This function should only be used when you can trust your input. There is some very rudimentary filtering but it should not be relied upon to prevent malicious activity:
import pygame
from math import sin, tan, cos
def move(pos, eq, width, height):
"""Eval() the equation to return a new position"""
x = pos[0] + 1
if x >= width:
x = 0 # reset X position
try:
y = eval(eq[3:]) # skip "y ="
if type(y) not in (int, float):
raise Exception("Unexpected eval() return")
except:
x, y = pos # don't move if equation is invalid
if abs(y) > (height // 2): # reset position when off the screen
return 0, 0
return x, y
pygame.init()
# grab the first installed font
sys_font = pygame.font.SysFont(None, 37)
clock = pygame.time.Clock()
width, height = 320, 240
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Movement Equation")
x, y = 0, 0
eq = "y = x" # initial equation
done = False
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.TEXTINPUT:
# very limited input filtering
if event.text in "0123456789 +-/*x().sintaco":
eq += event.text
elif event.type == pygame.KEYUP:
if event.key == pygame.K_BACKSPACE:
eq = eq[:-1]
elif event.key == pygame.K_ESCAPE:
x, y = 0, 0 # reset position
# move the ball position
x, y = move((x, y), eq, width, height)
# Update text
text = sys_font.render(eq, True, pygame.Color("turquoise"))
# Graphics
screen.fill(pygame.Color("black"))
# Draw Text in the center
screen.blit(text, text.get_rect(center=screen.get_rect().center))
# Draw the ball (shift y axis so zero is in the center)
pygame.draw.circle(screen, "red", (x, height // 2 - y), 10)
# Update Screen
pygame.display.update()
clock.tick(30)
pygame.quit()
Your question states that the ball will have a fixed velocity and friction, so I don't understand how trigonometric functions apply. It sounds like you should be using Vector2 as you indicate, but you also want to follow an equation, so that's what I've done. Perhaps it'll help you develop your own approach. If there's any take away, it could be to remove your Tkinter dependency.
You can type to add to the equation, Backspace removes the last character and Esc will reset the ball position to the origin. Once the ball moves outside of the screen, it will reset its position.

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
)

Pendulum simulation

I am trying to write simple pendulum simulation in Pygame. The point is that I am trying to simulate directly the forces on the pendulum (gravity and tension) rather than solving the differential equation that describes the motion. First I wrote a function that get a vector, rotate the axis-system by some angle, and return this vector's components in the new, rotated axis-system; the code of this function is fine and it works as expected.
Each tick of the simulation I rotate the gravity vector by the angle between the pendulum and the rope, and get the new components - one in the direction of the rope, and one is orthogonal to it. the tension the and component in the direction of the rope cancelling each other, so only the orthogonal component is important. After I calculate it, I rotate the acceleration vector back to the normal coordinates system, and integrate. However, the resulting behavior is not as intended. What can be the reason?
This is the code:
from __future__ import division
import copy
import pygame
import random
import math
import numpy as np
import time
clock = pygame.time.Clock()
pygame.init()
size = (width, height) = (600,500)
screen = pygame.display.set_mode(size)
def rotate(vector,theta):
#rotate the vector by theta radians around the x-axis
Vx,Vy = vector[0],vector[1]
cos,sin = math.cos(theta),math.sin(theta)
newX,newY = Vx*cos-Vy*sin, Vy*cos+Vx*sin #the newX axis is the result of rotating x axis by theta
return [newX,newY]
class pendulum:
def __init__(self,x,y,x0,y0):
self.x = x
self.y = y
self.x0 = x0
self.y0 = y0
self.velocity = [0,0]
self.a= [0,0]
self.angle = 0
def CalcForce(self):
self.angle = math.atan2(-(self.y-self.y0),self.x-self.x0)
gravity = rotate(g,self.angle)
self.a[1]=gravity[1]
self.a[0] = 0 #This component is cancelled by the tension
self.a = rotate(self.a,-self.angle)
def move(self):
#print pylab.dot(self.velocity,[self.x-self.x0,self.y-self.y0])
self.velocity[0]+=self.a[0]
self.velocity[1]+=self.a[1]
self.x+=self.velocity[0]
self.y+=self.velocity[1]
def draw(self):
pygame.draw.circle(screen, (0,0,0), (self.x0,self.y0), 5)
pygame.draw.line(screen, (0,0,0), (self.x0,self.y0), (int(self.x), int(self.y)),3)
pygame.draw.circle(screen, (0,0,255), (int(self.x),int(self.y)), 14,0)
g = [0,0.4]
p = pendulum(350,100,300,20)
while 1:
screen.fill((255,255,255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
p.CalcForce()
p.move()
p.draw()
clock.tick(60)
pygame.display.flip()
Thank you.
There are a bunch of problems here. I'll fix a few and leave a few for you.
What I fixed was: 1) since you've imported numpy, you should use it, and write things in terms of the vectors; 2) it's an unreasonable demand on yourself to write everything and have it work immediately; so you need to plot intermediate results, etc, like here I plot a as well, so you can see whether it makes sense; 3) your whole "rotation" approach is confusing; instead think of component parts; which I calculate here directly (it's shorter, easier to read and understand, etc); 4) in all simulations where you use a time step, you should explicitly use dt so you can change the timestep without changing other parameters.
Now if you watch it you can see it looks almost reasonable. Notice though that the acceleration never goes upward, so the ball just falls while it oscillates. The reason for this is that you did not include the tension of the rope into the forces on the ball. I'll leave that part to you.
import pygame
import math
import numpy as np
clock = pygame.time.Clock()
pygame.init()
size = (width, height) = (600,500)
screen = pygame.display.set_mode(size)
class pendulum:
def __init__(self,x,y,x0,y0):
self.x0 = np.array((x0, y0))
self.x = np.array((x, y), dtype=float)
self.v = np.zeros((2,), dtype=float)
self.a = np.zeros((2,), dtype=float)
def CalcForce(self):
dx = self.x0 - self.x
angle = math.atan2(-dx[0], dx[1])
a = g[1]*math.sin(angle) # tangential accelation due to gravity
self.a[0] = at*math.cos(angle)
self.a[1] = at*math.sin(angle)
def move(self):
#print np.dot(self.a, self.x-self.x0) #is a perp to string?
self.x += dt*self.v
self.v += dt*self.a
def draw(self):
pygame.draw.circle(screen, (0,0,0), self.x0, 5)
pygame.draw.line(screen, (0,0,0), self.x0, self.x.astype(int),3)
pygame.draw.circle(screen, (0,0,255), self.x.astype(int), 14,0)
pygame.draw.line(screen, (255, 0, 0), (self.x+200*self.a).astype(int), self.x.astype(int), 4)
dt = .001
g = [0,0.4]
p = pendulum(350,100,300,20)
while 1:
screen.fill((255,255,255))
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
for i in range(100): # don't plot every timestep
p.CalcForce()
p.move()
p.draw()
clock.tick(60)
pygame.display.flip()
If you want to do a simulation, I think you're doing it the hard way. I'd start with the equation of motion, see Equation 20, here. The dots mean differentiate with respect to time---so the equation is d^2/dt^2 \theta = ... Then you should implement a finite differences scheme in the time direction, and step through time. At each step (label with i), you can calculate the x and y coordinates of the bob, based on the length of the pendulum and \theta_i. Check out the wiki article on finite differences.

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

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.

Categories

Resources