中山大学软件工程-Unity牧师与魔鬼(动作分离版)作业

本文主要是介绍中山大学软件工程-Unity牧师与魔鬼(动作分离版)作业,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、 项目配置

  • 首先创建一个新项目,选择3D模板

  • 新项目的文件结构如下:

    相较于上一次作业,少了Moveable脚本,多了Judge和Action两个文件

    • Assets/Resources下存放的是项目动态加载所需的图片以及预制,预制包括按要求制作成预制的牧师、魔鬼、船、河流和河岸,图片则是用于GUI装饰

    • Assets/Materials

    • Assets/Scripts中则存放的是项目代码,各个类之间遵守MVC架构

  • 由于图片是动态加载,故需要将在Inspector菜单中,将图片设置为可读可写状态,才能保证图片能被正常加载。

  • 最后将FirstController代码拖到Main Camera中,Ctrl+B即可运行。

二、 实现过程和方法

1. 总体设计思路

  • 此次作业主要有两个任务:

    • 在原有的基础上新增一个裁判类,当游戏达到结束条件时,通知场景控制器游戏结束,也就是把负责判断游戏状态的职责从场景控制器中分离出去。

    • 新增动作管理器类,独立管理游戏对象的动作,也就是把负责管理控制游戏对象动作的职责从场景控制器中分离出去。

  • 这两个改进都是面向对象基于职责的思想,让场景控制器专注于本身应该实现的职责,如处理用户交互时间,加载游戏资源等,而把一些更具体专门的功能分离给更特定的对象,从而避免对象的臃肿,各个对象实现特定的功能,然后通过消息协同完成更为复杂的工作。

2. 代码分析

