cocos2dx 3d开源项目 fantasyWarrior3D 从零走起 5 [角色基类actor & AI实现]
1. 构造对象
从actor.lua中可以看到一些“面向对象”概念的实现,下面分别从基本属性的“继承”、动作的“多态”以及成员变量的拷贝三个方面进行详细介绍。
(1)基本属性的“继承”
Knight = class("Knight", function()
return require "Actor".create()
end)
Knight类在actor对象创建完成后重新定义了构造函数ctor(),这样不会影响基类ctor()的调用。这种方式实现了基本属性的继承,使得Knight类可以复用Actor类的基本属性和方法。
(2)动作的“多态”
Knight._action = {
idle = createAnimation(file, 267, 283, 0.7),
walk = createAnimation(file, 227, 246, 0.7),
attack1 = createAnimation(file, 103, 129, 0.7),
attack2 = createAnimation(file, 130, 154, 0.7),
specialattack1 = createAnimation(file, 160, 190, 0.3),
specialattack2 = createAnimation(file, 191, 220, 0.4),
defend = createAnimation(file, 92, 96, 0.7),
knocked = createAnimation(file, 254, 260, 0.7),
dead = createAnimation(file, 0, 77, 1)
}
在执行动作时,需要使用clone()方法,原因是actor中_action的内容会随着子类对象的创建被重新赋值。以下是使用clone()的代码示例:
self._curAnimation3d = self._action[name]:clone()
self._sprite3d:runAction(self._curAnimation3d)
(3)通过拷贝配置来获取自己独立的一套成员变量
copyTable(ActorDefaultValues, self)
copyTable(ActorCommonValues, self)
通过这两行代码,将ActorDefaultValues和ActorCommonValues中的配置信息拷贝到当前对象中,从而让对象拥有自己独立的成员变量。
2. 特效的实现
(1)添加被攻击特效
通过addEffect函数可以给角色身上添加特效,模拟被攻击的效果。
(2)添加跑起来的烟尘
initPuff()函数用于添加角色跑起来时的烟尘特效,增强角色的动态表现。
(3)实现影子特效
initShadow()函数用于实现角色的影子特效。角色属性_circle决定影子的大小,通过图片和透明度的设置来实现逼真的影子效果。
3. 角色动作实现,位置,朝向
(1)角色播放一个动作
function Actor:playAnimation(name, loop)
_action表中的动作由继承者们在各自的文件中实现。该函数用于播放指定名称的动画,并且可以设置是否循环播放。
(2)设置朝向
function Actor:setFacing(degrees)
self._curFacing = DEGREES_TO_RADIANS(degrees)
self._targetFacing = self._curFacing
self:setRotation(degrees)
end
_curFacing:表示当前朝向,用于攻击时提供发射方向。例如,在创建碰撞体时会用到该属性:BasicCollider.create(self._myPos, self._curFacing, self._normalAttack)_targetFacing:表示正对目标的朝向。可以通过以下方式计算:self._targetFacing = cc.pToAngleSelf(cc.pSub(p2, p1))
function cc.pToAngleSelf(self) return math.atan2(self.y, self.x) end
### (3)角色移动和转向
function Actor:movementUpdate(dt)
该函数在每一帧都会被调用,因为需要频繁调整角色的朝向。具体实现分为转向和加减速移动两个部分:
#### [1] 转向
不论目标角色在哪个方位,角色只需朝左或者朝右转动一个小于180度的角度即可完成转向。为了避免角色兜圈子,可以把右转`180 + N`度转换为向左转`360 – (180 + N)`度。以下是判断向左还是向右转的代码:
if self._curFacing ~= self._targetFacing then local angleDt = self._curFacing - self._targetFacing angleDt = angleDt % (math.pi 2) local turnleft = (angleDt - math.pi) < 0 local turnby = self._turnSpeed dt
if turnby > angleDt then self._curFacing = self._targetFacing elseif turnleft then self._curFacing = self._curFacing - turnby else self._curFacing = self._curFacing + turnby end
self:setRotation(-RADIANS_TO_DEGREES(self._curFacing)) end
#### [2] 加减速移动
角色属性`_speed`决定最大速度,`_acceleration`决定加速度。滑行距离可以通过公式`S = (Vt^2 - Vo^2) / (2a)`计算。为了更精确地避免“撞上”目标,可以略微调整攻击距离:
local attackDistance = self._attackRange + self._target._radius + S
### (4)受到伤害
Actor:hurt(collider, dirKnockMode)
该函数的参数包括攻击者和是否带有敲打效果,用于处理角色受到伤害的逻辑。
## 4. 状态机 和 AI
### (1)状态机中5种人物状态
状态机中有5种人物状态,分别由以下函数激活,并且负责执行相应的动作:
- `idleMode`:空闲状态
- `walkMode`:行走状态
- `attackMode`:攻击状态
- `knockMode`:敲飞状态
- `dyingMode`:死亡状态
以下是`dyingMode`和`knockMode`的具体实现:
#### `dyingMode`
function Actor:dyingMode(knockSource, knockAmount) -- 3秒后回收到pool的操作 local function recycle() self:setVisible(false) List.pushlast(getPoolByName(self._name), self) end -- 敲飞效果相关代码 end
#### `knockMode`
function Actor:knockMode(collider, dirKnockMode) -- 实际上就是对角色做一个位移,位移的距离取决于攻击属性的knock end
### (2)状态机的帧循环
function Actor:stateMachineUpdate(dt)
该函数负责执行相应状态的逻辑行为,确保状态机的正常运转。
### (3)`Actor:AI()`
该函数和`stateMachineUpdate`共同完成了状态机的正常运转。主要负责根据当前的状态选择下一步行动,并激活相应的状态。`Actor:baseUpdate(dt)`负责调用`AI()`函数,执行频率由配置表中的`_AIFrequency`决定。英雄的执行频率通常在1 - 1.3秒,NPC的执行频率在3 - 5秒,因为英雄的逻辑行为更丰富一些。
需要注意的是,AI计算的频率高可以减少角色“傻掉”的时间,但频繁调用会影响性能,所以需要折中考虑。有时候,在攻击间隙会执行`idle action`,但角色依然处在攻击状态。同时,画了一个大概的AI流程,其中圆圈部分由状态机`stateMachineUpdate`执行。