Pygame: Sprite animation Theory - Need Feedback - python

After some tweaking of some code I got from someone to cause a characters images to move in regards to its direction and up down left right input I've put this together: (hope the code isn't too messy)
Character Move Code + IMG
The Sprite sheet only runs lengthwise, so basically each sprite section is a different action. Now would there be a way I could make a code that functions with the current one to cycle down from a set 'action' in order to make an animation?
For example:
'Run Left' is sprite 3. So then after we designate that column would it be possible to loop down how ever many frames of the run animation (lets say 4) in order to make an animation?
Example Picture:
http://animania1.ca/ShowFriends/dev/example.jpg

It should be easy.
If you record the frame number in a variable, you can modulo this with the number of frames you have to get an animation frame number to display.
frame_count = 0
animation_frames = 4
while quit == False:
# ...
# snip
# ...
area = pygame.Rect(
image_number * 100,
(frame_count % animation_frames) * 150,
100,
150
)
display.blit(sprite, sprite_pos, area)
pygame.display.flip()
frame_count += 1
If different actions have different numbers of frames, you'll have to update animation_frames when you update image_number.
Also, this assumes that it's ok to play the animation starting at any frame. If this is not the case, you'll need to record what the frame count was when the action started, and take this away from frame count before the modulo:
area = pygame.Rect(
image_number * 100,
((frame_count - action_start_frame) % animation_frames) * 150,
100,
150
)
A note about your event handling. If you hold down, say, left, and tap right but keep holding down left, the sprite stops moving because the last event you processed was a keyup event, despite the fact that I'm still holding left.
If this is not what you want, you can get around it by either keeping a record of the up/down states of the keys you are interested in, or by using the pygame.key.get_pressed interface.
On another note, you appear to be aiming for a fixed frame rate, and at the same time determining how far to move your sprite based on the time taken in the last frame. In my opinion, this probably isn't ideal.
2D action games generally need to work in a predictable manner. If some CPU heavy process starts in the background on your computer and causes your game to no longer be able to churn out 60 frames a second, it's probably preferable for it to slow down, rather then have your objects start skipping huge distances between frames. Imagine if this happened in a 2D action game like Metal Slug where you're having to jump around avoiding bullets?
This also makes any physics calculations much simpler. You'll have to make a judgement call based on what type of game it is.

Related

how to sleep in one function without sleeping the whole program

So I am trying to make a game, in this game I call upon a function that I want to slowly execute, but when I use "time.sleep(x)" it pauses everything in the file instead of just pausing the process of the function. I am trying to add a jump feature to a 2-d game, so if there is a better way to do it then I would be grateful for any advice but this is just the first idea that game to me.
for n in range(15):
Dino.rect.bottom -= 5
update_screen(Dino, screen, cactus)
time.sleep(0.01)
time.sleep(0.25)
inair = False
for n in range(15):
Dino.rect.bottom += 5
update_screen(Dino, screen, cactus)
time.sleep(0.01)
so I have it so that when I jump, it gives me a slow jump instead of just teleporting but like I said, it pauses everything while jumping.
This is not a good approach to timing. As you say, this sleeps the entire program. Multi-threading is a bit complex for simply moving a sprite.
A simple way to solve this problem is to use the PyGame function time.get_ticks() which returns an ever-increasing time in milliseconds.
Using this time-stamp, record the previous-time of an operation, but then do not update again until enough time has elapsed.
For example:
DINO_UPDATE_DELAY = 100 # milliseconds
next_dino_update = 0 # time next move due
[...]
# Move dino, if necessary
time_now = pygame.time.get_ticks()
if ( time_now > next_dino_update ):
Dino.rect.bottom += 5
next_dino_update = time_now + DINO_UPDATE_DELAY # in the future
# Paint the dino, wherever it is
update_screen(Dino, screen, cactus)
It's also possible to request a timer to send the event-loop a message in the future.
MOVE_DINO_EVENT = pygame.USEREVENT + 1
[...]
pygame.time.set_timer( MOVE_DINO_EVENT, DINO_UPDATE_DELAY )
EDIT: More in-depth explanation.
So basically you're implementing an animation, like an anime/cartoon. The thing on the display moves at some speed. In the above code, you seem to be moving a "dino" in the direction of y + 5 every 0.01 seconds (10 milliseconds).
Right now, you paint the dino, then sleep(), then move & paint again. When it hits the apogee of the jump, you wait 250 milliseconds, then repeat the previous phase for the down-jump.
So the problem with using sleep() is that it holds up the whole program (including the event loop, which can cause bad stuff to happen).
So instead of sleeping for some time period, the above example simply looks at the PyGame clock to determine if that same time-period has past. This allows the program to know if the animation needs to be updated.
The whole idea is to keep looking at the clock, using the time as the arbiter of what/where to draw next, rather than multiple calls to sleep().

