3D游戏编程与设计 HW 6 物理系统与碰撞

3D游戏编程与设计 HW 6物理系统与碰撞

Hit UFO v2.0 完整游戏过程可见以下视频:
https://www.bilibili.com/video/BV19p4y1r7MJ/

Hit UFO v2.0 完整代码可见以下仓库:
https://gitee.com/beilineili/game3-d

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

①游戏内容要求

  • 按 adapter模式 设计图修改飞碟游戏
  • 使它同时支持物理运动与运动学(变换)运动

2.游戏设计

① 游戏玩法

  • 通过用鼠标点击飞出来的UFO来得分,其中绿色飞碟为1分,黄色飞碟为2分,红色飞碟为3分
  • 分为四个游戏模式,分为简单,普通,困难和无限模式,其中开始时游戏默认是 Normal 模式。
  • 除了无限模式有无限轮次的飞碟外,其他模式都是有10轮,所有飞碟发射完毕时游戏结束

② 与 Hit UFO v1.0 版本的不同

Hit UFO v1.0(基础版)完整代码:
https://gitee.com/beilineili/game3-d/tree/master/4.1.Hit_UFO

  • 这次的改动建立在之前的 Hit UFO 基础上,在 Hit UFO v2.0 中,我们不删除原本的正常运动模式,而是让其与新的物理运动模式共存,在游戏运行的时候来决定使用哪种。即,原本的动作管理器类不删除,它们是管理正常运动模式的。我们再实现另一个用来管理物理运动模式的动作管理器。最后将它们结合起来
  • 物理引擎将游戏世界对象赋予现实世界的物理属性,并抽象为刚体模型,使得游戏物体在力的作用下,仿真现实世界的运动及其之间的碰撞过程。
  • 关于物理引擎的动作改动肯定离不开对动作类的更改,这里我们引入 Adapter 模式

  Adapter Pattern(适配器模式)是作为两个不兼容的接口之间的桥梁。这种类型的设计模式属于结构型模式,它结合了两个独立接口的功能。
  这种模式涉及到一个单一的类,该类负责加入独立的或不兼容的接口功能。我们希望 FirstController 只需为同一个用途的所有组件保存1个变量,要将两个动作管理器同时接入 FirstController,就要实现一个适配器,让 FirstController 连接适配器,然后让适配器分别连接两个动作管理器。
  举个例子,当电源插头不够用时,我们会买一个插座来让更多的设备可以用上电。

③ 代码设计

一、Controller
1)修改 FlyAction.cs
  • 原先的 Start 函数是空实现,在这里因为 FlyAction 是运动学的飞行,所以生成对象时要将刚体设为运动学的,即不会碰撞检测
  • 将飞行运动拆分成水平和竖直两个方向,其中水平方向速度恒定,竖直方向添加重力加速度
  • 当飞碟到达底部时,即飞碟的高度在摄像机观察范围之下时,动作结束,将进行回调
public class FlyAction : SSAction
{
    float gravity;          //重力加速度
    float speed;            //初始速度
    float time;             //时间
    Vector3 direction;      //初始飞行方向

    public static FlyAction GetSSAction(Vector3 direction, float speed)
    {
        FlyAction action = ScriptableObject.CreateInstance<FlyAction>();
        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);
        }
        
    }
}

2)新增 IActionManager.cs
  • 动作管理类的接口,基于这个接口来实现动作管理类的 Adapter 模式
public interface IActionManager
{
      void Fly(GameObject disk, float speed, Vector3 direction);
}
3) 修改 ActionManager.cs
  • 飞行动作管理类,用于生成飞碟飞行动作,和接受飞行动作的回调信息,回收飞碟
  • ActionManager 现在需要实现 IActionManager 的接口
public class ActionManager : SSActionManager, ISSActionCallback, IActionManager
{
    //飞行动作
    public FlyAction flyAction;
    //控制器
    public FirstController controller;

    protected new void Start()
    {
        controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        controller.actionManager = this;
    }

    public void Fly(GameObject disk, float speed, Vector3 direction)
    {
        flyAction = FlyAction.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.diskFactory.FreeDisk(source.gameObject);
    }
}
4) 修改 IUserAction.cs
  • 用户动作接口,有点击,重新开始和选择游戏模式三个函数的接口
  • 现在 IUserAction 提供了设置飞行模式的接口
