Pygame - Wait for animation to finish - animation

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()

Related

error bad argument #1 to draw (drawable expected, got table), trying to animate a sprite for the main character

new to Lua and love. I'm trying to animate a character for my platform game. I know my key binds and all work but I have never animated before, so I don't even know if I'm going in the right direction or not. I've tried looking up guides so I can compare my code to someone else's but it seems that most of them are outdated. Originally my player was just a white rectangle but now im trying to actually give it a sprite.
Here's my main.lua
local STI = require("sti")
local anim8 = require('anim8')
require("MC")
function love.load()
map1 = STI("map/map1.lua", {"box2d"})
Physics = love.physics.newWorld(0,0)
Physics:setCallbacks(beginContact, endContact)
map1:box2d_init(Physics)
map1.layers.Solid.visible = false
background = love.graphics.newImage("assets/Base pack/bg.png")
mc:load()
end
function love.update(dt)
Physics:update(dt)
mc:update(dt)
end
function love.draw()
love.graphics.push()
love.graphics.scale(5,3)
love.graphics.draw(background)
love.graphics.pop()
map1:draw(0, 0, 2, 2)
love.graphics.push()
love.graphics.scale(2,2)
mc:draw()
love.graphics.pop()
end
function love.keypressed(key)
mc:jump(key)
if keypressed == "escape" then
love.event.quit(0)
end
end
function beginContact(a, b, collision)
mc:beginContact(a, b, collision)
end
function endContact(a, b, collision)
mc:endContact(a, b, collision)
end
I don't believe that there is anything wrong with my main but i think it's necessary to recreate the problem (sorry if it makes this too long). Now my mc.lua is where I'm having the problem since im trying to animate the player themselves by giving them a jump idle and walk animation, I think i will give them a damaged and death animation later.
local anim8 = require('anim8')
mc = {}
spr_mc_walk = love.graphics.newImage("assets/mc sprites/1 Pink_Monster/Pink_Monster_Walk_6.png")
local w = anim8.newGrid(32, 32, spr_mc_walk:getWidth(), spr_mc_walk:getHeight())
walk = anim8.newAnimation(w('1-6', 1), 0.1)
spr_mc_idle = love.graphics.newImage("assets/mc sprites/1 Pink_Monster/Pink_Monster_Idle_4.png")
local i = anim8.newGrid(32, 32, spr_mc_idle:getWidth(), spr_mc_idle:getHeight())
idle = anim8.newAnimation(i('1-4', 1), 0.1)
spr_mc_jump = love.graphics.newImage("assets/mc sprites/1 Pink_Monster/Pink_Monster_Jump_8.png")
local j = anim8.newGrid(32, 32, spr_mc_jump:getWidth(), spr_mc_jump:getHeight())
jump = anim8.newAnimation(j('1-8', 1), 0.1)
obj_mc = walk
function mc:load()
self.x = 100
self.y = 0
self.width = 20
self.height = 60
self.xvel = 0
self.yvel = 0
self.maxspeed = 200
self.acceleration = 4000 -- max speed
self.friction = 3900 -- how long it takes them to reach max speed
self.gravity = 1000
self.jumpAmount = -500
self.grounded = false
self.physics = {}
self.physics.body = love.physics.newBody(Physics, self.x, self.y, "dynamic")
self.physics.body:setFixedRotation(true)
self.physics.shape = love.physics.newRectangleShape(self.width, self.height)
self.physics.fixture = love.physics.newFixture(self.physics.body, self.physics.shape)
end
function mc:draw()
love.graphics.draw(obj_mc, self.x, self.y)
end
This is most of my mc or player function, except for movement and such but this has all the code that I'm confused with. I'm pretty sure I wrote the code right I'm just confused on how to actually implement it now that I have it written down. I thought just doing the draw function would actually draw the idle version of the character but then there's where I get the error. If you need anymore code or anymore details just let me know.
The error that this is happening at is
Error
MC.lua:41: bad argument #1 to 'draw' (Drawable expected, got table)
Traceback
[C]: in function 'draw'
MC.lua:41: in function 'draw'
main.lua:30: in function 'draw'
[C]: in function 'xpcall'
I haven't done any of that for a while, but I believe obj_mc, in this case, is a table of the frames. You need to loop through them during update.
local anim8timer = 0
local anim8index = 1
local anim8rate = 0.2 -- pick an animation rate that looks good for your game
function mc:update( dt )
anim8timer = anim8timer +dt -- add delta time
if anim8timer >= anim8rate then
anim8timer = anim8timer -anim8rate -- reset timer
anim8index = anim8index +1 -- next frame
if anim8index > #obj_mc then anim8index = 1 end -- loop frames
end
end
function mc:draw()
love.graphics.draw( obj_mc[ anim8index ], self.x, self.y )
end
They may have a built-in function that accomplishes some of that, so look for anything that resembles this in the Love2D forum.

