演示

实现

一次攻击过程:

思路与过程

可以先将三段攻击的完整动画按顺序放入一个Montage中,通过MontageSections使每一段动画从头到尾播完就结束:

然后将每一段攻击动画过程拆分为准备、释放、检查按键、恢复姿势四个部分,分别用notify通知(每段动画开始时默认为准备状态,所以准备的notify可以省略):

注意,图中这里才是实际的notifyName,改这里:

在准备状态按下按键,会记录起来;在释放状态按下按键或之前记录过,就在notify通知中通过设置初始section实现跳到下一段攻击动画。

代码部分

其中我将对actor的montage动画控制抽象到了一个类中(取名为AnimManager),它提供包括PlayMontage、StopMontage、JumpSectionSetPlayRate等方法,具体代码:

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
最后修改:2022 年 09 月 09 日
如果觉得我的文章对你有用,请随意赞赏