Cancel System for Combat System

[ explanation/begin ]

Now I would like to talk about the Cancel System. Everybody knows for what it is. This system must cancel or clean the attacks of players. But mostly it prevents one player from damaging other one which depends on time. As people say first wins always. Soo, as you’re already into it… I have many things which can get better but Ive got no idea how to do that…

[ What I had done ]

[ — 1 — ]

I have tried many of things which should work… For example I did repeat-until system which waits for variable “stunned” getting added to cancel the move from going on. Also I did system which sets the fire-event and if its getting called then it makes thread to stop; this system looked like:

--server-side (using skill, just example):
local animation = plr.character.humanoid:LoadAnimation(game.ReplicatedStorage.Animations.Anim1); -- setups animation track
game.ReplicatedStorage.CancelEvent.OnServerEvent:Connect(function(target) -- setups global fire server event to be called. It compares if its called for the right player who must be cancelled by this calling
        if target == plr.Character then -- comparing if its right player which we want to cancel
                animation:Stop() -- doing something... Also we can close the coroutine, using coroutine.close(thread)
        end
end)
animation:Play() -- main thread which keeps working

[ — 2 — ]

Also I learned many things from previous one… But I wanted to do good-looking system which is like wardrobe. I made the structure:

  • Script must have entry point and also it must have clean function which cleans all this mess if it suddenly stops. Its look became like this:
skills = {
       ["skill"] = {
              ["Clean"] = function()
              end,
              ["Main"] = function()
              end,
       },
}

local cancels = {}
function UseSkill(skill_name)
      local Table = skills[skill_name] or nil
      if table == nil then 
              warn("[client-side] This skill doesn't exist! Argument is bad!")
      return end
      local skill = coroutine.create(skills[skill_name]["Main"])
      local lp = game.Players.LocalPlayer
      local remotes = lp.Character.Remotes -- its folder where remotes are, its just example
      
      cancels[skill_name] = { --creates cancel handler, it creates like that to prevent bunch of skills' mixing up
            OriginTable = Table,
            CurrentThread = skill,
            Clean = function(this)
                      if coroutine.status(this.CurrentThread) ~= "dead" then
                              coroutine.close(this.CurrentThread)
                              this.OriginTable["Clean"]()
                      end
            end
      }
      remotes.CancelClientSkill.OnClientEvent(function()
              for _, v in pairs(cancels) do 
                        v:Clean() -- the same as v.Clean(v)
              end
              for i, v in pairs(cancels) do 
                        cancels[i] = nil -- clean it not to do double clean
              end
      end)
end

[what do i want]

I wanna make system which has the most accurate cancelling, which cancels to prevent one player from damaging other if other one is attacking. I just need improvement of my code, so that’s why i’m here. I want to make cancel system which cancels attacks depending on time: for example, two players used the same attack to hit each other but first uses it faster than other one, so first player must cancel the attack of other one blocking from damage…

All you really need to see is if a value has changed while a move is going on to cancel it.

stunCount = 0 
function Stunned()
     stunCount += 1
end

function Attack()
     local lastStun = stunCount
     
     task.wait(0.4)
     if lastStun ~= stunCount then return end
     dohitstuff()
     
     --let's say this attack hits twice or something.
     task.wait(0.2)
     if lastStun ~= stunCount then return end
     dohitstuff()
end

Any time the script is waiting, that is when you’d want to check if a new stun has occured.

4 Likes

This is a good way but a little outdated, I personally just use a PreAnimation Connection and in that stop the animation and cancel the Connection or other Connections when the necessary tags are added the Model

You really shouldn’t use animation events over task.wait()s. I’m probably going to make a post elsewhere on why they’re bad, but basically:

  • They’re a lot harder to adjust. You have to go in your animation editor, move the animation event around, then export the animation. Compare this to just changing a number in a script.
  • They’re more expensive, relatively.
  • They’re noticably less accurate than task.wait (at least last time I tested).
  • You shouldn’t need to listen anyways because each “event” is always a certain amount of time away from when the animation starts.
    • The only exception is if you plan to change the animation’s speed/position while it’s playing, but even then you’re better off making a custom wait function.

I think the only time they’re a preferable option over something else is for playing footstep sounds in walking animations, mostly because you only have to listen for one kind of event and the timing of each footstep may not be equally apart.

It’s really not hard at all. You get to see which frame the animation event is for, as well. Adjusting a number for task.wait doesn’t give you that bc you have to play the game in order to test out the timing when you play the animation.

I just connect to the animation event with Once and Ez pz.

It’s been very accurate for me.

Maybe “harder” isn’t the term, but it takes a lot longer to do compared to adjusting a task.wait. And if you really need to see what frame something needs to occur, you can open the animation and check the timestamp of the frame.

I also forgot to mention that it’s far easier to find the time a yield takes than it is to find the time between two animation events. With one you just have to read the yield time, for the other you have to find the animation then go into it.

Also, when I said “less accurate”, it was only by a few frames. task.wait() wasn’t perfect either, but it was more accurate.

1 Like

the struggles of 60 ticktrate besides that I personally use the animation events that starts the hit detection to cancel the preanimation event that handles cancelling the move so theres that and you can also run another event thats postsimulation that double checks if the move should be cancelled or not… or cancel it when the hitbox animation event is fired…