作业5-与游戏世界交互

1、编写一个简单的鼠标打飞碟(Hit UFO)游戏

  • 游戏内容要求:
    • 游戏有 n 个 round,每个 round 都包括10 次 trial;
    • 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
    • 每个 trial 的飞碟有随机性,总体难度随 round 上升;
    • 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
  • 游戏的要求:
    • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
    • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

如果你的使用工厂有疑问,参考:弹药和敌人:减少,重用和再利用;参考:Unity对象池(Object Pooling)理解与简单应用代码质量较低,比较凌乱

2、编写一个简单的自定义 Component (选做

如果你想了解跟多自定义插件或编辑器的话题,请参考:Unity3d自定义一个编辑器组件/插件的简易教程

游戏规则:

  1. 一共有三个回合。第一个回合获得5分可以进入第二个回合,第二个回合获得15分可以进入第三个回合,第三回合获得30分就获胜。每个回合的飞碟速度依次增加,飞碟发射的间隔时间依次减少。每回合生命值会恢复初始值。
  2. 击中飞碟加上相应分数,错过飞碟扣除生命值,当生命值降为0时,游戏失败。每个回合有10次trial,如果10次trial结束,没有进入下一回合,游戏失败。游戏失败会跳出restart按键,游戏界面右上角也有restart按键,点击可以重新开始游戏。
  3. 游戏界面右上角有pause按键,点击可以暂停发射飞碟,并且页面中间出现continue按键,点击continue按键可以继续游戏。

游戏效果:

图片

项目说明:

  1. 创建三个飞碟预设,挂载上DiskData,设置score、scale、color。这就是第二道选做题实现的自定义组件。
  2. 新建一个空对象,挂载上Controllor和DiskFactory。

代码:https://github.com/loudax/3d-game/tree/master/hw5-FlyingSaucer

下面对代码进行说明。

  1. Actions.cs

SSAction、SSActionManager类,SSActionEventType,ISSActionCallback接口都和之前的牧师与魔鬼都是相同的,直接复用就好。

(1) FlyAction类:实现了SSAction类,定义了飞碟飞行的速度、轨迹和回收条件等。

public class FlyAction : SSAction
{
    public float gravity = -0.1f; //飞碟飞行时受重力
    public bool go = true; //用于表示是否暂停,go为false时表示游戏暂停
    private float time;
    private Vector3 start_vector;                              
    private Vector3 gravity_vector = Vector3.zero;
    private Vector3 current_angle = Vector3.zero; 

    private FlyAction()
    {

    }

    public static FlyAction GetSSAction(Vector3 direction, float angle, float power, bool go_)
    {
        FlyAction action = CreateInstance<FlyAction>();
        action.go = go_;
        action.start_vector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        return action;
    }

    public override void Update()
    {
        if(go){
            time += Time.fixedDeltaTime;
            gravity_vector.y = gravity * time;
            transform.position += (start_vector + gravity_vector) * Time.fixedDeltaTime;
            current_angle.z = Mathf.Atan((start_vector.y + gravity_vector.y) / start_vector.x) * Mathf.Rad2Deg;
            transform.eulerAngles = current_angle;    
        }

        if (this.transform.position.x > 20){ //当x坐标大于20时,回收飞碟
            this.destroy = true;
            this.callback.SSActionEvent(this);      
        }
    }

    public override void Start()
    {
        
    }
}

(2)FlyActionManager类:定义了飞碟的飞行。

 public class FlyActionManager : SSActionManager
{
    public FlyAction fly;
    public Controllor scene;

    protected void Start()
    {
        scene = (Controllor)SSDirector.GetInstance ().CurrentSceneControllor;
        scene.fam = this;
    }

    public void UFOfly(GameObject disk, float angle, float power, bool go)
    {
        fly = FlyAction.GetSSAction (disk.GetComponent<DiskData> ().direction, angle, power, go);
        this.RunAction (disk, fly, this);
    }
}
  1. DiskFactory.cs

工厂用于生产和回收飞碟。使用飞碟时先在空闲list里找,如果有,就可以放到使用list中然后使用,如果没有空闲飞碟,就新建一个。飞碟使用完毕后,就放回空闲list中。

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

public class DiskFactory : MonoBehaviour
{
    public GameObject disk_prefab = null;                 
    private List<DiskData> used = new List<DiskData>();   
    private List<DiskData> free = new List<DiskData>();   

    public GameObject GetDisk(int round){//获得一个飞碟          
        string tag;
        disk_prefab = null;
        if (round == 1){
            tag = "disk1";;
        }
        else if(round == 2){
            tag = "disk2";
        }
        else{
            tag = "disk3";
        }
        for(int i = 0; i < free.Count; i++){
            if(free[i].tag == tag)
            {
                disk_prefab = free[i].gameObject;
                free.Remove(free[i]);
                break;
            }
        }
        
        if(disk_prefab == null){
            float start_y = Random.Range(1f, 4f);
            if (tag == "disk1"){
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk1"), new Vector3(0, start_y, 0), Quaternion.identity);

            }
            else if (tag == "disk2"){
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk2"), new Vector3(0, start_y, 0), Quaternion.identity);
            }
            else{
                disk_prefab = Instantiate(Resources.Load<GameObject>("Prefabs/disk3"), new Vector3(0, start_y, 0), Quaternion.identity);
            }
            disk_prefab.GetComponent<MeshRenderer> ().material.color = disk_prefab.GetComponent<DiskData>().color;
            disk_prefab.GetComponent<DiskData>().direction = new Vector3(1.0f, start_y, 0);
            disk_prefab.GetComponent<DiskData> ().tag = tag;
            disk_prefab.transform.localScale = disk_prefab.GetComponent<DiskData>().scale;
        }
        used.Add(disk_prefab.GetComponent<DiskData>());
        return disk_prefab;
    }

    public void FreeDisk(GameObject disk) //飞碟被击中或飞出后,进行回收
    {
        for(int i = 0;i < used.Count; i++){
            if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID()){
                used[i].gameObject.SetActive(false);
                free.Add(used[i]);
                used.Remove(used[i]);
                break;
            }
        }
    }
}
  1. ScoreRecorder.cs

