3D游戏-作业六-物理系统与碰撞

项目传送门:
Hit-UFO-v2
打靶小游戏

1、改进飞碟(Hit UFO)游戏:

改进要求

游戏内容要求:

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

有如下设计图:
在这里插入图片描述

实现思路

  • 首先,是飞碟的预制
    我们想要实现飞碟的物理学运动,那就要给飞碟增加一个刚体组件。
    在这里插入图片描述

  • 1.IActionManager
    根据设计要求,使用Adapter模式,需要我们实现一个IActionManager类,用于接口转换,把原本不兼容的类连接起来一起工作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IActionManager
{
    void Fly(GameObject disk, float speed, Vector3 direction);
}
  • 2.PhysisFlyAction
    这就是物理学运动的基类,对刚体进行操作,实现运动。
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);
        }
    }
}
  • 3.PhysisActionManager
    管理PhysisFlyAction,包括飞行动作控制,回收操作。
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);
    }
}


  • 4.Interface增加用户控制接口
    增加一个接口IUserAction,用于用户选择运动学还是物理学运动。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IUserAction
{
    void SetFlyMode(bool isPhysis);
    void Hit(Vector3 position);
    void Restart();
    float GetScore();
    int GetRound();
    //void GameOver();
}
  • 5.FirstController的改进
    根据设计图,我们需要简化FirstController的功能,否则会过于臃肿。
    所以我们把round的控制交给一个新的类——RoundController,现在FirstController只管理游戏的启动、资源加载等内容。
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>());
                userGUI.SetPoints(roundController.GetPoints());
            }
        }
    }

    public void Restart()
    {
        userGUI.SetMessage("");
        userGUI.SetPoints(0);
        roundController.Reset();
    }

    public float GetScore() 
    {
        return roundController.GetPoints();
    }

    public int GetRound()
    {
        return roundController.GetRound();
    }

    public void SetFlyMode(bool isPhysis)
    {
        roundController.SetFlyMode(isPhysis);
    }

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

    void Update()
    {
    }
}
  • 6.RoundController
    轮次控制
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 int GetRound()
    {
        return round;
    }

    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;
        if (sendTime > 1)
        {
            sendTime = 0;
            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++;
            }
        }
    }
}
  • 7.最后是UI界面的改动
    添加了两个按钮,用于用户选择运动学还是物理学模式。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour
{
    IUserAction userAction;
    string gameMessage;
    int points;
    GUIStyle text_style = new GUIStyle();
    GUIStyle bold_style = new GUIStyle();
    GUIStyle over_style = new GUIStyle();

    //控制规则显示
    bool isShow = false;

    private bool game_start = false;

    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;

        text_style.normal.textColor = new Color(0, 0, 0, 1);
        text_style.fontSize = 16;
        bold_style.normal.textColor = new Color(1, 0, 0);
        bold_style.fontSize = 16;
        over_style.normal.textColor = new Color(1, 0, 0);
        over_style.fontSize = 25;

        GUIStyle button_style = new GUIStyle("button")
		{
			fontSize = 15
		};

        if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
		{
			if (isShow)
				isShow = false;
			else
				isShow = true;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 400 , 70, 100, 50), "点击Start开始游戏", text_style);
			GUI.Label(new Rect(Screen.width / 2 - 400, 90, 250, 50), "游戏过程中鼠标左键为hit", text_style);
			GUI.Label(new Rect(Screen.width / 2 - 400, 110, 250, 50), "随着时间的推移,难度会上升", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 400, 130, 250, 50), "上吧!!!", text_style);
		}

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


        if (game_start) {

            GUI.Label(new Rect(Screen.width - 150, 5, 200, 50), "Score:"+ points, text_style);
            GUI.Label(new Rect(100, 5, 50, 50), "Round:" + userAction.GetRound().ToString(), text_style);

            if (userAction.GetRound() == 4 ) {
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 100, 100, 100), "GAME OVER", over_style);
                GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 50, 50, 50), "YOUR SCORE:   " + userAction.GetScore().ToString(), over_style);
                if (GUI.Button(new Rect(Screen.width / 2 - 20, Screen.height / 2, 100, 50), "RESTART")) {
                    userAction.Restart();
                    return;
                }
                //userAction.GameOver();
            }
        }
        else {
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 100, 100, 100), "Hit UFO", over_style);
            
            if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2, 100, 50), "START")) {
                game_start = true;
                userAction.Restart();
            }
        }
    }
}

运行演示

将FirstController挂载到一个空对象上,就可以运行游戏了。
运动学演示:
在这里插入图片描述

物理学演示:
在这里插入图片描述
此时飞碟有碰撞现象,并且不会直接消失。

2、打靶游戏(可选作业):

设计要求

游戏内容要求:

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

设计思路

  • 模型预制
    首先是箭的预制,我发现自己使用自带的Object做出来的很丑,所以就到Asset Store搜到了一个做的比较好的箭的模型,就拿来使用了。
    在这里插入图片描述
    然后是靶的预制,本来也想搜一个,但是发现搜到的预制不能实现不同环上分数不同的效果,所以就自己做了一个。思路就是五个圆盘叠加,为了区分出着五个圆盘,把直径小的圆盘置于更上面,就能实现出类似“圆环”的效果。
    在这里插入图片描述

  • 代码实现
    根据MVC架构,实现一个靶模型类(Circle),一个箭模型类(Arrow),一个箭控制类(ArrowController),然后就是Score和ScoreRecoder,最后是UserGUI用于UI界面实现。

