Python and tile based game: limiting player movement speed - python

I've been putting together an isometric tile-based RPG using Python and the Pyglet library. I've run into the following problem, however:
My player movement is based on positions on the three-dimensional array that consists of tiles. To limit movement speed, I use a global variable: TILE_TO_TILE_DELAY = 200.
TILE_TO_TILE_DELAY is supposed to be the amount of time in milliseconds it takes for the player to move from one tile to another. During this time, they should not be able to make a new movement.
The system I've been using is that I have a timer class, like this:
import time
def GetSystemTimeInMS(): #Return system time in milliseconds
systime = round((time.clock()*1000), 0)
return systime
class Timer:
def __init__(self,time):
#time = time for which the timer runs
self.time = time
self.start_time = 0
self.stop_time = 0
def StartTimer(self):
#start_time = time at which the timer was started
self.start_time = GetSystemTimeInMS()
self.stop_time = self.start_time + self.time
def TimerIsStopped(self):
if GetSystemTimeInMS() >= self.stop_time:
return True
else:
return False
The player class has a Timer object:
self.MoveTimer = Timer(TILE_TO_TILE_DELAY)
When the player presses the W-key, a function is called that checks for player.MoveTimer.TimerIsStopped(). If it returns True, it calls player.MoveTimer.StartTimer() and starts a new movement to the next position.
In the even loop, the update(dt) function is set to happen 30 times a second:
def update(dt):
if player.MoveTimer.TimerIsStopped()
player.UpdatePosition()
pyglet.clock.schedule_interval(update, 1/30)
Now, by my logic, update(dt) should check 30 times a second whether or not enough time has passed to warrant the player a new movement event. However, for some reason the player moves much faster at lower FPS.
When my FPS is around 30, the player moves much faster than in areas where there are less tile sprites, pumping the framerate to 60. In areas where FPS is high, the player indeed by my measurements moves almost twice as slowly.
I just cannot figure it out, nor did I find anything off the internet after a day of searching. Some help would be much appreciated.
Edit: The code that starts the MoveTimer:
def StartMovement(self, new_next_pos):
self.RequestedMovement = False
if self.GetCanMoveAgain():
self.SetNextPos(new_next_pos)
self.SetMoveDirection(self.GetDirection())
#Start the timer for the movement
self.MoveTimer.StartTimer()
self.SetIsMoving(True)
self.SetStartedMoving(True)
self.SetWalking(True)
self.SetNeedUpdate(True)
self.MOVE_EVENT_HANDLER.CreateMoveEvent()
GetCanMoveAgain() returns the value of player.can_move_again, which is set back to True by the UpdatePosition() in update(dt)

Alright, I fixed the problem, how ever I'm still unsure about what caused it. Maybe it was some sort of a rounding error with milliseconds, having to do with more checks being made to the clock when the FPS is higher. Anyways, the solution:
Instead of using the system clock, I decided to use Pyglet's own "dt" argument in their update functions:
The dt parameter gives the number of seconds (due to latency, load and timer inprecision, this might be slightly more or less than the requested interval).
The new timer looks like this:
class dtTimer: #A timer based on the dt-paremeter of the pyglet event loop
def __init__(self,time):
self.time = time
self.time_passed = 0
def StartTimer(self):
self.time_passed = 0
def UpdateTimer(self, dt):
self.time_passed += dt*1000
def GetTime(self):
return self.time
def GetTimePassed(self):
if not self.TimerIsStopped():
return self.time_passed
else:
return 0
def TimerIsStopped(self):
if self.time_passed > self.time:
return True
else:
return False
When the loop attempts to update the player's position, if the TimerIsStopped returns false, dt is simply added to the Timer's self.time_passed. This has fixed the issue: the time it takes for the player to move is now constant.
Thanks for looking at my issue.

Related

Pygame make sprite invisible/flashing while invincible

I have already implemented the invincibility / immunity part for when bullet collides with tank, now I want the sprite to "flash"/less opacity/invisible while immune. Here is the relevant code and TODOs where I want to implement a change in the tank image.
class Tank(GamePhysicsObject):
# Handle respawn
self.time_since_death = pygame.time.get_ticks()
self.immune = False
self.immune_time = 3000
def collision_bullet_tank(arb, space, data):
# Set immunity, save time since death, start flashing
tank.parent.immune = True
tank.parent.time_since_death = pygame.time.get_ticks()
tank.parent.start_flashing()
def post_update(self):
# If tank has been immune for some time, make tank vulnerable again
current_time = pygame.time.get_ticks()
if current_time - self.time_since_death > self.immune_time:
self.immune = False
# TODO: Stop flashing
Basically, my question is how can I make a visual sprite change?
Edit: More specifically, what do I type here?
def start_flashing(self):
""" Call this function to make the tank flash. """
#TODO: White layer opacity 50% ?
1
The simplest solution is to draw the object Tank only in every 2nd frame as long as self.immune is set:
class Tank(GamePhysicsObject):
def __init__(self):
self.immune = False
self.frame_count = 0
def draw(self)
draw_tank = True
if self.immune:
self.frame_count += 1
draw_tank = self.frameCount % 2 == 0
if draw_tank:
# draw the tank

