添加NPC攻击逻辑与动画
assets/Scripts/WoodenSkeleton/WoodenSkeletonManager.ts
判断角色是否在NPC的上下左右四个格子中;当前X坐标相等,且Y坐标相距小于等于1格,或者Y坐标相等,X坐标小于等于1格
async init() {
// 攻击检测,角色出现在NPC附近,NPC主动发起攻击
EventManager.Instance.on(EVENT_ENUM.PLAYER_MOVE_END, this.onAttack, this)
}
// NPC攻击角色检测
onAttack() {
const { x: playerX, y: playerY } = DataManager.Instance.player
// 判断角色是否在NPC的上下左右四个格子中
const disX = Math.abs(this.x - playerX)
const disY = Math.abs(this.y - playerY)
// 当前X坐标相等,且Y坐标相距小于等于1格,或者Y坐标相等,X坐标小于等于1格
if ((this.x === playerX && disY <= 1) || (this.y === playerY && disX <= 1)) {
this.state = ENTITY_STATE_ENUM.ATTACK
} else {
this.state = ENTITY_STATE_ENUM.IDLE
}
}
assets/Scripts/WoodenSkeleton/AttackSubStateMachine.ts
添加NPC攻击动画
const BASE_URL = 'texture/woodenskeleton/attack'
@ccclass('AttackSubStateMachine')
export class AttackSubStateMachine extends DirectionSubStateMachine {
constructor(fsm: StateMachine) {
super(fsm)
// NPC攻击动画
this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`))
this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`))
this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`))
this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`))
}
}
assets/Scripts/WoodenSkeleton/WoodenSkeletonStateMachine.ts
注册状态机,将攻击状态加入状态机
// 初始化参数
initParams() {
//...
this.params.set(PARAMS_NAME_ENUM.ATTACK, initParamsTrigger())
}
// 初始化状态机
initStateMachine() {
//...
// NPC打架动画
this.stateMachines.set(PARAMS_NAME_ENUM.ATTACK, new AttackSubStateMachine(this))
}
// 初始化动画
initAnimationEvent() {
this.animationComponent.on(Animation.EventType.FINISHED, () => {
// 执行完动画需要恢复默认idle动画的白名单
const whiteList = ['attack']
const name = this.animationComponent.defaultClip.name
if (whiteList.some(v => name.includes(v))) {
// 统一state入口
this.node.getComponent(EntityManager).state = ENTITY_STATE_ENUM.IDLE
// this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE)
}
})
}
run() {
//...
case this.stateMachines.get(PARAMS_NAME_ENUM.ATTACK):
if (this.params.get(PARAMS_NAME_ENUM.ATTACK).value) {
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.ATTACK)
} else if...
}
添加角色死亡逻辑与动画
assets/Scripts/WoodenSkeleton/WoodenSkeletonManager.ts
在NPC攻击处通知角色死亡
// ...
// NPC攻击角色检测
onAttack() {
const { x: playerX, y: playerY, state: playState } = DataManager.Instance.player
// 判断角色是否在NPC的上下左右四个格子中
const disX = Math.abs(this.x - playerX)
const disY = Math.abs(this.y - playerY)
// 当前X坐标相等,且Y坐标相距小于等于1格,或者Y坐标相等,X坐标小于等于1格,且判断当前角色不是死亡状态,不然会出现鞭尸现象
if (
((this.x === playerX && disY <= 1) || (this.y === playerY && disX <= 1)) &&
playState !== ENTITY_STATE_ENUM.DEATH &&
playState !== ENTITY_STATE_ENUM.AIRDEATH
) {
this.state = ENTITY_STATE_ENUM.ATTACK
// 通知角色,在地面死亡
EventManager.Instance.emit(EVENT_ENUM.ATTACK_PLAYER, ENTITY_STATE_ENUM.DEATH)
} else {
this.state = ENTITY_STATE_ENUM.IDLE
}
}
// ...
assets/Scripts/Player/PlayerManager.ts
注册角色死亡事件
// ...
async init() {
//...
EventManager.Instance.on(EVENT_ENUM.ATTACK_PLAYER, this.onDead, this)
}
// 角色死亡,直接给状态即可
onDead(type: ENTITY_STATE_ENUM) {
this.state = type
}
inputHandler(inputDirection: CONTROLLER_ENUM) {
// 死亡之后不可移动
if (this.state === ENTITY_STATE_ENUM.DEATH || this.state === ENTITY_STATE_ENUM.AIRDEATH) {
return
}
// ...
}
assets/Scripts/Player/DeathSubStateMachine.ts
添加角色死亡动画
const BASE_URL = 'texture/player/death'
@ccclass('DeathSubStateMachine')
export class DeathSubStateMachine extends DirectionSubStateMachine {
constructor(fsm: StateMachine) {
super(fsm)
// 角色死亡动画
this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`))
this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`))
this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`))
this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`))
}
}
assets/Scripts/Player/PlayerStateMachine.ts
注册角色死亡动画
// ...
// 初始化参数
initParams() {
//...
this.params.set(PARAMS_NAME_ENUM.DEATH, initParamsTrigger())
}
// 初始化状态机
initStateMachine() {
//...
// 死亡
this.stateMachines.set(PARAMS_NAME_ENUM.DEATH, new DeathSubStateMachine(this))
}
run() {
case this.stateMachines.get(PARAMS_NAME_ENUM.DEATH):
if (this.params.get(PARAMS_NAME_ENUM.DEATH).value) {
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.DEATH)
} else if//...
}
添加人物攻击与动画
assets/Scripts/Player/PlayerManager.ts
攻击逻辑,当前方向与下一步方向相同,且前方一格是NPC,则发动攻击
//...
inputHandler{
//...
//当前是否可以攻击NPC
if (this.willAttack(inputDirection)) {
return
}
//...
}
// 判断攻击敌人,与碰撞检测类似
willAttack(type: CONTROLLER_ENUM): boolean {
// 所有NPC
const enemies = DataManager.Instance.enemies
for (let i = 0; i < enemies.length; i++) {
const { x: enemyX, y: enemyY } = enemies[i]
// 如果当前方向朝上,且下一步也是往上走,NPC的X坐标与角色X坐标相等,在同一条线上,且NPC的Y坐标在角色Y坐标上2格,也就是在兵器的前一格,那么触发攻击
if (
type === CONTROLLER_ENUM.TOP &&
this.direction === DIRECTION_ENUM.TOP &&
enemyX === this.x &&
enemyY === this.targetY - 2
) {
this.state = ENTITY_STATE_ENUM.ATTACK
return true
} else if (
type === CONTROLLER_ENUM.BOTTOM &&
this.direction === DIRECTION_ENUM.BOTTOM &&
enemyX === this.x &&
enemyY === this.targetY + 2
) {
this.state = ENTITY_STATE_ENUM.ATTACK
return true
} else if (
type === CONTROLLER_ENUM.LEFT &&
this.direction === DIRECTION_ENUM.LEFT &&
enemyY === this.targetY &&
enemyX === this.targetX - 2
) {
this.state = ENTITY_STATE_ENUM.ATTACK
return true
} else if (
type === CONTROLLER_ENUM.RIGHT &&
this.direction === DIRECTION_ENUM.RIGHT &&
enemyY === this.targetY &&
enemyX === this.targetX + 2
) {
this.state = ENTITY_STATE_ENUM.ATTACK
return true
}
}
return false
}
assets/Scripts/Player/AttackSubStateMachine.ts
添加人物攻击动画
const BASE_URL = 'texture/player/attack'
@ccclass('AttackSubStateMachinePlayer')
export class AttackSubStateMachinePlayer extends DirectionSubStateMachine {
constructor(fsm: StateMachine) {
super(fsm)
// 人物攻击动画
this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`))
this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`))
this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`))
this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`))
}
}
assets/Scripts/Player/PlayerStateMachine.ts
注册攻击动画
// ...
// 初始化参数
initParams() {
//...
this.params.set(PARAMS_NAME_ENUM.ATTACK, initParamsTrigger())
}
// 初始化状态机
initStateMachine() {
//...
// 死亡
this.stateMachines.set(PARAMS_NAME_ENUM.ATTACK, new AttackSubStateMachinePlayer(this))
}
run() {
case this.stateMachines.get(PARAMS_NAME_ENUM.ATTACK):
if (this.params.get(PARAMS_NAME_ENUM.ATTACK).value) {
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.ATTACK)
} else if//...
}
添加NPC死亡逻辑与动画
assets/Base/EntityManager.ts
在实体类中加上一个随机id,人物,NPC都将拥有一个id
///...
export class EntityManager extends Component {
id: string = randomString(12)
//...
}
// 生成随机字符串
export const randomString = (length: number): string => {
const str = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
let result = ''
for (let i = length; i > 0; --i) result += str[Math.floor(Math.random() * str.length)]
return result
}
assets/Scripts/Player/PlayerManager.ts
添加攻击后NPC死亡的逻辑
//...
inputHandler(inputDirection: CONTROLLER_ENUM) {
//...
//当前是否可以攻击NPC
const attackId = this.willAttack(inputDirection)
if (attackId) {
// 通知NPC去世
EventManager.Instance.emit(EVENT_ENUM.ATTACK_ENEMY, attackId)
return
}
// 当前人物死亡||人物没渲染||当前状态是攻击
if (
this.state === ENTITY_STATE_ENUM.DEATH ||
this.state === ENTITY_STATE_ENUM.AIRDEATH ||
this.state === ENTITY_STATE_ENUM.ATTACK
) {
return
}
//...
}
willAttack(type: CONTROLLER_ENUM): string {
//...
for (let i = 0; i < enemies.length; i++) {
// 如果NPC已死亡,跳过攻击
if (enemies[i].state === ENTITY_STATE_ENUM.DEATH) {
continue
}
//...
const { x: enemyX, y: enemyY, id: enemyId } = enemies[i]
//...
//在每一个攻击情况下,都返回NPC的id
return enemyId
//...
return ''
}
assets/Scripts/WoodenSkeleton/WoodenSkeletonManager.ts
注册NPC死亡动画
//...
async init() {
//...
// 死亡
EventManager.Instance.on(EVENT_ENUM.ATTACK_ENEMY, this.onDead, this)
}
// NPC朝向角色检测
onChangeDirection(isInit?: boolean) {
if (this.state === ENTITY_STATE_ENUM.DEATH || !DataManager.Instance.player) {
// 已死亡
return
}
//...
}
// NPC攻击角色检测
onAttack() {
if (this.state === ENTITY_STATE_ENUM.DEATH || !DataManager.Instance.player) {
// 已死亡
return
}
//...
}
// NPC死亡
onDead(attackId: string) {
if (this.state === ENTITY_STATE_ENUM.DEATH) {
return
}
// 判断当前NPC死亡才死亡
if (this.id === attackId) {
this.state = ENTITY_STATE_ENUM.DEATH
}
}
assets/Scripts/WoodenSkeleton/DeathSubStateMachineWooden.ts
添加NPC死亡动画
const BASE_URL = 'texture/woodenskeleton/death'
@ccclass('DeathSubStateMachineWooden')
export class DeathSubStateMachineWooden extends DirectionSubStateMachine {
constructor(fsm: StateMachine) {
super(fsm)
// NPC死亡动画
this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`))
this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`))
this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`))
this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`))
}
}
渲染门以及门打开逻辑
assets/Scripts/Door/DeathSubStateMachineDoor.ts
门打开动画-代码与其他动画代码几乎一样,改一个BASE_URL即可,代码略
assets/Scripts/Door/IdleSubStateMachineDoor.ts
门关闭动画-代码略
assets/Scripts/Door/DoorManager.ts
添加开门事件与逻辑,当所有NPC都死亡时,门打开
@ccclass('DoorManager')
export class DoorManager extends EntityManager {
async init() {
this.fsm = this.addComponent(DoorStateMachine)
await this.fsm.init()
super.init({
x: 7,
y: 8,
type: ENTITY_TYPE_ENUM.PLAYER,
direction: DIRECTION_ENUM.TOP, // 设置初始方向
state: ENTITY_STATE_ENUM.IDLE, // 设置fsm化动画
})
EventManager.Instance.on(EVENT_ENUM.DOOR_OPEN, this.onOpen, this)
}
// 解绑事件
onDestroy() {
super.onDestroy()
EventManager.Instance.off(EVENT_ENUM.DOOR_OPEN, this.onOpen)
}
onOpen() {
// 门还在,所有NPC都已经死亡,就让门消失
if (
this.state !== ENTITY_STATE_ENUM.DEATH &&
DataManager.Instance.enemies.every(enemy => enemy.state === ENTITY_STATE_ENUM.DEATH)
)
this.state = ENTITY_STATE_ENUM.DEATH
}
}
assets/Scripts/Door/DoorStateMachine.ts
注册门打开关闭动画,与其他的注册方式一样
@ccclass('DoorStateMachine')
export class DoorStateMachine extends StateMachine {
resetTrigger() {
for (const [_, value] of this.params) {
if (value.type === FSM_PARAMS_TYPE_ENUM.TRIGGER) {
value.value = false
}
}
}
// 初始化参数
initParams() {
this.params.set(PARAMS_NAME_ENUM.IDLE, initParamsTrigger())
this.params.set(PARAMS_NAME_ENUM.DIRECTION, initParamsNumber())
this.params.set(PARAMS_NAME_ENUM.DEATH, initParamsTrigger())
}
// 初始化状态机
initStateMachine() {
// 门初始化
this.stateMachines.set(PARAMS_NAME_ENUM.IDLE, new IdleSubStateMachineDoor(this))
// 门消失
this.stateMachines.set(PARAMS_NAME_ENUM.DEATH, new DeathSubStateMachineDoor(this))
}
// 初始化动画
initAnimationEvent() {}
async init() {
// 添加动画组件
this.animationComponent = this.addComponent(Animation)
this.initParams()
this.initStateMachine()
this.initAnimationEvent()
// 确保资源资源加载
await Promise.all(this.waitingList)
}
run() {
switch (this.currentState) {
case this.stateMachines.get(PARAMS_NAME_ENUM.IDLE):
case this.stateMachines.get(PARAMS_NAME_ENUM.DEATH):
if (this.params.get(PARAMS_NAME_ENUM.DEATH).value) {
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.DEATH)
} else if (this.params.get(PARAMS_NAME_ENUM.IDLE).value) {
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE)
} else {
// 为了触发子状态机的改变
this.currentState = this.currentState
}
break
default:
this.currentState = this.stateMachines.get(PARAMS_NAME_ENUM.IDLE)
}
}
}
assets/Scripts/Player/PlayerManager.ts
触发事件,每当NPC去世时触发检测
//...
inputHandler(inputDirection: CONTROLLER_ENUM) {
if (this.isMoving) {
return
}
//当前是否可以攻击NPC
const attackId = this.willAttack(inputDirection)
if (attackId) {
// 通知NPC去世
EventManager.Instance.emit(EVENT_ENUM.ATTACK_ENEMY, attackId)
// 通知判断门是否隐藏
EventManager.Instance.emit(EVENT_ENUM.DOOR_OPEN)
return
}
//...
}
本节源码地址: