联机游戏的前后端交互逻辑(Cocos)【第二章 单机StateMachine and DynamicCreation】

2024-01-07 17:23:45

前一章我们详细讲解了一种数据驱动的单机游戏框架。

主要思路为,将游戏内所有实体的状态储存在DataManager的state字段下,从输入系统拿到Input,调用DataManager中的applyInput方法进行处理。

其中有一种特殊的Input,作为时间流逝的量度,周期性地使用applyInput方法应用它,达成某些事件累积状态发生改变的效果。

这一章将要讲解状态机和动态创建。

一、stateMachine(状态机)

状态机是一种很常见的概念。在游戏中,一个实体如果只具有有限状态的动画,我们就可以使用状态机进行实体的控制。

举个栗子,一个玩家可能有站着和跑着两种状态,这两种状态下播放的动画是不同的。如果我们想当玩家移动时播放跑着的动画,当玩家站着时播放静止的动画,可以将玩家的两种状态封装到一个状态机中。

如果输入系统没有Input,我们就认为玩家静止,然后设置state为idle。如果输入系统有Input,我们就认为玩家正在跑动,就设置state为run。这样就可以很方便的控制动画的播放。

在Cocos的3.4版本官方开始引入动画状态机的概念,称为Marionette 动画系统。在此之前,需要手动进行状态机的编码。一个只有idle和run两种状态的状态机编码示例如下

import { _decorator, Animation, AnimationClip } from "cc";
import State from "../../Base/State";
import StateMachine, { getInitParamsTrigger } from "../../Base/StateMachine";
import { EntityTypeEnum } from "../../Common";
import { EntityStateEnum, ParamsNameEnum } from "../../Enum";
const { ccclass } = _decorator;

@ccclass("ActorStateMachine")
export class ActorStateMachine extends StateMachine {
  init(type: EntityTypeEnum) {
    this.type = type;
    this.animationComponent = this.node.addComponent(Animation);
    this.initParams();
    this.initStateMachines();
    this.initAnimationEvent();
  }

  initParams() {
    this.params.set(ParamsNameEnum.Idle, getInitParamsTrigger());
    this.params.set(ParamsNameEnum.Run, getInitParamsTrigger());
  }

  initStateMachines() {
    this.stateMachines.set(ParamsNameEnum.Idle, new State(this, `${this.type}${EntityStateEnum.Idle}`, AnimationClip.WrapMode.Loop));
    this.stateMachines.set(ParamsNameEnum.Run, new State(this, `${this.type}${EntityStateEnum.Run}`, AnimationClip.WrapMode.Loop));
  }

  initAnimationEvent() {}

  run() {
    switch (this.currentState) {
      case this.stateMachines.get(ParamsNameEnum.Idle):
      case this.stateMachines.get(ParamsNameEnum.Run):
        if (this.params.get(ParamsNameEnum.Run).value) {
          this.currentState = this.stateMachines.get(ParamsNameEnum.Run);
        } else if (this.params.get(ParamsNameEnum.Idle).value) {
          this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
        } else {
          this.currentState = this.currentState;
        }
        break;
      default:
        this.currentState = this.stateMachines.get(ParamsNameEnum.Idle);
        break;
    }
  }
}

这种写法显然比较麻烦,建议使用3.4以上版本的Cocos进行可视化状态机制作。

二、dynamicCreation(动态创建)

也举个例子。玩家射击时发射的子弹,本来是不存在的。我们需要它出现在玩家发射的一瞬间的枪口上。这就需要用到动态创建的概念。

一个比较好的思路是,在DataManager中建立资源的Map用于维护各种需要加载或者复用的资源。在游戏初始化的时候,把所有资源都加载到DataManager的Map中。比如,我可能需要加载子弹的prefab,然后在战斗过程中动态的加载它。为此我需要先建立一个资源名称和资源路径的映射Map

resourceMap:<ResourceTypeEnum,string> = new Map()

然后建立一个prefabMap来将资源名称映射到对应的的prefab上

prefabMap:<ResourceTypeEnum,cc.Prefab> = new Map()

在游戏初始化的时候对这个map进行赋值,假定我们的Bullet预制体位于resources的bullet/bullet1

onLoad(){
    DataManager.Instance.resourceMap.set(ResourceTypeEnum.Bullet,'bullet/bullet1')
}

然后加载资源

cc.resources.load(DataManager.Instance.resourceMap.get(ResouceTypeEnum.Bullet),cc.Prefab,(pre)=>{
    DataManager.Instance.prefabMap.set(ResouceTypeEnum.Bullet,pre)
})

加载完毕后,我们就可以通过DataManager里面的prefabMap映射方便地获取到子弹的prefab,然后进行动态创建。

一个可能的代码示例如下

onLoad(){

    EventManager.Instance.on(EventTypeEnum.PlayerShoot,this.createBullet,this)
    ...
}

createBullet(position:cc.Vec2,direction:cc.Vec2){
    const bullet = cc.instantiate(DataManager.Instance.prefabMap.get(ResourceTypeEnum.Bullet))
    DataManager.Instance.stage.addChild(bullet)
    bullet.setPosition(position.x,position.y)
    const angle =
      direction.x > 0
        ? rad2Angle(Math.asin(direction.y / side))
        : rad2Angle(Math.asin(-direction.y / side)) + 180;
    this.node.setRotationFromEuler(0, 0, angle);
}

在这段代码中,我们先监听了PlayerShoot这个事件,当玩家发射子弹触发这个事件的时候,我们就调用createBullet这个函数,传入position和direction两个参数。通过资源名称,利用DataManager中的map映射到对应的prefab,获取prefab之后,我们调用instantiate方法进行实例化,将新生成子弹的父节点设为舞台,然后设置子弹的位置和方向。

这仅仅是一个很简单的动态创建栗子。值得注意的是,动态创建实体很消耗电脑的性能。更加优越的解决方案是建立对象池。这个我们下节再讲。

文章来源:https://blog.csdn.net/2301_80005044/article/details/135425669
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。