Smooth Jump over different FPS - python

I've created a game, but I have problems controlling the jump of the player. This code is a simplified version only showing the jump problem. Keep in mind that in the game itself the FPS may vary in mid jump. I've separated the jump code so it's the easier to identify, and also with the UP/DOWN arrows you can change the FPS to make tests.
Problem
The higher the FPS the smaller the jumps are, and the lower the FPS are the higher the jumps.
Expected Result
The jump reach the same height over many different FPS. Example: 30 and 120 FPS
Code
import pygame
Screen = pygame.display.set_mode((250,300))
Clock = pygame.time.Clock()
X = 50; Y = 250
FPS = 60; Current_FPS = FPS
Move = 480 / Current_FPS; Dir = "Up"
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if pygame.key.get_pressed()[pygame.K_UP]: FPS += 10 / Current_FPS
elif pygame.key.get_pressed()[pygame.K_DOWN] and FPS > 2: FPS -= 10 / Current_FPS
pygame.display.set_caption("FPS: "+str(int(FPS)))
Screen.fill((255,255,255))
X += 120 / Current_FPS
#---------------- JUMP CODE ---------------------#
if Dir == "Up":
if Move <= 0.0: Dir = "Down"
else:
Move -= 10 / Current_FPS
Y -= Move
else:
#RESET \/
if Y >= 250:
Dir = "Up"
X = 50; Y = 250
Move = 480 / Current_FPS; Dir = "Up"
#RESET /\
else:
Move += 120 / Current_FPS
Y += Move
#--------------------------------------------------#
pygame.draw.circle(Screen,(0,0,0),(int(X),int(Y)),5)
pygame.display.update()
Current_FPS = 1000.0 / Clock.tick_busy_loop(FPS)

You should set your initial jump velocity to be independent of frame rate, i.e:
Move = 480
Note that when you update the velocity (or in your case, it looks, speed) you do need to divide by the frame rate, since you are essentially multiplying by the time interval: v ~ u + a*dt. The same applies when updating the position, so this should be Y += Move / Current_FPS.
There are a couple of other things worth mentioning. Why do you track the direction variable? Why not just have your variable Move be the velocity. That way you would just need: Y += Move / Current_FPS and have positive/negative values indicate the direction. Also, your gravity is currently a lot stronger on the way down than on the way up, but this could be entirely deliberate!

Related

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...

Problems with creating a UI and glRotatef()

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

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.

Dual Keyboards/Joysticks in pygame [duplicate]

I am trying to create a project that I have made before but apart from this time I am going to be using a play-station one remote I was given for free from my school. The problem that when I move the joystick upwards and downwards it shows the same coordinates.(If you do not understand what I mean then look at the picture below). Then also I am not sure what I wold need to put into the if statement so that it checks if the joystick is upwards or downwards. I am also having trouble thinking on how you would check if the joystick is going in no direction.
I have already tried using an if statement where if the joystick is more than one number and less than another one (the first number being in the top half of the joystick and the other number meaning that it is in the bottom half of the joystick it will move downwards. The current if statement does not give off any errors but does not work. I have tried an if statement to check if it is in the middle but I am not too sure about it.
joystick_count = pygame.joystick.get_count()
if joystick_count == 0:
# No joysticks!
print("Error, I didn't find any joysticks.")
else:
# Use joystick #0 and initialize it
joystick = pygame.joystick.Joystick(0)
joystick.init()
if pygame.joystick.Joystick(0).get_axis(0) >= -0.0 and pygame.joystick.Joystick(0).get_axis(0) <= 0.0:
player_one.speed_y = 5
elif pygame.joystick.Joystick(0).get_axis(0) > -0.1 and pygame.joystick.Joystick(0).get_axis(0) < -0.9:
player_one.speed_y = -5
elif pygame.joystick(0).get_axis(0) == 0.0:
player_one.speed_y = -5
#The first if statement checks if the joystick is up and the second one
#checks if the joystick is downwards
# the middle one checks if the if statement is in the middle (not too sure)
#player one and two speed is what gets added on each time
The actual results that are that the sprite does not move when the joystick is moved downwards.
Joystick axis
First ensure that you have a joystick, by getting the number of joysticks by pygame.joystick.get_count(). Initialize the joystick by pygame.joystick.Joystick.init:
joystick = None
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
Once a joystick is initialized, ist axis value can be get by pygame.joystick.Joystick.get_axis. The value returned by this function is in range [-1, 1]. -1 for the maximum negative tilt and +1 for the maximum positive tilt.
Note, each analog stick of a gamepad or joystick has 2 axis, one for the horizontal direction and 1 for the vertical direction. Since the amount of the value returned by get_axis() depends on the tilting of the analog stick you should multiply the speed by the value.
Further you should ignore values near 0 for the dead center, because of the inaccuracy of the hardware. This can be don by a simple check using the built in function abs(x) e.g. abs(axisval) > 0.1:
if joystick:
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
player_one.speed_x = 5 * axis_x
if abs(axis_y) > 0.1:
player_one.speed_y = 5 * axis_y
See the following simple demo app:
import pygame
pygame.init()
size = (800,600)
screen = pygame.display.set_mode(size)
clock = pygame.time.Clock()
pos = [size[0]/2, size[1]/2]
speed = 5
joystick = None
done = False
while not done:
clock.tick(60)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.KEYDOWN:
if event.key==pygame.K_RETURN:
done = True
if joystick:
axis_x, axis_y = (joystick.get_axis(0), joystick.get_axis(1))
if abs(axis_x) > 0.1:
pos[0] += speed * axis_x
if abs(axis_y) > 0.1:
pos[1] += speed * axis_y
else:
if pygame.joystick.get_count() > 0:
joystick = pygame.joystick.Joystick(0)
joystick.init()
print("joystick initialized")
screen.fill((0, 0, 255))
pygame.draw.rect(screen, (255,255,255), (*pos, 10, 10))
pygame.display.flip()

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