Attack intervals?

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
dyl4n
Prole
Posts: 3
Joined: Fri Jun 05, 2015 1:04 am

Attack intervals?

Post by dyl4n » Fri Jun 05, 2015 1:08 am

Hey guys, I am a huge noob to lua and love, and I need help. I have a game, and in my game a character can shoot, but the only problem is that if you hold down space he will attack every single time it runs love.update. Now obviously this is bad cus he attacks like 200 times/second :crazy:. To put it simply, it want it to unbind space for ~1 second everytime you attack. How can I do this?

User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Attack intervals?

Post by micha » Fri Jun 05, 2015 5:47 am

Hi and welcome to the forum.

You can implement time intervals by having a cooldown-timer.

Code: Select all

function love.load()
  cooldown = 0
end

function love.update(dt)
  cooldown = math.max(cooldown - dt,0)
  if love.keyboard.isDown(' ') and cooldown == 0 then
    cooldown = 1 -- cooldown-time in seconds
    shoot()
  end
end

User avatar
Kingdaro
Party member
Posts: 395
Joined: Sun Jul 18, 2010 3:08 am

Re: Attack intervals?

Post by Kingdaro » Fri Jun 05, 2015 10:35 am

That works, but there's a more correct way to do it, that doesn't require limiting the timer and doesn't slowly offset it over time (though this only really matters where the timing is important, like in rhythm games). This method also accounts for lag spikes, where shots may have missed their queue over the period of the spike.

Code: Select all

function love.load()
  -- initialize timer
  cooldown = 0
end

function love.update(dt)
  -- count down with delta time
  cooldown = cooldown - dt
  
  -- repeatedly check if both the space key is down, and if our timer is "expired"
  while love.keyboard.isDown(' ') and cooldown <= 0 do
    
    -- if the timer is expired, add to it until it isn't
    cooldown = cooldown + 1
    
    -- shoot as well whenever we add to the timer
    shoot()
  end
end
And for future reference, kikito's cron library does this as well, if you happen to have multiple things you need timed. This would be for more advanced to intermediate users, however, but it's a good option to look into regardless.

User avatar
Robin
The Omniscient
Posts: 6506
Joined: Fri Feb 20, 2009 4:29 pm
Location: The Netherlands
Contact:

Re: Attack intervals?

Post by Robin » Fri Jun 05, 2015 11:13 am

Kingdaro wrote:That works, but there's a more correct way to do it,
Your method is flawed, though, because if you wait a minute before shooting, you can shoot 60 consecutive frames. Hell, you can sit for an hour, and fire for 3600 consecutive frames. If that doesn't insta-kill all your enemies I don't know what will.
Help us help you: attach a .love.

User avatar
micha
Inner party member
Posts: 1083
Joined: Wed Sep 26, 2012 5:13 pm

Re: Attack intervals?

Post by micha » Fri Jun 05, 2015 11:33 am

Kingdaros solution is better in one aspect: If the framerate is really low, less than one frame per cooldown, then the effective cooldown is raised to the framerate (if you follow my solution).

This small correction should make Kingdaros solution correct.

Code: Select all

    function love.load()
      -- initialize timer
      cooldown = 0
    end

    function love.update(dt)
      -- count down with delta time
      cooldown = cooldown - dt
     
      -- repeatedly check if both the space key is down, and if our timer is "expired"
      while love.keyboard.isDown(' ') and cooldown <= 0 do
       
        -- if the timer is expired, add to it until it isn't
        cooldown = cooldown + 1
       
        -- shoot as well whenever we add to the timer
        shoot()
      end
      -- if no shot is fired, make sure cooldown is not negative.
      cooldown = math.max(cooldown,0)
    end
This will guarantee, that if you hold down space for one hour (3600 seconds), then you get exactly 3600 shots. My initial solution would only give approximately 3600 shots.

User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Attack intervals?

Post by kikito » Fri Jun 05, 2015 12:25 pm

@kingdaro thanks for mentioning cron.

This post has made me think about how cron is used. The problem it has always tried to solve is the one you guys have just exemplified.

This is how cron.lua could be used to solve this problem:

Code: Select all

local cron = require 'cron'

local canFire = false
local cooldown = cron.after(1, function() canFire = true end)
cooldown:reset(1) -- start expired

function love.update(dt)

  cooldown:update(dt)

  if canFire and love.keyboard.isDown(' ') then
    shoot()
    canFire = false
    cooldown:reset()
  end
end

Lately I have been thinking about removing the callbacks - I always use them just to set a flag to true, as in the previous example. I am considering changing cron so that you use "if" and "while" directly instead of callbacks, like so:

Code: Select all

-- WARNING: This is not how cron.lua works at the moment
-- This is example code of how it could work in the future
local cron = require 'cron'

local cooldown = cron.after(1) -- fire every second
cooldown:reset(1) -- start expired

function love.update(dt)
  local expired = cooldown:update(dt)
  if expired and love.keyboard.isDown(' ') then
    shoot()
    cooldown:reset()
  end
end
 
Expired would be nil when not expired, and the "number of iterations done" when expired. So one can use a loop:

Code: Select all