Creating a class instance that will lose 1hp every minute?

I want to create several "Players" instances that they will automatically lose hp after some time (e.g. 60 seconds).
Say I have a class:
class Player:
def __init__(self, hp=1000):
self.hp = hp
def lose_hp(self): #not sure if there's a better way to do this than to call this function every 60 seconds
pass
Is there a way to do that without having to call the lose_hp function for each player? (if I have 10000 players it might be difficult).
Save the base HP at object creation and the object creation time, and compute the actual HP based on the current time on the fly:
import time
class Player:
HP_LOSS_INTERVAL = 60
HP_LOSS_PER_INTERVAL = 10
def __init__(self, hp=1000):
self.base_hp = hp
self.birth_time = time.time()
def hp(self):
hp_loss = (time.time() - self.birth_time) // HP_LOSS_INTERVAL * HP_LOSS_PER_INTERVAL
return max(0, self.base_hp - hp_loss)
Here I did it with time.time(), probably in a game you want to use a timer based on your game loop ticks.

How to change an integer every certain amount of time

I am making a pygame code where a helicopter sprite changes its images 3 times to make it look like the propellers are moving. I came up with something like this:
if heli == 1:
self.surf = pygame.image.load("Heli_1.png")
if heli == 2:
self.surf = pygame.image.load("Heli_2.png")
if heli == 3:
self.surf = pygame.image.load("Heli_3.png")
I need to make it so that every say... .05 seconds the variable heli changes from 1 to 2, 2 to 3, and then 3 to 1. I tried looking into the time module, but I couldn't find any answers.
Update:
I have tried using time.sleep, but it will only display the image as the last one called (Heli_3).
I think what you want is the modulo operator (%). You can think of it as getting the remainder when dividing two numbers (With some technicalities). Here is a quick example showing how it could be used. If you want to, time could even be replaced with game ticks passed.
import time
class Animation:
def __init__(self, imgs, delay):
self.imgs = imgs
self.delay = delay
def __call__(self, time):
# Get index to use
frame = int(time / self.delay) % len(self.imgs)
return self.imgs[frame];
helicopter = Animation(helicopter_imgs, 0.5);
while True:
img_to_draw = helicopter(time.time());
You can find more information here:
What is the result of % in Python?
An answer to the problem would be to use the time module:
import time
heli = 1
while True:
self.surf = pygame.image.load(f"Heli_{heli}.png")
pygame.time.wait(500)
if heli == 3:
heli = 0
heli += 1

Trying to do animation but the program would close before it

I'm a beginner and was wondering if there was any way to finish the current animation before the program closes. Here is the sample of the code:
if playerHealth <= 0: # If the player dies
expl = Explosion(hit.rect.center, 'lg')
all_sprites.add(expl) # Show animation of him blowing up
running = False # End the game
Basically the running = False code would run before the animation(expl) starts. Is there a better way of showing this animation fully?
This sounds like a case for using a callback function. Pygame's animation class has the on_finished property which is intended to be assigned to a callback. The callback would be called once the animation is finished playing and would stop the game. Here's an example Explosion class.
class Explosion:
def __init__(self, rect, size, cb_func):
self.animate_explosion(cb_func)
...
def animate_explosion(self, cb_func):
# start animation here
...
# when animation finishes
self.on_finished = cb_func()
And then within your game logic you have something like the following:
def callback():
running = False
if playerHealth <= 0: # If the player dies
expl = Explosion(hit.rect.center, 'lg', callback())
all_sprites.add(expl) # Show animation of him blowing up
Probably You need more modifications, but one of them is:
if playerHealth <= 0 and not blowing_up_animation_started: # If the player dies
expl = Explosion(hit.rect.center, 'lg')
all_sprites.add(expl) # Show animation of him blowing up
blowing_up_animation_started = True
blowing_up_animation_finished = False
if blowing_up_animation_finished:
running = False # End the game

I want to add delay between jumps in my platforming game

So i have this platforming game that im programming in pygame. I have a jump method and i want to add a dely between jumps. That way i dont keep jumping into the ceiling. Heres the code
def jump(self):
if (self.onGround == True):
return
self.velocity = 10
self.onGround = False
This is what calls it in the main game loop:
if (event.key==pygame.K_UP):
player.jump()
Keep track of the last time you jumped, or of the next time you're allowed to jump (with pygame.time):
def __init__(self, …):
# ...
self.next_jump = 0
def jump(self):
if pygame.time.get_ticks() < self.next_jump:
return
# ...
self.next_jump = pygame.time.getticks() + 500
Depending on which type of loop you've gone with (traditional event loop, frame-rate-limited loop, unlimited-frame-rate loop) there may be better answers involving, e.g., setting a timer or using a Clock, but this will work with any type.

Categories

Resources