public interface IUserAction
{
    void Hit(Vector3 position); //点击
    void Restart(); //重新开始
    void SetMode(int mode); //选择游戏模式
    void SetFlyMode(int flyMode); //设置飞碟运动模式
}
5)修改 FirstController.cs
  • 场景控制器,负责游戏逻辑
  • 发送飞碟 SendDisk – 从工厂获得一个飞碟并设置初始位置和速度,飞行动作
  • 点击判断 Hit – 处理用户点击动作,将被点击到的飞碟回收,计算得分

在游戏中,玩家通过鼠标点击飞碟,从而得分。这当中涉及到一个点击判断的问题。我们调用 ScreenPointToRay 方法,构造由摄像头和屏幕点击点确定的射线,与射线碰撞的游戏对象即为玩家点击的对象

  • 为了降低 FirstController 代码的复杂度,我们分解它的部分功能到新增的 RoundConrtoller 中实现
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    public ActionManager actionManager;
    public DiskFactory diskFactory;                       //飞碟工厂
    RoundController roundController;
    UserGUI userGUI;

    void Start()
    {
        LoadResources();
    }

    public void LoadResources()
    {
        SSDirector.GetInstance().CurrentScenceController = this;
        gameObject.AddComponent<DiskFactory>();
        gameObject.AddComponent<ActionManager>();
        gameObject.AddComponent<PhysisActionManager>();
        gameObject.AddComponent<RoundController>();
        gameObject.AddComponent<UserGUI>();

        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.SetResult("");
        userGUI.SetPoints(0);
        userGUI.SetRound(0);
        userGUI.SetMode(1);
        userGUI.SetFlyMode(0);
        roundController.Reset();
    }
    
    //设置模式
    public void SetMode(int mode)
    {
        roundController.SetMode(mode);
    }

    public void SetFlyMode(int flyMode){
        roundController.SetFlyMode(flyMode);
    }

    public void FreeDisk(GameObject disk){
        diskFactory.FreeDisk(disk);
    }

    void Update()
    {
       
    }
}
6)新增 PhysisFlyAction.cs
  • 实现物体的物理学飞行
  • 当不使用物理引擎时启用 isKinematic 运动学变换,当使用物理引擎时关闭运动学变换
  • 给飞碟预制体添加刚体属性,选择Use Gravity,最后增加一个初速度,飞碟就会自动实现重力和碰撞效果
  • 利用的是物体的刚体属性,以及物理引擎实现物理运动

在这里插入图片描述

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);
        }

    }
}
7)新增 PhysisActionManager.cs
  • 物理模式的动作管理类,代码与 ActionManager 类似
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);
    }
}
8)新增 RoundController.cs
  • RoundController 实现的是 Hit UFO 1.0 中 FirstController 每回合发送飞碟、计分、计轮次等功能的代码
public class RoundController : MonoBehaviour
{
    FirstController controller;
    IActionManager actionManager;                   //动作管理者
    DiskFactory diskFactory;                         //飞碟工厂
    UserGUI userGUI;
    int[] roundDisks;           //对应轮次的飞碟数量
    int mode;                   //当前模式,简单-0,正常-1,困难-2,或者无限模式-3
    int points;                 //当前分数
    int round;                  //当前轮次
    int sendCount;                //当前已发送的飞碟数量
    float sendTime;             //发送时间

    void Start()
    {
        controller = (FirstController)SSDirector.GetInstance().CurrentScenceController;
        actionManager = Singleton<ActionManager>.Instance;
        diskFactory = Singleton<DiskFactory>.Instance;
        userGUI = Singleton<UserGUI>.Instance;
        roundDisks = new int[] { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55 };
        mode = 1;
        points = 0;
        round = 1;
        sendCount = 0;
        sendTime = 0;
    }

    public void Reset()
    {
        mode = 1;
        points = 0;
        round = 1;
        sendCount = 0;
        sendTime = 0;
    }

    public void Record(DiskData disk)
    {
        points += disk.points;
    }

    public int GetPoints()
    {
        return points;
    }

    public void SetMode(int mode)
    {
        this.mode = mode;
    }

    public void SetFlyMode(int flyMode)
    {
        actionManager = flyMode == 1 ? Singleton<PhysisActionManager>.Instance : Singleton<ActionManager>.Instance as IActionManager;
    }

