pygame sprite move faster if window is smaller - python

my character sprite moves faster if my game is in window mode. to set the velocity i used ROOTwidth, in theory the velocity should be scaled...
this is my code (simplified)
#MAIN CODE
#ROOT dimension don't change (window can't be resized while playing,
#only in main menu function where ROOTwidth, ROOTheight are obtained)
ROOTwidth, ROOTheight = pygame.display.get_surface().get_size()
velocity = ROOTheight/450
playertopx = ROOTwidth/2.2
playertopy = ROOTwidth/2
playermovement = PlayerMovement(playertopx, playertopy)
while True:
key = pygame.key.get_pressed()
if key[pygame.K_w]:
playermovement.human_moveup(velocity)
#PLAYER MOVEMENT CLASS
import pygame
class PlayerMovement:
#init
def __init__(self, playertopx, playertopy):
self.x = playertopx
self.y = playertopy
#movement
def human_moveup(self, velocity):
self.y -= velocity
#MAIN CODE
ROOT.blit(playermovement.spritesheet_human, (playermovement.x, playermovement.y), (0, 50, 25, 18))
I don't know what to do... for every element in my game, using ROOT dimensions works fine, only in velocity I have problems

I guess it might be a case of your main event loop's looping speed being dependent on the time it takes to draw on the window and that main event's code not accounting for that.
Execution speed
Assuming you have this code as your main event loop:
while NotExited:
doGameLogic() # Your velocity computations and other stuff
drawOnWindow() # Filling entire window with background, drawing sprites over, refreshing it, etc
Now, imagine doGameLogic() always takes 1ms (0.001 seconds) of time, and drawOnWindow() always takes 50ms. While this loop is running, therefore, the loop will take up 51 milliseconds total, and hence doGameLogic() will be called once every 51ms.
Then you perform your velocity computation in there. Let's, for simplicity, say you do playermovement.x += 5 in there every time.
As a result, your player's X coordinate is increased by 5 units every 51 milliseconds. That amounts to an increase of about 98 units in one second.
Variance in execution speed
Now imagine that drawOnWindow() starts taking 20ms of time instead. Then the loop takes 21ms of time total to run, which causes doGameLogic() to run every 21ms aswell. In that case the X coordinate increases by 5 units every 21 milliseconds instead, amounting to increasing by 238 units every second.
That is way faster than the previous 98 units every second. Because drawing takes less time now, your character ends up moving way faster.
This is what I assume is happening in your case. As you make the window smaller, drawing calls (like drawing a background/filling it with a color) take less time as there's less pixels to draw on, and therefore change how long drawOnWindow() takes time, and therefore the frequency at which doGameLogic() is run changes.
Fixing
There are many different ways to fix this. Here are some:
Enforcing loop speed
One of them is to ensure that your loop always takes the exact same amount of time to run regardless of how much time the calls take:
import time
while NotExited:
startTime = time.time() # Record when the loop was started
doGameLogic()
drawOnWindow()
# Calculate how long did it take the loop to run.
HowLong = time.time() - startTime
# Sleep until this loop takes exactly 0.05 seconds.
# The "max" call is to ensure we don't try to sleep
# for a negative value if the loop took longer than that.
time.sleep(max(0, 0.05-HowLong))
Or alternatively, the library you are using for rendering may allow you to set an upper limit to FPS (frames per second), which can also work to make sure the time it takes to draw is constant.
This method has a disadvantage in that it becomes ineffective if the loop takes longer than the designated time, and restricts how fast your game runs in the opposite case, but it is very easy to implement.
Scaling with speed
Instead of making sure playermovement.x += 5 and the rest of the logic is ran exactly once every 50 milliseconds, you can make sure that it is run with values scaled proportionally to how often it is run, producing the same results.
In other words, running playermovement.x += 5 once every 50ms is fully equivalent to running playermovement.x += 1 once every 10ms: as a result of either, every 50ms the value is increased by 5 units.
We can calculate how long it took to render the last frame, and then adjust the values in the calculations proportionally to that:
import time
# This will store when was the last frame started.
# Initialize with a reasonable value for now.
previousTime = time.time()
while NotExited:
# Get how long it took to run the loop the last time.
difference = time.time() - previousTime
# Get a scale value to adjust for the delay.
# The faster the game runs, the smaller this value is.
# If difference is 50ms, this returns 1.
# If difference is 100ms, this returns 2.
timeScale = difference / 0.05
doGameLogic(timeScale)
drawOnWindow()
previousTime = time.time()
# ... in the game logic:
def doGameLogic(timeScale):
# ...
# Perform game logic proportionally to the loop speed.
playermovement.x += 5 * timeScale
This method is more adaptable depending on the speed, but requires to be taken in account whereever time dependent actions like this one are done.
It can also be a source of unique problems: for example, if your game runs very very slowly even for one frame, the time scale value might get disproportionally large, causing playermovement.x to be incremented by 5*100000, teleporting your player character very far away. It can also produce jerky results if the loop speed is unstable, and provide more problems since it is performed with floating point math.
Decoupling logic and rendering
Another more reliable than the other ones but harder to implement way is to decouple doGameLogic() from drawOnWindow(), allowing one to be run independently from the other. This is most often implemented with use of multithreading.
You could make two loops running concurrently: one that runs doGameLogic() on a fixed interval, like 10ms, with the aforementioned "Enforcing loop speed" method, and another one that runs drawOnWindow() as fast as it can to render on the window at any arbitrary speed.
This method also involves questions of interpolation (if drawOnWindow() runs twice as fast as doGameLogic(), you probably don't want every second time to draw an identical image, but an intermediate one that appears smoother), and threading management (make sure you don't draw on the window while doGameLogic() is still running, as you might draw an incomplete game state in the middle of processing).
Unfortunately I am not knowledgeable enough to provide an example of code for that, nor I am even sure if that is doable in Python or PyGame.

