Simulating gravity/jump in game - issue - python

I'm using a game engine to make my own 3D game in python. I need to simulate a jump and/or gravity, but I'm running into an issue : either the calcul is immediate and my character isn't moving, like, there's no animation, instant blink, or either (if for exemple I had a bigger number in my for loop) it takes wayyyy more time, really slow, it lags and everything, struggling to calculate the jump. I got both extremes, and none works, so I'd like to find a way to make my jump. All I have at my disposition to do this is :
player.y +=
#can be -=, =, +=, etc.
So, do you got any ideas of ways to do this ? I'm not really asking a specific problem, I'd just like to gather some ideas ! And there's even no need to give predone examples, just throw your ideas, like : use this, try with this formula, this function, etc.
Adding some details : what I already tried.
Here is the main solution I tried, pretty basic :
velocity = 3
def input(key):
global velocity
if key == "space":
for i in range(7):
print(velocity)
player.y += velocity
velocity -= 1
velocity = 3
Which is pretty cool, as you had to your height 3, then 2, then 1 (deceleration as your energy lowers), then you add -1, -2, -3 (acceleration due to gravity), and go back to your starting point. Perfect ! But, as said, instantly done. So if I try this :
velocity = 3
def input(key):
global velocity
if key == "space":
for i in range(61):
print(velocity)
player.y += velocity
velocity -= 0.1
velocity = 3
Again, instantly done. And if I try higher and higher intervals, at some point I just get it to lag, no in-between where it's done correctly

