Unity_lab8

  1. 项目地址和演示动画:

  2. 实验要求和目的:

    1. 实验目的

      • 了解物理引擎及其基本原理。
      • 掌握常见物理部件,包括角色控制器、刚体、力、碰撞器等。特别是碰撞器的组合。
      • 学习面向对象的游戏编程技术,在游戏中综合使用对象池技术,及相关设计模式。
    2. 实验内容

      • 完成课件上操作 6-01 到 6-03 的内容

      • 按课件(第五章,作业1 “鼠标打飞碟(Hit UFO)”)要求,编写“打飞碟游戏” 。

      • 必须使用对象池管理飞碟对象。

      • 建议使用 ScriptableObject 配置不同的飞碟

      • 建议使用物理引擎管理飞碟飞行路径。

  3. 代码介绍:

    1. Controllers控制器

      1. DiskFactory飞碟生成器

        这个飞碟生成器(DiskFactory)的作用是创建和管理飞碟游戏对象。它使用了工厂模式,通过利用对象池来对飞碟进行管理、创建和销毁操作。

        GetDisk(int round)被主控制器调用,round(回合数)会影响所生产的飞碟的速度、大小等属性。有两个列表used和free,存放的是飞碟属性(包括分数、速度),可以循环使用,提高飞碟的产生效率。还需要初始飞碟位置随机,可能为屏幕的四个角落之一,需要根据飞碟的分数和回合数设置飞碟的大小和速度。该飞碟生成器可以根据不同的回合数和属性设置生成不同属性的飞碟,并在飞碟完成任务后将其回收到未使用的列表中。这种设计模式可以提高游戏对象的重复使用性和性能效率。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //自定义异常类
        public class MyException : System.Exception
        {
            //无参构造函数
            public MyException() {}
            //带异常信息的有参构造函数
            public MyException(string message) : base(message) {}
        }
        //用于存储飞碟的属性信息
        public class DiskAttributes : MonoBehaviour
        {
            //public GameObject gameobj;
            //分数
            public int score;
            //飞碟的水平方向上的速度
            public float speedX;
            //飞碟的垂直方向上的速度
            public float speedY;
        }
         
        //飞碟生成器
        //工厂模式
        //用于创建和管理飞碟
        public class DiskFactory : MonoBehaviour
        {
            //用于存储已使用的飞碟游戏对象的列表
            List<GameObject> used;
            //用于存储非使用的飞碟游戏对象的列表
            List<GameObject> free;
            //用于生成随机数
            System.Random rand;
         
            // Start is called before the first frame update
            void Start()
            {
                //进行了初始化操作
                //将used和free实例化
                //并且创建了一个rand的实例
                used = new List<GameObject>();
                free = new List<GameObject>();
                rand = new System.Random();
            }
         
            // Update is called once per frame
            void Update(){}
         
            //用于获取一个飞碟游戏对象
            public GameObject GetDisk(int round) {
                GameObject disk;
                //首先检查未使用的飞碟列表free是否为空
                //如果不为空,就从列表中获取第一个飞碟
                //并且将这个飞碟从free列表中移除
                if (free.Count != 0) {
                    disk = free[0];
                    free.Remove(disk);
                }
                else {
                    //如果free列表为空,就从资源列表中获取一个飞碟预制体
                    disk = GameObject.Instantiate(Resources.Load("Prefabs/disk", typeof(GameObject))) as GameObject;
                    //添加飞碟属性组件
                    disk.AddComponent<DiskAttributes>();
                    //添加刚体组件
                    disk.AddComponent<Rigidbody>();
                }
              
                //根据不同round设置diskAttributes的值
         
                //为飞碟的角度设置一个随意的欧拉角
                //其中X轴的旋转角度在-20到-40之间
                disk.transform.localEulerAngles = new Vector3(-rand.Next(20,40),0,0);
         
                //获取飞碟对象上的飞碟属性组件
                DiskAttributes attri = disk.GetComponent<DiskAttributes>();
                //设置该飞碟属性组件的分数属性为1到3之间
                attri.score = rand.Next(1,4);
                //由分数来决定速度、颜色、大小
                attri.speedX = (rand.Next(1,5) + attri.score + round) * 0.2f;
                attri.speedY = (rand.Next(1,5) + attri.score + round) * 0.2f;
              
                //如果飞碟的分数为3,就将该飞碟的颜色设置为红色,并且稍微缩小一定的比例
                if (attri.score == 3) {
                    disk.GetComponent<Renderer>().material.color = Color.red;
                    disk.transform.localScale += new Vector3(-0.5f,0,-0.5f);
                }
                //如果飞碟的分数为2,就将该飞碟的颜色设置为绿色,并且稍微缩小一定的比例
                else if (attri.score == 2) {
                    disk.GetComponent<Renderer>().material.color = Color.green;
                    disk.transform.localScale += new Vector3(-0.2f,0,-0.2f);
                }
                //如果飞碟的分数为1,就将该飞碟的颜色设置为蓝色
                else if (attri.score == 1) {
                    disk.GetComponent<Renderer>().material.color = Color.blue;
                }
              
                //飞碟可从四个方向飞入(左上、左下、右上、右下)
                //随机生成一个随机数,用于确定飞碟从哪一个方向飞入
                int direction = rand.Next(1,5);
                //根据前面生成的随机数确定飞碟的飞入方向,根据不同的方向
                //使用disk.transform.Translate将飞碟的初始位置设置在屏幕的不同边缘
                //同时,根据飞碟的不同方向对飞碟的速度进行适当的调整
                //使得飞碟在X轴和Y轴上反向移动
                if (direction == 1) {
                    disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight * 1.5f, 8)));
                    attri.speedY *= -1;
                }
                else if (direction == 2) {
                    disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(0, Camera.main.pixelHeight * 0f, 8)));
                  
                }
                else if (direction == 3) {
                    disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight * 1.5f, 8)));
                    attri.speedX *= -1;
                    attri.speedY *= -1;
                }
                else if (direction == 4) {
                    disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, Camera.main.pixelHeight * 0f, 8)));
                    attri.speedX *= -1;
                }
                //将生成的飞碟添加到已使用的飞碟列表中
                used.Add(disk);
                //将飞碟设置为激活状态
                disk.SetActive(true);
                //输出日志信息
                Debug.Log("generate disk");
                //返回该飞碟实例
                return disk;
            }
         
            //用于释放一个飞碟游戏对象
            public void FreeDisk(GameObject disk) {
                //将飞碟的激活状态设置为False
                disk.SetActive(false);
                //将位置和大小恢复到预制,这点很重要!
                disk.transform.position = new Vector3(0, 0,0);
                disk.transform.localScale = new Vector3(2f,0.1f,2f);
                //检查是否在已使用的飞碟列表used中包含该飞碟对象
                if (!used.Contains(disk)) {
                    //如果不包含,就抛出自定义异常
                    throw new MyException("Try to remove a item from a list which doesn't contain it.");
                }
                //输出日志信息
                Debug.Log("free disk");
                //将飞碟从已使用的飞碟列表中移除
                used.Remove(disk);
                //将该飞碟对象添加到free对象列表中
                free.Add(disk);
            }
        }
        
      2. ScoreController分数控制器

        这个分数控制器(ScoreController)的作用就是记录游戏中的得分并与用户交互类交互然后将得分显示在用户界面上。它与场景中的其他组件(如 RoundController 和 UserGUI)进行交互,以实现游戏得分的管理和显示功能。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //分数控制器
        //用于记录游戏中的得分
        public class ScoreController : MonoBehaviour
        {
            //用于存储游戏得分
            int score;
            //用于引用当前场景的RoundController控制器
            public RoundController roundController;
            //用于引用当前场景的UserGUI组件
            public UserGUI userGUI;
            // Start is called before the first frame update
            void Start()
            {
                //首先通过SSDirector.getInstance().currentSceneController获取当前场景的控制器
                //并且将其转换为RoundController的类型
                //同时将结果赋值给roundController
                roundController = (RoundController)SSDirector.getInstance().currentSceneController;
                //将当前的ScoreController实例赋值给roundController的scoreController属性
                roundController.scoreController = this;
                //通过GetComponent<UserGUI>()方法获取当前游戏对象上的UserGUI组件,并将结果赋值给userGUI
                userGUI = this.gameObject.GetComponent<UserGUI>();
            }
         
            //用于记录得分
            public void Record(GameObject disk) {
                //通过disk.GetComponent<DiskAttributes>().score获取disk游戏对象上的DiskAttributes组件,并获取其score属性的值,
                //然后将其累加到score变量上
                score += disk.GetComponent<DiskAttributes>().score;
                //接着,将累加后的score赋值给userGUI的score属性
                userGUI.score = score;
            }
        }
        
      3. Singleton单实例代码

        这个控制器(Singleton)是一个泛型类,用于实现单例模式。它可以用于任何类型 T 的类,确保在整个游戏运行期间只有一个实例存在。它可以通过获取单例对象来提供统一的访问点,方便其他组件或类与该对象进行交互和共享数据。在 Unity 中,单例模式常用于管理全局的游戏状态、资源、配置等。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //用于单实例化飞碟工厂
        //<T>表示该类是一个泛型类,可以用任何类型来实例化
        public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
        {
        	//声明一个静态的受保护的字段
        	//用于存储实例化的对象
        	protected static T instance;
         
        	//公共的只读属性
        	//通过该属性获取单例对象
        	public static T Instance {  
        		get {  
        			//首先检查instance是否为空
        			if (instance == null) { 
        				//如果为空,就使用FindObjectOfType查找场景中第一个符合类型T的对象
        				//并且将其赋值给instance
        				instance = (T)FindObjectOfType (typeof(T));  
        				//如果instance仍然为空,就输出错误日志,提示需要往场景中添加类型T的实例
        				if (instance == null) {  
        					Debug.LogError ("An instance of " + typeof(T) +
        					" is needed in the scene, but there is none.");  
        				}  
        			}  
        			//返回唯一的单实例对象
        			return instance;  
        		}  
        	}
        }
        
      4. ISceneController场景控制器接口

        通过定义这个接口,可以实现不同场景控制器之间的统一管理和交互。在 Unity 中,有多个场景控制器可能需要实现这个接口,例如游戏关卡控制器、菜单控制器等。通过实现相同的接口,可以使这些控制器具备相似的功能,并且可以通过接口类型进行统一的调用和管理。同时这个场景控制器接口定义了场景控制器的公共功能和方法,使得不同的场景控制器可以具备相似的接口,并实现各自的场景逻辑和功能。这样可以实现场景控制器之间的统一管理和交互。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //场景控制器接口
        public interface ISceneController 
        {
            //用于加载资源的方法
            void LoadSource();
            //用于处理击中事件的方法
            void GetHit();
        }
        
      5. SSDirector导演类

        通过这个类,可以实现全局的导演类,用于管理游戏的整体流程和场景切换。它可以作为一个中心控制器,负责协调和调度不同场景之间的切换和逻辑处理。其他场景控制器可以通过该类的实例(通过 SSDirector.getInstance() 获取)来获取当前的场景控制器,并进行统一的管理和交互。同时这个导演类用于实现游戏的整体控制和场景切换,通过单例模式确保只有一个实例存在,并提供接口(currentSceneController)来管理和切换不同的场景控制器。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //导演类
        public class SSDirector : System.Object
        {
            //用于声明一个私有的静态字段_instance,用于存储SSDirector的唯一实例
            private static SSDirector _instance;
            //定义了一个公共的属性currentSceneController
            //用于获取或设置当前场景控制器,该属性的类型为ISceneController接口
            public ISceneController currentSceneController {get; set;}
          
            //用于获取SSDirector的实例
            public static SSDirector getInstance() {
                //首先检查_instance是否为空
                if (_instance == null) {
                    //如果为空,就创建一个新的SSDirector实例
                    //并且将该实例赋值给_instance
                    _instance = new SSDirector();
                }
                //最后返回_instance
                return _instance;
            }
        }
        
      6. RoundController 主控制器

        这个主控制器(RoundController)是游戏的核心控制类,负责连接用户与游戏,实现场景控制器的接口和用户操作的接口,它还负责管理游戏的回合控制、动作管理、得分记录和界面显示等核心逻辑。该控制器的Update的主要工作:在每个回合中从工厂获取飞碟,为飞碟绑定动作,令其开始运动。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        using System.Threading;
         
        //主控制器
        //连接用户与游戏,分别需要实现场景控制器的接口和用户操作的接口
        public class RoundController : MonoBehaviour, ISceneController, IUserAction
        {
            //表示当前游戏的回合数
            int round = 0;
            //表示游戏的最大回合数
            int max_round = 10;
            //表示每回合的计时器
            float timer = 0.5f;
            //游戏对象,表示飞碟游戏对象
            GameObject disk;
            //飞碟工厂类型,用于创建飞碟对象
            DiskFactory factory ;
            //实现了IActionManager接口的变量,用于处理飞碟对象的动作(运动学和刚体物理)
            public IActionManager actionManager;
            //用于管理得分
            public ScoreController scoreController;
            //用于管理用户界面
            public UserGUI userGUI;
            // Start is called before the first frame update
            void Start(){}
         
            // Update is called once per frame
            void Update()
            {
                //首先检查userGUI的模式是否为0,如果是就直接返回
                if (userGUI.mode == 0) return;
                //接着根据userGUI的isKinematic属性选择合适的actionManager
                //如果isKinematic是false,表明对象的动作为物理刚体运动,就需要使用PhysicActionManager
                if (userGUI.isKinematic == false) {
                    actionManager = gameObject.GetComponent<PhysicActionManager>() as IActionManager;
                }
                //如果为True,就表明对象的动作为运动学动作,就需要使用CCActionManager
                else {
                    actionManager = gameObject.GetComponent<CCActionManager>() as IActionManager;
                }
                //调用GetHit()方法处理玩家的点击操作
                GetHit();
                //调用gameOver()方法检查游戏是否结束
                gameOver();
                //如果当前回合数大于最大回合数,就直接返回,不再执行后续的操作
                if (round > max_round) {
                    return;
                }
                //计时器递减
                timer -= Time.deltaTime;
                //判断计时器是否小于等于0并且actionManager的剩余动作是否为0
                if (timer <= 0 && actionManager.RemainActionCount() == 0) {
                    //如果满足条件,就从工厂中得到10个飞碟,为其加上动作
                    for (int i = 0; i < 10; ++i) {
                        disk = factory.GetDisk(round);
                        //使用Fly函数为飞碟添加动作
                        actionManager.Fly(disk);
                    }
                    //回合数加1
                    round += 1;
                    //如果回合数小于等于最大回合数
                    //将回合数更新到userGUI的round上
                    if (round <= max_round) {
                        userGUI.round = round;
                    }
                    //将timer重置到4.0f
                    timer = 4.0f;
                }
            }
         
            //Awake函数,该方法在脚本被唤醒时执行一次
            void Awake() {
                //首先通过SSDirector.getInstance()获取SSDirector的实例
                SSDirector director = SSDirector.getInstance();
                //并将当前场景的控制器设置为RoundController
                director.currentSceneController = this;
                //调用LoadSource()方法加载资源
                director.currentSceneController.LoadSource();
                //通过gameObject.AddComponent<T>()方法向游戏对象添加多个组件
                //依次为UserGUI、PhysicActionManager、CCActionManager、ScoreController和DiskFactory
                gameObject.AddComponent<UserGUI>();
                gameObject.AddComponent<PhysicActionManager>();
                gameObject.AddComponent<CCActionManager>();
                gameObject.AddComponent<ScoreController>();
                gameObject.AddComponent<DiskFactory>();
                //通过Singleton<DiskFactory>.Instance 获取DiskFactory的单实例
                //将获取到的单实例赋值给factory
                factory = Singleton<DiskFactory>.Instance;
                //通过gameObject.GetComponent<UserGUI>()获取当前游戏对象上的UserGUI组件
                //将结果赋值给当前的userGUI
                userGUI = gameObject.GetComponent<UserGUI>();
            }
         
            public void LoadSource() {}
         
            //用于判断游戏是否结束
            public void gameOver() 
            {
                //判断当前回合数大于最大回合数并且actionManager的剩余动作数量为0
                //将userGUI的gameMessage设置为"Game Over!",表示游戏结束
                if (round > max_round && actionManager.RemainActionCount() == 0)
                    userGUI.gameMessage = "Game Over!";
            }
         
            //用于处理玩家的点击操作
            public void GetHit() {
                //首先通过Input.GetButtonDown("Fire1")来判断玩家是否按下鼠标左键
                if (Input.GetButtonDown("Fire1")) {
                    //如果是,就创建一条射线,其原点为摄像机,方向为鼠标点击位置
        			Camera ca = Camera.main;
        			Ray ray = ca.ScreenPointToRay(Input.mousePosition);
         
                    //通过Physics.Raycast(ray, out hit)返回射线与场景物体的碰撞结果
        			RaycastHit hit;
        			if (Physics.Raycast(ray, out hit)) {
                        //如果射线与物体发生碰撞,则调用scoreController.Record()方法记录得分
                        //将碰撞到的物体设置为非活动状态,即隐藏该物体
                        scoreController.Record(hit.transform.gameObject);
                        hit.transform.gameObject.SetActive(false);
        			}
        		}
            }
        }
        
    2. Views 用户接口与用户GUI

      1. IUserAction用户接口

        该用户接口(IUserAction)定义了一些逻辑接口函数,用于处理游戏结束和玩家点击的操作。通过定义用户接口(IUserAction),可以将游戏逻辑与用户操作解耦,使得游戏的逻辑和用户操作的处理可以分开实现,提高代码的可维护性和可扩展性。同时,该接口定义了游戏结束和玩家点击的操作,方便开发者在游戏中根据需求进行相应的处理。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //用户接口
        public interface IUserAction {
            //用于处理游戏结束的逻辑接口函数
            void gameOver();
            //用于处理玩家点击的逻辑接口函数
            void GetHit();
        }
        
      2. UserGUI 用户界面

        这个用户界面(UserGUI)用于实现游戏的界面显示和用户交互。它包含了一些变量和方法,用于记录和显示游戏的状态信息,并根据用户的交互进行相应的操作。它主要用于实现游戏的界面显示和用户交互,负责绘制主菜单界面和游戏开始界面,显示游戏的状态信息,响应用户的点击操作,并将用户的操作传递给主控制器(RoundController)进行游戏逻辑的处理。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //用户界面
        public class UserGUI : MonoBehaviour
        {
            //用于记录用户处于哪一个界面
            public int mode;
            //用于记录用户的总得分
            public int score;
            //用于记录游戏处于的游戏轮数
            public int round;
            //用于记录需要返回给用户的游戏描述
            public string gameMessage;
            //用于记录当前飞碟的动作为运动学还是物理刚体
            public bool isKinematic;
            //表示用户行为动作接口
            private IUserAction action;
            //用于定义自定义字体格式
            public GUIStyle bigStyle, blackStyle, smallStyle;
            //用于存储像素字体
            public Font pixelFont;
            //表示主菜单每一个按键的宽度和高度
            private int menu_width = Screen.width / 5, menu_height = Screen.width / 10;
            // Start is called before the first frame update
            void Start()
            {
                //首先进行初始化,将动作初始化为运动学动作
                isKinematic = true;
                //将当前的用户界面初始化为0
                mode = 0;
                //将需要显示的信息初始化为空字符
                gameMessage = "";
                //获取SSDirector的实例,并将其当前场景控制器转换为IUserAction接口类型,然后赋值给action变量
                action = SSDirector.getInstance().currentSceneController as IUserAction;
              
                //大字体初始化
                //创建一个名为bigStyle的新GUIStyle对象
                bigStyle = new GUIStyle();
                //设置bigStyle的文本颜色为白色
                bigStyle.normal.textColor = Color.white;
                //设置bigStyle的背景为 null
                bigStyle.normal.background = null;
                //设置bigStyle的字体大小为 50
                bigStyle.fontSize = 50;
                //设置bigStyle的对齐方式为居中
                bigStyle.alignment=TextAnchor.MiddleCenter;
         
                //类似地,对blackStyle进行了相应的初始化
                //black
                blackStyle = new GUIStyle();
                blackStyle.normal.textColor = Color.black;
                blackStyle.normal.background = null;
                blackStyle.fontSize = 50;
                blackStyle.alignment=TextAnchor.MiddleCenter;
         
                //小字体初始化
                //类似地,对smallStyle进行了相应的初始化
                smallStyle = new GUIStyle();
                smallStyle.normal.textColor = Color.white;
                smallStyle.normal.background = null;
                smallStyle.fontSize = 20;
                smallStyle.alignment=TextAnchor.MiddleCenter;
            }
         
            // Update is called once per frame
            void Update(){}
         
            //是Unity的生命周期方法,在每个渲染帧之后被调用,用于绘制GUI元素
            void OnGUI() {
                GUI.skin.button.fontSize = 20;
                //根据mode的值进行分支调用
                switch(mode) {
                    case 0:
                        mainMenu();
                        break;
                    case 1:
                        GameStart();
                        break;
                }     
            }
         
            //主菜单界面
            void mainMenu() {
                //在指定位置绘制标签,显示文本为"Hit UFO",使用预定义的bigStyle样式
                GUI.Label(new Rect(Screen.width / 2 - menu_width * 0.5f, Screen.height * 0.1f, menu_width, menu_height), "Hit UFO", bigStyle);
                //在指定位置绘制按钮,显示文本为"Start",使用指定的位置和大小,返回一个布尔值表示按钮是否被点击
                bool button = GUI.Button(new Rect(Screen.width / 2 - menu_width * 0.5f, Screen.height * 3 / 7, menu_width, menu_height), "Start");
                //如果按钮被点击,就将mode设置为1,即会调用GameStart函数
                if (button) {
                    mode = 1;
                }
            }
            //游戏开始的界面
            void GameStart() {
                //创建三个label
                //一个用于显示返回的游戏信息
                GUI.Label(new Rect(300, 60, 50, 200), gameMessage, bigStyle);
                //用于返回游戏的得分
                GUI.Label(new Rect(0,0,100,50), "Score: " + score, smallStyle);
                //用于返回游戏的轮数
                GUI.Label(new Rect(560,0,100,50), "Round: " + round, smallStyle);
                //在指定位置绘制按钮,显示文本为"Kinematic/Not Kinematic",使用指定的位置和大小,返回一个布尔值表示该按钮是否被点击
                if (GUI.Button(new Rect(Screen.width / 2 - menu_width * 0.9f, 0, menu_width * 1.8f, menu_height), "Kinematic/Not Kinematic")) {
                    //如果该按钮被点击
                    //就将isKinematic设置为原来的负值
                    isKinematic = !isKinematic;
                }
            }
        }
        
    3. Action动作和动作管理器

      1. SSAction动作基类

        动作基类(SSAction)作为一个抽象类,用于定义我们的游戏中各种动作的基本行为和属性。它继承自ScriptableObject,可以创建自定义的脚本化文件,用于在编辑器和运行时配置动作的属性。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //动作基类
        //使用ScriptableObject文件定制属性配置
        //创建可在编辑器和运行中使用的自定义脚本化文件
        public class SSAction : ScriptableObject
        {
            //表示是否启用该动作
            public bool enable = true;
            //表示是否销毁该动作
            public bool destroy = false;
            //用于获取和设置与该动作关联的游戏对象
            public GameObject gameObject { get; set;}
            //用于获取和设置与该动作关联的游戏对象的变换组件
            public Transform transform {get; set;}
            //用于获取和设置与该动作关联的回调函数
            public IActionCallback callback {get; set;}
         
            //无参构造函数,用于创建实例
            protected SSAction() {}
            // Start is called before the first frame update
            //在子类中要求实现,如果没有实现,就抛出异常
            public virtual void Start()
            {
                throw new System.NotImplementedException();
            }
         
            // Update is called once per frame
            //在子类中要求实现,如果没有实现,就抛出异常
            public virtual void Update()
            {
                throw new System.NotImplementedException();
            }
        }
        
      2. CCFlyAction飞碟动作类

        飞碟的动力学运动有两个简单的属性:水平方向运动速度和垂直方向运动速度。
        飞碟从飞碟工厂DiskFactory出来的时候被定位在相机的四个视角边缘,随着运动进入相机视角,在被玩家点击或者飞出相机视角(即玩家不能再看到它时)时,飞碟和动作一起被销毁(回收)。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
        //飞碟动作类(运动学)
        //继承自动作基类
        //飞碟从界面左右两侧飞入,离开界面时运动结束
        //飞碟的运动有两个方向,主要为水平运动方向和垂直运动方向
        public class CCFlyAction : SSAction
        {
            //在水平方向上的运动速度
            public float speedX;
            //在垂直方向上的运动速度
            public float speedY;
            //用于创建并返回飞碟运动的实例
            //接收飞碟在水平和垂直方向上的速度并且设置速度
            //返回该飞碟运动的动作
            public static CCFlyAction GetSSAction(float x, float y) {
                CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
                action.speedX = x;
                action.speedY = y;
                return action;
            }
            // Start is called before the first frame update
            public override void Start()
            {
                //将刚体的是否为运动学运动的属性变量设置为True
                gameObject.GetComponent<Rigidbody>().isKinematic = true;
            }
         
            // Update is called once per frame
            public override void Update()
            {
                //首先检查飞碟游戏对象是否处于非激活状态(被销毁)
                if (this.transform.gameObject.activeSelf == false) {
                    //将销毁destroy设置为True
                    this.destroy = true;
                    //通过回调函数接口通知事件发生
                    this.callback.SSActionEvent(this);
                    //随后返回
                    return;
                }
              
                //将飞碟的世界坐标转换为屏幕坐标
                Vector3 vec3 = Camera.main.WorldToScreenPoint (this.transform.position);
                //如果飞碟已经超出了屏幕的范围,就将destroy设置为True,同时通过回调函数接口通知事件发生,同时返回
                if (vec3.x < -100 || vec3.x > Camera.main.pixelWidth + 100 || vec3.y < -100 || vec3.y > Camera.main.pixelHeight + 100) {
                    this.destroy = true;
                    this.callback.SSActionEvent(this);
                    return;
                }
                //如果飞碟还在屏幕的范围内,就通过在水平和垂直的速度更新飞碟的速度状态
                transform.position += new Vector3(speedX, speedY, 0) * Time.deltaTime * 2;
            }
        }
        
      3. PhysicFlyAction飞碟动作类

        这一个动作类与CCFlyAction同样继承自SSAction,但是由于添加了刚体属性,即该物体本身就有了重力,现在只需要一个水平方向的初速度即可实现物体的运动了。我们可以在Update函数里看到两种运动的明显差别,CCFlyAction在Update函数中通过position.translate来不断更新改变物体的位置,而PhysicFlyAction什么都不用做,物体自身带有的物理属性(重力和初速度)就会使它运动起来。而为了区分这一个物体的运动究竟是属于动力学运动还是物理刚体运动,我们还需要添加isKinematic变量来进行标记,需要注意的是做刚体物理运动时要将isKinematic设为false,而作动力学运动时需要将其设为true。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //飞碟动作类(物理刚体运动)
        //只需要水平方向上的初速度,在垂直方向上考虑重力
        //飞碟从界面左右两侧飞入,离开界面时运动结束
        public class PhysicFlyAction : SSAction
        {
            //只需要定义一个变量用于1存储水平方向上的运动速度
            public float speedX;
            //用于创建一个PhysicFlyAction实例,并且设置其速度
            //返回创建的该实例
            public static PhysicFlyAction GetSSAction(float x) {
                PhysicFlyAction action = ScriptableObject.CreateInstance<PhysicFlyAction>();
                action.speedX = x;
                return action;
            }
            // Start is called before the first frame update
            public override void Start()
            {
                //将飞碟的刚体的isKinematic设置为了False,表示这是一个刚体的物理运动
                gameObject.GetComponent<Rigidbody>().isKinematic = false;
                //设置飞碟的初始速度
                gameObject.GetComponent<Rigidbody>().velocity = new Vector3(speedX * 10, 0, 0);
                //将飞碟的阻力设置为1,以模拟飞碟在空气中受到的空气阻力
                gameObject.GetComponent<Rigidbody>().drag = 1;
            }
         
            //与运动学的动作不同,这里不需要不断更行飞碟的位置
            //只需要给飞碟一个初速度以及重力,飞碟自己就会运动
            // Update is called once per frame
            public override void Update()
            {
                //检查飞碟是否处于非激活状态(被销毁)
                if (this.transform.gameObject.activeSelf == false) {
                    //如果是,将destroy设置为True
                    this.destroy = true;
                    //调用回调接口的方法通知动作已经结束
                    this.callback.SSActionEvent(this);
                    //然后返回
                    return;
                }
              
                //将飞碟的世界坐标转换为屏幕坐标
                Vector3 vec3 = Camera.main.WorldToScreenPoint (this.transform.position);
                //判断飞碟是否超出了屏幕的范围
                if (vec3.x < -100 || vec3.x > Camera.main.pixelWidth + 100 || vec3.y < -100 || vec3.y > Camera.main.pixelHeight + 100) {
                    //如果超出,将destroy设置为True
                    this.destroy = true;
                    //调用回调接口的方法通知该动作已经结束
                    this.callback.SSActionEvent(this);
                    //然后返回
                    return;
                }
            }
        }
        
        
      4. IActionCallback 事件回调接口

        可以接收事件通知,该回调函数允许其他对象或系统向特定的对象发送事件通知。当某个事件发生时,可以调用回调函数,将事件信息传递给回调函数,以便对象能够作出相应的响应。回调函数还可以实现事件处理逻辑、实现定制化行为以及实现对象间的通信等。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //定义了一个枚举类型,主要有两个状态,分别表示动作事件的开始和完成
        public enum SSActionEventType:int {Started, Completed}
        //定义一个接口,用于接收动作事件的回调函数
        public interface IActionCallback
        {
            //回调函数
            //source是触发动作事件的SSAction对象
            //events是事件类型(开始和完成)
            void SSActionEvent(SSAction source,
                SSActionEventType events = SSActionEventType.Completed,
                int intParam = 0,
                string strParam = null,
                Object objectParam = null);
        }
        
      5. IActionManager动作管理接口

        这个接口类(IActionManager)提供了一个最简单的接口给主控制器调用,用于控制飞碟的飞行动作和获取当前回合飞碟的剩余数量。主要运用了Adapter模式,将原来的运动学动作管理类和物理刚体动作管理类进行了合并以及整理。通过这个接口,主控制器可以调用动作管理器来控制飞碟的飞行行为,并获取飞碟的状态信息。这样可以实现飞碟游戏的逻辑控制和状态管理。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //提供一个最简单的接口给主控制器调用
        public interface IActionManager
        {
            //控制飞碟的飞行动作
            void Fly(GameObject disk);
            //用于返回当前回合飞碟的剩余数
            int RemainActionCount() ;
        }
        
      6. SSActionManager动作管理类基类

        该动作管理类基类(SSActionManager)用于管理和调度游戏中的各种动作。它是一个抽象类,提供了动作管理的基本框架和方法,可以被具体的动作管理器子类继承和扩展。提供了一个通用的动作管理框架,用于集中管理和调度游戏中的各种动作。它简化了动作的管理和更新过程,使得游戏开发者可以更方便地处理和控制动作的执行。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //动作管理类基类
        public class SSActionManager : MonoBehaviour
        {
            //定义一个字典,用于存储正在执行的动作,以动作实例的唯一标识符作为键
            public Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
            //定义一个列表,用于存储等待添加到aactions字典中的动作实例
            private List<SSAction> waitingAdd = new List<SSAction>();
            //定义一个列表,用于存储等待从actions字典中删除动作实例的唯一标识符
            private List<int> waitingDelete = new List<int>(); 
            // Start is called before the first frame update
            protected void Start(){}
         
            // Update is called once per frame
            protected void Update()
            {
                //首先将列表中等待添加到actions字典中的动作实例添加到actions字典中
                foreach (SSAction ac in waitingAdd) {
                    actions[ac.GetInstanceID()] = ac;
                }
                //清空waitingAdd列表
                waitingAdd.Clear();
         
                //遍历在actions字典中的动作实例
                foreach(KeyValuePair<int, SSAction> kv in actions) {
                    SSAction ac = kv.Value;
                    //如果动作实例中的destroy为True,将该动作实例添加到等待删除waitingDelete的列表中
                    if (ac.destroy) {
                        waitingDelete.Add(ac.GetInstanceID());
                    } 
                    //如果动作实例的enable为True,就调用其Update方法进行更新
                    else if (ac.enable) {
                        ac.Update();
                    }
                }
         
                //根据waitingDelete列表中的唯一标识符,从actions字典中删除相对应的动作实例
                foreach(int key in waitingDelete) {
                    SSAction ac = actions[key];
                    actions.Remove(key);
                    Destroy(ac);
                }
                //清空waitingDelete列表
                waitingDelete.Clear();
            }
         
            //定义一个方法,用于运行一个动作
            //接收一个游戏对象,一个动作实例,以及一个动作回调接口
            public void RunAction(GameObject gameObject, SSAction action, IActionCallback manager) {
                //为动作实例设置游戏对象和变换属性
                action.gameObject = gameObject;
                action.transform = gameObject.transform;
                action.callback = manager;
                //将动作实例添加到waitingAdd列表中
                waitingAdd.Add(action);
                //调用动作实例的start方法
                action.Start();
            }
         
            //定义一个虚函数,用于移动飞碟的行为
            //通过子类重写来实现特定的移动行为
            public virtual void MoveDisk(GameObject disk){}
        }
        
      7. CCActionManager动作管理类

        该动作管理类主要继承于SSActionManager、IActionCallback和IActionManager,主要用于管理和控制飞碟的运动学运动行为,与场景控制器进行交互,并提供方法来控制飞碟的飞行和获取飞行中的飞碟数量。通过该类的实现,可以实现飞碟的创建、飞行和回收等动作的管理和控制。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //(运动学)动作管理类
        public class CCActionManager : SSActionManager, IActionCallback, IActionManager
        {
            //定义一个控制器变量,用于获取当前场景控制器
            public RoundController sceneController;
            //用于存储飞碟的飞行动作(运动学)
            public CCFlyAction action;
            //用于获取飞碟的工厂实例
            public DiskFactory factory;
          
            // Start is called before the first frame update
            protected new void Start()
            {
                //获取当前场景控制器,并且调用导演类中的函数来确保场景的单实例
                sceneController = (RoundController)SSDirector.getInstance().currentSceneController;
                //将当前的CCActionManager设置为场景控制器的动作管理器
                sceneController.actionManager = this as IActionManager;
                //获取飞碟的工厂实例,确保工厂的单实例
                factory = Singleton<DiskFactory>.Instance;
            }
         
            //重写回调函数
            public void SSActionEvent(SSAction source,
                SSActionEventType events = SSActionEventType.Completed,
                int intParam = 0,
                string strParam = null,
                Object objectParam = null) {
                    //通过访问source的transform属性获取飞碟的游戏对象,并调用飞碟工厂的FreeDisk方法将飞碟回收
                    factory.FreeDisk(source.transform.gameObject);
            }
         
            public override void MoveDisk(GameObject disk) {
                //根据飞碟的速度创建一个CCFlyAction动作实例
                action = CCFlyAction.GetSSAction(disk.GetComponent<DiskAttributes>().speedX, disk.GetComponent<DiskAttributes>().speedY);
                //将该动作实例作为参数传递给基类的RunAction方法,
                //同时传递当前的CCActionManager实例作为动作的回调接口
                RunAction(disk, action, this);
            }
         
            //这是IActionManager中的Fly函数,通过调用MoveDisk来控制飞碟的飞行
            public void Fly(GameObject disk) {
                MoveDisk(disk);
            }
         
            //返回当前正在(尚未)执行的飞碟的数量
            public int RemainActionCount() {
                return actions.Count;
            }
        }
        
      8. PhysicActionManager动作管理类

        该动作管理类(PhysicActionManager)继承自基类 SSActionManager,并实现了接口 IActionCallback 和 IActionManager。该类使用物理刚体(Rigidbody)来实现飞碟的运动行为,与前面的运动学动作管理类有所不同,但是其它的各个方面还是基本上一致的。主要提供方法来控制飞碟的飞行和获取飞行中的飞碟数量,通过该类的实现,可以实现飞碟的创建、飞行和回收等动作的管理和控制,并与场景控制器进行交互。

        using System.Collections;
        using System.Collections.Generic;
        using UnityEngine;
         
        //(物理刚体)动作管理器
        //体现Adapter模式,一个简单接口实现多个功能
        public class PhysicActionManager : SSActionManager, IActionCallback, IActionManager
        {
            //定义一个控制器变量,用于获取当前场景控制器
            public RoundController sceneController;
            //用于存储飞碟的飞行动作(刚体物理)
            public PhysicFlyAction action;
            //用于获取飞碟的工厂实例,用于创建和管理飞碟
            public DiskFactory factory;
          
            // Start is called before the first frame update
            protected new void Start()
            {
                //获取当前场景控制器,并且调用导演类中的函数来确保场景的单实例
                sceneController = (RoundController)SSDirector.getInstance().currentSceneController;
                //将当前的CCActionManager设置为场景控制器的动作管理器
                sceneController.actionManager = this as IActionManager;
                //获取飞碟的工厂实例,确保工厂的单实例
                factory = Singleton<DiskFactory>.Instance;
            }
         
            //Update is called once per frame
            protected new void Update(){}
         
            public void SSActionEvent(SSAction source,
                SSActionEventType events = SSActionEventType.Completed,
                int intParam = 0,
                string strParam = null,
                Object objectParam = null) {
                    //通过访问source的transform属性获取飞碟的游戏对象,并调用飞碟工厂的FreeDisk方法将飞碟回收
                    factory.FreeDisk(source.transform.gameObject);
            }
         
            public override void MoveDisk(GameObject disk) {
                //首先获取飞碟的水平速度,并且创建一个PhysicFlyAction实例对象,并传递给该方法速度
                action = PhysicFlyAction.GetSSAction(disk.GetComponent<DiskAttributes>().speedX);
                //将该动作实例作为参数传递给基类的RunAction方法,
                //同时传递当前的CCActionManager实例作为动作的回调接口
                RunAction(disk, action, this);
            }
         
            //这是IActionManager中的Fly函数,通过调用MoveDisk来控制飞碟的飞行
            public void Fly(GameObject disk) {
                MoveDisk(disk);
            }
         
            //返回当前正在(尚未)执行的飞碟的数量
            public int RemainActionCount() {
                return actions.Count;
            }
        }
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值