As for my previous question, I'm trying to simulate a 3d space in pygame. So far, I came up with a very simple idea that uses the third coordenate as a denominator to 'compress' (pretty sure there's some terminology here I'm not aware of) the furthest points around the center of the screen and reduce their sizes.
Could anyone suggest a simple improvement to this idea? I feel like I can just tune that denominator used for the projection (see the code) to create a way more accurate simulation.
If you run the code bellow you'll have a not-that-bad simulation of (let's say) a spaceship passing by some stars (pressing w or s). They dissapear if they get to far and a new one is created after that. But, if I apply a rotation (a or d), it becomes obvious that the simulation is not doing well, as I'm not really projecting the 3d points onto the 2d screen.
import pygame
import random
import numpy as np
pygame.init()
run=True
#screensize
screensize = (width,height)=(600,600)
center=(int(width/2),int(height/2))
screen = pygame.display.set_mode(screensize)
#delta mov
ds=0.1
do=0.0001
#Stars
points=[]
for i in range(1000):
n1 = random.randrange(-5000,5000)
n2 = random.randrange(-5000,5000)
n3 = random.randrange(-30,30)
points.append([n1,n2,n3])
while run:
pygame.time.delay(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
################## keys
keys=pygame.key.get_pressed()
if keys[pygame.K_w]:
for p in points:
p[2]-=ds
if keys[pygame.K_s]:
for p in points:
p[2]+=ds
if keys[pygame.K_a] or keys[pygame.K_d]:
if keys[pygame.K_a]:
for p in points:
p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]
else:
for p in points:
p[0]=np.cos(do)*p[0]-np.sin(do)*p[2]
p[2]=np.sin(do)*p[0]+np.cos(do)*p[2]
###############################projection###################
for p in points:
#this is to create new stars
if p[2]<=-30 or p[2]>=30:
p[0] = random.randrange(-5000,5000)
p[1] = random.randrange(-5000,5000)
p[2] =30
else:
#this is to ignore stars which are behind the ship
if p[2]<=0:
pass
else:
try:
#THIS IS THE PROJECTION I USE, I TAKE THE RADIUS BECAUSE I GUESS I'LL NEED IT... BUT I DON'T USE IT XD
r = ((p[0]**2+p[1]**2+p[2]**2)**(1/2))
pygame.draw.circle(screen,(255,255,0),(int(p[0]/p[2]+center[0]),int(p[1]/p[2]+center[1])),int(10/p[2]))
#this is to prevent division by cero and alike
except Exception as e:
pass
pygame.display.update()
screen.fill((0,0,0))
pygame.quit()
In general perspective is achieved by Homogeneous coordinates. Your approach is close to that.
I recommend to operate Cartesian coordinates, where the 3 dimensions have the same scale.
Emulate a Perspective projection when you draw the points.
This means you've to calculate the w component of the Homogeneous coordinates dependent on the depth (z coordiante) of the point (e.g. w = p[2] * 30 / 5000) and to perform a "perspective divide" of the x, y and z components by the w component, before you draw the points. e.g:
#delta mov
ds=10
do=0.01
#Stars
points=[]
for i in range(1000):
n1 = random.randrange(-5000,5000)
n2 = random.randrange(-5000,5000)
n3 = random.randrange(-5000,5000)
points.append([n1,n2,n3])
while run:
pygame.time.delay(20)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run=False
################## keys
keys=pygame.key.get_pressed()
if keys[pygame.K_w]:
for p in points:
p[2]-=ds
if keys[pygame.K_s]:
for p in points:
p[2]+=ds
if keys[pygame.K_a] or keys[pygame.K_d]:
if keys[pygame.K_a]:
for p in points:
p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]
else:
for p in points:
p[0], p[2] = np.cos(do)*p[0]-np.sin(do)*p[2], np.sin(do)*p[0]+np.cos(do)*p[2]
###############################projection###################
screen.fill((0,0,0))
for p in points:
#this is to create new stars
if p[2]<=-5000 or p[2]>=5000:
p[0], p[1], p[2] = random.randrange(-5000,5000), random.randrange(-5000,5000), 5000
else:
#this is to ignore stars which are behind the ship
if p[2]<=0:
pass
else:
w = p[2] * 30 / 5000
pygame.draw.circle(screen,(255,255,0),(int(p[0]/w+center[0]),int(p[1]/w+center[1])),int(10/w))
pygame.display.update()
Furthermore, the rotation is not correct. When you do
p[0]=np.cos(-do)*p[0]-np.sin(-do)*p[2]
p[2]=np.sin(-do)*p[0]+np.cos(-do)*p[2]
the p[0] is changed in the first line, but the original value should be used in the 2nd line.
Do "tuple" assignment to solve the issue:
p[0], p[2] = np.cos(-do)*p[0]-np.sin(-do)*p[2], np.sin(-do)*p[0]+np.cos(-do)*p[2]
Related
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.
I wrote a program to model a phenomenon called diffusion limited aggregation, using the random motion of squares in pygame. The idea is that there is a stem and every particle (square) that touches it sticks to it and becomes part of the stem.
The code seems to work, but after like 30 seconds to a minute it starts to slow down quite a bit. I cannot figure out why.
import pygame
import random
#changing the row number will change the size of the squares, and bassically the size of the invisible 'array'
width = 1000
rows = 500
d = width//rows
e = {}
squares = []
accreted = []
#note: all positions are noted in array-like notation (a matrix of dimensions rows x rows)
#to convert it back to normal notation, do (square.position[0] * d, square.position[1] * d)
class square:
def __init__(self, position):
self.position = (position)
#method to move a square in a random direction (no diagonal) (brownian motion)
def move(self):
a = random.randint(0, 3)
if a == 0:
new_posi = (self.position[0] + 1)
new_posj = (self.position[1])
elif a == 1:
new_posi = (self.position[0] - 1)
new_posj = (self.position[1])
elif a == 2:
new_posi = (self.position[0])
new_posj = (self.position[1] + 1)
else:
new_posi = (self.position[0])
new_posj = (self.position[1] - 1)
if new_posj<0 or new_posi<0 or new_posi>rows or new_posj>rows:
self.move()
else:
self.position = (new_posi, new_posj)
pygame.draw.rect(win, (255, 255, 255), [new_posi * d, new_posj * d, d, d])
def accrete(square):
accreted.append(square)
if square in squares:
squares.remove(square)
def redrawWindow(win):
win.fill((0, 0, 0))
pygame.draw.rect(win, (255, 255, 255), [stem.position[0] * d, stem.position[1] * d, d, d])
for square in squares:
square.move()
# here we make it so that every square that touches the stem stops moving, then a square that touches this square stops moving, etc.
for accret in accreted:
if square.position[1] == accret.position[1]+1 and square.position[0] == accret.position[0]:
accrete(square)
elif square.position[1] == accret.position[1]-1 and square.position[0] == accret.position[0]:
accrete(square)
elif square.position[1] == accret.position[1] and square.position[0] == accret.position[0]+1:
accrete(square)
elif square.position[1] == accret.position[1] and square.position[0] == accret.position[0]-1:
accrete(square)
for accret in accreted:
pygame.draw.rect(win, (255, 255, 255), [accret.position[0] * d, accret.position[1] * d, d, d])
pygame.display.update()
def main():
global win
win = pygame.display.set_mode((width, width))
clock = pygame.time.Clock()
while True:
# pygame.time.delay(5)
# clock.tick(64)
redrawWindow(win)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
#by changing the range here, we change how many squares are created
for i in range(5000):
e["accreted{0}".format(i)] = square((random.randint(0, rows), random.randint(0, rows)))
squares.append(e["accreted{0}".format(i)])
#a stem to start accretion from
stem = square((rows/2, rows/2))
accrete(stem)
main()
I've watched your simulation run for a while on my MacBook Pro. It seems that on my system, it takes quite a bit longer than a minute...maybe more like 5 or so...before it starts to quite obviously slow down. But it does nonetheless.
I think the problem is that you are building this "accreted" structure during your simulation, and due to this, the number of "accreted" squares (those stored in the accreted list) keeps increasing. For each iteration of your program, your code needs to compare the position of each live square against the position of each "accreted" square. So over time, the number of comparisons you have to do continues to grow.
You likely need to find a way to optimize your algorithm if you want to be able to maintain the update speed (the frame rate) as the sim progresses. You need to figure out how to be smarter about performing your comparisons to somehow avoid this geometric progression in iteration time that occurs as the structure you're building grows.
UPDATE AND POSSIBLE OPTIMIZATION: I see a pretty simple optimization that you could add to your code to greatly speed it up. What you can do is maintain a bounding box around your "accreted" squares. When you add a new square to that list, you increase the size of the bounding box, if necessary, so that it contains the new square. Now, when you first check for a collision between a live square and an the list of accreted squares, you can first check for if the live square is within the bounding box of the accreted squares (with a little extra margin as appropriate) before testing for a collision between that square and any one accreted squares. This would let you immediately rule out collisions between most of the live squares and the accreted squares with just one collision test for each live square. This should have the effect of allowing your code to stay about as fast in later rounds as it is in the early rounds, since most of the live squares will always be trivially rejected as collision candidates regardless who big the accreted structure gets.
UPDATE 2: What I describe is definitely what's going on with your code. I added a little code to count the number of collision tests you perform in each round of your sim. Here's the number of tests being performed at one second intervals, along with how long one iteration of your sim is taking, in seconds:
0 5000 0.023629821000000106
1 9998 0.023406135000000106
2 24980 0.03102543400000002
...
30 99680 0.07482247300000111
31 99680 0.08382184299999551
...
59 114563 0.08984024400000123
60 114563 0.087317634999998
The first iteration of your code does 5000 hit tests, as expected, and a single iteration of your sim takes about .023 seconds. After a minute, your sim is having to do more than 20 times as many tests per iteration, 114563, and now a single iteration is taking .087 seconds. This problem just keeps growing, and your code just keeps slowing down. (It's interesting to note that most of the "progress" of your sim up to one minute has occurred in the first 30 seconds. For this run, only 3 accretes occurred in the second 30 second interval.)
The first and probably best optimization is to keep a list of "forbidden" squares that will trigger the freeze of a particle instead of iterating over all positions in accreted multiple times.
So e.g. when we start with the first stem particle (or square, or whatever you call it), we also store the positions above, beneath, left and right of that particle's position in a set. It's important to use a set because looking up items in a set is much, much faster that using a list.
After moving a square, we now check if that square's new position is in this set. If it is, we add all adjacent positions to that set, also.
The next thing we can improve is the list of squares itself. Instead of removing a freezed square from the list, we create a new list every frame and add all squares that are not freezed this turn. (Your code is actually buggy by not making a copy of the list of squares and iterating over that copy while removing items from the original list) I'll use a deque since adding to it is slightly faster that a regular list.
Another bottleneck is the huge amount of random numbers you create each frame. random.randint() becomes painfully slow. We could create a list of random numbers at the start of the script and use that so we don't have to create new random numbers while running.
We could also change the drawing. Using pygame.draw.rect 5000 times is also quite slow. Let's create a surface and blit it with pygame's new batch function Surface.blits (I guess using pygame.surfarray to manipulate the screen surface directly would be even faster).
In the code below I also implemented the bounding box suggested by CryptoFool, because why not, but the biggest speed up is using a set as I described above.
With these changes, I get ~200 FPS without any slowdown over time:
import pygame
import numpy as np
import random
from collections import deque
def get_rand_factory():
length = 100000000
sample = np.random.randint(1, 5, length).tolist()
index = -1
def inner():
nonlocal index
index += 1
if index == length:
index = 0
return sample[index]
return inner
get_rand = get_rand_factory()
def move(x, y, rows):
dx, dy = x, y
a = get_rand()
if a == 1: dx += 1
elif a == 2: dx -= 1
elif a == 3: dy += 1
else: dy -= 1
if dx<0 or dy<0 or dx>rows or dy>rows:
return move(x, y, rows)
return dx, dy
def get_adjacent(x, y):
for dx, dy in (1, 0), (-1, 0), (0, 1), (0, -1):
yield x + dx, y + dy
def get_bounds(positions):
min_x, min_y, max_x, max_y = 9999, 9999, 0, 0
for x, y in positions:
min_x = min(min_x, x)
min_y = min(min_y, y)
max_x = max(max_x, x)
max_y = max(max_y, y)
return min_x, min_y, max_x, max_y
def main():
width = 1000
rows = 500
d = width//rows
squares = deque()
accreted = set()
adjacent_accreted = set()
win = pygame.display.set_mode((width, width))
clock = pygame.time.Clock()
for i in range(5000):
pos = (random.randint(0, rows), random.randint(0, rows))
squares.append(pos)
stem = (rows/2, rows/2)
accreted.add(stem)
adjacent_accreted.add(stem)
for adj in get_adjacent(*stem):
adjacent_accreted.add(adj)
rect_white = pygame.Surface((d, d))
rect_white.fill('white')
rect_blue = pygame.Surface((d, d))
rect_blue.fill((255, 0, 255))
bounds = get_bounds(adjacent_accreted)
min_x, min_y, max_x, max_y = bounds
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
win.fill((0, 0, 0))
new_state = deque()
for x, y in squares:
die = False
new_pos = move(x, y, rows)
if min_x <= new_pos[0] <= max_x and min_y <= new_pos[1] <= max_y:
if new_pos in adjacent_accreted:
accreted.add(new_pos)
adjacent_accreted.add(new_pos)
for adj in get_adjacent(*new_pos):
adjacent_accreted.add(adj)
die = True
bounds = get_bounds(adjacent_accreted)
min_x, min_y, max_x, max_y = bounds
if not die:
new_state.append(new_pos)
squares = new_state
win.blits(blit_sequence=((rect_blue, (pos[0]*d, pos[1]*d)) for pos in accreted))
win.blits(blit_sequence=((rect_white, (pos[0]*d, pos[1]*d)) for pos in squares))
pygame.draw.rect(win, (0, 255, 255), [bounds[0] * d, bounds[1] * d, (bounds[2]-bounds[0]) * d, (bounds[3]-bounds[1]) * d], 1)
pygame.display.update()
pygame.display.set_caption(f'{clock.get_fps():.2f} {len(squares)=} {len(accreted)=}')
clock.tick()
main()
Have a look at your code:
def accrete(square):
accreted.append(square)
if square in squares:
squares.remove(square)
squares is a list containing up to 5000 items. Searching for any content needs up to 5000 comparisons because there is no index and all items have to be checked until it is found in the list. Use a set instead if the entries are unique and the order does not care. A set in indexed and searching for an item runs very fast.
I am making a minimal Doom-style FPS game engine using Python, PyGame and Legacy PyOpenGL. I hope that the player will be able to look around in four directions - forward, backwards, left and right - using glRotatef() by pressing the left and right arrow keys.
A few problems have arisen:
A gun (a cube with a texture applied that changes the texture coordinates depending on the direction the player is facing) that should always appear 0.5 units ahead of the camera in the corresponding x and z position depending on the angle glRotatef() sets it to face towards, is moving to a strange position if I move on the x axis and then look left unless I stand dead centre in the room. The cube also appears to be static when I move left and right even though I am supplying it the x value I obtained from glGetDoublev(), and when I move forward the gun appears to be scaling even though I never implemented such functionality.
When I call
if event.type == pygame.KEYDOWN: # key pressed events
if event.key == pygame.K_LEFT:
glRotatef(-90,0,1,0)
if direction == 0:
direction = 3
else:
direction -= 1
to look to the left of the room, I occasionally get moved inside the wall and this sometimes affects the gun's position further.
I've tried adding fixed x and z variables (x_steps and z_steps) that are incremented by 0.1 every time the player moves. I'm not particularly sure why that removes the "static gun" problem but it did. However, when I rotated the camera, the same problem (of the gun moving to a strange position) still occurred.
## pygame/opengl initialisation code
def main():
pygame.init()
display = (800,600)
global displaySurface
displaySurface = pygame.display.set_mode(display, DOUBLEBUF|OPENGL)
pygame.display.set_caption("Wolfenstein 4D")
glEnable(GL_TEXTURE_2D)
glEnable(GL_DEPTH_TEST)
gluPerspective(45, (display[0]/display[1]),0.1,50.0)
## game loop, obtaining x,y,z positions and looking around the room
def room1():
direction = 3 ## 3 = forward, 2 = left, 1 = backward, 0 = right
while True:
pos = glGetDoublev(GL_MODELVIEW_MATRIX)
x = pos[3][0]
y = pos[3][1]
z = pos[3][2]
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
quit()
if event.type == pygame.KEYDOWN: # key pressed events
if event.key == pygame.K_LEFT:
glRotatef(-90,0,1,0)
if direction == 0:
direction = 3
else:
direction -= 1
x_steps = 0
z_steps = 0
if event.key == pygame.K_RIGHT:
glRotatef(90,0,1,0)
if direction == 3:
direction = 0
else:
direction += 1
x_steps = 0
z_steps = 0
## movement code
spd = 0.1
keys = pygame.key.get_pressed()
if direction == 3:
if keys[pygame.K_a]:
glTranslatef(spd,0,0)
x_steps -= spd
if keys[pygame.K_d]:
glTranslatef(-spd,0,0)
x_steps += spd
if keys[pygame.K_w]:
glTranslatef(0,0,spd)
z_steps -= spd
if keys[pygame.K_s]:
glTranslatef(0,0,-spd)
z_steps += spd
if direction == 2:
if keys[pygame.K_a]:
glTranslatef(0,0,-spd)
x_steps += spd
if keys[pygame.K_d]:
glTranslatef(0,0,spd)
x_steps -= spd
if keys[pygame.K_w]:
glTranslatef(spd,0,0)
z_steps -= spd
if keys[pygame.K_s]:
glTranslatef(-spd,0,0)
z_steps += spd
## gun drawing code in game loop
if direction == 3:
loadTexture("gun1.png")
drawHUDGun(x,-0.1,z-0.5,3,0.1,0.1,0.1)
if direction == 2:
loadTexture("gun.png")
drawHUDGun(z-0.5,-0.1,x+0.5,2,0.1,0.1,0.1)
## gun drawing function
def drawHUDGun(x,y,z,angle,width,height,depth=0.5,color = ((1,1,1))):
vertices = (
(width+x,-height+y,-depth+z),
(width+x,height+y,-depth+z),
(-width+x,height+y,-depth+z),
(-width+x,-height+y,-depth+z),
(width+x,-height+y,depth+z),
(width+x,height+y,depth+z),
(-width+x,-height+y,depth+z),
(-width+x,height+y,depth+z)
)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glBegin(GL_QUADS)
if angle == 3:
j = 0
if angle == 2:
j = 8
i = 0
for surface in surfaces:
i += 1
for vertex in surface:
glColor4f(1,1,1,1)
setTexCoord(0, texCoords, j)
if angle == 3:
if i >= 0 and i < 4:
if j < 4:
j += 1
if angle == 2:
if i == 2:
if j < 12:
j += 1
glVertex3fv(vertices[vertex])
glEnd()
glDisable(GL_BLEND)
glBegin(GL_LINES)
for edge in edges:
glColor3fv((0,1,0))
for vertex in edge:
glVertex3fv(vertices[vertex])
glEnd()
## implementation of the functions
main()
room1()
I expect the gun to appear 0.5 units ahead of the player in any direction regardless of where they are in the room, but the gun is often out of view due to being assigned incorrect x or z co-ordinates.
In legacy OpenGL there exists different current matrices. The current matrix which is affected by matrix operations can be chosen by glMatrixMode. Each matrix is organized on a stack. Matrices can be pushed and popped by glPushMatrix/glPopMatrix.
The projection matrix should be placed set to the projection matrix stack, the view and model transformations to the modelview matrix stack:
// choose projection matrix stack
glMatrixMode(GL_PROJECTION)
gluPerspective(45, (display[0]/display[1]),0.1,50.0)
// choose modelview matrix stack, for the following matrix operations
glMatrixMode(GL_MODELVIEW)
The gun should be placed in a first person view ("in front of you"). The esiest wy to achieve this is to draw the gun in viewspace, this means you've to cancel all the previous transformations to the modelview matrix. The matrix can be replace with the identity matrix by glLoadIdentity.
Save the modelview matrix onto the stack, set the identity matrix, draw the gun and finally restore the modelview matrix.e.g:
glPushMatrix()
glLoadIdentity()
drawHUDGun(x,-0.1,z-0.5,3,0.1,0.1,0.1)
glPopMatrix()
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.
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.