Display text on another process' screen (overlay)

I have a question, its more an OS-based one.
I'm playing a video game and I want to be able to put a textual timer ontop of the game's screen as if it was a part of the game itself.
Now, I can write a program in any language that displays a TextBox with a timer on the screen, but if I run it, the game's process (lets call it game.exe) "loses" its focus and I get my TextBox focused and interactive by the OS.
Is there any option to display that text "ontop" of the game.exe that comes from an entire different process? as if there were "layers" to the screen. Also, this text shouldn't be intractable, clickable or make the game.exe process lose its focus.
Here's a very simple example I drew:
Thanks a lot!
Solved this using a window trick with python and tkinter with some windows api stuff.
The trick is to create a transparent non-clickable window and keep it always on top.
I've basically combined this answer with a bunch of simpler stuff like removing window's border and set to auto fullscreen.
from tkinter import *
import time
import win32gui
import win32api
from win32api import GetSystemMetrics
# WIDTH = 500
# HEIGHT = 500
WIDTH = GetSystemMetrics(0)
HEIGHT = GetSystemMetrics(1)
LINEWIDTH = 1
TRANSCOLOUR = 'gray'
title = 'Virtual whiteboard'
global old
old = ()
global HWND_t
HWND_t = 0
tk = Tk()
# tk.title(title)
tk.lift()
tk.wm_attributes("-topmost", True)
tk.wm_attributes("-transparentcolor", TRANSCOLOUR)
tk.attributes('-fullscreen', True)
state_left = win32api.GetKeyState(0x01) # Left button down = 0 or 1. Button up = -127 or -128
canvas = Canvas(tk, width=WIDTH, height=HEIGHT, highlightthickness=0)
canvas.pack()
canvas.config(cursor='tcross')
canvas.create_rectangle(0, 0, WIDTH, HEIGHT, fill=TRANSCOLOUR, outline=TRANSCOLOUR)
canvas.create_text(WIDTH/2,HEIGHT/2,fill="white",font="Arial 20", text="TEXT GOES HERE")
def putOnTop(event):
event.widget.unbind('<Visibility>')
event.widget.update()
event.widget.lift()
event.widget.bind('<Visibility>', putOnTop)
def drawline(data):
global old
if old !=():
canvas.create_line(old[0], old[1], data[0], data[1], width=LINEWIDTH)
old = (data[0], data[1])
def enumHandler(hwnd, lParam):
global HWND_t
if win32gui.IsWindowVisible(hwnd):
if title in win32gui.GetWindowText(hwnd):
HWND_t = hwnd
win32gui.EnumWindows(enumHandler, None)
tk.bind('<Visibility>', putOnTop)
tk.focus()
running = 1
while running == 1:
try:
tk.update()
time.sleep(0.01)
if HWND_t != 0:
windowborder = win32gui.GetWindowRect(HWND_t)
cur_pos = win32api.GetCursorPos()
state_left_new = win32api.GetKeyState(0x01)
if state_left_new != state_left:
if windowborder[0] < cur_pos[0] and windowborder[2] > cur_pos[0] and windowborder[1] < cur_pos[1] and windowborder[3] > cur_pos[1]:
drawline((cur_pos[0] - windowborder[0] - 5, cur_pos[1] - windowborder[1] - 30))
else:
old = ()
except Exception as e:
running = 0
print("error %r" % (e))

Remove a screenGUI when unequipping