Related

Using recursion in pygame

I'm new to python and programming in general and am working on a final project for a python centric class. One of the requirements that I cant seem to figure out how to make work is to integrate recursion into our code in order to show a working knowledge. I've worked up a simple "bullet hell" style game using pygame.
My goal is that when contact is made between a bullet and an enemy, that a series of bullet sets will be launched from the player position as a sort of short-term modifier.
This code runs in the main loop whenever a bullet hits an enemy:
for i in reversed(range(len(bullets))):
for j in reversed(range(len(enemies))):
if bullets[i].collided(enemies[j].rect):
del enemies[j]
del bullets[I]
s.global_score += 100
more_bullets(10)
#print("Hit!")
#print(s.global_score)
break
The "more_bullets" function is the focus of my recursion, and calls this:
def more_bullets(n):
if(n > 0):
spawnx = sq.rect.x+10 + sq.rect.width/2 - 10
b = Square(s.red, spawnx,sq.rect.y, 10,30)
b.direction = 'N'
b.player_speed = 10
bullets.append(b)
spawnx = sq.rect.x-10 + sq.rect.width/2 - 10
b = Square(s.red, spawnx,sq.rect.y, 10,30)
b.direction = 'N'
b.player_speed = 10
bullets.append(b)
pygame.display.update()
more_bullets(n-1)
print(f"Fired x {n}")
The outcome currently is that my debug does print 10 times making me think that the recursion is functioning correctly, however only one set of bullets is firing when the collision occurs. I'm thinking that all 10 bullets are firing faster than I can register it and just stacking on the screen.
Is there an easy-to-use function that might slow down the firing of the bullets? or have messed up something more fundamentally here?
I'm thinking that all 10 bullets are firing faster than I can register it and just stacking on the screen.
You're correct.
I don't believe there's an "easy way" to do what you're asking the way you think. The recursion is immediate, meaning that the function runs 10 times right away when it's called. For it to send out a burst of staggered bullets, you'd need some kind of timer or queue or something along those lines that runs alongside your main loop, and recursion isn't really a natural fit for that. The same will go for any kind of game logic function that plays out over a period of time.
This isn't what's being asked, but here's an idea of what you could do, even though it's kind of a redundant use of recursion: add a parameter to that function that dictates the angle the bullet is being shot. Then, add a little bit to that parameter on every recursive call. That way an arc of bullets will be shot at the same time.

How to speed up or optimize for loop used for collision detection using pygame/pytmx?

