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

相关文章

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略

Kubernetes PodSecurityPolicy:PSP能实现的5种主要安全策略 1. 特权模式限制2. 宿主机资源隔离3. 用户和组管理4. 权限提升控制5. SELinux配置 💖The Begin💖点点关注,收藏不迷路💖 Kubernetes的PodSecurityPolicy(PSP)是一个关键的安全特性,它在Pod创建之前实施安全策略,确保P

计算机毕业设计 大学志愿填报系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

🍊作者:计算机编程-吉哥 🍊简介:专业从事JavaWeb程序开发,微信小程序开发,定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事,生活就是快乐的。 🍊心愿:点赞 👍 收藏 ⭐评论 📝 🍅 文末获取源码联系 👇🏻 精彩专栏推荐订阅 👇🏻 不然下次找不到哟~Java毕业设计项目~热门选题推荐《1000套》 目录 1.技术选型 2.开发工具 3.功能

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、