-- WARNING: This is not how cron.lua works at the moment
... -- same as before
function love.update(dt)
  local expired = cooldown:update(dt)
  if expired and love.keyboard.isDown(' ') then
    for i=1,expired do shoot() end -- notice the loop here
    cooldown:reset()
  end
end
 
This way we can do what kingdaro said, but also what robin said.
When I write def I mean function.

dyl4n
Prole
Posts: 3
Joined: Fri Jun 05, 2015 1:04 am

Re: Attack intervals?

Post by dyl4n » Fri Jun 05, 2015 9:44 pm

Thanks guys, I kinda combined all these ideas into one, and it works perfectly. Have a good day :))))

Whatthefuck
Party member
Posts: 106
Joined: Sat Jun 21, 2014 3:45 pm

Re: Attack intervals?

Post by Whatthefuck » Tue Jun 09, 2015 7:03 am

You could also make a global variable called curTime, for example, and increase it's value by deltaTime in love.update.
Then when you need to delay something, you just assign a variable on the object, ie.

Code: Select all

object.delayUntilAction = curTime + 0.2
Since curTime is incremented by the delta time, 0.2 is always 1/5 of a second.
And then you just check:

Code: Select all

if curTime > object.delayUntilAction then
-- do stuff
end
This is more of a personal preference though :)

Lapin
Prole
Posts: 17
Joined: Fri Mar 20, 2015 11:53 am

Re: Attack intervals?

Post by Lapin » Tue Jun 09, 2015 11:52 am

Or you can create a entire new file, for the timers, here is the timer lib, adapted from Gmod, pfhv

Code: Select all

timer = {}

local PAUSED = -1
local STOPPED = 0
local RUNNING = 1

local timerList = {}
local timerListSimple = {}

local function createTimer( name )

	if ( timerList[name] == nil ) then
		timerList[name] = {}
		timerList[name].Status = STOPPED
		return true
	end

	return false

end

function timer.exists( name )

	return timerList[name] ~= nil

end

function timer.create( name, delay, reps, func, ... )

	if ( timer.exists( name ) ) then
		timer.remove( name )
	end

	timer.adjust( name, delay, reps, func, ... )
	timer.start( name )

end

function timer.start( name )

	if ( not timer.exists( name ) ) then return false end
	timerList[name].n = 0
	timerList[name].Status = RUNNING
	timerList[name].Last = love.timer.getTime()
	return true
	
end

function timer.adjust( name, delay, reps, func, ... )
	
	local args = {...}
	
	createTimer( name )
	timerList[name].Delay = delay
	timerList[name].Repetitions = reps
	if ( func ~= nil ) then 
		timerList[name].Func = func
		timerList[name].Args = args
	end
	
	return true
	
end


function timer.pause( name )

	if ( not timer.exists( name ) ) then return false; end
	if ( timerList[name].Status == RUNNING ) then
		timerList[name].Diff = love.timer.getTime() - timerList[name].Last
		timerList[name].Status = PAUSED
		return true
	end
	return false
	
end

function timer.unPause( name )

	if ( not timer.exists( name ) ) then return false; end
	if ( timerList[name].Status == PAUSED ) then
		timerList[name].Diff = nil
		timerList[name].Status = RUNNING
		return true
	end
	return false
	
end

-- toggle pause
function timer.toggle( name )

	if ( timer.exists( name ) ) then
		if ( timerList[name].Status == PAUSED ) then
			return timer.unPause( name )
		elseif ( timerList[name].Status == RUNNING ) then
			return timer.pause( name )
		end
	end
	return false
	
end

function timer.stop( name )

	if ( not timer.exists( name ) ) then return false; end
	if ( timerList[name].Status ~= STOPPED ) then
		timerList[name].Status = STOPPED
		return true
	end
	return false
	
end

-- check all timers
function timer.check()

	for key, value in pairs( timerList ) do
	
		if ( value.Status == PAUSED ) then
		
			value.Last = love.timer.getTime() - value.Diff
			
		elseif ( value.Status == RUNNING and ( value.Last + value.Delay ) <= love.timer.getTime() ) then
				
			value.Last = love.timer.getTime()
			value.n = value.n + 1 
			
			if ( value.n >= value.Repetitions and value.Repetitions ~= 0) then
				Stop( key )
			end

			value.Func(unpack(value.Args))
				
		end
		
	end
	
	-- Run Simple timers
	for key, value in pairs( timerListSimple ) do

		if ( value.Finish <= love.timer.getTime() ) then
			
			table.remove( timerListSimple, key )
			value.Func(unpack(value.Args))
			
		end
	end
	
end

function timer.remove( name )

	timerList[ name ] = nil
	
end

function timer.simple( delay, func, ... )

	local new_timer = {}
	
	new_timer.Finish = love.timer.getTime() + delay
	new_timer.Func = func
	new_timer.Args = {...}
	
	table.insert( timerListSimple, new_timer )
	
	return true
	
end



timer.Simple = timer.simple
timer.Remove = timer.remove
timer.UnPause = timer.unPause
timer.Pause = timer.pause

Code: Select all

function onusedAttack()
  canuseattack = false
  timer.Simple(4, function() canuseattack = true end)
end

Post Reply

Who is online

Users browsing this forum: No registered users and 15 guests