靶模型类(Circle)
创建靶,并且给定各个环的分数。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Circle
{
    private GameObject circle1;
    private GameObject circle2;
    private GameObject circle3;
    private GameObject circle4;
    private GameObject circle5;

    public Circle()
    {
        circle1 = Resources.Load<GameObject>("Prefab/Circle1");
        circle1 = GameObject.Instantiate(circle1);

        circle2 = Resources.Load<GameObject>("Prefab/Circle2");
        circle2 = GameObject.Instantiate(circle2);

        circle3 = Resources.Load<GameObject>("Prefab/Circle3");
        circle3 = GameObject.Instantiate(circle3);

        circle4 = Resources.Load<GameObject>("Prefab/Circle4");
        circle4 = GameObject.Instantiate(circle4);

        circle5 = Resources.Load<GameObject>("Prefab/Circle5");
        circle5 = GameObject.Instantiate(circle5);

        circle3.transform.parent = circle1.transform;
        circle4.transform.parent = circle1.transform;
        circle5.transform.parent = circle1.transform;

        circle1.AddComponent<Score>();
        circle2.AddComponent<Score>();
        circle3.AddComponent<Score>();
        circle4.AddComponent<Score>();
        circle5.AddComponent<Score>();

        circle1.name = "1";
        circle2.name = "2";
        circle3.name = "3";
        circle4.name = "4";
        circle5.name = "5";
    }
}

一个箭模型类(Arrow)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Arrow : MonoBehaviour
{
    private GameObject sth;
    private int screen_width;
    private int screen_height;

    // Start is called before the first frame update
    void Start()
    {
        sth = Resources.Load<GameObject>("Prefab/Arrow");
        screen_width = Screen.width;
        screen_height = Screen.height;
    }

    // Update is called once per frame
    void Update()
    {
    	//射线检测
        if(Input.GetMouseButtonDown(0))
        {
            Ray _ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit _hit;
            Vector3 vec;
            if(Physics.Raycast(_ray, out _hit))
            {
                vec = _hit.point;
                vec.z = Camera.main.transform.position.z + 1;
                sth.transform.position = vec;
                GameObject arrow = GameObject.Instantiate(sth);
                arrow.AddComponent<ArrowController>();
            }
        }
    }
}

一个箭控制类(ArrowController)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArrowController : MonoBehaviour
{
    private float speed;
    private bool fly;

    // Start is called before the first frame update
    void Start()
    {
        fly = true;
        speed = 20.0f;
    }

    // Update is called once per frame
    void Update()
    {
        if(this.gameObject.transform.position.y < -5)
        {
            Destroy(this.gameObject);
        }
        if(fly)
        {
            Vector3 vec = this.gameObject.transform.position;
            vec.z -= -speed * Time.deltaTime;
            this.gameObject.transform.position = vec;
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        fly = false;
        Destroy(GetComponent<Rigidbody>());
        Destroy(GetComponent<Collider>());
    }
}

Score和ScoreRecoder

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Score : MonoBehaviour
{
    public int goal;

    // Start is called before the first frame update
    void Start()
    {
        goal = int.Parse(this.gameObject.name);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnCollisionEnter(Collision collision)
    {
        ScoreRecorder.score += goal;
    }
}

/ScoreRecoder//
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder 
{
    public static int score = 0;

    // public void SetScore(int temp)
    // {
    //     score = temp;
    // }
    
    // public int GetScore()
    // {
    //     return score;
    // }
}

UserGUI

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour
{
    private GUIStyle style;
    private Circle mcircle;

    //控制规则显示
    bool isShow = false;
    //字体样式
    GUIStyle text_style = new GUIStyle();


    // Start is called before the first frame update
    void Start()
    {
        style = new GUIStyle();
        style.fontSize = 20;
        mcircle = new Circle();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void OnGUI()
    {
        GUI.Label(new Rect(Screen.width / 2 + 320, Screen.height / 2 - 180, 100, 100), "Score:" + ScoreRecorder.score, style);

        text_style.normal.textColor = new Color(0, 0, 0, 1);
        text_style.fontSize = 16;

        GUIStyle button_style = new GUIStyle("button")
		{
			fontSize = 15
		};

        if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
		{
			if (isShow)
				isShow = false;
			else
				isShow = true;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 400 , 70, 100, 50), "直接点击靶子开始游戏", text_style);
			GUI.Label(new Rect(Screen.width / 2 - 400, 90, 250, 50), "游戏只有一轮", text_style);
			GUI.Label(new Rect(Screen.width / 2 - 400, 110, 250, 50), "越靠近中心的环分数越高", text_style);
            GUI.Label(new Rect(Screen.width / 2 - 400, 130, 250, 50), "加油!得到更高的分数", text_style);
		}
    }
}
  • 项目说明及优化思路
    由于最近实在很忙,所以没有把打靶的增强功能实现,关键是我也想不到要怎样实验箭尾的抖动,然后关于风向的问题倒是有思路,我想到通过给箭施加一个力来模拟风的作用,并且可以在界面中指示风的大小和方向,参考下图。但是要实现这个功能还得多些好几个类,比较麻烦,就放弃了,接下来有时间的话会继续更新。
    在这里插入图片描述

然后是优化思路,其实像这样的打靶小游戏网上可以搜到很多,我参考其中的一些提出几个可以继续提升的方向:第一,可以提升难度,让靶子变成移动靶,这里需要使用到后面的动画的知识; 第二,设定关卡,可以逐步提升难度; 第三,射箭的动作可以继续优化,我们实现的射箭是直接就发射出去的,这天然减少了“瞄准”的难度,所以可以给一个瞄准的过程,提高难度。

运行演示

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值