• cocos小游戏实战-04-碰撞检测与NPC渲染


    碰撞检测

    https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220719101335.png

    assets/Scripts/Tile/TileManager.ts

    在设置人物移动的地方加上判断当前位置是否可移动与转向的逻辑

    import { _decorator, Component, Sprite, SpriteFrame, UITransform } from 'cc'
    import { TILE_TYPE_ENUM } from '../../Enum'
    
    const { ccclass, property } = _decorator
    
    export const TILE_WIDTH = 55
    export const TILE_HEIGHT = 55
    
    @ccclass('TileManager')
    export class TileManager extends Component {
      type: TILE_TYPE_ENUM
      moveable: boolean //可走
      turnable: boolean //可转向
      async init(type: TILE_TYPE_ENUM, spriteFrame: SpriteFrame, i: number, j: number) {
        this.type = type
        // 墙壁
        const wallet: Array<TILE_TYPE_ENUM> = [
          TILE_TYPE_ENUM.WALL_COLUMN,
          TILE_TYPE_ENUM.WALL_ROW,
          TILE_TYPE_ENUM.WALL_LEFT_TOP,
          TILE_TYPE_ENUM.WALL_RIGHT_TOP,
          TILE_TYPE_ENUM.WALL_LEFT_BOTTOM,
          TILE_TYPE_ENUM.WALL_RIGHT_BOTTOM,
        ]
        // 悬崖
        const cliff: Array<TILE_TYPE_ENUM> = [
          TILE_TYPE_ENUM.CLIFF_CENTER,
          TILE_TYPE_ENUM.CLIFF_LEFT,
          TILE_TYPE_ENUM.CLIFF_RIGHT,
        ]
        if (wallet.indexOf(this.type) !== -1) {
          // 当前是墙壁,不可走也不可旋转
          this.moveable = false
          this.turnable = false
        } else if (cliff.indexOf(this.type) !== -1) {
          // 当前是悬崖 不可走 可转
          this.moveable = false
          this.turnable = true
        } else if (this.type === TILE_TYPE_ENUM.FLOOR) {
          // 当前是地板 可走 可转
          this.moveable = true
          this.turnable = true
        }
        const sprite = this.addComponent(Sprite)
        sprite.spriteFrame = spriteFrame
        const transform = this.getComponent(UITransform)
        transform.setContentSize(TILE_WIDTH, TILE_HEIGHT)
        this.node.setPosition(i * TILE_WIDTH, -j * TILE_HEIGHT)
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    assets/Runtime/DataManager.ts

    给数据中心单例加上位置信息,位置类型,包括是否可转向的信息

    import Singleton from '../Base/Singleton'
    import { ITile } from '../Levels'
    import { TileManager } from '../Scripts/Tile/TileManager'
    /**
     * 单例模式
     * 当前渲染的地图数据
     */
    export default class DataManager extends Singleton {
      static get Instance() {
        return super.GetInstance<DataManager>()
      }
      mapInfo: Array<Array<ITile>> = [] // 地图数据
      tileInfo: Array<Array<TileManager>> //当前位置信息,当前位置类型,是否可走可转
      mapRowCount: number = 0 //行数
      mapColumnCount: number = 0 //列数
      levelIndex: number = 1 // 当前关卡
      reset() {
        this.mapInfo = []
        this.tileInfo = []
        this.mapColumnCount = 0
        this.mapRowCount = 0
      }
    }
    export const DataManagerInstance = new DataManager()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    assets/Scripts/Tile/TileMapManager.ts

    在渲染地图时,将瓦片信息存储到数据中心中的瓦片信息中,DataManager.Instance.tileInfo

    @ccclass('TileMapManager')
    export class TileMapManager extends Component {
      async init() {
        // 从数据中心取出
        const { mapInfo } = DataManager.Instance
        // 加载资源
        const spriteFrames = await ResourceManager.Instance.loadDir('texture/tile/tile')
        DataManager.Instance.tileInfo = []
        for (let i = 0; i < mapInfo.length; i++) {
          DataManager.Instance.tileInfo[i] = []
          for (let j = 0; j < mapInfo[i].length; j++) {
            const item = mapInfo[i][j]
            if (item.src === null || item.type === null) {
              continue
            }
            const node = createUINode()
            let srcNumber = item.src
            // 指定渲染随机瓦片,并且加条件,偶数的瓦片才随机
            if ((srcNumber === 1 || srcNumber === 5 || srcNumber === 9) && i % 2 === 0 && j % 2 === 0) {
              srcNumber += randomByRange(0, 4)
            }
            const spriteFrame = spriteFrames.find(v => v.name === `tile (${srcNumber})`) || spriteFrames[0]
            const tileManager = node.addComponent(TileManager)
            const type = item.type
            tileManager.init(type, spriteFrame, i, j)
            DataManager.Instance.tileInfo[i][j] = tileManager
            node.setParent(this.node)
          }
        }
      }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    assets/Scripts/Player/PlayerManager.ts

    在人物移动的地方加上碰撞检测,碰撞包括了人物碰撞,兵器碰撞,左右转向碰撞

    并在碰撞的时候加上碰撞动画,碰撞动画与转向动画用法一样

    // 碰撞检测
      willBlock(inputDirection: CONTROLLER_ENUM): boolean {
        /**
         * 移动:需要判断当前所处4个方向,且判断下一步四个移动方向
         * 转向:左右两边需要判断当前所处方向,且判断下一步转向方向
         */
    
        const { targetX, targetY, direction } = this
        const { tileInfo } = DataManager.Instance
        // 输入方向向上
        if (inputDirection === CONTROLLER_ENUM.TOP) {
          // 人物下一个位置
          let playerNextY = targetY
          const playerNextX = targetX
          // 枪的下一个位置
          let weaponNextY = targetY
          let weaponNextX = targetX
    
          // 预测下一个位置
          if (direction === DIRECTION_ENUM.TOP) {
            // 当前方向向上
            playerNextY = targetY - 1
            weaponNextY = targetY - 2
          } else if (direction === DIRECTION_ENUM.LEFT) {
            // 当前方向向左
            playerNextY = targetY - 1
            weaponNextY = targetY - 1
            weaponNextX = targetX - 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            // 当前方向向右
            playerNextY = targetY - 1
            weaponNextY = targetY - 1
            weaponNextX = targetX + 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            // 当前方向向下
            playerNextY = targetY - 1
          }
          // 判断走出地图
          if (playerNextY < 0) {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
          const playerTile = tileInfo[playerNextX][playerNextY]
          const weaponTile = tileInfo[weaponNextX][weaponNextY]
          // 人物不可以移动&不可以转向
          if (!(playerTile && playerTile.moveable && (!weaponTile || weaponTile.turnable))) {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
        }
        if (inputDirection === CONTROLLER_ENUM.BOTTOM) {
          let playerNextY = targetY
          let playerNextX = targetX
          let weaponNextY = targetY
          let weaponNextX = targetX
    
          if (direction === DIRECTION_ENUM.TOP) {
            playerNextY = targetY + 1
          } else if (direction === DIRECTION_ENUM.LEFT) {
            playerNextY = targetY + 1
            weaponNextY = targetY + 1
            weaponNextX = targetX - 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            playerNextX = targetX + 1
            weaponNextY = targetY + 1
            weaponNextX = targetX + 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            playerNextY = targetY + 1
            weaponNextY = targetY + 2
          }
          if (playerNextY > tileInfo.length) {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
          const playerTile = tileInfo[playerNextX][playerNextY]
          const weaponTile = tileInfo[weaponNextX][weaponNextY]
          // 人物不可以移动&不可以转向
          if (!(playerTile && playerTile.moveable && (!weaponTile || weaponTile.turnable))) {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
        }
        if (inputDirection === CONTROLLER_ENUM.LEFT) {
          const playerNextY = targetY
          let playerNextX = targetX
          let weaponNextY = targetY
          let weaponNextX = targetX
    
          if (direction === DIRECTION_ENUM.TOP) {
            weaponNextY = targetY - 1
            weaponNextX = targetX - 1
            playerNextX = targetX - 1
          } else if (direction === DIRECTION_ENUM.LEFT) {
            weaponNextX = targetX - 2
            playerNextX = targetX - 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            playerNextX = targetX - 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            weaponNextY = targetY + 1
            weaponNextX = targetX - 1
            playerNextX = targetX - 1
          }
          if (playerNextX < 0) {
            this.state = ENTITY_STATE_ENUM.BLOCK_TURN_LEFT
            return true
          }
          const playerTile = tileInfo[playerNextX][playerNextY]
          const weaponTile = tileInfo[weaponNextX][weaponNextY]
          if (!(playerTile && playerTile.moveable && (!weaponTile || weaponTile.turnable))) {
            this.state = ENTITY_STATE_ENUM.BLOCK_TURN_LEFT
            return true
          }
        }
        if (inputDirection === CONTROLLER_ENUM.RIGHT) {
          let playerNextY = targetY
          let playerNextX = targetX
          let weaponNextY = targetY
          let weaponNextX = targetX
    
          if (direction === DIRECTION_ENUM.TOP) {
            weaponNextY = targetY - 1
            weaponNextX = targetX + 1
            playerNextY = targetY - 1
          } else if (direction === DIRECTION_ENUM.LEFT) {
            weaponNextX = targetX + 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            weaponNextX = targetX + 2
            playerNextX = targetX + 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            playerNextX = targetX + 1
            weaponNextX = targetY + 1
            weaponNextY = targetY + 1
          }
          if (playerNextX > tileInfo[0].length) {
            this.state = ENTITY_STATE_ENUM.BLOCK_TURN_LEFT
            return true
          }
          const playerTile = tileInfo[playerNextX][playerNextY]
          const weaponTile = tileInfo[weaponNextX][weaponNextY]
          if (!(playerTile && playerTile.moveable && (!weaponTile || weaponTile.turnable))) {
            this.state = ENTITY_STATE_ENUM.BLOCK_TURN_LEFT
            return true
          }
        }
        if (inputDirection === CONTROLLER_ENUM.TURN_LEFT) {
          // 方向左转,判断方向对角位置和方向位置是否可转向
          // 对角
          let nextX1 = targetX
          let nextY1 = targetY
          // 侧边
          let nextX2 = targetX
          let nextY2 = targetY
          if (direction === DIRECTION_ENUM.TOP) {
            // 如果当前角色面朝上,需要获取左边和左上角两块位置
            nextX1 = targetX - 1
            nextY1 = targetY - 1
            nextX2 = targetX - 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            nextX1 = targetX + 1
            nextY1 = targetY + 1
            nextX2 = targetX + 1
          } else if (direction === DIRECTION_ENUM.LEFT) {
            nextX1 = targetX - 1
            nextY1 = targetY + 1
            nextY2 = targetY + 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            nextX1 = targetX + 1
            nextY1 = targetY - 1
            nextY2 = targetY - 1
          }
          // 没有瓦片或者可以转弯
          if (
            (!tileInfo[nextX1][nextY1] || tileInfo[nextX1][nextY1].turnable) &&
            (!tileInfo[nextX2][nextY2] || tileInfo[nextX2][nextY2].turnable)
          ) {
            //
          } else {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
        }
        if (inputDirection === CONTROLLER_ENUM.TURN_RIGHT) {
          // 方向右转,判断方向对角位置和方向位置是否可转向
          // 对角
          let nextX1 = targetX
          let nextY1 = targetY
          // 侧边
          let nextX2 = targetX
          let nextY2 = targetY
          if (direction === DIRECTION_ENUM.TOP) {
            // 如果当前角色面朝上,需要获取左边和左上角两块位置
            nextX1 = targetX + 1
            nextY1 = targetY - 1
            nextX2 = targetX + 1
          } else if (direction === DIRECTION_ENUM.BOTTOM) {
            nextX1 = targetX - 1
            nextY1 = targetY + 1
            nextX2 = targetX - 1
          } else if (direction === DIRECTION_ENUM.LEFT) {
            nextY1 = targetY - 1
            nextX1 = targetX - 1
            nextY2 = targetY - 1
          } else if (direction === DIRECTION_ENUM.RIGHT) {
            nextX1 = targetX + 1
            nextY1 = targetY + 1
            nextY2 = targetY + 1
          }
          // 没有瓦片或者可以转弯
          if (
            (!tileInfo[nextX1][nextY1] || tileInfo[nextX1][nextY1].turnable) &&
            (!tileInfo[nextX2][nextY2] || tileInfo[nextX2][nextY2].turnable)
          ) {
            //
          } else {
            this.state = ENTITY_STATE_ENUM.BLOCK_FRONT
            return true
          }
        }
        return false
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220

    assets/Scripts/Player/PlayerManager.ts

    移动的地方判断碰撞

    inputHandler(inputDirection: CONTROLLER_ENUM) {
        if (this.willBlock(inputDirection)) {
          return
        }
        this.move(inputDirection)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    碰撞检测总结

    1、将瓦片信息在初始化时存储每一块瓦片是否可转向,可移动

    2、在人物移动的地方判断移动方向和当前朝向,再通过判断下一动作的瓦片在瓦片信息中是否可以移动,并在判断的时候播放碰撞动画

    实现NPC渲染

    assets/Scripts/WoodenSkeleton/WoodenSkeletonStateMachine.ts

    添加一个状态机,与player几乎一样,将移动动画删除即可

    @ccclass('WoodenSkeletonStateMachine')
    export class WoodenSkeletonStateMachine 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())
      }
    
      // 初始化状态机
      initStateMachine() {
        // NPC动画,无限播放
        this.stateMachines.set(PARAMS_NAME_ENUM.IDLE, new IdleSubStateMachineWooden(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):
            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)
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    assets/Scripts/WoodenSkeleton/IdleSubStateMachineWooden.ts

    添加NPC资源动画,所有IdleSubStateMachine 都类似,只是加载了不同的路径

    const BASE_URL = 'texture/woodenskeleton/idle'
    @ccclass('IdleSubStateMachineWooden')
    export class IdleSubStateMachineWooden extends DirectionSubStateMachine {
      constructor(fsm: StateMachine) {
        super(fsm)
        // NPC动画,无限播放
        this.stateMachines.set(DIRECTION_ENUM.TOP, new State(fsm, `${BASE_URL}/top`, AnimationClip.WrapMode.Loop))
        this.stateMachines.set(DIRECTION_ENUM.BOTTOM, new State(fsm, `${BASE_URL}/bottom`, AnimationClip.WrapMode.Loop))
        this.stateMachines.set(DIRECTION_ENUM.LEFT, new State(fsm, `${BASE_URL}/left`, AnimationClip.WrapMode.Loop))
        this.stateMachines.set(DIRECTION_ENUM.RIGHT, new State(fsm, `${BASE_URL}/right`, AnimationClip.WrapMode.Loop))
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    assets/Scripts/WoodenSkeleton/WoodenSkeletonManager.ts

    加载NPC入口,与player也是基本一样的、NPC目前不能移动,将移动与碰撞检测干掉就可以了

    @ccclass('WoodenSkeletonStateManager')
    export class WoodenSkeletonStateManager extends EntityManager {
      async init() {
        this.fsm = this.addComponent(WoodenSkeletonStateMachine)
        await this.fsm.init()
    
        super.init({
          x: 7,
          y: 7,
          type: ENTITY_TYPE_ENUM.PLAYER,
          direction: DIRECTION_ENUM.TOP, // 设置初始方向
          state: ENTITY_STATE_ENUM.IDLE, // 设置fsm化动画
        })
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    assets/Scripts/Scene/BattleManager.ts

    将NPC添加到地图上

    start() {
    	this.generateEnemies()
    }
    
    // 创建NPC
    generateEnemies() {
      const woodenSkeleton = createUINode()
      woodenSkeleton.setParent(this.stage)
      const woodenSkeletonManager = woodenSkeleton.addComponent(WoodenSkeletonStateManager)
      woodenSkeletonManager.init()
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220720152805.png

    实现NPC朝向人物

    https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220721113929.png

    assets/Runtime/DataManager.ts

    在数据中心加上人物以及NPC的信息

    /**
     * 单例模式
     * 当前渲染的地图数据
     */
    export default class DataManager extends Singleton {
      static get Instance() {
        return super.GetInstance<DataManager>()
      }
      mapInfo: Array<Array<ITile>> = [] // 地图数据
      tileInfo: Array<Array<TileManager>> //当前位置信息,当前位置类型,是否可走可转
      mapRowCount: number = 0 //行数
      mapColumnCount: number = 0 //列数
      levelIndex: number = 1 // 当前关卡
      player: PlayerManager // 当前人物信息
      enemies: WoodenSkeletonStateManager[] // NPC信息
      reset() {
        this.mapInfo = []
        this.tileInfo = []
        this.mapColumnCount = 0
        this.mapRowCount = 0
        this.player = null
        this.enemies = []
      }
    }
    export const DataManagerInstance = new DataManager()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    assets/Scripts/WoodenSkeleton/WoodenSkeletonManager.ts

    NPC朝向逻辑

    取人物位置与NPC位置,以NPC为原点判断人物所在象限,根据靠近±XY轴判断NPC朝向不同的角度

    @ccclass('WoodenSkeletonStateManager')
    export class WoodenSkeletonStateManager extends EntityManager {
      async init() {
        this.fsm = this.addComponent(WoodenSkeletonStateMachine)
        await this.fsm.init()
    
        super.init({
          x: 7,
          y: 7,
          type: ENTITY_TYPE_ENUM.PLAYER,
          direction: DIRECTION_ENUM.TOP, // 设置初始方向
          state: ENTITY_STATE_ENUM.IDLE, // 设置fsm化动画
        })
        // 角色创建完成 或者 角色移动 触发更新NPC方向
        EventManager.Instance.on(EVENT_ENUM.PLAYER_MOVE_END, this.onChangeDirection, this)
        EventManager.Instance.on(EVENT_ENUM.PLAYER_BORN, this.onChangeDirection, this)
      }
      onChangeDirection(isInit?: boolean) {
        if (!DataManager.Instance.player) {
          return
        }
        const { x: playerX, y: playerY } = DataManager.Instance.player
        const disX = Math.abs(this.x - playerX)
        const disY = Math.abs(this.y - playerY)
        // 如果disY = disX,表示在某个象限夹角处,如果 disY > disX 靠近Y轴,如果 disY < disX 则靠近X轴
    
        // 当人物在移动为对角线的时候,NPC不做转向操作
        if (disX === disY && !isInit) {
          return
        }
    
        if (playerX >= this.x && playerY <= this.y) {
          // 在第一象限
          if (disY > disX) {
            // 在第一象限0~45°夹角中 靠近Y轴,朝上
            this.direction = DIRECTION_ENUM.TOP
          } else {
            // 在第一象限45~90°夹角中 靠近X轴,朝右
            this.direction = DIRECTION_ENUM.RIGHT
          }
        } else if (playerX <= this.x && playerY <= this.y) {
          // 在第二象限
          if (disY > disX) {
            // 第二象限靠近Y轴,向上
            this.direction = DIRECTION_ENUM.TOP
          } else {
            // 第二象限靠近X轴,向左
            this.direction = DIRECTION_ENUM.LEFT
          }
        } else if (playerX <= this.x && playerY >= this.y) {
          // 在第三象限
          this.direction = disY > disX ? DIRECTION_ENUM.BOTTOM : DIRECTION_ENUM.LEFT
        } else if (playerX >= this.x && playerY >= this.y) {
          // 在第四象限
          this.direction = disY > disX ? DIRECTION_ENUM.BOTTOM : DIRECTION_ENUM.RIGHT
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    assets/Scripts/Scene/BattleManager.ts

    在人物初始化时触发PLAYER_BORN事件,

    // 创建人物
      async generatePlayer() {
        const player = createUINode()
        player.setParent(this.stage)
        const playerManager = player.addComponent(PlayerManager)
        await playerManager.init()
        DataManager.Instance.player = playerManager
        EventManager.Instance.emit(EVENT_ENUM.PLAYER_BORN, true)
      }
      // 创建NPC
      async generateEnemies() {
        const woodenSkeleton = createUINode()
        woodenSkeleton.setParent(this.stage)
        const woodenSkeletonManager = woodenSkeleton.addComponent(WoodenSkeletonStateManager)
        await woodenSkeletonManager.init()
        DataManager.Instance.enemies.push(woodenSkeletonManager)
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    assets/Scripts/Player/PlayerManager.ts

    人物移动的时候触发事件

    move(){
    	//...
    	EventManager.Instance.emit(EVENT_ENUM.PLAYER_MOVE_END)
    	//...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220721103805.png

    https://web03-1252477692.cos.ap-guangzhou.myqcloud.com/blog/images/QQ%E6%88%AA%E5%9B%BE20220721103707.png
    本节源码地址:

    https://gitee.com/yuan30/cramped-room-of-death/tree/day4/

  • 相关阅读:
    -邻接点-
    【中间件篇-Redis缓存数据库07】Redis缓存使用问题及互联网运用
    QML使用C++ model(基本用法)
    Lyft Presto Gateway源码机制分析
    Symfony 上传文件
    使用OpenVINOTM预处理API进一步提升YOLOv5推理性能
    2024深圳杯数学建模竞赛A题(东三省数学建模竞赛A题):建立火箭残骸音爆多源定位模型
    ②【Docker】安装Docker可视化工具——Portainer
    数字人直播软件排名推荐,铭顺科技数字人品牌抢占“日不落”流量新技能
    滚雪球学Java(44):掌握Java编程的关键:深入解析System类
  • 原文地址:https://blog.csdn.net/weixin_43840202/article/details/125909239