ball animation is really junky while running pong game

the ball animation while running the program is very stuttery and i can't figure out why.
this is just the beggining of the program so ignore the fact that the game isn't ready to play yet.
this is the code: https://www.codepile.net/pile/dqKZa8OG
i want the ball to move smoothly without stuttering.
and in addition how do i make it so the program deletes the last location of the ball after each update?
It's not "junky" because of the timer, you can only see updates since you're probably moving the mouse in the meantime, then you're updating the ball position everytime you move it (which is wrong, as you're updating the position anytime any events is processed).
The problem is that you're using Clock in the wrong way: the pygame.time.Clock class creates an «object that can be used to track an amount of time», meaning that it is not a timer that can "react" once it times out. The tick method you're calling only updates the current Clock returning how many milliseconds have passed since the last call to tick itself, based on the fps argument you are providing.
What you need is to set a timer, possibly by using a specific eventid just for the updates. Also, since you're updating the ball position based on the events, you'll get more movements if you move the mouse (or any other event is called) making the ball move faster even if it shouldn't - and that's what you'll need the Clock object for.
# I'm using a smaller speed, otherwise it'll be too fast; it should
# depend on the window size to ensure that bigger windows don't get
# slower speeds
ball_velocity = .2
fps = 60
UPDATEEVENT = 24
[...]
def start_move_ball(angle):
ticks = clock.tick()
speed = ball_velocity * ticks
# "global ball" is not required, as you are assigning values to an
# existing object, not declaring it everytime.
# You can also use in-place operations to set the speeds, which is
# better for readability too.
ball[0] += angle[0] * speed
ball[1] += angle[1] * speed
def main_loop():
angle = choose_angle()
pygame.time.set_timer(UPDATEEVENT, 1000 // fps)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
return False
# update only if necessary!
elif event.type == UPDATEEVENT:
window.fill((0,0,0))
start_move_ball(angle)
draw_mid(window)
draw_players(window)
draw_ball(window)
pygame.display.flip()
The timer is set outside the while cycle, as it automatically sends the event each time interval. You could also leave it within the while (the once=True argument is not really required in this case, as it automatically updates the timer based on the same eventid), but wouldn't make much sense, since set_timer always fires the event after the first time it's set.
I'm using 24 (pygame.USEREVENT) as an eventid, but you can set its id up to pygame.NUMEVENTS - 1, as suggested in the event documentation. If for any reason you want to stop the timer, just use pygame.time.set_timer(eventid, 0) and the timer for that eventid (24 in this case) will not be fired up again.
A couple of suggestions, besides all:
Remove the pygame.display.update() line in draw_mid(window), since it causes flickering while painting.
Avoid using external services to link code (it's not your case, but if the code is too long, first try to reduce it to minimal, reproducible example, eventually leaving all the relevant parts), as they could become unavailable sometime in the future, making your question hard to understand to somebody that is facing a similar issue and reads your answer some time after you posted it.

Slowing down PyGame sprite animation without lowering frame rate

I am trying to learn pygame sprite animation. I have tried to follow a tutorial and everything is fine. There is just one problem that I can't get my head around.
In the below code I am trying to run a sprite animation of simples square.
This is the sprite:
https://www.dropbox.com/s/xa39gb6m3k8085c/playersprites.png
I can get it working, but the animation is too fast. I want it to be little smoother so that the fading effect can be seen. I saw some solutions using clock.tick but that would slow down the whole game I guess.
how can I get the animation slower while maintaing the usual frame rate for my window?
Below is my code:
lightblue=(0,174,255)
import pygame
pygame.init()
screen = pygame.display.set_mode((400, 300))
done = False
screen.fill(lightblue)
images=pygame.image.load('playersprites.png')
noi=16
current_image=0
while not done:
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
pygame.quit()
quit()
if(current_image>noi-1):
current_image=0
else:
current_image+=1
screen.blit(images,(50,100),(current_image*32,0,32,32))
pygame.display.flip()
Use an integer value which you add to in the loop and check for divisibility. See here for more.
You could use time
So your sprite has 16 frames and lets say it wants to run at ten frames a second regardless of the frame rate of the game.
You could do something along the lines of
import time
start_frame = time.time()
noi = 16
frames_per_second = 10
Then in your loop put
current_image = int((time.time() - start_frame) * frames_per_second % noi)
time.time() counts up in seconds and has several decimal places after it. If you leave leave out frames_per_second then just use the modulo operator on noi (presumably "number of images"), every time a second has gone by, the result will tick up one until it reaches 16 and return to 0.
When you multiply the difference between start_frame and current time (time.time()), you force the "seconds" to go by frames_per_second times as fast.
Coercing the result which will be a float to an int, will allow you to use it as an index.
Now if the frame rate of the game fluctuates, it won't matter because the frame rate of the sprite is tied to the system clock. The sprites should still run at as close to exactly the chosen frame rate as can be rendered.
Clock.tick() is the correct solution.
While you may want to run the code of your game as fast as possible, you don't want the animation to run at an arbitrary speed. Animation is always "frames per second" and this value should be stable; otherwise it will look ugly or confusing. A good value is the same as the refresh rate of your monitor (usually 60 FPS) to avoid tearing.

How can I solve a maze using stack or queue?

I'm using a scribbler robot to navigate a self made maze (which I will improve once I get the functionality working, the one I have in a video is just for testing purposes.)
Maze
I initially was trying to make the robot move left and right solely on the getObstacle sensors. I had trouble tho.. It wouldn't turn left or right as I desired. So I'm thinking of using stack or a queue.
I would like to just tell the robot
if(getObstacle(1) <=6000): # once sensor reaches 6000, it will stop doing the command
forward(1,0) # will move forward as long as getObstacle(1) is less than 6000
Then, once it detects the first obstacle, turn left and go forward
Then once it detects another obstacle, turn right.
I essentially want to map out the left and right turns of the maze and tell the scribbler when to turn beforehand.
So when it senses a wall for the first time = turn left.. then go forward
When it senses a wall for the second time = turn right.. then go forward.
How can I implement this code into a stack or queue?
The code I have now is based purely on the sensors, and it's unreliable.
Here's an example of what I'm working with:
def main():
setIRPower(135)
while True:
left = getObstacle(0)
center = getObstacle(1)
right = getObstacle(2)
# 2 feet per 1 second at 1 speed
if (center <= 6000):
forward(0.5, 0.3)
elif (center >= 6000 and right > left): #if right sensor detects value higher than left
turnLeft(1, .55) #left turn
else:
turnRight(1, .55) #otherwise, turn right
I'm working with different variations of the code above. I just would like to implement the turns in a queue or stack, and I'm not sure how to do it.
Thanks

How to make a shot impact in a rectangle only output one event?

In pygame, I am trying to make my points increase by 1000 every time a shot impacts my zombie, who's positions are zhot[XX] and zhot[YY]. I attempted to accomplish this by creating a rectangle around my zombie, and using the collidepoint function, however as my shot passes through the rectangle, every change in it's position counts as 1000 points, so shooting one zombie will give me something like 30000 points. How can I fix this?
for shot in shots:
zomrect2=Rect(zhot[XX],zhot[YY],49,38)
if zomrect2.collidepoint(shot[X],shot[Y]):
points+=1000
Once you've awarded points, you need to break out of the for loop.
for shot in shots:
zomrect2=Rect(zhot[XX],zhot[YY],49,38)
if zomrect2.collidepoint(shot[X],shot[Y]):
points+=1000
break #no more points will be awarded, there's no point wasting computation checking the rest.
I guess the loop you posted runs inside your main loop and is invoked every iteration of the main loop.
You should remove the shot from the shots list once it hit your zombie, so it won't be checked again for collision in the next iteration of the main loop.
zomrect2=Rect(zhot[XX],zhot[YY],49,38)
# make a copy of the list so we can savely remove items
# from it while iterating over the list
for shot in shots[:]:
if zomrect2.collidepoint(shot[X],shot[Y]):
points += 1000
shots.remove(shot)
# and also do whatever you have to do to
# erase the shot objects from your game
You need to track the fact that the points have been awarded already. It's not really clear how/when the method or function awarding points is being called, but something like this perhaps:
points_awarded = False
for shot in shots:
zomrect2=Rect(zhot[XX],zhot[YY],49,38)
if zomrect2.collidepoint(shot[X],shot[Y]) and not points_awarded:
points_awarded = True
points+=1000

Categories