I am using a for loop combined with .colliderect() for collision detection while attempting to make a game using pygame, the loop gets too slow with ~340 wall Rectangles, I was wondering if it could be faster somehow because it severely effects the game play loop?
I have tried using coordinate points on different places on every wall but only works if you're moving certain amounts of pixels at a time and every time you half the movement speed it quadruples the amount of coordinate points you save.
#disregard indent, this is all in an update function that is called every time that a player decides to move.
self._old_position = self.position
PlayerRectangle = pygame.Rect(self.position[0]+ x,self.position[1]+y,16,16)
cwd = os.getcwd()
tmxData = load_pygame(cwd+"\\Maps\\TestfileMap.tmx")
movement = True
for obj in self.walls:
if(pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)):
movement = False
self.move_back()
else:
continue
if movement:
self.position[0] += x
self.position[1] += y
self.stats["position"]=self.position
self.rect.topleft = self.position
self.feet.midbottom = self.rect.midbottom
The provided code works, however it is too slow, I was wondering if there is a different method in collision detection or if there is a way to make what is shown faster, it bogs down things greatly. Thank you
EDIT:
So the solution was basically that I had load_pygame that ran literally every time it looped simply take out the line that does that and it clears things up a lot more, for further optimization change the line that makes a Rect for each object and just use a list of Rects that are already constructed, this limits function calls.
Without a minimal working example it is tough to give assertive advice.
As I stated in the comments, there is a spurious call to a "load_pygame" function that seems to read file data inside this code - that alone could be the cause of your slowdown. Moreover, the data read is not used in the collision detection.
The other advice I't have is to let your walls rectangles pre-calculated in a sprite group, instead of creating new rectangles for all walls in every check with:
pygame.Rect(obj[0],obj[1],16,16).colliderect(PlayerRectangle)). then you can use the sprite method "spritecollideany" - https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollideany - that should optimize the rectangle-collision verification.
(This will also require your Player object to be a Sprite if it is not already).

python curses while loop and timeout

I am having a hard time understanding the window.timeout() function. To be more specific, I am toying with a "snake" game in python:
s = curses.initscr()
curses.curs_set(0)
w = curses.newwin()
w.timeout(100)
while True:
move snake until it hits the wall
I understand that in this case, timeout(100) determines how fast the snake "moves", i.e. printing out new characters on the screen. However, I got stuck when I want to amend the code so that it waits until someone press "start". I wrote something like:
w.timeout(100)
while True:
if w.getch() is not start:
stay at the initial screen
else:
while True:
move the snake until it hits the wall
However, in this case, the timeout(100) seems to govern how long each time the program waits for w.getch(), not how long to wait between each time the snake moves. Also, I notice that in the first example, the timeout is declared at the top, outside the while loop. This looks weird to me because normally if I want to pause a while loop, I would put sleep() at the bottom inside the while loop.
If you want to pause between snake moves, you could use napms to wait a given number of milliseconds (and unlike sleep, does not interfere with screen updates). Setting w.timeout to 100 (milliseconds) is probably too long. If you're not concerned with reading function-keys, you could use nodelay to set the w.getch to non-blocking, relying on the napms to slow down the loop.
Regarding the followup comment: in ncurses, the wtimeout function sets a property of the window named _delay, which acts within the getch function, ultimately passed to a timed-wait function that would return early if there's data to be read.

Drawing a pyglet batch increases memory usage?

