原创

cocos小游戏实战-02-事件中心


事件中心(发布订阅模式)

Runtime/EventManager.ts

发布和订阅模式,发布事件

import Singleton from '../Base/Singleton'

interface IItem {
  ctx: unknown // this
  func: Function
}

/**
 * 单例模式
 * 按钮事件
 */
export default class EventManager extends Singleton {
  static get Instance() {
    return super.GetInstance<EventManager>()
  }
  // 使用key->Array<func>的结构存储事件
  private eventDic: Map<string, Array<IItem>> = new Map()

  // 添加绑定事件
  on(eventName: string, func: Function, ctx?: unknown) {
    if (this.eventDic.has(eventName)) {
      this.eventDic.get(eventName).push({ func, ctx })
    } else {
      this.eventDic.set(eventName, [{ func, ctx }])
    }
  }

  // 解绑事件
  off(eventName: string, func: Function) {
    if (this.eventDic.has(eventName)) {
      const index = this.eventDic.get(eventName).findIndex(i => i.func === func)
      index > -1 && this.eventDic.get(eventName).slice(index, 1)
    }
  }

  // 调用事件
  emit(eventName: string, ...params: unknown[]) {
    console.log('this.eventDic.has(eventName)', eventName)
    if (this.eventDic.has(eventName)) {
      this.eventDic.get(eventName).forEach((i: IItem) => {
        if (i.ctx) {
          i.func.apply(i.ctx, params)
        } else {
          i.func(...params)
        }
      })
    }
  }

  // 清除事件
  clear() {
    this.eventDic.clear()
  }
}
export const DataManagerInstance = new EventManager()

切换地图

Scripts/Scene/BattleManager.ts

事件中心的使用-关键代码

onLoad() {
    // 绑定事件-关卡切换
    EventManager.Instance.on(EVENT_ENUM.NEXT_LEVEL, this.nextLevel, this)
  }

// 关卡切换-下一关
nextLevel() {
  DataManager.Instance.levelIndex++
  this.initLevel()
}

// 关卡清空
clearLevel() {
  this.stage.destroyAllChildren()
  DataManager.Instance.reset()
}
onDestroy() {
    // 解绑事件
    EventManager.Instance.off(EVENT_ENUM.NEXT_LEVEL, this.nextLevel)
  }

控制台添加动画

新建Sprite—>添加Animation组件→点击动画编辑器→拖入每一帧图片,设置帧率,循环播放

组件勾选PlayOnLoad

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

程序化编辑动画剪辑

文档地址:

程序化编辑动画剪辑

Scripts/Player/PlayerManager.ts

import {
  _decorator,
  Component,
  Node,
  Sprite,
  UITransform,
  Animation,
  AnimationClip,
  animation,
  Vec3,
  SpriteFrame,
} from 'cc'
import { TILE_HEIGHT, TILE_WIDTH } from '../Tile/TileManager'
import ResourceManager from '../../Runtime/ResourceManager'
const { ccclass, property } = _decorator

const ANIMATION_SPEED = 1 / 8 // 1秒8帧

@ccclass('PlayerManager')
export class PlayerManager extends Component {
  async init() {
    const sprite = this.addComponent(Sprite)
    sprite.sizeMode = Sprite.SizeMode.CUSTOM
    const transform = this.getComponent(UITransform)
    transform.setContentSize(TILE_WIDTH * 4, TILE_HEIGHT * 4)

    // 加载资源文件夹
    const spriteFrames = await ResourceManager.Instance.loadDir('texture/player/idle/top')
    // 添加动画
    const animationComponent = this.addComponent(Animation)

    const animationClip = new AnimationClip()
    // 创建一个对象轨道
    const track = new animation.ObjectTrack()
    // 添加轨道路径为Sprite组件
    track.path = new animation.TrackPath().toComponent(Sprite).toProperty('spriteFrame')

    const frames: Array<[number, SpriteFrame]> = spriteFrames.map((item, index) => [index * ANIMATION_SPEED, item])

    // 设置一条通道channel的关键帧
    track.channel.curve.assignSorted(
      frames,
      /*      [
      // 为 x 通道的曲线添加关键帧
      [0.4, { value: 0.4 }], //[时间,属性]
      [0.6, { value: 0.6 }],
      [0.8, { value: 0.8 }],
    ]*/
    )
    // 最后将轨道添加到动画剪辑以应用
    animationClip.addTrack(track)
    // 整个动画剪辑的周期 帧数*帧率
    animationClip.duration = frames.length * ANIMATION_SPEED
    // 循环播放
    animationClip.wrapMode = AnimationClip.WrapMode.Loop
    // 设置动画,defaultClip,并且播放
    animationComponent.defaultClip = animationClip
    animationComponent.play()
  }
}