下面分别根据两个任务对相较于上一次作业有修改的地方进行分析。

  • 新增裁判类

    • 首先要先定义一个新的类——Judge,把原来在FirstController中实现SceneController接口的checkGameStatus()函数移到Judge类中,作为其一个公用成员函数,以供FirstController调用。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public class Judge : MonoBehaviour{private Land startLand;// 起始河岸private Land endLand;// 终点河岸private Boat boat;// 船
    ​public Judge(Land _startLand, Land _endLand, Boat _boat){startLand = _startLand;endLand = _endLand;boat = _boat;}
    ​public int checkGameState() {   // 船在出发点int startNumOfPriest = startLand.getNumOfPriest();int startNumOfDevil = startLand.getNumOfDevil();int endNumOfPriest = endLand.getNumOfPriest();int endNumOfDevil = endLand.getNumOfDevil();int boatNumOfPriest = boat.getNumOfPriest();int boatNumOfDevil = boat.getNumOfDevil();if(endNumOfPriest + endNumOfDevil + boatNumOfPriest + boatNumOfDevil == 6){return 2;}if(boat.getCurPosition() == 1){if((startNumOfPriest  < startNumOfDevil && startNumOfPriest > 0)||(endNumOfPriest +  boatNumOfPriest < endNumOfDevil + boatNumOfDevil && endNumOfPriest +  boatNumOfPriest > 0)){return 1;}}else{if((startNumOfPriest + boatNumOfPriest < startNumOfDevil + boatNumOfDevil && startNumOfPriest + boatNumOfPriest > 0) || (endNumOfPriest < endNumOfDevil && endNumOfPriest > 0)){return 1;}}return 0;}
    }
    ​

    • 然后SceneController接口中便不再需要checkGameState函数

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    ​
    public interface SceneController {void loadResources ();//void checkGameState();
    }
    ​
    • 最后在FirstController新增一个Judge类对象,并在需要的地方调用它的成员函数checkGameState()

  • 新增动作管理类

    • 这一部分可以根据教学网站上的提示,先定义动作基类SSAction

      public class SSAction : ScriptableObject{           //动作
      ​public bool enable = true;                      //是否正在进行此动作public bool destroy = false;                    //是否需要被销毁
      ​public GameObject gameobject{get;set;}                   //动作对象public Transform transform{get;set;}                     //动作对象的transformpublic ISSActionCallback callback{get;set;}              //回调函数
      ​protected SSAction() { }                        //保证SSAction不会被new
      ​public virtual void Start()                    //子类可以使用这两个函数{throw new System.NotImplementedException();}
      ​public virtual void Update(){throw new System.NotImplementedException();}
      }
    • 然后定义一个继承自SSAction的具体动作类,实现的是将一个物体移动到目标位置

      public class SSMoveToAction : SSAction                        //移动
      {public Vector3 target;        //移动到的目的地public float speed;           //移动的速度
      ​private SSMoveToAction() { }public static SSMoveToAction GetSSAction(Vector3 target, float speed){SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();//让unity自己创建一个MoveToAction实例,并自己回收action.target = target;action.speed = speed;return action;}
      ​public override void Update(){this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);if (this.transform.position == target){this.destroy = true;this.callback.SSActionEvent(this);      //告诉动作管理或动作组合这个动作已完成}}
      ​public override void Start(){//移动动作建立时候不做任何事情}
      }
    • 接着定义动作事件接口ISSActionCallBack,作为动作和动作管理者的接口,所有动作事件管理者都必须实现这个接口以实现事件的调度,当动作完成时,动作对象会调用这个接口,通知动作管理器对象动作已经完成,以便管理器能对下一个动作进行处理。

      public enum SSActionEventType : int { Started, Competeted }
      ​
      public interface ISSActionCallback
      {void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,int intParam = 0, string strParam = null, Object objectParam = null);
      }
    • 有了动作基类和动作事件接口,就可以定义一个动作组合序列类CCSequenceAction,由于组合动作实际上就是按顺序完成每一个动作,因此也需要实现ISSActionCallback接口,以便动作序列中的每一个小动作完成之后可以通知它,然后处理下一个小动作。

      public class SequenceAction : SSAction, ISSActionCallback
      {public List<SSAction> sequence;    //动作的列表public int repeat = -1;            //-1就是无限循环做组合中的动作public int start = 0;              //当前做的动作的索引
      ​public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence){SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例action.repeat = repeat;action.sequence = sequence;action.start = start;return action;}
      ​public override void Update(){if (sequence.Count == 0) return;if (start < sequence.Count){sequence[start].Update();     //一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现}}
      ​public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,int intParam = 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); //告诉组合动作的管理对象组合做完了}}}
      ​public override void Start(){foreach (SSAction action in sequence){action.gameobject = this.gameobject;action.transform = this.transform;action.callback = this;                //组合动作的每个小的动作的回调是这个组合动作action.Start();}}
      ​void OnDestroy(){//如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放}
      }
    • 定义好了动作基类,也就可以实现动作管理器了,先定义一个动作管理器基类SSActionManager,实现对SequenceAction和SSAction对象的管理,如为动作类传递游戏对象,决定动作执行的顺序,切换动作等等。

      public class SSActionManager : MonoBehaviour, ISSActionCallback                      //action管理器
      {
      ​private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合,int为key,SSAction为valueprivate List<SSAction> waitingAdd = new List<SSAction>();                       //等待去执行的动作列表private List<int> waitingDelete = new List<int>();                              //等待删除的动作的key                //不断更新待处理的动作protected void Update(){foreach (SSAction ac in waitingAdd){actions[ac.GetInstanceID()] = ac;                                      //获取动作实例的ID作为key}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();}}
      ​foreach (int key in waitingDelete){SSAction ac = actions[key];actions.Remove(key);DestroyObject(ac);}waitingDelete.Clear();}
      ​public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager){action.gameobject = gameobject;action.transform = gameobject.transform;action.callback = manager;waitingAdd.Add(action);action.Start();}
      ​public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,int intParam = 0, string strParam = null, Object objectParam = null){//牧师与魔鬼的游戏对象移动完成后就没有下一个要做的动作了,所以回调函数为空}
      }
    • 最后就可以定义一个继承自游戏管理器基类SSActionManager的自定义的游戏管理器用于管理本游戏的游戏对象动作,由于本游戏的游戏对象只有移动这一个动作,所以实际上就是对SSMoveToAction对象的管理。

      public class MySceneActionManager : SSActionManager  //本游戏管理器
      {
      ​private SSMoveToAction moveBoatToEndOrStart;     //移动船到结束岸,移动船到开始岸private SequenceAction moveRoleToLandorBoat;     //移动角色到陆地,移动角色到船上
      ​public FirstController sceneController;
      ​protected new void Start(){sceneController = (FirstController)Director.getInstance().currentSceneController;sceneController.actionManager = this;}public void moveBoat(GameObject boat, Vector3 target, float speed){moveBoatToEndOrStart = SSMoveToAction.GetSSAction(target, speed);//创建移动动作this.RunAction(boat, moveBoatToEndOrStart, this);//执行动作}
      ​public void moveCharacter(GameObject role, Vector3 middle_pos, Vector3 end_pos, float speed){SSAction action1 = SSMoveToAction.GetSSAction(middle_pos, speed);//创建前半部分路径的移动动作SSAction action2 = SSMoveToAction.GetSSAction(end_pos, speed);//创建后半部分路径的移动动作moveRoleToLandorBoat = SequenceAction.GetSSAcition(1, 0, new List<SSAction> { action1, action2 });//将两个动作组合成一个动作序列this.RunAction(role, moveRoleToLandorBoat, this);//执行动作序列}
      }
    • 完成好游戏动作管理器的定义,就已经实现了把动作管理的职责分配到了动作管理器,接下来在FirstController中进行修改

      • 添加自定义的动作管理器并将其作为一个组件

      • 修改了场景控制器中对用户动作的响应函数,对于实现游戏对象动作,不再是调用游戏对象,而是将游戏对象以及动作执行所需参数,如移动的起始、目标位置传入动作管理者对象中实现。

      // 动作分离版新增,获取游戏对象到达目的地的路径上的中间位置,以便实现折线移动public Vector3 getMiddlePosition(GameObject gameObject,Vector3 dest){Vector3 middle = dest;if (dest.y < gameObject.transform.position.y) { // character from coast to boatmiddle.y = gameObject.transform.position.y;} else {                                // character from boat to coastmiddle.x = gameObject.transform.position.x;}return middle;}
      ​// 动作分离版修改,修改了原有的goButtonIsClickedpublic void goButtonIsClicked(){                 if (!boat.isEmpty()){actionManager.moveBoat(boat.getGameObject(),boat.boatMoveToPosition(),boat.move_speed);   //动作分离版本改变state = judge.checkGameState();}}
      ​// 动作分离版修改,修改了原有的characterIsClickedpublic void characterIsClicked(Character ch){// 角色在船上if(ch.getIsOnBoat()){if(boat.getCurPosition() == 0){Vector3 end_pos = startLand.getOnLand(ch);////ch.moveToPosition(startLand.getOnLand(ch));// 原来的//Vector3 end_pos = land.GetEmptyPosition();                                         //动作分离版本改变Vector3 middle_pos = getMiddlePosition(ch.getGameObject(),end_pos);  //动作分离版本改变actionManager.moveCharacter(ch.getGameObject(), middle_pos, end_pos, ch.move_speed);  //动作分离版本改变ch.getOnLand(startLand);}else{Vector3 end_pos = endLand.getOnLand(ch);Vector3 middle_pos = getMiddlePosition(ch.getGameObject(),end_pos);  //动作分离版本改变actionManager.moveCharacter(ch.getGameObject(), middle_pos, end_pos, ch.move_speed);  //动作分离版本改变//ch.moveToPosition(endLand.getOnLand(ch));ch.getOnLand(endLand);}ch.getOffBoat();boat.removePassenger(ch);}// 角色在岸上else{if(!boat.isFull()){if(ch.getIsFinished() && boat.getCurPosition() == 1){ch.getOnBoat(boat);endLand.leaveLand(ch);
      ​Vector3 end_pos = boat.getEmptyPosition();Vector3 middle_pos = getMiddlePosition(ch.getGameObject(),end_pos);//ch.moveToPosition(boat.getEmptyPosition());actionManager.moveCharacter(ch.getGameObject(), middle_pos, end_pos, ch.move_speed);  //动作分离版本改变boat.addPassenger(ch);}if(!ch.getIsFinished() && boat.getCurPosition() == 0){ch.getOnBoat(boat);startLand.leaveLand(ch);
      ​Vector3 end_pos = boat.getEmptyPosition();Vector3 middle_pos = getMiddlePosition(ch.getGameObject(),end_pos);actionManager.moveCharacter(ch.getGameObject(), middle_pos, end_pos, ch.move_speed);  //动作分离版本改变
      ​//ch.moveToPosition(boat.getEmptyPosition());boat.addPassenger(ch);}}
      ​}}

三、总结

本次作业依据面向对象基于职责的思想,在上一次作业的基础上对代码结构进行了修改,将游戏状态的判断和动作管理的功能从场景控制器中分离出来,使得各个对象的职责分工更加明确。

对于课程网站上所提供的动作管理器的实现,可以视作一个框架,通过这一框架,我认为可以更加有效地组织管理复杂的动作,也提高了动作实现的代码的可重用性,尽管在本游戏中,游戏对象只有移动这一简单的动作或由两个移动组合的移动动作序列,用这一框架似乎有些“大材小用”,但我认为当游戏涉及的动作多且复杂时,就体现了它的优势——可以通过动作管理器统一的对游戏对象进行管理,而不用像之前一样,为每个需要动作的游戏对象添加特定动作脚本作为组件。

至于裁判类的添加,正是基于职责思想的体现,各个不同的对象有特定的职责,实现特定的功能,这样子不仅设计的时候简单直观,在实现的时候也可以相对独立地实现各个模块,最终整个游戏的实现就是靠各个对象之间的通信合作,各司其职

四、效果展示

(由于此次作业只是修改代码的实现,并没有对最终游戏效果产生影响,因此仍然使用上一次作业的效果展示视频)

[]: Unity3D作业-牧师与魔鬼过河演示视频_哔哩哔哩_bilibili

这篇关于中山大学软件工程-Unity牧师与魔鬼(动作分离版)作业的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

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

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

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

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

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

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

请解释Java Web应用中的前后端分离是什么?它有哪些好处?什么是Java Web中的Servlet过滤器?它有什么作用?

请解释Java Web应用中的前后端分离是什么?它有哪些好处? Java Web应用中的前后端分离 在Java Web应用中,前后端分离是一种开发模式,它将传统Web开发中紧密耦合的前端(用户界面)和后端(服务器端逻辑)代码进行分离,使得它们能够独立开发、测试、部署和维护。在这种模式下,前端通常通过HTTP请求与后端进行数据交换,后端则负责业务逻辑处理、数据库交互以及向前端提供RESTful

Unity Post Process Unity后处理学习日志

Unity Post Process Unity后处理学习日志 在现代游戏开发中,后处理(Post Processing)技术已经成为提升游戏画面质量的关键工具。Unity的后处理栈(Post Processing Stack)是一个强大的插件,它允许开发者为游戏场景添加各种视觉效果,如景深、色彩校正、辉光、模糊等。这些效果不仅能够增强游戏的视觉吸引力,还能帮助传达特定的情感和氛围。 文档

Unity协程搭配队列开发Tips弹窗模块

概述 在Unity游戏开发过程中,提示系统是提升用户体验的重要组成部分。一个设计良好的提示窗口不仅能及时传达信息给玩家,还应当做到不干扰游戏流程。本文将探讨如何使用Unity的协程(Coroutine)配合队列(Queue)数据结构来构建一个高效且可扩展的Tips弹窗模块。 技术模块介绍 1. Unity协程(Coroutines) 协程是Unity中的一种特殊函数类型,允许异步操作的实现

【软件工程】软件开发模型

三、瀑布模型  四、几种软件开发模型的主要特点 题目 判断题 选择题 小结

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光

Unity 资源 之 Super Confetti FX:点亮项目的璀璨粒子之光 一,前言二,资源包内容三,免费获取资源包 一,前言 在创意的世界里,每一个细节都能决定一个项目的独特魅力。今天,要向大家介绍一款令人惊艳的粒子效果包 ——Super Confetti FX。 二,资源包内容 💥充满活力与动态,是 Super Confetti FX 最显著的标签。它宛如一位

Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(4)

本文仅作笔记学习和分享,不用做任何商业用途 本文包括但不限于unity官方手册,unity唐老狮等教程知识,如有不足还请斧正​​ Unity数据持久化 之 一个通过2进制读取Excel并存储的轮子(3)-CSDN博客  这节就是真正的存储数据了   理清一下思路: 1.存储路径并检查 //2进制文件类存储private static string Data_Binary_Pa