I'm trying to use Pyglet's batch drawing capabilities to speed up the rendering of an animation which mainly consists of drawing a large set of vertices many times per second using GL_LINES. Speed-wise, I succeeded, as previously laggy animations now run crisp up to 60 fps. However, I noticed that every time a new frame of animation is drawn (by calling batch.draw() on a few batches, within a function scheduled to be called every frame by pyglet.clock.schedule_interval()), the memory used by the program steadily rises.
I expect the memory usage to substantially increase while creating the batches (and it does), but I don't understand why a mere call to batch.draw() also seems to be causing it, or at least why the memory usage seems to accumulate over future frames. Do I need to somehow manually delete previously drawn images from memory each time I need to redraw a frame? If so, I've found nothing in the Pyglet documentation about needing to do such a thing.
In my program, I call pyglet.gl.glClear(pyglet.gl.GL_COLOR_BUFFER_BIT) at the beginning of each new frame update in order to clear the previous frame, but perhaps the previous frame is still persisting in memory?
Below is an independent example script which exhibits this precise behavior:
import pyglet as pg
import random
currentFrame = 0
window = pg.window.Window(600,600)
# A list of lists of batches to be drawn at each frame.
frames = []
# Make 200 frames...
for frameCount in range(200):
batches = []
for n in range(3):
batch = pg.graphics.Batch()
# Generate a random set of vertices within the window space
batch.add(1000, pg.gl.GL_LINES, None,
("v2f", tuple(random.random()*600 for i in range(2000))),
("c3B", (255,255,255)*1000))
batches.append(batch)
frames.append(batches)
# This function will be called every time the window is redrawn
def update(dt, frames=frames):
global currentFrame
if currentFrame >= len(frames):
pg.clock.unschedule(update)
print("Animation complete")
return
batches = frames[currentFrame]
# Clear the previous frame
pg.gl.glClearColor(0,0,0,1) # Black background color
pg.gl.glClear(pg.gl.GL_COLOR_BUFFER_BIT)
# Define line width for this frame and draw the batches
pg.gl.glLineWidth(5)
for batch in batches:
batch.draw()
currentFrame += 1
# Call the update() function 30 times per second.
pg.clock.schedule_interval(update, 1.0/30)
pg.app.run()
While the animation is running, no new batches should be created, and yet the memory used by Python steadily increases thruout the duration of the animation.
I'm sorry that this might be an extremely late answer, but for anyone coming across this...
you have created 2 lists which are the source of the linear memory usage increase:
frames = []
and
batches = []
on lines 8 and 12 respectively.
Then, in your for loop you add 3 batches into the "batches" list and then add those into the "frames" list.
for frameCount in range(200):
batches = []
for n in range(3):
batch = pg.graphics.Batch()
# Generate a random set of vertices within the window space
batch.add(1000, pg.gl.GL_LINES, None,
("v2f", tuple(random.random()*600 for i in range(2000))),
("c3B", (255,255,255)*1000))
batches.append(batch)
frames.append(batches)
fortunately, you do override whatever is in "batches" in here:
batches = frames[currentFrame]
but that still leaves out "frames", which is never reset and for every frame, has 3 more batches in it.
if you start with 0 frames and end with 200 frames, your memory usage with this "frames" list will go from storing nothing to storing 600 batches.
try to reset the "frames" lst after each frame and keeping track of your count of frames with a simple integer.
I hope this helps at least someone

pygame: player fire delay

I want to have a delay between firing bullets for my character. I used to do this before in Java like:
if (System.currentTimeMillis() - lastFire < 500) {
return;
}
lastFire = System.currentTimeMillis();
bullets.add(...);
However, how can I do this in pygame way, in order to get that sys currentTimeMillis.This is how my run (game loop) method looks like:
time_passed = 0
while not done:
# Process events (keystrokes, mouse clicks, etc)
done = game.process_events()
# Update object positions check for collisions...
game.update()
# Render the current frame
game.render(screen)
# Pause for the next frame
clock.tick(30)
# time passed since game started
time_passed += clock.get_time()
As you can see in the previous code, I have created time passed, but I'm not sure if that is correct way of code order, and what else im missing.
Your code is fine, if game.process_events and game.update works as expected.
Edit:
Use pygame.time.get_ticks instead of the clock I mentioned earlier. It was using python's time module, so that clock and clock in your code meant different clocks. This is much better way.
#this should be done only once in your code *anywhere* before while loop starts
newtime=pygame.time.get_ticks()
# when you fire, inside while loop
# should be ideally inside update method
oldtime=newtime
newtime=pygame.time.get_ticks()
if newtime-oldtime<500: #in milliseconds
fire()
Let me explain you what pygame.time.get_ticks() returns:
On first call, it returns time since pygame.init()
On all later calls, it returns time (in milliseconds) from the first call to get_ticks.
So, we store the oldtime and substract it from newtime to get time diff.
Or, even simpler, you can use pygame.time.set_timer
Before your loop:
firing_event = pygame.USEREVENT + 1
pygame.time.set_timer(firing_event, 500)
Then, an event of type firing_event will be posted onto the queue every 500 milliseconds. You can use this event to signal when to fire.

Categories

Resources