Scripts/Scene/BattleManager.ts

start() {
    this.generatePlayer()
  }

// 创建人物
  generatePlayer() {
    const player = createUINode()
    player.setParent(this.stage)
    const playerManager = player.addComponent(PlayerManager)
    playerManager.init()
  }

人物移动

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

Enum/index.ts

/**
 * 人物移动方向枚举
 */

export enum CONTROLLER_ENUM {
  TOP = 'TOP',
  LEFT = 'LEFT',
  RIGHT = 'RIGHT',
  BOTTOM = 'BOTTOM',
  TURN_LEFT = 'TURN_LEFT',
  TURN_RIGHT = 'TURN_RIGHT',
}

Scripts/Player/PlayerManager.ts

import {
  _decorator,
  Component,
  Node,
  Sprite,
  UITransform,
  Animation,
  AnimationClip,
  animation,
  Vec3,
  SpriteFrame,
} from 'cc'
import { TILE_HEIGHT, TILE_WIDTH } from '../Tile/TileManager'
import ResourceManager from '../../Runtime/ResourceManager'
import { CONTROLLER_ENUM, EVENT_ENUM } from '../../Enum'
import EventManager from '../../Runtime/EventManager'
const { ccclass, property } = _decorator

const ANIMATION_SPEED = 1 / 8 // 1秒8帧

@ccclass('PlayerManager')
export class PlayerManager extends Component {
  // 坐标
  x: number = 0
  y: number = 0
  // 目标坐标
  targetX: number = 0
  targetY: number = 0
  // 速度
  private readonly sped = 1 / 10
  init() {
    this.render()
    EventManager.Instance.on(EVENT_ENUM.PLAY_CTRL, this.move, this)
  }
  update() {
    // 更新人物移动坐标数据
    this.updateXY()
    // 更新移动位置,由于人物占4个格子居中需要偏移
    this.node.setPosition(this.x * TILE_WIDTH - TILE_WIDTH * 1.5, -this.y * TILE_HEIGHT + TILE_HEIGHT * 1.5)
  }

  // 更新xy,让xy无限趋近于targetX targetY(位移过渡)
  updateXY() {
    if (this.targetX < this.x) {
      this.x -= this.sped
    } else if (this.targetX > this.x) {
      this.x += this.sped
    }

    if (this.targetY < this.y) {
      this.y -= this.sped
    } else if (this.targetY > this.y) {
      this.y += this.sped
    }

    if (Math.abs(this.targetY - this.y) <= 0.1 && Math.abs(this.targetX - this.x) <= 0.1) {
      this.x = this.targetX
      this.y = this.targetY
    }
  }

  // 人物移动
  move(inputDirection: CONTROLLER_ENUM) {
    switch (inputDirection) {
      case CONTROLLER_ENUM.BOTTOM:
        this.targetY += 1
        break
      case CONTROLLER_ENUM.LEFT:
        this.targetX -= 1
        break
      case CONTROLLER_ENUM.TOP:
        this.targetY -= 1
        break
      case CONTROLLER_ENUM.RIGHT:
        this.targetX += 1
        break
      case CONTROLLER_ENUM.TURN_LEFT:
        break
      case CONTROLLER_ENUM.TURN_RIGHT:
        break
    }
  }
}

需要给上下左右按钮指定事件并在CustomEventData加上对应的事件类型枚举

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

本节源码地址:

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

Cocos
  • 作者:零三(联系作者)
  • 最后更新时间:2022-07-10 23:19
  • 版权声明:自由转载-非商用-非衍生-保持署名
  • 转载声明:来源地址 https://web03.cn