Unity实现Priests and Deivls动作分离版(V2)

2023-10-25 04:50

本文主要是介绍Unity实现Priests and Deivls动作分离版(V2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Unity实现Priests and Deivls动作分离版(V2)

前言

这是中山大学数据科学与计算机学院2019年3D游戏编程与设计的第四次作业
所有项目与代码已上传至github当中,欢迎大家访问。
github个人主页: https://starashzero.github.io
3D游戏编程与设计主页: https://starashzero.github.io/3DGameDesign
本次作业项目地址: https://github.com/StarashZero/3DGameDesign/tree/master/hw4

项目新特性

V1版Priests and Deivls请参考Unity实现Priests and Deivls游戏

  • 使用专用的对象来管理运动。在本游戏中的运动即对象的移动,现在对象的移动由动作管理器接手,而不是上一个版本的MoveController,现在不再需要为每个对象都加上Move.cs了.
  • 【2019新要求】:设计一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束.
    现在游戏胜利与失败的条件均由JudgeController接管,FirstController新增处理JudgeController反馈的JudgeCallback函数,而取消了Check函数.

项目结构

新的代码文件:
在上个版本MVC的基础上,增加了Actions,以及JudgeController.
在这里插入图片描述
新的UML图:
在这里插入图片描述

(尽力地与老师的UML无缝衔接了)

新增结构解释:

  • 旧动作管理主帅MoveController与其助手Move宣布下课!
    由来自第四节课的CCActionManager及他的团队出任新帅。
    在这里插入图片描述
    现在游戏中所有的动作(其实只有移动)都与主控制器FirstController分离开来,FirstController通过调用CCActionManager的函数来触发动作。具体各部分的功能会在后面讲述。
  • 判断游戏状态的功能从FirstController中分离,撤销了原有的Check函数引进裁判JudgeController专门处理这项事物。
    在这里插入图片描述

各部分代码解释

这里只解释新增或者出现改动的代码,其他代码请参考上一版本。

Actions

大部分代码与老师课件是一致的,改动的地方我会指出。

  1. ISSActionCallback.cs
    public interface ISSActionCallback
    {//回调函数void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null);
    }
    
    ISSActionCallback作用是作为向其他函数通信的接口,当一个动作有了结果需要向另一个函数传递这个信息,可通过SSActionEvent回调。
    例如说船移动是一个动作,当船从一岸移动了到另一岸时表示动作结束。如果需要告诉管理者这个信息,那么就调用管理者的SSActionEvent(前提是管理者类实现了ISSActionCallback)进行处理。
  2. SSAction.cs
    public class SSAction : ScriptableObject
    {public bool enable = true;public bool destroy = false;public GameObject gameObject { get; set; }public Transform transform { get; set; }public ISSActionCallback callback { get; set; }protected SSAction(){}// Start is called before the first frame updatepublic virtual void Start(){throw new System.NotImplementedException();}// Update is called once per framepublic virtual void Update(){throw new System.NotImplementedException();}
    }
    
    • SSAction是动作类的基类,其继承了ScriptableObject,可以不绑定实体对象运作。
    • gameObject为动作作用的实体对象。
    • transform = gameObject.transform
    • callback是回调接口,在1中已经解释过了,当动作类需要向别的类(大部分时候为调用这个动作的类)传递信息时,便需要通过ISSActionCallback接口。
  3. SSActionManager.cs
    public class SSActionManager : MonoBehaviour
    {//动作集,以字典形式存在private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();//等待被加入的动作队列(动作即将开始)private List<SSAction> waitingAdd = new List<SSAction>();//等待被删除的动作队列(动作已完成)private List<int> waitingDelete = new List<int>();protected void Update(){//将waitingAdd中的动作保存foreach (SSAction ac in waitingAdd)actions[ac.GetInstanceID()] = ac;waitingAdd.Clear();//运行被保存的事件foreach (KeyValuePair<int, SSAction> kv in actions){SSAction ac = kv.Value;if (ac.destroy){waitingDelete.Add(ac.GetInstanceID());}else if (ac.enable){ac.Update();}}//销毁waitingDelete中的动作foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);Destroy(ac);}waitingDelete.Clear();}//准备运行一个动作,将动作初始化,并加入到waitingAddpublic void RunAction(GameObject gameObject, SSAction action, ISSActionCallback manager){action.gameObject = gameObject;action.transform = gameObject.transform;action.callback = manager;waitingAdd.Add(action);action.Start();}// Start is called before the first frame updateprotected void Start(){}}
    
    • SSActionManager是动作管理类的基类,作为动作生成、运行与销毁的管理者。
    • actions以字典的形式存储正在运行中的动作。
    • waitingAdd保存即将被运行的动作。
    • waitingDelete保存即将被删除的动作。
    • Update()每次会将waitingAdd中的动作加入到actions当中,然后遍历运行actions中的动作,如果动作已经结束,则加入到waitingDelete中,最后将waitingDelete中的动作删除并销毁。
  4. CCMoveToAction.cs
    public class CCMoveToAction : SSAction
    {//目的地public Vector3 target;//速度public float speed;private CCMoveToAction(){}//生产函数(工厂模式)public static CCMoveToAction GetSSAction(Vector3 target, float speed){CCMoveToAction action = ScriptableObject.CreateInstance<CCMoveToAction>();action.target = target;action.speed = speed;return action;}// Start is called before the first frame updatepublic override void Start(){}// Update is called once per framepublic override void Update(){//判断是否符合移动条件if (this.gameObject == null || this.transform.localPosition == target){this.destroy = true;this.callback.SSActionEvent(this);return;}//移动this.transform.localPosition = Vector3.MoveTowards(this.transform.localPosition, target, speed * Time.deltaTime);}
    }
    
    • CCMoveToAction是移动动作类,作用是将物体以一定速度(speed)移动到目的地(target)
    • GetSSAction()是生成目标SSAction的函数,遵循工厂模式。
    • 每次调用Update()会使得对象向目的地运动一段距离。
    • 改动:
      对Update()进行了少量修改,在老师的代码基础上。
      1. 将判断的语句放在了移动的前面,原因是因为在游戏重开时,也需要对移动动作删除,而我重开是依靠将游戏对象销毁再重新生成一个来实现的,因此需要判断游戏对象是否已经被销毁来判断动作是否被中断。
      2. 将改变position变为改变localPosition,这样就支持相对坐标的移动了。
  5. CCSequenceAction.cs
    public class CCSequenceAction : SSAction, ISSActionCallback
    {//动作序列public List<SSAction> sequence;//重复次数public int repeat = -1;//动作开始指针public int start = 0;//生产函数(工厂模式)public static CCSequenceAction GetSSAction(int repeat, int start, List<SSAction> sequence){CCSequenceAction action = ScriptableObject.CreateInstance<CCSequenceAction>();action.repeat = repeat;action.start = start;action.sequence = sequence;return action;}//对序列中的动作进行初始化public override void Start(){foreach (SSAction action in sequence){action.gameObject = this.gameObject;action.transform = this.transform;action.callback = this;action.Start();}}//运行序列中的动作public override void Update(){if (sequence.Count == 0)return;if (start < sequence.Count){sequence[start].Update();}}//回调处理,当有动作完成时触发public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int Param = 0,string strParam = null,Object objectParam = null){source.destroy = false;this.start++;if (this.start >= sequence.Count){this.start = 0;if (repeat > 0)repeat--;if (repeat == 0){this.destroy = true;this.callback.SSActionEvent(this);}}}void OnDestroy(){}
    }
    
    • CCSequenceAction是组合动作类,其包含了一系列要进行的动作,并按顺序运行这些动作。
    • 例如人上船,为了避免人物掉进地里,所以需要先让人平移、再让人垂直移动,这就是两个动作的组合,需要使用CCSequenceAction。
    • sequence中是要执行的动作序列。
    • start指向当前要进行的动作。
    • repeat表示动作序列需要重复执行的次数。
    • GetSSAction作用与4一致。
    • Start()会将动作序列中的动作进行初始化。
    • 每次调用Update()将对序列中目前存在的所有动作执行一次。
    • SSActionEvent是回调函数,因为CCSequenceAction是组合动作,因此需要获得每个动作执行状态的信息,当一个动作已经完成,就需要执行下一个动作,或者所有动作完成时销毁自己。
      因此当CCSequenceAction中有一个动作完成时,就会调用SSActionEvent通知CCSequenceAction,如果仍有需要执行的动作,则执行下一个动作,否则所有动作已完成,需要销毁自己,即调用callback.SSActionEvent通知其他类来处理。
  6. CCActionManager.cs
    public class CCActionManager : SSActionManager, ISSActionCallback
    {//是否正在运动private bool isMoving = false;//船移动动作类public CCMoveToAction moveBoatAction;//人移动动作类(需要组合)public CCSequenceAction moveRoleAction;//控制器public FirstController controller;protected new void Start(){controller = (FirstController)SSDirector.GetInstance().CurrentSenceController;controller.actionManager = this;}public bool IsMoving(){return isMoving;}//移动船public void MoveBoat(GameObject boat, Vector3 target, float speed){if (isMoving)return;isMoving = true;moveBoatAction = CCMoveToAction.GetSSAction(target, speed);this.RunAction(boat, moveBoatAction, this);}//移动人public void MoveRole(GameObject role, Vector3 mid_destination, Vector3 destination, int speed){if (isMoving)return;isMoving = true;moveRoleAction = CCSequenceAction.GetSSAction(0, 0, new List<SSAction> { CCMoveToAction.GetSSAction(mid_destination, speed), CCMoveToAction.GetSSAction(destination, speed) });this.RunAction(role, moveRoleAction, this);}//回调函数public void SSActionEvent(SSAction source,SSActionEventType events = SSActionEventType.Competed,int intParam = 0,string strParam = null,Object objectParam = null){isMoving = false;}
    }
    
    • CCActionManager是动作管理者,继承和实现了SSActionManager与 ISSActionCallback。
    • isMoving标识当前是否正在运动,本游戏当有物体在运动时不允许用户产生其他操作(除了重开),因此需要使用isMoving来记下当前状态。
    • moveBoatAction是船移动类,因为船只需要横向移动,因此为单一动作,使用CCMoveToAction。
    • moveRoleAction是人移动类,因为人需要在两个方向上移动,是组合动作,因此使用CCSequenceAction。
    • controller为当前与动作管理者关联的控制器。
    • 其他函数不难理解了,MoveBoat即移动船,MoveRole即移动人,在移动时会将isMoving设为true,当动作完成后通过SSActionEvent回调,则会将isMoving设为false。

Controllers

  1. JudgeController.cs
    public class JudgeController : MonoBehaviour
    {public FirstController mainController;public LandModel leftLandModel;public LandModel rightLandModel;public BoatModel boatModel;// Start is called before the first frame updatevoid Start(){mainController = (FirstController)SSDirector.GetInstance().CurrentSenceController;this.leftLandModel = mainController.leftLandController.GetLandModel();this.rightLandModel = mainController.rightLandController.GetLandModel();this.boatModel = mainController.boatController.GetBoatModel();}// Update is called once per framevoid Update(){if (!mainController.isRuning)return;if (mainController.time <= 0){mainController.JudgeCallback(false, "Game Over!");return;}this.gameObject.GetComponent<UserGUI>().gameMessage = "";//判断是否已经胜利if (rightLandModel.priestNum == 3){mainController.JudgeCallback(false, "You Win!");return;}else{//判断是否已经失败/*leftPriestNum: 左边牧师数量leftDevilNum: 左边恶魔数量rightPriestNum: 右边牧师数量rightDevilNum: 右边恶魔数量若任意一侧,牧师数量不为0且牧师数量少于恶魔数量,则游戏失败*/int leftPriestNum, leftDevilNum, rightPriestNum, rightDevilNum;leftPriestNum = leftLandModel.priestNum + (boatModel.isRight ? 0 : boatModel.priestNum);leftDevilNum = leftLandModel.devilNum + (boatModel.isRight ? 0 : boatModel.devilNum);if (leftPriestNum != 0 && leftPriestNum < leftDevilNum){mainController.JudgeCallback(false, "Game Over!");return;}rightPriestNum = rightLandModel.priestNum + (boatModel.isRight ? boatModel.priestNum : 0);rightDevilNum = rightLandModel.devilNum + (boatModel.isRight ? boatModel.devilNum : 0);if (rightPriestNum != 0 && rightPriestNum < rightDevilNum){mainController.JudgeCallback(false, "Game Over!");return;}}}
    }
    
    • JudgeController是新要求的裁判类,其会在每一帧判断当前游戏是否已经结束,如果已经结束则通过mainController.JudgeCallback()通知主控制器处理。
    • 代码逻辑与上一版本中的Check基本一致,因此不多解释了。
  2. FirstController.cs
    public class FirstController : MonoBehaviour, ISceneController, IUserAction
    {public CCActionManager actionManager;public LandModelController rightLandController;                        //右岸控制器public LandModelController leftLandController;                         //左岸控制器public RiverModel riverModel;                                              //河流Modelpublic BoatModelController boatController;                                  //船控制器public RoleModelController[] roleControllers;                         //人物控制器集合//private MoveController moveController;                                      //移动控制器public bool isRuning;                                                      //游戏进行状态public float time;                                                         //游戏进行时间public void JudgeCallback(bool isRuning, string message){this.gameObject.GetComponent<UserGUI>().gameMessage = message;this.gameObject.GetComponent<UserGUI>().time = (int)time;this.isRuning = isRuning;}//导入资源public void LoadResources(){//人物初始化roleControllers = new RoleModelController[6];for (int i = 0; i < 6; i++){roleControllers[i] = new RoleModelController();roleControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);}//左右岸初始化leftLandController = new LandModelController();leftLandController.CreateLand("left_land", PositionModel.left_land);rightLandController = new LandModelController();rightLandController.CreateLand("right_land", PositionModel.right_land);//将人物添加并定位至左岸  foreach (RoleModelController roleModelController in roleControllers){roleModelController.GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleModelController.GetRoleModel());}//河流Model实例化riverModel = new RiverModel(PositionModel.river);//船初始化boatController = new BoatModelController();boatController.CreateBoat(PositionModel.left_boat);//移动控制器实例化//moveController = new MoveController();//数据初始化isRuning = true;time = 60;}//移动船public void MoveBoat(){//判断当前游戏是否在进行,同时是否有对象正在移动if ((!isRuning) || actionManager.IsMoving())return;//判断船在左侧还是右侧Vector3 destination = boatController.GetBoatModel().isRight ? PositionModel.left_boat : PositionModel.right_boat;actionManager.MoveBoat(boatController.GetBoatModel().boat, destination, 5);//移动后,将船的位置取反boatController.GetBoatModel().isRight = !boatController.GetBoatModel().isRight;}//移动人物    public void MoveRole(RoleModel roleModel){//判断当前游戏是否在进行,同时是否有对象正在移动if ((!isRuning) || actionManager.IsMoving())return;Vector3 destination, mid_destination;if (roleModel.isInBoat){//若人在船上,则将其移向岸上if (boatController.GetBoatModel().isRight)destination = rightLandController.AddRole(roleModel);elsedestination = leftLandController.AddRole(roleModel);if (roleModel.role.transform.localPosition.y > destination.y)mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);elsemid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);roleModel.isRight = boatController.GetBoatModel().isRight;boatController.RemoveRole(roleModel);}else{//若人在岸上,则将其移向船if (boatController.GetBoatModel().isRight == roleModel.isRight){if (roleModel.isRight){rightLandController.RemoveRole(roleModel);}else{leftLandController.RemoveRole(roleModel);}destination = boatController.AddRole(roleModel);if (roleModel.role.transform.localPosition.y > destination.y)mid_destination = new Vector3(destination.x, roleModel.role.transform.localPosition.y, destination.z);elsemid_destination = new Vector3(roleModel.role.transform.localPosition.x, destination.y, destination.z);actionManager.MoveRole(roleModel.role, mid_destination, destination, 5);}}}//游戏重置public void Restart(){//对各数据进行初始化time = 60;leftLandController.CreateLand("left_land", PositionModel.left_land);rightLandController.CreateLand("right_land", PositionModel.right_land);for (int i = 0; i < 6; i++){roleControllers[i].CreateRole(PositionModel.roles[i], i < 3 ? true : false, i);roleControllers[i].GetRoleModel().role.transform.localPosition = leftLandController.AddRole(roleControllers[i].GetRoleModel());}boatController.CreateBoat(PositionModel.left_boat);isRuning = true;}void Awake(){SSDirector.GetInstance().CurrentSenceController = this;LoadResources();this.gameObject.AddComponent<UserGUI>();this.gameObject.AddComponent<CCActionManager>();this.gameObject.AddComponent<JudgeController>();}void Update(){if (isRuning){time -= Time.deltaTime;this.gameObject.GetComponent<UserGUI>().time = (int)time;}}}
    • FirstController相比上一版本的变化主要是需要对动作分离进行适配,将上个版本调用MoveController的代码改为调用CCActionManager的代码。
    • 增加了JudgeCallback()函数来处理裁判类的反馈信息。
      之所以没有使用ISSActionCallback接口是因为ISSActionCallback接口属于Actions当中的组成部分,若主控制器实现这个接口,反而会导致结构混乱。
    • 具体可以参照上一版本和这一版本的FirstController代码,大部分逻辑都没变。

其他一些类也有小改动,不过影响不大,因此就不多解释了。
这些就是这次项目的全部内容了,改完之后相比上一版本直接创建一个MoveController来管理动作,现在的程序结构显得更健全了,虽然代码上复杂了许多,但是可以很方便的添加许多新动作了。
感谢师兄的优质博客Unity3d学习之路-牧师与魔鬼V2(动作分离版)

这篇关于Unity实现Priests and Deivls动作分离版(V2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/280139

相关文章

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端

Java CompletableFuture如何实现超时功能

《JavaCompletableFuture如何实现超时功能》:本文主要介绍实现超时功能的基本思路以及CompletableFuture(之后简称CF)是如何通过代码实现超时功能的,需要的... 目录基本思路CompletableFuture 的实现1. 基本实现流程2. 静态条件分析3. 内存泄露 bug

C#实现添加/替换/提取或删除Excel中的图片

《C#实现添加/替换/提取或删除Excel中的图片》在Excel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更加美观,下面我们来看看如何在C#中实现添加/替换/提取或删除E... 在Excandroidel中插入与数据相关的图片,能将关键数据或信息以更直观的方式呈现出来,使文档更

C#实现系统信息监控与获取功能

《C#实现系统信息监控与获取功能》在C#开发的众多应用场景中,获取系统信息以及监控用户操作有着广泛的用途,比如在系统性能优化工具中,需要实时读取CPU、GPU资源信息,本文将详细介绍如何使用C#来实现... 目录前言一、C# 监控键盘1. 原理与实现思路2. 代码实现二、读取 CPU、GPU 资源信息1.