第六次作业 物理系统与碰撞

1. 改进飞碟(Hit UFO)游戏

  1. 游戏内容要求

    在前一个作业的基础上增加以下要求:

    1. 按 adapter模式 设计图修改飞碟游戏
    2. 使它同时支持物理运动与运动学(变换)运动
  2. 认识概念

    适配器(Adapter)模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。
    运动学:描述物体位置随时间的变化规律。它将一个物体抽象为质点运动模型,利用几何学(如线性代数)的方法,在不考虑外部力作用的前提下研究物体的位置、速度、角度等运动特征随时间的变化。
    刚体:指在运动中和受力作用后,形状和大小不变,而且内部各点的相对位置不变的物体。绝对刚体实际上是不存在的,只是一种理想模型,因为任何物体在受力作用后,都或多或少地变形,如果变形的程度相对于物体本身几何尺寸来说极为微小,在研究物体运动时变形就可以忽略不计。
    动力学:它主要研究作用于物体的力与物体运动的关系。动力学的研究对象是运动速度远小于光速的宏观物体。动力学的研究以牛顿运动定律为基础;牛顿运动定律的建立则以实验为依据。动力学是牛顿力学或经典力学的一部分,但自20世纪以来,动力学又常被人们理解为侧重于工程技术应用方面的一个力学分支。在游戏物理引擎中,主要是刚体动力学。主要包括质点系动力学的基本定理,由动量定理、动量矩定理、动能定理以及由这三个基本定理推导出来的其他一些定理。动量、动量矩和动能(见能)是描述质点、质点系和刚体运动的基本物理量。

  3. 实现
    (1)资源目录树
    在这里插入图片描述
    AllSkyFree:从资源商店导入的资源
    Materials:存放背景图
    Resources:存放预制(飞碟)
    Scripts:存放代码脚本

    Resources:
    在这里插入图片描述
    Scripts:
    在这里插入图片描述
    (2)UML设计图
    在这里插入图片描述
    (3)代码实现
    DiskData.cs

    飞碟数据,包括飞碟的速度、得分以及方向。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class DiskData : MonoBehaviour
    {
        public float speed;         //水平速度
        public int points;          //得分
        public Vector3 direction;   //初始方向
    }
    

    DiskFactory.cs

    GetDisk:生产飞碟,首先从free空闲队列中查找是否有可用的飞碟,如果没有则新建一个飞碟。
    FreeDisk:用于释放飞碟,将飞碟从used队列中移除并添加到free队列中。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class DiskFactory : MonoBehaviour
    {
        public GameObject disk_Prefab;              //飞碟预制
    
        private List<DiskData> used;                //正被使用的飞碟
        private List<DiskData> free;                //空闲的飞碟
    
        public void Start()
        {
            used = new List<DiskData>();
            free = new List<DiskData>();
            disk_Prefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
            disk_Prefab.SetActive(false);
        }
    
        public GameObject GetDisk(int round)
        {
            GameObject disk;
            //如果有空闲的飞碟,则直接使用,否则生成一个新的
            if (free.Count > 0)
            {
                disk = free[0].gameObject;
                free.Remove(free[0]);
            }
            else
            {
                disk = GameObject.Instantiate<GameObject>(disk_Prefab, Vector3.zero, Quaternion.identity);
                disk.AddComponent<DiskData>();
            }
    
            //按照round来设置飞碟属性
            //飞碟的等级 = 0~2之间的随机数 * 轮次数
            //0~4:  红色飞碟  
            //4~7:  绿色飞碟  
            //7~10: 蓝色飞碟
            float level = UnityEngine.Random.Range(0, 2f) * (round + 1);
            if (level < 4)
            {
                disk.GetComponent<DiskData>().points = 1;
                disk.GetComponent<DiskData>().speed = 4.0f;
                disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<Renderer>().material.color = Color.red;
            }
            else if (level > 7)
            {
                disk.GetComponent<DiskData>().points = 3;
                disk.GetComponent<DiskData>().speed = 8.0f;
                disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<Renderer>().material.color = Color.blue;
            }
            else
            {
                disk.GetComponent<DiskData>().points = 2;
                disk.GetComponent<DiskData>().speed = 6.0f;
                disk.GetComponent<DiskData>().direction = new Vector3(UnityEngine.Random.Range(-1f, 1f) > 0 ? 2 : -2, 1, 0);
                disk.GetComponent<Renderer>().material.color = Color.green;
            }
    
            used.Add(disk.GetComponent<DiskData>());
    
            return disk;
        }
    
        public void FreeDisk(GameObject disk)
        {
            //找到使用中的飞碟,将其踢出并加入到空闲队列
            foreach (DiskData diskData in used)
            {
                if (diskData.gameObject.GetInstanceID() == disk.GetInstanceID())
                {
                    disk.SetActive(false);
                    free.Add(diskData);
                    used.Remove(diskData);
                    break;
                }
    
            }
        }
    }
    

    FirstController.cs

    Hit:当鼠标点击飞碟时,将飞碟移除并计算分数。
    Restart:重置游戏。
    Update:用于发射飞碟与更新状态,飞碟每1s发射一次,每次最多5个。飞碟发射完毕后判断是否重置或结束游戏。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class FirstController : MonoBehaviour, ISceneController, IUserAction
    {
        DiskFactory diskFactory;                         //飞碟工厂
        RoundController roundController;
        UserGUI userGUI;
    
        void Start()
        {
            SSDirector.GetInstance().CurrentScenceController = this;
            gameObject.AddComponent<DiskFactory>();
            gameObject.AddComponent<CCActionManager>();
            gameObject.AddComponent<PhysisActionManager>();
            gameObject.AddComponent<RoundController>();
            gameObject.AddComponent<UserGUI>();
            LoadResources();
        }
    
        public void LoadResources()
        {
            diskFactory = Singleton<DiskFactory>.Instance;
            roundController = Singleton<RoundController>.Instance;
            userGUI = Singleton<UserGUI>.Instance;
        }
    
        public void Hit(Vector3 position)
        {
            Camera ca = Camera.main;
            Ray ray = ca.ScreenPointToRay(position);
    
            RaycastHit[] hits;
            hits = Physics.RaycastAll(ray);
    
            for (int i = 0; i < hits.Length; i++)
            {
                RaycastHit hit = hits[i];
                if (hit.collider.gameObject.GetComponent<DiskData>() != null)
                {
                    //将飞碟移至底端,触发飞行动作的回调
                    hit.collider.gameObject.transform.position = new Vector3(0, -7, 0);
                    //积分
                    roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());
                    //更新GUI数据
                    userGUI.SetPoints(roundController.GetPoints());
                }
            }
        }
    
        public void Restart()
        {
            userGUI.SetMessage("");
            userGUI.SetPoints(0);
            roundController.Reset();
        }
    
        public void SetMode(bool isInfinite)
        {
            roundController.SetMode(isInfinite);
        }
    
        public void SetFlyMode(bool isPhysis)
        {
            roundController.SetFlyMode(isPhysis);
        }
    
        public void FreeDisk(GameObject disk)
        {
            diskFactory.FreeDisk(disk);
        }
    
        void Update()
        {
        }
    }
    

    SSAction.cs

    动作基类。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    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 update
        public virtual void Start()
        {
            throw new System.NotImplementedException();
        }
    
        // Update is called once per frame
        public virtual void Update()
        {
            throw new System.NotImplementedException();
        }
    }
    

    SSActionManager.cs

    动作管理者基类。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    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();
        }
    
        //准备运行一个动作,将动作初始化,并加入到waitingAdd
        public 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 update
        protected void Start()
        {
    
        }
    
    }
    

    CCFlyAction.cs

    同时支持物理运动与运动学,将飞行动作拆分为水平和竖直两个方向,水平速度恒定,竖直方向施加重力加速度。飞碟到达底部时,动作结束,进行回调。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CCFlyAction : SSAction
    {
        float gravity;          //重力加速度
        float speed;            //水平速度
        Vector3 direction;      //飞行方向
        float time;             //时间
    
        //生产函数(工厂模式)
        public static CCFlyAction GetSSAction(Vector3 direction, float speed)
        {
            CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
            action.gravity = 9.8f;
            action.time = 0;
            action.speed = speed;
            action.direction = direction;
            return action;
        }
    
        public override void Start()
        {
            gameObject.GetComponent<Rigidbody>().isKinematic = true;
        }
    
        public override void Update()
        {
            time += Time.deltaTime;
            transform.Translate(Vector3.down * gravity * time * Time.deltaTime);
            transform.Translate(direction * speed * Time.deltaTime);
            //如果飞碟到达底部,则动作结束,进行回调
            if (this.transform.position.y < -6)
            {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
    
        }
    }
    

    CCActionManager.cs

    飞行动作管理者。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager
    {
    
        //飞行动作
        CCFlyAction flyAction;
        //控制器
        FirstController controller;
    
        protected new void Start()
        {
            controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        }
    
        public void Fly(GameObject disk, float speed, Vector3 direction)
        {
            flyAction = CCFlyAction.GetSSAction(direction, speed);
            RunAction(disk, flyAction, this);
        }
    
        //回调函数
        public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null)
        {
            //飞碟结束飞行后进行回收
            controller.FreeDisk(source.gameObject);
        }
    }
    

    IActionManager.cs

    动作管理类的接口,Adapter模式基于这个接口来实现。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public interface IActionManager
    {
        void Fly(GameObject disk, float speed, Vector3 direction);
    }
    

    IsceneController.cs

    场景控制类接口。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public interface ISceneController
    {
        void LoadResources();
    }
    

    ISSActioncallback.cs

    回调函数。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public enum SSActionEventType : int { Started, Competed }
    public interface ISSActionCallback
    {
        //回调函数
        void SSActionEvent(SSAction source,
            SSActionEventType events = SSActionEventType.Competed,
            int intParam = 0,
            string strParam = null,
            Object objectParam = null);
    }
    

    IUserAction.cs

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public interface IUserAction
    {
        void SetFlyMode(bool isPhysis);
        void Hit(Vector3 position);
        void Restart();
        void SetMode(bool isInfinite);
    }
    

    RoundController.cs
    设置游戏的轮次,实现飞碟的发送、计分等功能。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class RoundController : MonoBehaviour
    {
        FirstController controller;
        IActionManager actionManager;                   //动作管理者
        DiskFactory diskFactory;                         //飞碟工厂
        ScoreRecorder scoreRecorder;
        UserGUI userGUI;
        int[] roundDisks;           //对应轮次的飞碟数量
        bool isInfinite;            //游戏当前模式
        int round;                  //游戏当前轮次
        int sendCnt;                //当前已发送的飞碟数量
        float sendTime;             //发送时间
    
        void Start()
        {
            controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
            actionManager = Singleton<CCActionManager>.Instance;
            diskFactory = Singleton<DiskFactory>.Instance;
            scoreRecorder = new ScoreRecorder();
            userGUI = Singleton<UserGUI>.Instance;
            sendCnt = 0;
            round = 0;
            sendTime = 0;
            isInfinite = false;
            roundDisks = new int[] { 3, 5, 8, 13, 21 };
        }
    
        public void Reset()
        {
            sendCnt = 0;
            round = 0;
            sendTime = 0;
            scoreRecorder.Reset();
        }
    
        public void Record(DiskData disk)
        {
            scoreRecorder.Record(disk);
        }
    
        public int GetPoints()
        {
            return scoreRecorder.GetPoints();
        }
    
        public void SetMode(bool isInfinite)
        {
            this.isInfinite = isInfinite;
        }
    
        public void SetFlyMode(bool isPhysis)
        {
            actionManager = isPhysis ? Singleton<PhysisActionManager>.Instance : Singleton<CCActionManager>.Instance as IActionManager;
        }
    
        public void SendDisk()
        {
            //从工厂生成一个飞碟
            GameObject disk = diskFactory.GetDisk(round);
            //设置飞碟的随机位置
            disk.transform.position = new Vector3(-disk.GetComponent<DiskData>().direction.x * 7, UnityEngine.Random.Range(0f, 8f), 0);
            disk.SetActive(true);
            //设置飞碟的飞行动作
            actionManager.Fly(disk, disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction);
        }
    
        // Update is called once per frame
        void Update()
        {
            sendTime += Time.deltaTime;
            //每隔1s发送一次飞碟
            if (sendTime > 1)
            {
                sendTime = 0;
                //每次发送至多5个飞碟
                for (int i = 0; i < 5 && sendCnt < roundDisks[round]; i++)
                {
                    sendCnt++;
                    SendDisk();
                }
                //判断是否需要重置轮次,不需要则输出游戏结束
                if (sendCnt == roundDisks[round] && round == roundDisks.Length - 1)
                {
                    if (isInfinite)
                    {
                        round = 0;
                        sendCnt = 0;
                        userGUI.SetMessage("");
                    }
                    else
                    {
                        userGUI.SetMessage("Game Over!");
                    }
                }
                //更新轮次
                if (sendCnt == roundDisks[round] && round < roundDisks.Length - 1)
                {
                    sendCnt = 0;
                    round++;
                }
            }
        }
    }	
    

    ScoreRecorder.cs
    实现计分功能。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class ScoreRecorder
    {
        int points;                 //游戏当前分数
    
        public ScoreRecorder()
        {
            points = 0;
        }
    
        public void Record(DiskData disk)
        {
            points += disk.points;
        }
    
        public int GetPoints()
        {
            return points;
        }
        public void Reset()
        {
            points = 0;
        }
    }
    

    UserGUI.cs

    用户界面的实现。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class UserGUI : MonoBehaviour
    {
        IUserAction userAction;
        string gameMessage;
        int points;
    
        public void SetMessage(string gameMessage)
        {
            this.gameMessage = gameMessage;
        }
    
        public void SetPoints(int points)
        {
            this.points = points;
        }
    
        void Start()
        {
            points = 0;
            gameMessage = "";
            userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;
        }
    
        void OnGUI()
        {
            //小字体初始化
            GUIStyle style = new GUIStyle();
            style.normal.textColor = Color.white;
            style.fontSize = 30;
    
            //大字体初始化
            GUIStyle bigStyle = new GUIStyle();
            bigStyle.normal.textColor = Color.white;
            bigStyle.fontSize = 50;
    
            GUI.Label(new Rect(300, 30, 50, 200), "Hit UFO", bigStyle);
            GUI.Label(new Rect(20, 0, 100, 50), "Points: " + points, style);
            GUI.Label(new Rect(310, 100, 50, 200), gameMessage, style);
            if (GUI.Button(new Rect(20, 50, 100, 40), "Restart"))
            {
                userAction.Restart();
            }
            
            if (GUI.Button(new Rect(20, 100, 100, 40), "一般模式"))
            {
                userAction.SetMode(false);
            }
            if (GUI.Button(new Rect(20, 150, 100, 40), "无限模式"))
            {
                userAction.SetMode(true);
            }
            if (GUI.Button(new Rect(20, 200, 100, 40), "运动学"))
            {
                userAction.SetFlyMode(false);
            }
            if (GUI.Button(new Rect(20, 250, 100, 40), "物理学"))
            {
                userAction.SetFlyMode(true);
            }
            if (Input.GetButtonDown("Fire1"))
            {
                userAction.Hit(Input.mousePosition);
            }
            
        }
    }
    

    SSDirector.cs

    导演类。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class SSDirector : System.Object
    {
        private static SSDirector _instance;
        public ISceneController CurrentScenceController { get; set; }
        public static SSDirector GetInstance()
        {
            if (_instance == null)
            {
                _instance = new SSDirector();
            }
            return _instance;
        }
    }
    

    PhysisFlyAction.cs

    实现物体的物理学飞行,飞碟预制设置为刚体属性。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PhysisFlyAction : SSAction
    {
        float speed;            //水平速度
        Vector3 direction;      //飞行方向
    
        //生产函数(工厂模式)
        public static PhysisFlyAction GetSSAction(Vector3 direction, float speed)
        {
            PhysisFlyAction action = ScriptableObject.CreateInstance<PhysisFlyAction>();
            action.speed = speed;
            action.direction = direction;
            return action;
        }
    
        public override void Start()
        {
            gameObject.GetComponent<Rigidbody>().isKinematic = false;
            //为物体增加水平初速度
            gameObject.GetComponent<Rigidbody>().velocity = speed * direction;
        }
    
        public override void Update()
        {
            //如果飞碟到达底部,则动作结束,进行回调
            if (this.transform.position.y < -6)
            {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
    
        }
    }	
    

    PhysisActionManager.cs

    物理学飞行管理类。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager
    {
        //飞行动作
        PhysisFlyAction flyAction;
        //控制器
        FirstController controller;
    
        protected new void Start()
        {
            controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        }
    
        public void Fly(GameObject disk, float speed, Vector3 direction)
        {
            flyAction = PhysisFlyAction.GetSSAction(direction, speed);
            RunAction(disk, flyAction, this);
        }
    
        //回调函数
        public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null)
        {
            //飞碟结束飞行后进行回收
            controller.FreeDisk(source.gameObject);
        }
    }
    

    Singleton.cs

    创建对象的模式是单例模式,即某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例。以下是单例模式的实现。

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
    {
        protected static T instance;
    
        public static T Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = (T)FindObjectOfType(typeof(T));
                    if (instance == null)
                    {
                        Debug.LogError("An instance of " + typeof(T) + " is needed in the scene, but there is none");
                    }
                }
                return instance;
            }
        }
    }
    

    (4) 运行游戏
    游戏效果图:
    在这里插入图片描述
    游戏视频:

    Hit-UFO v2

2. 打靶游戏(可选作业)

  • 游戏内容要求:
    1. 靶对象为 5 环,按环计分;
    2. 箭对象,射中后要插在靶上
    3. 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
    4. 游戏仅一轮,无限 trials;
    5. 增强要求:添加一个风向和强度标志,提高难度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值