Slightly off-topic: You don't want to name your function input() because it shadows the inbuilt input() function.
The problem is that you change the velocity and then iteratively decrement it inside a loop! Because of the way python (or most programming languages, for that matter) works, the program execution moves on to the "draw on screen" step only after it's finished executing your input() function. So when you press a key, here's what your program is doing:
Draw a frame and listen for keypress
Key pressed! Call input() to handle the keypress (let's assume player.y = 0)
Is the key a space? Enter the loop
velocity = 3. Move player up by 3. Decrement velocity. player.y = 3
velocity = 2. Move player up by 2. Decrement velocity. player.y = 5
... and so on until you exit the loop
player.y is 0 again
Draw another frame. Player is at the same place they started, so it looks like nothing happened.
When you add iterations to your loop, this process takes longer (so you see lag), but essentially the same thing happens.
To fix this, you need to add the effect of gravity inside the function that draws your frames. For example, you could set a flag when the jump key is pressed, and if you had a function step() that was called at each timestep of your simulation, you could check if the flag is set and then handle the situation
def user_input(key):
global jump_velocity, is_player_jumping
if key == "space":
is_player_jumping = True
jump_velocity = 3
def step():
global jump_velocity, is_player_jumping
if is_player_jumping:
player.y += jump_velocity
jump_velocity -= 0.1
if player.y == 0: # Player is back on the ground
is_player_jumping = False
This way, you only change the player's location a little bit before the next frame is drawn, and you can actually see the animation.

You first need to know what is the current time step because if you have 2 ms between your frames and 20 ms your need to adapt the amount you get into the player's y position each step.
then it would be great to have a player velocity variable somewhere in addition to its position. Then you would have to decide on a velocity to add instantly to the player when the jump occurs and each time step adds a bit of acceleration down due to gravity.

Related

How could I make a dash in a 2D pygame python program?

If the pygame program is just a basic entity you can move normally with arrow keys, how could i make it so that if space is pressed, based on the arrow key that was being held at the moment of pressing, a player dashes slightly to the specified direction? My idea is that when the space is pressed, program checks if other keys are being held down and based on that it rapidly increases the x and/or y coordinate and then stops at specific time, but I don't know how to make it stop as all of this is happening inside a main game loop. Any insight is highly appreciated.
You can use time.perf_counter.
So for using that function you will need to import module time
import time
And now you can store the value of time.perf_counter in a variable when space is pressed.
import time
jumping = False
start_time = 0
while True:
if ("space is pressed"): # Replace condition with what you want
jumping = True
start_time = time.perf_counter()
# Add your code here
if jumping:
# change of x, y and other jumping mechanism code here
if jumping and time.perf_counter() - start_time > 1: # Replace 1 with the amount you want this if condition to trigger, also add value in seconds
jumping = False
I'm not sure what your code is like, but this is how I'd go about it:
def dash(self):
keys = pygame.keys.get_pressed()
if keys[pygame.K_SPACE] and not self.dashing and self.jumping:
# ^^ Can be any key
if self.facing_right:
self.x += 20
if self.facing_left:
self.x -= 20
self.dashing = True
Make sure that when you hit the ground it does self.dashing = False else you will be able to dash forever.
Just put this function in the player class if you are doing it OOP.
Else if you are not using classes, put it anywhere in the file and take out all self..

Sprite collide with object function?

Is it possible for me to create a function where it displays a message if the Sprite (Rocket) collides with the astroid objects?
class Rocket(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.rect=self.image.get_rect()
self.image=Rocket.image
self.firecountdown = 0
def setup(self):
self.rect.x=700
self.rect.y=random.randint(20,380)
def updateposition(self):
self.rect.x=self.rect.x-1
time.sleep(0.005)
if self.rect.x == 0 :
self.rect.x = 700 + random.randint(0, 100)
self.rect.y=random.randint(20,380)
asteroids=[]
asteroidsize=[]
for i in range(25):
x=random.randrange(700,10000)
y=random.randrange(0,400)
asteroids.append([x,y])
asteroids[i]=Asteroid()
for i in range(25):
asteroidsize.append(random.randint(6,15))
while True:
for i in range(len(asteroids)):
pygame.draw.circle(screen,GREY,asteroids[i],asteroidsize[i])
asteroids[i][0]-=2
if asteroids[i][0]<0:
y=random.randrange(0,400)
asteroids[i][1]=y
x=random.randrange(700,720)
asteroids[i][0]=x
You could write a function on your Rocket class that checks for collisions. Since the asteroids are circles, you'll want to check if the closest point on the circle to the center of your sprite's rect is within the rect's bounds:
def check_asteroid_collision( self, asteroid, size ) :
# Create a vector based on the distance between the two points
distx = self.rect.centerx - asteroid[0];
disty = self.rect.centery - asteroid[1];
# Get magnitude (sqrt of x^2 + y^2)
distmag = ((distx * distx) + (disty * disty)) ** 0.5;
# Get the closest point on the circle:
# Circle center + normalized vector * radius
clsx = asteroid[0] + distx / distmag * size;
clsy = asteroid[1] + disty / distmag * size;
# Check if it's within our rect
if self.rect.collidepoint( clsx, clsy ) :
# We're colliding!! Do whatever
print( "Oh no!" );
Then in your game loop, you could check collisions by calling this:
while True:
for i in range(len(asteroids)):
...
# Collision checking
myrocket.check_asteroid_collision( asteroids[i], asteroidsize[i] );
Keep in mind this process is somewhat expensive and it will check every asteroid if it's colliding. If there's a large number of asteroids, it'll run slowly.
While I dont code python I can give you a simple example of how to accomplish something like this.
Make all your game objects inherit from a general game item class, this way you know all items have a position and a collision radius.
class:
int positionX
int positionY
int radius
Then keep all your items in a global list of game objects.
Loop over your game list and see if any two items collide
foreach object1 in gameObjectsList:
foreach object2 in gameObjectsList:
if(object1 != object2)
if(math.sqrt(object1.positionX - object2.positionX)**2 +
(object1.positionY - object2.positionY)**2)
<= object1.radius + object2.radius.
//Things are colliding
As the game progresses make sure you keep the position variables updated in each object.
What this means is basically that you have your list of game objects, and you loop over these every game frame and check if any of them are touching each other.
Or in terms of pure math, use the distance formula (link) to get the distance between the two items, and then check if their combined radius is greater than this distance. If it is they are touching.
Yes, making the score is possible. I am asuming you know the sprite collision function : pygame.sprite.spritecollide(). If you don't, look into the PyGame Docs. or google it. But here is how you do it. First. add these lines of code at the beginning of your code after the pygame,init() line:
variable = 0
variable_font = pygame.font.Font(None, 50)
variable_surf = variable_font.render(str(variable), 1, (0, 0, 0))
variable_pos = [10, 10]
Clearly, variable can be a string, just remove the str() in line 3. Line 1 is self-explanatory - it is the value you will see on the screen (just the stuff after the = sign and/or the parantheses). Line 2 decides what font and size you want the message to be in. If you choose None and 50, it means that you want the message in a normal font and in size 50. The third line renders, or pastes, the message on the screen the name of the variable that contains the string/number, the number 1 (I have no idea why), and the color your message will be. If the variable contains a number, put a str() around it. The last line will be the position of the message. But you will need to blit it first. To prevent the message from appearing on the screen forever, make a variable:
crashed = 0
Then make your instances and groups:
ship = Rocket(None)
asteroids = pygame.sprite.Group() #This is recommended, try making a class for the asteroids
And finally your collision code:
if pygame.sprite.spritecollide(Rocket, asteroids, True):
crashed = 1
You can make your blits controlled with the crashed variable:
if crashed == 0:
screen.blit(ship.image, ship.rect)
elif crashed == 1:
screen.blit(ship.image, ship.rect)
screen.blit(variable_surf, variable_pos)
The last blit line will blit your message on the screen at the location listed (variable_pos) when your ship crashes (crashed = 1 in the collision code). You can use make some code to make crashed back to 0. Remember to do pygame.display.flip() or weird stuff will happen. I hope this answer helps you!

Coin Collecting Function for game in Python

I am currently working on creating a function for my side scroller game in pyhton 3.2.3 and pygame that allows the main character to collect coins. What I am trying to do is when my character's postion is equal to the money's postition, the money disappears. Here is my code:
def drawMoney(x,y):
offset = 0 - guy[X]
guyPos = guy[X]+640
moneyPos = x
print(guyPos,moneyPos)
if lvlNum == level1:
money1 = screen.blit(money,(offset+x, y))
if guyPos == moneyPos or guy[Y] == y:
y = 10000
I am trying to make the money disappear by re-blitting the level picture but for some reason, this does not happen. I am not sure why but for some reason it is not working. Everything seems to be correct but am assuming that the guyPos never equals the moneyPos. Any help is appreciated.
By doing:
offset = 0 - guy[X]
guyPos = guy[X] + offset
you're practically assigning zero to guyPos
For something like this I'd use the colliderect() function already built-in in pygame. Just check, whether the player collides with any of the coins and if so, add the coin to the player's collection by adding the respective amount to that variable and remove the coin from the list of sprites to blit.
In Pseudo-Code:
For coin in coin_list:
Does the coin collide with the player (check by using pygame.Rect.colliderect())
If so: add coin amount to player's coin variable and remove coin from coin_list

Physics used in gravity of falling and jumping objects in Python

I am trying to make a flappy bird clone and I can't seem to get the physics right. I'm not great at physics and everytime I try numbers, it always seems to choppy and not like the original. Right now I have a fall and jump increment that is changed each time by multiplying it by a constant to make it fall faster and get slower as it jumps, but it doesn't look right.
Is there another way to do the physics of jumping?
EDIT I can't add the rest of the code since that doesn't relate to the problem, so this code will not run without cetain module variables in the rest of my code.
Here is my bird class
class Player():
def __init__(self,root,canvas,x=150,y=300,size=40):
global jumped
#Sets attributes
self.size=size
self.faller=True
self.x=x
self.y=y
self.root=root
self.fell=4 #The initial amount to fall
jingle=13 #The initial amount to jump
self.canvas=canvas
#sets the image
im=PhotoImage(file="unnamed copy 2.gif")
self.photo=im
self.current=self.canvas.create_image((self.x,self.y),image=self.photo)
def fall(self): #Always runs in the background, if the user isn't jumping, to fall
global done,t,points,j,height
if self.faller and not done:
self.y+=self.fell
self.fell*=t #Falls and multiplies how much it fell by the exponential constant
if self.y+(height/2)>=600: # if it hit the ground, it ends the game
done=True
self.fall() #Runs the method again to execute the code when done is True
return
self.canvas.coords(self.current,(self.x,self.y))
self.canvas.after(20,self.fall) #Runs it again after 20 milliseconds
elif done and self.faller:
self.faller=False #Stops the falling
end()
def jump(self,e):
global done,j,jingle,orange,t
if not done and orange: #If it isnt dead and it has been a
#sufficient time since the user last jumped
self.faller=False #Stops the falling
x=1
while x<=10:
if not done:
for item in pipes: #Checks if it has hit each time it goes up
if item.hit(self): # if it has, it stops and dies
done=True
return
self.canvas.after(12*x,self.move) # it moves up a little, 10 times
x+=1
self.faller=True #After it is done, it lets itself fall again
self.fell=4 #Sets the amount it falls back to the default
jingle=13 #Sets the amount it jumps back to default
orange=False #Stops the user from jumping really fast, like holding space
j=.97 #Sets the exponential constants back to normal
t=1.09
self.canvas.after(100,self.toll) #After 100 ms, it lets the user jump again
def toll(self): #sets the boolean that stops jumping back to True
global orange
orange=True
def move(self): #Moves and multiplies how much it moves by the constant
global jingle,j
self.y-=jingle
jingle*=j
self.canvas.coords(self.current,(self.x,self.y))
def changey(self,a): #A method to change the user's position
self.y=a
self.canvas.coords(self.current,(self.x,self.y))
This is more of a physics question than a programming question, but:
For the physics to be realistic, you need to keep track of the bird's position (self.x and self.y), velocity (self.vx and self.vy), and acceleration (self.ax and self.ay).
self.ay should be set to a constant which determines how fast you want objects to fall.
self.ax should typically be 0.0.
In the run loop, this needs to happen:
self.x += self.vx * t
self.y += self.vy * t
self.vx += self.ax * t
self.vy += self.ay * t

Pygame stop movement on collision detection

I'm essentially trying to make a "solid" object with pygame. The goal is to repel the player when they come in contact. What I'm currently using (but doesn't work correctly) is the following:
keys_pressed = pygame.key.get_pressed()
if 1 in keys_pressed:
if keys_pressed[K_w]:
self.player_l[1] += -2
if self.player_r.colliderect(self.tower_r): self.player_l[1] -= -2
if keys_pressed[K_a]:
self.player_l[0] += -2
if self.player_r.colliderect(self.tower_r): self.player_l[0] -= -2
if keys_pressed[K_s]:
self.player_l[1] += 2
if self.player_r.colliderect(self.tower_r): self.player_l[1] -= 2
if keys_pressed[K_d]:
self.player_l[0] += 2
if self.player_r.colliderect(self.tower_r): self.player_l[0] -= 2
The problem with this is that the player gets "stuck" inside the tower Rect, despite returning to a location where they were before the collision is initiated, the player Rect will always be pulled back in to the tower, and the collision will continue to trigger. After initially touching the tower Rect, the player will be unable to move in any direction.
I have done the same thing in a pygame game of mine. What you want to do is make a function for moving that all objects will use. It makes it impossible to go through any sprite in a render updates group called everything. If a sprite is not part of everything, it will not collide. Here is the function. This creates a resistance of a certain amount for collisions. Basically, upon pushing on an object, it will push a certain amount back. Any object that doesn't call the move function will not move even if it is pushed upon, so only objects that can move in the first place can be pushed, while things like walls will not slide across the board when you push them.
def moveRelative(self,other,speed): #This function is a function the one you need uses, which you may find useful. It is designed to move towards or a way from another sprite. Other is the other sprite, speed is an integer, where a negative value specifies moving away from the sprite, which is how many pixels it will move away from the target. This returns coordinates for the move_ip function to move to or away from the sprite, as a tuple
dx = other.rect.x - self.rect.x
dy = other.rect.y - self.rect.y
if abs(dx) > abs(dy):
# other is farther away in x than in y
if dx > 0:
return (+speed,0)
else:
return (-speed,0)
else:
if dy > 0:
return (0,+speed)
else:
return (0,-speed)
def move(self,dx,dy):
screen.fill((COLOR),self.rect) #covers over the sprite's rectangle with the background color, a constant in the program
collisions = pygame.sprite.spritecollide(self, everything, False)
for other in collisions:
if other != self:
(awayDx,awayDy) = self.moveRelative(other,-1) #moves away from the object it is colliding with
dx = dx + 9*(awayDx) #the number 9 here represents the object's resistance. When you push on an object, it will push with a force of nine back. If you make it too low, players can walk right through other objects. If you make it too high, players will bounce back from other objects violently upon contact. In this, if a player moves in a direction faster than a speed of nine, they will push through the other object (or simply push the other object back if they are also in motion)
dy = dy + 9*(awayDy)
self.rect.move_ip(dx,dy) #this finally implements the movement, with the new calculations being used
it is kind of a lot of code an you may want to change it for your purposes, but this is a pretty good way to do it. If you want to eliminate the bounce back feature, you could consider just setting any movement towards the object to zero, and allowing movement away from it only. I have found the bounce back feature useful and more accurate for my game, however.

Categories