https://hctra.cn/usr/uploads/2022/09/3849803929.mp4
一次攻击过程:
可以先将三段攻击的完整动画按顺序放入一个Montage中,通过MontageSections使每一段动画从头到尾播完就结束:
然后将每一段攻击动画过程拆分为准备、释放、检查按键、恢复姿势四个部分,分别用notify通知(每段动画开始时默认为准备状态,所以准备的notify可以省略):
注意,图中这里才是实际的notifyName,改这里:
在准备状态按下按键,会记录起来;在释放状态按下按键或之前记录过,就在notify通知中通过设置初始section实现跳到下一段攻击动画。
其中我将对actor的montage动画控制抽象到了一个类中(取名为AnimManager),它提供包括PlayMontage、StopMontage、JumpSection
、SetPlayRate
等方法,具体代码:
AnimManager.lua(点击展开)
local GD = require("Global.Dispatcher")
local GEVT = require("Global/Events")
local GF = require("Global/Function")
local GE = require("Global/Enums")
local cls = {
-- 拥有者pawn
own = nil,
--播放缓存数据
mesh_ = nil,
montage_ = nil,
playRate_ = nil,
startPosition_ = nil,
startSection_ = nil,
_CompletedFunc = nil,
_BlendOutFunc = nil,
_InteruptedFunc = nil,
_NotifyBeginFunc = nil,
_NotifyEndFunc = nil,
}
function cls:ReceiveBeginPlay()
self.own = {}
end
--[[
===========================================
定时调用
===========================================
--]]
-- 延迟调用
---@param time 延迟时间
function cls:DelayCall(time, ...)
GD:PostDelay(self, self, time, GEVT.DELAY_CALL_PARAM, ...)
end
-- 定时循环调用
---@param loopTime 循环调用时间
---@param runNow 是否要马上执行一次
---@param func 循环调用的函数
function cls:LoopCall(loopTime, runNow, func)
return GF:LoopCall(self, loopTime, runNow, func)
end
function cls:OnDelayCall(func, ...)
if type(func) == "function" then
func(...)
else
print("hcLog Error OnDelayCall func type", type(func))
end
end
--[[
===========================================
蓝图调用
===========================================
--]]
function cls:LUA_OnCompleted()
if(self._CompletedFunc) then
self._CompletedFunc(self.own)
end
self.isPlayMontage_bp = false
end
function cls:LUA_OnBlendOut()
if(self._BlendOutFunc) then
self._BlendOutFunc(self.own)
end
self.isPlayMontage_bp = false
end
function cls:LUA_OnInterupted()
if(self._InteruptedFunc) then
self._InteruptedFunc(self.own)
end
end
function cls:LUA_OnNotifyBegin(notifyName)
if(self._NotifyBeginFunc) then
self._NotifyBeginFunc(notifyName)
end
end
function cls:LUA_OnNotifyEnd(notifyName)
if(self._NotifyEndFunc) then
self._NotifyEndFunc(self.own, notifyName)
end
end
--[[
===========================================
私有函数
===========================================
--]]
function cls:_clearCached()
self.mesh_ = nil
self.montage_ = nil
self.playRate_ = nil
self._CompletedFunc = nil
self._BlendOutFunc = nil
self._InteruptedFunc = nil
self._NotifyBeginFunc = nil
self._NotifyEndFunc = nil
self.isPlayMontage_bp = false
end
--[[
===========================================
公有函数
===========================================
--]]
function cls:IsPlayMontage()
return self.isPlayMontage_bp
end
function cls:PlayMontage5(mesh, montage, completedFunc, notifyBeginFunc, notifyEndFunc)
self:PlayMontage(mesh, montage, nil, nil, nil, completedFunc, nil, nil, notifyBeginFunc, notifyEndFunc)
end
function cls:PlayMontage(mesh, montage, playRate, startPosition, startSection, completedFunc, blendOutFunc, interuptedFunc, notifyBeginFunc, notifyEndFunc)
if self:IsPlayMontage() then
print("hcErr Error: 当前正在播放montage")
return
end
self:_clearCached()
if playRate == nil then
playRate = 1
end
if startPosition == nil then
startPosition = 0
end
if startSection == nil then
startSection = "None"
end
self.mesh_ = mesh
self.montage_ = montage
self.playRate_ = playRate
self.startPosition_ = startPosition
self.startSection_ = startSection
if completedFunc then
self._CompletedFunc = completedFunc
end
if blendOutFunc then
self._BlendOutFunc = blendOutFunc
end
if interuptedFunc then
self._InteruptedFunc = interuptedFunc
end
if notifyBeginFunc then
self._NotifyBeginFunc = notifyBeginFunc
end
if notifyEndFunc then
self._NotifyEndFunc = notifyEndFunc
end
self.isPlayMontage_bp = true
self:BP_PlayMontage(mesh, montage, playRate, startPosition, startSection)
end
-- 跳到指定Section
function cls:JumpSection(sectionName)
self:BP_PlayMontage(self.mesh_, self.montage_, self.playRate_, self.startPosition_, sectionName)
end
-- 设置播放速率
function cls:SetPlayRate(rate)
assert(self.montage_ ~= nil, "Error")
self.mesh_:GetAnimInstance():Montage_SetPlayRate(self.montage_, rate)
end
-- 停止当前montage动画
function cls:StopMontage()
if self.isPlayMontage_bp == false then
return
end
self.mesh_:GetAnimInstance():Montage_Stop(0, self.montage_)
self:_clearCached()
end
return cls
-- 普通三连击
function cls:NormalThreeAttack()
local own = self.own
self.attackType = CharacterEnums.AttackType.NormalFire
self.normalThreeAttackIdx = 1
own:UseEndurance(self.normalThreeAttackEndurance)
local compFunc = function()
own:ChangeNormalStatus_()
end
local nofityBeginFunc = function(name)
if name == GE.MontageNotifyName.CheckKey then
if self.saveAttackKey == true then
self.saveAttackKey = false --使用存储
self.normalThreeAttackIdx = self.normalThreeAttackIdx + 1
if own:HaveEndurance() then
own:UseEndurance(self.normalThreeAttackEndurance)
own.animManager_:JumpSection(tostring(self.normalThreeAttackIdx))
self.attackStatus = GE.MontageStatus.Ready
end
else
self.attackStatus = GE.MontageStatus.CheckKey
end
elseif name == GE.MontageNotifyName.Release then
-- 1 1.3 1.6
own:OpenAttack_(1 + (self.normalThreeAttackIdx - 1) * 0.3)
self.attackStatus = GE.MontageStatus.Release
elseif name == GE.MontageNotifyName.Recovery then
own:CloseAttack_()
if GF:VSize(own.inputXYVector_bp) > 0.1 then
own.animManager_:JumpSection("End")
end
self.attackStatus = GE.MontageStatus.Recovery
elseif name == GE.MontageNotifyName.HitGround then
if self.normalThreeAttackIdx == 3 then
GD:Post(own, nil, GEVT.CAMERA_SHAKE, GE.CameraShakeType.Attack)
end
end
end
self.attackStatus = GE.MontageStatus.Ready
own.animManager_:PlayMontage5(own["Mesh"], own.AM_normalThreeAttack123_bp, compFunc, nofityBeginFunc)
end
-- 监听按键
function cls:InputKeyPressed(key, value)
local own = self.own
-- 对于普通三连击,如果按下攻击键,且状态是CheckKey就释放下一阶段,否则如果状态是Ready或Release就存储这个键
if self.attackType == CharacterEnums.AttackType.NormalFire then
if key == GE.InputKey.NormalFire then
if self.attackStatus == GE.MontageStatus.CheckKey then
self.normalThreeAttackIdx = self.normalThreeAttackIdx + 1
if own:HaveEndurance() then
own:UseEndurance(self.normalThreeAttackEndurance)
own.animManager_:JumpSection(tostring(self.normalThreeAttackIdx))
self.attackStatus = GE.MontageStatus.Ready
end
elseif self.attackStatus == GE.MontageStatus.Release then
self.saveAttackKey = true
end
end
end
end