    public void SendDisk(int mode)
    {
        //从工厂生成一个飞碟
        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);
        //设置飞碟的飞行动作
        if (mode == 0){
            actionManager.Fly(disk, disk.GetComponent<DiskData>().speed*0.5f, disk.GetComponent<DiskData>().direction);
        }
        else if (mode == 2) {
            actionManager.Fly(disk, disk.GetComponent<DiskData>().speed*1.5f, disk.GetComponent<DiskData>().direction);
        }
        else {
            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 && sendCount < roundDisks[round-1]; i++)
            {
                sendCount++;
                SendDisk(mode);
            }
            //判断是否需要重置轮次,不需要则输出游戏结束
            if (sendCount == roundDisks[round-1] && round == roundDisks.Length)
            {
                if (mode == 3)
                {
                    round = 1;
                    sendCount = 0;
                    userGUI.SetResult("");
                }
                else
                {
                    userGUI.SetResult("Game Over!");
                }
            }
            //更新轮次
            if (sendCount == roundDisks[round-1] && round < roundDisks.Length)
            {
                sendCount = 0;
                round++;
                userGUI.SetRound(round);
            }
        }
    }
}

二、Model
  • Model 的三个代码文件都不需要更改,直接复用 Hit UFO 基础版的代码

在这里插入图片描述

三、 View

在这里插入图片描述

1)修改 UserGUI.cs
  • 界面类,用于构建 UI 和捕捉用户动作,将分数,Round,游戏模式显示出来
  • 新增两个设置运动模式的按钮,且将UserGUI的成员设置成了私有
public class UserGUI : MonoBehaviour
{
    IUserAction userAction;
    string result;
    int points;
    int round;
    int mode;
    int flyMode;

    public void SetResult(string result){
        this.result = result;
    }

    public void SetPoints(int points){
        this.points = points;
    }

    public void SetRound(int round){
        this.round = round;
    }

    public void SetMode(int mode){
        this.mode = mode;
    }

    public void SetFlyMode(int flyMode){
        this.flyMode = flyMode;
    }

    void Start()
    {
        result = "";
        points = 0;
        round = 1;
        mode = 1;
        flyMode = 0;
        userAction = SSDirector.GetInstance().CurrentScenceController as IUserAction;
    }
    
    //打印和用户交互提示界面
    void OnGUI()
    {
        GUIStyle titleStyle = new GUIStyle();
        titleStyle.normal.textColor = Color.black;
        titleStyle.fontSize = 50;

        GUIStyle style = new GUIStyle();
        style.normal.textColor = Color.white;
        style.fontSize = 30;

        GUIStyle resultStyle = new GUIStyle();
        resultStyle.normal.textColor = Color.red;
        resultStyle.fontSize = 50;

        GUIStyle modeStyle = new GUIStyle();
        modeStyle.normal.textColor = Color.blue;
        modeStyle.fontSize = 40;

        GUI.Label(new Rect(600, 30, 50, 200), "Hit UFO", titleStyle);
        GUI.Label(new Rect(20, 10, 100, 50), "Points: " + points, style);
        GUI.Label(new Rect(220, 10, 100, 50), "Round: " + round, style);
        GUI.Label(new Rect(1000, 100, 50, 200), result, resultStyle);

        if (GUI.Button(new Rect(1300, 50, 100, 50), "Restart"))
        {
            userAction.Restart();
        }
        //简单模式
        if (GUI.Button(new Rect(1300, 125, 100, 50), "Easy Mode"))
        {
            userAction.SetMode(0);
        }
        //通常模式
        if (GUI.Button(new Rect(1300, 200, 100, 50), "Normal Mode"))
        {
            userAction.SetMode(1);
        }
        //困难模式
        if (GUI.Button(new Rect(1300, 275, 100, 50), "Hard Mode"))
        {
            userAction.SetMode(2);
        }
        //无限模式
        if (GUI.Button(new Rect(1300, 350, 100, 50), "Infinite Mode"))
        {
            userAction.SetMode(3);
        }
        //运动学模式
        if (GUI.Button(new Rect(1300, 425, 100, 50), "Kinematics"))
        {
            userAction.SetFlyMode(0);
        }
        //物理模式
        if (GUI.Button(new Rect(1300, 500, 100, 50), "Physis"))
        {
            userAction.SetFlyMode(1);
        }

        //捕捉鼠标点击
        if (Input.GetButtonDown("Fire1"))
        {
            userAction.Hit(Input.mousePosition);
        }
    }
}

3. 游戏界面

  • 调整了飞碟预制的形状和大小,让它相比之前看起来更像是飞碟

在这里插入图片描述

  • 物理运动模式下游戏画面,飞碟之间会互相碰撞

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值