物理系统与碰撞

物理系统与碰撞

这是3D游戏编程的第六次作业

说明文档

本次实验完成了所有基本要求,尽量将步骤展示出。
闪光点:
附有详细类图以及详细的代码注释

作业内容

改进飞碟(Hit UFO)游戏

  • 游戏内容要求:
    • adapter模式 设计图修改飞碟游戏
    • 使它同时支持物理运动与运动学(变换)运动
改进后效果展示
  • 这里只展示增加了物理运动后的效果,游戏正常运行的效果还请移步到与游戏世界交互文章里的效果展示

    可以看到明显的碰撞效果的产生,这是因为飞碟物理运动是通过设置刚体来完成的,这与原本的运动学的运动不同,后者不包含碰撞。

改进版设计
  • 改进版类图
    在这里插入图片描述
    • 改进
      • 将场景控制器(FisrtController)与回合控制器(RoundController)解耦,场景控制器只需要接管回合控制器即可,具体的记分以及飞碟生产的人物都转交给回合控制器来完成,减轻场景控制器的任务量。
      • 新增记分员(ScoreRecorder),简化场景控制器代码。
      • 回合控制器和动作管理者接口(IActionManager)交接,使得动作管理者可以动态切换为不同的具体的动作管理者,代码中对应着两种动作管理者,一种是运动学(CCActionManager),一种是动力学(PhysisActionManager)。

  • 文件结构(标蓝部分为新增):
    ModelControllerViewAction
改动代码细节