I already made a question on cooldown a weapon and I made a screenGUI to show the player when they can shoot again implementing the same debounce code. The problem is I've got no clue on how to delete the screengui/textlabel from the screen. As every tool I'm planing on doing has its own GUI, if the screenGUI of one tool doesn't delete, it will overlap with the same tool's GUI/ other tools GUI.
I already tried hiding the text label as stated in this question like this
player.PlayerGui.ScreenGui.TextLabel.Visible = false but
1) It only makes it disappear first time its unequipped and
2) Im afraid that given it doesn't get deleted, but rather hidden, after some time, stacked hidden GUIs will somehow affect the games smoothness in some way.
local player = game.Players.LocalPlayer
local mouse = player:GetMouse()
local tool = script.Parent
--Creaets a text label with the text Block Ready! on it when the player
local function onEquip()
print("screengui1")
local screenGui = Instance.new("ScreenGui")
screenGui.Parent = player.PlayerGui
local textLabel = Instance.new("TextLabel")
textLabel.Parent = screenGui
textLabel.BackgroundTransparency = 0.85
textLabel.Position = UDim2.new(0, 1100, 0, 550)
textLabel.Size = UDim2.new(0, 150, 0, 50)
textLabel.BackgroundColor3 = BrickColor.White().Color
textLabel.Text = "Block ready!"
end
local isGunOnCooldown = false
local cooldownTime = 3 --seconds
mouse.Button1Down:Connect(function()
-- debounce clicks so the text dont changes (its cooldown is the same as the gun cooldown the GUI is within)
if isGunOnCooldown then
return
end
-- change the text,
isGunOnCooldown = true
player.PlayerGui.ScreenGui.TextLabel.Text = "Reloading..."
-- start the cooldown and reset it along with the test
spawn(function()
wait(cooldownTime)
isGunOnCooldown = false
player.PlayerGui.ScreenGui.TextLabel.Text = "Block ready!"
end)
end)
local function onUnequip()
--code to delete the gui goes here
end
tool.Equipped:connect(onEquip)
tool.Unequipped:connect(onUnequip)
I just need explanation on how to delete the screenGUI that contains the textlabel shown to the player when they unequip the weapon
The easiest way to handle this is to keep a reference to the UIElement when you first create it. Then when you equip the tool, you simply set the Parent. When you unequip, you set the Parent to nil. This way, you know that there will always be one element, you are simply controlling when it is visible.
local function createAmmoUI()
--Creates a text label with the text Block Ready! on it when the player
local screenGui = Instance.new("ScreenGui")
local textLabel = Instance.new("TextLabel")
textLabel.Name = "Message"
textLabel.Parent = screenGui
textLabel.BackgroundTransparency = 0.85
textLabel.Position = UDim2.new(0, 1100, 0, 550)
textLabel.Size = UDim2.new(0, 150, 0, 50)
textLabel.BackgroundColor3 = BrickColor.White().Color
textLabel.Text = "Block ready!"
return screenGui
end
-- make a persistent UI element to show off how much ammo is left and when reload is done
local ammoUI = createAmmoUI()
local function onEquip()
-- when the tool is equipped, also show the UI
print("Equipping Tool to current Player")
ammoUI.Parent = player.PlayerGui
end
local function onUnequip()
-- when the tool is unequipped, also remove the UI from the screen entirely
print("Unequiping Tool UI")
ammoUI.Parent = nil
end
local isGunOnCooldown = false
local cooldownTime = 3 --seconds
mouse.Button1Down:Connect(function()
-- debounce clicks so the text dont changes (its cooldown is the same as the gun cooldown the GUI is within)
if isGunOnCooldown then
return
end
-- change the text,
isGunOnCooldown = true
ammoUI.Message.Text = "Reloading..."
-- start the cooldown and reset it along with the test
spawn(function()
wait(cooldownTime)
isGunOnCooldown = false
ammoUI.Message.Text = "Block ready!"
end)
end)
tool.Equipped:connect(onEquip)
tool.Unequipped:connect(onUnequip)

Controlling sprite animation with keypress

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.).

Bullets are Not Visible, Will Not Remove From Screen After Removal

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.

Resources