I've been working on a boxing game. After spending hours creating a sprite sheet, I now have trouble using it in my game. The animations load fine, but I can't seem to find a good way to control them. I would like to have each line of my sprite_sheet loop only once, but using this code, the punch keeps looping until I release the key.
left_jab = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
quit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_UP:
left_jab = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
left_jab = False
if left_jab:
player.update(dt)
player.render_left_jab(gameDisplay)
This is part of the player class:
def update(self, dt):
self.current_time += dt
if self.current_time >= self.animation_time:
self.current_time = 0
if self.current_image >= self.numImages - 1:
self.current_image = 0
else:
self.current_image += 1
Is there an easier way to do this?
There are 8 images per line in my sprite sheet. Is it just a matter of creating a smoother animation using more images?
Think about how long the animation (or the punching) should take, and if that time is up, set left_jab to False again.
A very simple way is to change your code to something like:
if self.current_image >= self.numImages - 1:
self.current_image = 0
left_jab = False
So once the animation looped once, we just stop it.
Of course this is not the final solution, but you'll get the idea. You didn't show your full code but I suggest moving all logic that belongs to the player/boxer entity into its class (the check for key presses, the left_jab flag etc.).
Related
I have made a Button class that basically draws a Rect object and a text if specified and I implemented an animation that basically shrinks the button and inflates it back quickly on click
class Button:
def __init__(self, surface, pos, size, bg_color, hover_color):
self.surface = surface
self.x, self.y = pos[0], pos[1]
self.width, self.height = size[0], size[1]
self.bgColor = bg_color
self.hoverColor = hover_color
self.rect = pygame.Rect(pos, size)
self.text = None
self.clicked = False
def addText(self, font, text, txt_color):
self.text = font.render(text, True, txt_color)
self.textRect = self.text.get_rect(center = self.rect.center)
def update(self):
if self.isHovered():
pygame.draw.rect(self.surface, self.hoverColor, self.rect, border_radius=20)
else:
pygame.draw.rect(self.surface, self.bgColor, self.rect, border_radius=20)
if(self.text):
self.surface.blit(self.text, self.textRect)
self.checkClicked()
def checkClicked(self):
if(self.rect.collidepoint(pygame.mouse.get_pos())):
if(pygame.mouse.get_pressed()[0]):
if(not self.clicked):
self.clicked = True
self.rect.inflate_ip(-7, -7)
else:
if(self.clicked):
self.clicked = False
self.rect.inflate_ip(7, 7)
def isHovered(self):
return True if self.rect.collidepoint(pygame.mouse.get_pos()) else False
It's probably not the most effecient way to do it, but I'll worry about that later.
The implementation then would be :
startBtn = Button(display, (100, 160), (200, 40), (40, 40, 40), (70, 70, 70))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
if(startBtn.rect.collidepoint(pygame.mouse.get_pos())):
showNextScreen() # Here lies the issue
print('pressed')
display.fill((50, 50, 50))
startBtn.update()
pygame.display.update()
I have a function that would draw a new screen and bascially starts the game, however it happens too quick that you can't see the animation of the button and I am struggling to find a way to let the script wait for the button animation to finish then do stuff, I have tried pygame.time.wait() and pygame.time.delay() but the whole script freezes and makes it worse, How could I make that happen?
You could just use time.sleep()
I wrote a function called sleep that waits a given amount of time without freezing the game.
Function
def sleep(ms): # ms --> time in milliseconds
start = pygame.time.get_ticks()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit(0)
now = pygame.time.get_ticks()
if now - start == ms:
running = False
Explanation
It takes one parameter: ms, which is the amount of time that you want the program to wait in milliseconds (1 second = 1000 milliseconds). In the function, I set a variable called start to pygame.time.get_ticks(), which returns the current time. Then, inside a while loop, I created an event loop which checks for the pygame.QUIT event so that if the user clicks X while the function is still running, the program will respond and quit the program. After the event loop, I set a variable called now to pygame.time.get_ticks() to get the current time. Then I checked if now (the current time) subtracted by start (the start time) is equal to ms (the given amount of waiting time in milliseconds). This checks if the given amount of time has passed. If it has, the while loop ends. If not, the while loop keeps running until that condition is True.
I have found the way to achieve this, again it might not be the optimal way to do it but it does the job :
Importing Timer from threading to create a timer and passing the function that would draw the next screen/scene as an argument to delay it for some milliseconds does the trick.
#up in the script
from threading import Timer
import pygame
#then some code goes here
.
.
.
def showNextScreen():
#code related to the next screen goes here
startBtn = Button(display, (100, 160), (200, 40), (40, 40, 40), (70, 70, 70))
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
elif (event.type == pygame.MOUSEBUTTONDOWN and event.button == 1):
if(startBtn.rect.collidepoint(pygame.mouse.get_pos())):
t = Timer(0.3, showNextScreen) #Timer(seconds, function)
t.start() #starts the timer and once its over it calls the function passed
#giving the button animation enough time to fully finish.
print('pressed')
display.fill((50, 50, 50))
startBtn.update()
pygame.display.update()
I am very new to coding and I'm still trying different languages out, I started off with GameMaker Studio and changed to Godot due to its compatibility with Mac I might as well learn something newer since GameMaker has been out for quite some time.
I want to create a RPG game and apply animation to each direction the character moves but the animation only plays after the key is pressed AND lifted. This means that while my key is pressed, the animation stops, and the animation only plays while my character is standing still, which is the complete opposite of what I want. The script looked really straight forward, but doesn't seem to be working.
I would tag this as the GDScript language instead of Python, but I guess I'm not reputable enough to make a new tag, so I tagged it under python because it is the most similar.
#variables
extends KinematicBody2D
const spd = 100
var direction = Vector2()
var anim_player = null
func _ready():
set_fixed_process(true)
anim_player = get_node("move/ani_move")
#movement and sprite change
func _fixed_process(delta):
if (Input.is_action_pressed("ui_left")) :
direction.x = -spd
anim_player.play("ani_player_left")
elif (Input.is_action_pressed("ui_right")):
direction.x = spd
anim_player.play("ani_player_right")
else:
direction.x = 0
if (Input.is_action_pressed("ui_up")) :
direction.y = -spd
anim_player.play("ani_player_up")
elif (Input.is_action_pressed("ui_down")):
direction.y = (spd)
anim_player.play("ani_player_down")
else:
direction.y = 0
if (Input.is_action_pressed("ui_right")) and (Input.is_action_pressed("ui_left")):
direction.x = 0
if (Input.is_action_pressed("ui_up")) and (Input.is_action_pressed("ui_down")) :
direction.y = 0
# move
var motion = direction * delta
move(motion)
As you check the input in _fixed_process, you call anim_player.play() several times a frame, which always seems to restart the animation, and thus, keeps the very first frame of the animation visible all the time.
As soon as you release the key, anim_player.play() stops resetting the animation back to start, and it can actually proceed to play the following frames.
A simple straight-forward solution would be to remember the last animation you played, and only call play() as soon as it changes.
You need to know if the animation has changed
First you need to put these variables in your code:
var currentAnim = ""
var newAnim = ""
And then you add this in your _fixed process:
if newAnim != anim:
anim = newAnim
anim_player.play(newAnim)
To change the animation you use:
newAnim = "new animation here"
Here is my gaming code:
This is my entire piece of code. Why are my bullets not appearing on the screen when I fire them and
why does the bullets are still on the screen when I remove them (see lines 242 through 250)? If you have time, can you tell me why my game is lagging since I first start my game? Thank you for your help! (Already answered by another user)
class Fire_User(pygame.sprite.Sprite):
def __init__(self, image_file):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
#Move function#
def update(self):
self.rect.x -= 10
#Class for the player 1 to be able to fire projectiles(Steve's head from Minecraft) at player 2#
class Fire_Comp(pygame.sprite.Sprite):
def __init__(self, image_file):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(image_file)
self.rect = self.image.get_rect()
#Move function#
def update(self):
self.rect.x += 10
And the Pygame events that shoot the bullet:
elif event.key == K_LSHIFT:
bullet = Fire_User('bomb_user.jpg')
bullet.rect.x = my_ball.rect.x + 10
bullet.rect.y = my_ball.rect.y
fired = 1
bulletGroup.add(bullet)
elif event.key == K_RSHIFT:
otherBullet = Fire_Comp('steve.png')
otherBullet.rect.x = dad.rect.x - 10
otherBullet.rect.y = dad.rect.y
fired = 1
otherBulletGroup.add(otherBullet)
Update:
I am trying to blit my pictures (the bullets) onto the screen. It gives me an error:
Traceback (most recent call last):
File "E:/PyCharm_Tony/Hero's_War.py", line 273, in <module>
screen.blit(bullet.image, bullet.rect)
NameError: name 'bullet' is not defined
Why is this happening? I have edited my program. Oh, running the code snippet will only load my code in a sentence (very long).
The initial delay in starting the game is because of the statement
pygame.time.delay(1000)
Remove it or comment it to remove lag.
Now, instead of doing:
bullet = Fire_User()
otherBullet = Fire_Comp()
Create the instances at every event. For example:
elif event.key == K_LSHIFT:
bullet1 = Fire_User()
bullet1.rect.x = my_ball.rect.x+10
bullet1.rect.y = my_ball.rect.y
bulletGroup.add(bullet1)
This will make a bullet appear whenever you press K_LSHIFT
. I would recommend giving the positions also as the arguments to __init__ of the Fire_User() class. Reduce the increment of the rect to see the movement clearly.
As far as removing the bullet is concerned, the spritecollide function has an attribute dokill for this.
spritecollide(sprite, group, dokill, collided = None) -> Sprite_list
The dokill argument is a bool. If set to True, all Sprites that collide will be removed from the Group.
I am coding a game using Kivy. I have a Screen class where I put my animation code. It's not a usual game, it's more like several screens, each with its own animation, with button commands for going back and forth to different screens.
It works ok, but when I make more classes like this and put it all in a ScreenManager, the animation is disrupted with random white screens.
class Pas(Screen):
def __init__(self, **kwargs):
super(Pas, self).__init__(**kwargs)
Clock.schedule_interval(self.update, 1 / 60.0)
self.ani_speed_init = 15
self.ani_speed = self.ani_speed_init
self.ani = glob.glob("img/pas_ani*.png")
self.ani.sort()
self.ani_pos = 0
self.ani_max = len(self.ani)-1
self.img = self.ani[0]
self.update(1)
back = Button(
background_normal=('img/back-icon.png'),
background_down=('img/back-icon.png'),
pos=(380, 420))
self.add_widget(back)
def callback(instance):
sm.current = 'game'
back.bind(on_press=callback)
def update(self, dt):
self.ani_speed -= 1
if self.ani_speed == 0:
self.img = self.ani[self.ani_pos]
self.ani_speed = self.ani_speed_init
if self.ani_pos == self.ani_max:
self.ani_pos = 0
else:
self.ani_pos += 1
with self.canvas:
image = Image(source=self.img, pos=(0, 0), size=(320, 480))
What am I doing wrong? I am also accepting ideas for a different way of doing this.
If you want to use Screen and ScreenManager for your screens, it would be better to use the transition system they define and use, so, to define your own Transitions, and apply them. If you want more control, i would advise getting ride of Screen and ScreenManager, and just using Widgets, to control the whole drawing/positioning process.
Also, Clock.schedule_interval(self.update, 0) is equivalent to the call you are making, the animation will be called each frame, and you can use dt to manage the animation progress.
Also, kivy can manage gifs, as well as zip archives of images to directly do animations (useful to have animated pngs), you can let kivy manage the whole animation process this way.
This seemed simple to do but I do not know how to monitor for continued touching. I want to touch the display or an image and as long as the user has not lifted his finger continue to rotate an Image. Here is a snip the code I have:
local rotate = function(event)
if event.phase == "began" then
image1.rotation = image1.rotation + 1
end
return true
end
Runtime:addEventListener("touch", rotate)
I want the rotation to occur until the finger is lifted from the screen.
Thanks for any advice.
How about this?
local crate = ...
local handle
local function rotate(event)
if event.phase == "began" and handle == nil then
function doRotate()
handle=transition.to(crate,
{delta=true, time=1000, rotation=360, onComplete=doRotate})
end
doRotate()
elseif event.phase == "ended" and handle then
transition.cancel(handle)
handle = nil
end
end
Runtime:addEventListener("touch", rotate)
This allows for better control of the rate of rotation. Relying on enterFrame might be problematic if you start dropping frames for some reason.
Also, the checks for handle and not handle are to accomodate multi touch. There are other ways (and better ways) to handle this but it's expedient (and shouldn't matter at all if you aren't using multitouch.)
I ended up doing this. Please post your answer if you have a better method !!
local direction = 0
function scene:move()
crate.rotation = crate.rotation + direction
end
Runtime:addEventListener("enterFrame", scene.move)
local function onButtonEvent( event )
if event.phase == "press" then
direction = 1 -- ( -1 to reverse direction )
elseif event.phase == "moved" then
elseif event.phase == "release" then
direction = 0
end
return true
end
local button = widget.newButton{
id = "rotate_button",
label = "Rotate",
font = "HelveticaNeue-Bold",
fontSize = 16,
yOffset = -2,
labelColor = { default={ 65 }, over={ 0 } },
emboss = true,
onEvent = onButtonEvent
}