在旧版的基础上,改动的部分不少,与上一次作业重复的代码我将不会列出,而是将修改的都列出,并配上详细注释。

  • 预制的修改

    • 为了让物体能够支持物理运动,可以给它添加刚体组件:
      在这里插入图片描述
    • 两处细节:
      • 记得勾选上Use Gravity选项
      • 为了防止飞碟乱跑(乱跑可能会导致无法正确判断游戏是否结束),所以我们有必要在冻结飞碟的某个轴的移动,根据我游戏的设置这里冻结的是z轴,这样子就能够避免飞碟跑到屏幕后太远或者跑到摄像机后(这里的“后”指的是z轴坐标更大导致飞碟没有进入视野)
  • 新增代码

    • PhysisFlyAction

      简单动作类,这里是依靠预制的刚体来实现飞碟的物理运动,所以可以看到代码中该动作的参数不需要重力以及时间,因为物理运动由刚体帮我们完成,我们只需要指定初始的飞行速度和方向即可。

      public class PhysisFlyAction : SSAction {
        float speed;            //水平速度
        Vector3 direction;      //飞行方向
      
        //生产函数
        public static PhysisFlyAction GetSSAction(float speed, Vector3 direction) {
          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 = 1.5f * speed * direction;
        }
      
        public override void Update() {
          // 飞碟到达底部
          if (this.transform.position.y < -5) {
            this.destroy = true;
            this.enable = false;
            // 进行回调
            this.callback.SSActionEvent(this);
          }
        }
      }
      
    • IActionManager

      动作管理者接口,目的是为了简化回合控制器的使用。对外提供了简单易用的接口,屏蔽内部的飞行细节,以及具体的物理运动、运动学运动的细节。

      public interface IActionManager {
        void Fly(GameObject disk, float speed, Vector3 direction);
      }
      
    • PhysisActionManager

      物理动作管理者,实现了动作管理者接口,其下的飞行动作实现是物理运动,也即使用了PhysisFlyAction类。

      public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager {
      
        // 飞行动作基对象
        public PhysisFlyAction flyAction;
        // 场景控制器
        public FirstController controller;
        // Start is called before the first frame update
        protected new void Start() {
          controller = (FirstController)SSDirector.GetInstance().CurrentSceneController;
        }
      
        public void Fly(GameObject disk, float speed, Vector3 direction) {
          flyAction = PhysisFlyAction.GetSSAction(speed, direction);
          RunAction(disk, flyAction, this);
        }
      
        //回调函数
        public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Computed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null) {
          //飞碟结束飞行后进行回收
          controller.FreeDisk(source.gameObject);
        }
      }
      
    • ScoreRecorder

      记分员,记分员根据飞碟数据来进行记分,供回合控制器使用。

      public class ScoreRecorder {
        int score;    //游戏当前分数
      
        public ScoreRecorder() {
          // 初始化分数为0
          score = 0;
        }
      
        public void UpdateScore(Disk disk) {
          score += disk.points;
        }
      
        public int GetScore() {
          return score;
        }
        public void Reset() {
          score = 0;
        }
      }
      
    • RoundController

      回合控制器,回合控制器里的代码看起来很熟悉,没错,其实就是对上一节的场景控制器的抽取,抽取的部分为回合控制的代码,比如发送飞碟、调用记分员记分等等。这里具体要说明的是玩家可根据设置飞行模式来切换飞碟的动作是运动学运动还是物理运动:

      public void SetFlyMode(bool isPhysis) {
      	actionManager = isPhysis ? Singleton<PhysisActionManager>.Instance : 
      			Singleton<CCActionManager>.Instance as IActionManager;
      }
      

      完整代码:

      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().CurrentSceneController;
          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, 7, 9, 11, 13, 15 };
        }
      
        public void Reset() {
          sendCnt = 0;
          round = 0;
          sendTime = 0;
          scoreRecorder.Reset();
        }
      
        public void UpdateScore(Disk disk) {
          scoreRecorder.UpdateScore(disk);
        }
      
        public int GetScore() {
          return scoreRecorder.GetScore();
        }
      
        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<Disk>().direction.x * 7, UnityEngine.Random.Range(0f, 8f), 0);
          disk.SetActive(true);
          //设置飞碟的飞行动作
          actionManager.Fly(disk, disk.GetComponent<Disk>().speed, disk.GetComponent<Disk>().direction);
        }
      
        public void FastClear() {
          diskFactory.FastClear();
        }
      
        public void FreeDisk(GameObject disk) {
          diskFactory.FreeDisk(disk);
        }
      
      void Update() {
          sendTime += Time.deltaTime;
          // 间隔1s发送飞碟
          if (sendTime >= 2) {
            sendTime = 0;
            // 每次至多发送5个飞碟
            for (int i = 0; i < 5 && sendCnt < roundDisks[round]; i++) {
              sendCnt++;
              SendDisk();
            }
            // 如果在最后一轮并且已经发送完所有该轮的所有飞碟
            if (round == roundDisks.Length - 1 && sendCnt == roundDisks[round]) {
              if (isInfinite) {
                // 如果是无限模式
                // 则重复循环
                round = 0;
                sendCnt = 0;
                userGUI.SetMessage("");
              } else {
                // 否则游戏结束,注意这里必须全部飞碟消失才是游戏结束
                if (!diskFactory.AllFree())
                  return;
                userGUI.SetMessage("Game Over!");
              }
            }
      
            // 如果发完本轮的飞碟,更新轮次
            if (sendCnt == roundDisks[round] && round < roundDisks.Length - 1) {
              sendCnt = 0;
              round++;
            }
          }
        }
      }
      
  • 修改代码

    这里列出的是旧文件新增的代码:

    • UserGUI

      新增了设置messagepoints的对外方法,目的是使用者更好的封装这些ui组件。
      另外还新增了两个切换飞碟飞行模式的按钮:在这里插入图片描述
      完整代码:

      public class UserGUI : MonoBehaviour {
        private IUserAction userAction;
        public string gameMessage;
        public int points;
      
        void Start() {
          points = 0;
          gameMessage = "";
          userAction = SSDirector.GetInstance().CurrentSceneController as IUserAction;
        }
      
        public void SetMessage(string gameMessage) {
          this.gameMessage = gameMessage;
        }
      
        public void SetScore(int points) {
          this.points = points;
        }
      
        void OnGUI() {
          // 小号字体
          GUIStyle small = new GUIStyle {
            padding = new RectOffset(0, 0, 0, 0),
            fontSize = 30,
            normal = { textColor = Color.red },
            alignment = TextAnchor.MiddleCenter
          };
          // 大号字体
          GUIStyle big = new GUIStyle {
            fontSize = 50,
            normal = { textColor = Color.white },
            alignment = TextAnchor.MiddleCenter
          };
      
          GUI.Label(new Rect(Screen.width / 2 - 50, 30, 100, 100), "Hit UFO", big);
          GUI.Label(new Rect(20, 0, 100, 50), "Points: " + points, small);
          GUI.Label(new Rect(Screen.width / 2 - 100, 100, 200, 50), gameMessage, small);
          if (GUI.Button(new Rect(20, 50, 100, 40), "Restart")) {
            userAction.Restart();
          }
          if (GUI.Button(new Rect(20, 100, 100, 40), "Normal Mode")) {
            userAction.SetMode(false);
            userAction.Restart();
          }
          if (GUI.Button(new Rect(20, 150, 100, 40), "Infinite Mode")) {
            userAction.SetMode(true);
            userAction.Restart();
          }
          if (GUI.Button(new Rect(20, 200, 100, 40), "Kinematics")) {
            userAction.SetFlyMode(false);
            userAction.Restart();
          }
          if (GUI.Button(new Rect(20, 250, 100, 40), "Physis")) {
            userAction.SetFlyMode(true);
            userAction.Restart();
          }
        }
      
        void Update() {
          if (Input.GetButtonDown("Fire1")) {
            userAction.Hit(Input.mousePosition);
          }
        }
      }
      
    • FirstController

      场景控制器的改动很大,变得很简洁,因为飞碟、分数等动作都交由回合控制器处理。

      public class FirstController : MonoBehaviour, ISceneController, IUserAction {
      
        // public CCActionManager actionManager;  //动作管理者
        // 不需要动作管理者,只要与roundcontroller对接即可
        RoundController roundController;       // 回合控制器
        // DiskFactory diskFactory;        // 飞碟工厂
        UserGUI userGUI;
      
        // Start is called before the first frame update
        void Start() {
          SSDirector.GetInstance().CurrentSceneController = 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 Restart() {
          userGUI.SetMessage("");
          userGUI.SetScore(0);
          // 回合初始化
          roundController.Reset();
          // 快速回收场上的飞碟
          // diskFactory.FastClear();
          roundController.FastClear();
        }
      
        public void Hit(Vector3 position) {
      
          // 获取主摄像头
          Camera ca = Camera.main;
          Ray ray = ca.ScreenPointToRay(position);
      
          // position是鼠标位置
          // 下面的到鼠标射线得到的碰撞物体
          RaycastHit[] hits;
          hits = Physics.RaycastAll(ray);
      
          for(int i = 0; i < hits.Length; i++) {
            RaycastHit hit = hits[i];
            if (hit.collider.gameObject.GetComponent<Disk>() != null) {
              // 飞碟移动到底端,从而触发飞行动作的回调。
              hit.collider.gameObject.transform.position = new Vector3(0, -7, 0);
              // 算分
              roundController.UpdateScore(hit.collider.gameObject.GetComponent<Disk>());
              // 更新显示
              userGUI.SetScore(roundController.GetScore());
            }
          }
        }
      
        public void SetMode(bool isInfinite) {
          roundController.SetMode(isInfinite);
        }
      
        public void SetFlyMode(bool isPhysis) {
          roundController.SetFlyMode(isPhysis);
        }
      
        public void FreeDisk(GameObject disk) {
          roundController.FreeDisk(disk);
        }
      }
      
传送门

如果想要完整的代码细节,还请移步gitee仓库

源码地址: https://pan.quark.cn/s/d1f41682e390 miyoubiAuto 米游社每日米游币自动化Python脚本(务必使用Python3) 8更新:更换cookie的获取地址 注意:禁止在B站、贴吧、或各大论坛大肆传播! 作者已退游,项目不维护了。 如果有能力的可以pr修复。 小引一波 推荐关注几个非常可爱有趣的女孩! 欢迎B站搜索: @嘉然今天吃什么 @向晚大魔王 @乃琳Queen @贝拉kira 第三方库 食用方法 下载源码 在Global.py中设置米游社Cookie 运行myb.py 本地第一次运行时会自动生产一个文件储存cookie,请勿删除 当前仅支持单个账号! 获取Cookie方法 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 按刷新页面,按下图复制 Cookie: How to get mys cookie 当触发时,可尝试按关闭,然后再次刷新页面,最后复制 Cookie。 也可以使用另一种方法: 复制代码 浏览器无痕模式打开 http://user.mihoyo.com/ ,登录账号 按,打开,找到并点击 控制台粘贴代码并运行,获得类似的输出信息 部分即为所需复制的 Cookie,点击确定复制 部署方法--腾讯云函数版(推荐! ) 下载项目源码和压缩包 进入项目文件夹打开命令行执行以下命令 xxxxxxx为通过上面方式或取得米游社cookie 一定要用双引号包裹!! 例如: png 复制返回内容(包括括号) 例如: QQ截图20210505031552.png 登录腾讯云函数官网 选择函数服务-新建-自定义创建 函数名称随意-地区随意-运行环境Python3....
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CharlesKai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值