用于计算得分。

public class ScoreRecorder : MonoBehaviour 
{
    public int score = 0;
    void Start()
    {
        score = 0;
    }
    public void Record(GameObject disk)
    {
        score = score + disk.GetComponent<DiskData>().score;
    }
}
  1. Controllor.cs

用于控制游戏。

public class Controllor : MonoBehaviour,ISceneControllor,IUserAction
{
    public FlyActionManager fam;
    public DiskFactory df;
    public UserGUI ug;
    public ScoreRecorder sr;
    public RoundControllor rc;

    private Queue<GameObject> dq = new Queue<GameObject> ();
    private List<GameObject> dfree = new List<GameObject> ();

    private int round = 1;
    private int trail = 0;
    private float t = 1;
    private float speed = 2; //飞碟发射间隔时间

    void Start(){
        SSDirector director = SSDirector.GetInstance();     
        director.CurrentSceneControllor = this;             
        df = Singleton<DiskFactory>.Instance;
        sr = gameObject.AddComponent<ScoreRecorder> () as ScoreRecorder;
        fam = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        ug = gameObject.AddComponent<UserGUI>() as UserGUI;
        rc = gameObject.AddComponent<RoundControllor> () as RoundControllor;
        t = speed;
    }
    void Update ()
    {
        if(ug.go){
            t-=Time.deltaTime;

            if(trail > 10){ //超过10次trial后,判为游戏失败
                ug.life = 0;
            }

            if (t < 0) { //发射一个飞碟
                LoadResources ();
                SendDisk ();
                t = speed;
                trail++;
            }
            
            if ((sr.score >= 5 && round == 1) || (sr.score >= 15 && round == 2) || (sr.score >= 30 && round == 3)) { //判断是否进入下一轮
                round++;
                trail = 0;
                rc.loadRoundData (round);
                ug.RecoverBlood();
            }
        }
    }
    public void setting(float speed_)
    {
        speed = speed_; 
    }
    public void LoadResources()
    {
        dq.Enqueue(df.GetDisk(round)); 
    }

    private void SendDisk() //发射飞碟
    {
        float position_x = 16;                       
        if (dq.Count != 0)
        {
            GameObject disk = dq.Dequeue();
            dfree.Add(disk);
            disk.SetActive(true);
            float ran_y = Random.Range(1f, 4f);
            disk.GetComponent<DiskData>().direction = new Vector3(1f, ran_y, 0);
            Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * position_x, ran_y, 0);
            disk.transform.position = position;
            float power = Random.Range(1f * round, 2f * round);
            float angle = Random.Range(0f, 5f);
            fam.UFOfly(disk,angle,power,ug.go);
        }

        for (int i = 0; i < dfree.Count; i++)
        {
            GameObject temp = dfree[i];
            if (temp.transform.position.x > 20 && temp.gameObject.activeSelf == true)
            {
                df.FreeDisk(dfree[i]);
                dfree.Remove(dfree[i]);
                ug.ReduceBlood();
            }
        }
    }

    public void Hit (Vector3 pos){ //集中飞碟
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        bool not_hit = false;
        for (int i = 0; i < hits.Length; i++)
        {
            RaycastHit hit = hits[i];
            if (hit.collider.gameObject.GetComponent<DiskData>() != null)
            {
                for (int j = 0; j < dfree.Count; j++)
                {
                    if (hit.collider.gameObject.GetInstanceID() == dfree[j].gameObject.GetInstanceID())
                    {
                        not_hit = true;
                    }
                }
                if(!not_hit)
                {
                    return;
                }
                dfree.Remove(hit.collider.gameObject);
                sr.Record(hit.collider.gameObject);
                hit.collider.gameObject.transform.position = new Vector3(0, -100, 0);
                df.FreeDisk(hit.collider.gameObject);
                break;
            }
        }
    }

    public void Restart (){ //重启游戏
        SceneManager.LoadScene(0);
    }

    public int GetScore (){ //得到得分
        return sr.score;
    }

    public int GetRound(){ //得到轮次
        return round;
    }

    public int GetTrail(){
        return trail;
    }

}
  1. RoundControllor.cs

用于控制游戏轮次,主要是控制每一轮的飞碟发射间隔时间。

public class RoundControllor : MonoBehaviour 
{
    private IUserAction action;
    private float speed; //飞碟发射间隔时间

    void Start()
    {
        action = SSDirector.GetInstance().CurrentSceneControllor as IUserAction;
        speed = 2.0f;
    }

    public void loadRoundData(int round)
    {
        switch (round){
            case 1:    
                break;
            case 2:    
                speed = 1.5f;
                action.setting (speed);
                break;
            case 3:
                speed = 1.0f;
                action.setting (speed);
                break;
        }
    }
}
  1. SSDirector.cs

和之前牧师与魔鬼的SSDirector一样。

  1. Singletion.cs

和课程网站上给出的一样。

  1. interface.cs

给出一些函数接口,在Controllor.cs中进行实现。

public interface ISceneControllor
{
    void LoadResources ();
}
public interface IUserAction
{
    void Hit(Vector3 pos);
    void Restart();
    int GetScore();
    void setting(float speed);
    int GetRound();
    int GetTrail();
}
  1. UserGUI.cs
public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    public int life = 6;   
    public bool go = true;

    void Start ()
    {
        action = SSDirector.GetInstance().CurrentSceneControllor as IUserAction;
    }

    void OnGUI ()
    {   
        if (Input.GetButtonDown("Fire1")){
            Vector3 pos = Input.mousePosition;
            action.Hit(pos);
        }

        GUI.Label(new Rect(20, 10, 200, 50), "score:");
        GUI.Label(new Rect(60, 10, 200, 50), action.GetScore().ToString());
        GUI.Label(new Rect(20, 30, 50, 50), "hp:");
        for (int i = 0; i < life; i++){
            GUI.Label(new Rect(40 + 10 * i, 30, 50, 50), "X");
        }
        GUI.Label(new Rect(20, 50, 50, 50), "trail:");
        GUI.Label(new Rect(60, 50, 50, 50), action.GetTrail().ToString());

        if(action.GetRound() == 1){
            GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
            GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
            GUI.Label(new Rect(200, 30, 300, 50), "分数到达5分进入下一关,打中一个飞碟得1分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
        }
        else if(action.GetRound() == 2){
            GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
            GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
            GUI.Label(new Rect(200, 30, 300, 50), "分数到达15分进入下一关,打中一个飞碟得2分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
        }
        else if(action.GetRound() == 3){
            GUI.Label(new Rect(200, 10, 200, 50), "ROUND");
            GUI.Label(new Rect(250, 10, 200, 50), action.GetRound().ToString());
            GUI.Label(new Rect(200, 30, 300, 50), "分数到达30分获得胜利,打中一个飞碟得3分,错过一个飞碟生命值减1,生命值归零时游戏失败。");
        }
        else{
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 300, 100, 100), "You Win!");
        }

        if(GUI.Button(new Rect(550, 10, 100, 40), "Pause")){
            go = false;
        }
        if(go == false){
            if(GUI.Button(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 250, 100, 40), "Continue")){
                go = true;
            }
        }

        if (GUI.Button(new Rect(670, 10, 100, 40), "Restart")){
            action.Restart();
            return;
        }

        if (life == 0){
            GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 300, 100, 100), "Game Over!");
            if (GUI.Button(new Rect(Screen.width / 2 - 60, Screen.width / 2 - 250, 100, 40), "Restart")){
                action.Restart();
                return;
            }
        }   
    }
    
    public void ReduceBlood() //生命值减1
    {
        if(life > 0)
            life--;
    }

    public void RecoverBlood() //生命值恢复初始状态
    {
        life = 6;
    }
}
  1. DiskData.cs

定义了飞碟的一些变量,包括分数、颜色、方向、大小、tag(属于第几回合)。

public class DiskData : MonoBehaviour
{
    public int score = 1;                               
    public Color color = Color.white;                   
    public Vector3 direction;                           
    public Vector3 scale = new Vector3( 1 ,0.1f, 1);  
    public string tag;
}
  1. DiskEditor.cs

实现自定义组件,给飞碟赋予分数、颜色、大小的属性。

public class DiskEditor : Editor
{
    SerializedProperty score;  
    SerializedProperty color;    
    SerializedProperty scale;    

    void OnEnable()
    {
        score = serializedObject.FindProperty("score");
        color = serializedObject.FindProperty("color");
        scale = serializedObject.FindProperty("scale");
    }

    public override void OnInspectorGUI()
    {
        serializedObject.Update();
        EditorGUILayout.IntSlider(score, 0, 5, new GUIContent("score"));
        EditorGUILayout.PropertyField(color);
        EditorGUILayout.PropertyField(scale);
        serializedObject.ApplyModifiedProperties();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值