实验:Hit those UFO

游戏名:飞碟(Hit UFO)游戏

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

游戏效果预览图:

一,飞碟的基本参数初始化

根据实验要求,飞碟应具有如下参数:飞碟的尺寸大小、颜色、运动速度、出现位置和运动方向。实现中将这些参数初始化于名为UFO的namespace中:

//UFO的基本参数
namespace UFO {//初始化UFO所需参数
	public class Disk : MonoBehaviour {
	    public float size;  		//大小
	    public Color color; 		//颜色
	    public float speed; 		//速度
	    public Vector3 position;  	//初始位置
	    public Vector3 direction;  	//运动方向
	}
}

二,飞碟的生产、回收与参数设置

根据实验要求,在此处使用了工厂模式,借此管理不同飞碟的生产与回收。

需要注意的是,在调整飞碟参数时需要反复启动项目,确保游戏运行正常;

在直接修改参数前,需要注意飞碟所受外力与速度、质量的关系。

//设置飞碟参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;
// 工厂模式
public class DiskFactory : MonoBehaviour {
    private List<Disk> toDelete = new List<Disk>();//生成后,先放进Delete队列,等待发射
    private List<Disk> toUse = new List<Disk>();
    // 可选6种颜色
    public Color[] colors = {Color.white, Color.yellow, Color.red, Color.blue, Color.green, Color.black};
    public GameObject GetDisk(int round) {  
        
        //根据回合数对飞碟设置属性并返回
        GameObject newDisk = null;
        if (toUse.Count > 0) 
        {
            //队列toUse中有飞碟,则弹出一个
            newDisk = toUse[0].gameObject;
            toUse.Remove(toUse[0]);
        }
        else 
        {
            //队列toUse中没有飞碟,则加载一个UFO模型
            newDisk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"), Vector3.zero, Quaternion.identity);
            newDisk.AddComponent<Disk>();
        }

        // 飞碟的速度
        newDisk.GetComponent<Disk>().speed = 2.0f * round + 4.3f;

        // 飞碟尺寸:随 round 越来越小
        newDisk.GetComponent<Disk>().size = (1.0f - round * 0.01f);

        // 飞碟颜色随机
        int color = UnityEngine.Random.Range(0, 6);
        newDisk.GetComponent<Disk>().color = colors[color];

        // 飞碟的发射方向
        float RanX = UnityEngine.Random.Range(-3f, 3f);
        float RanY = UnityEngine.Random.Range(1.1f, 1.3f);
        float RanZ = UnityEngine.Random.Range(-0.4f, 0.4f);
        newDisk.GetComponent<Disk>().direction = new Vector3(-RanX, RanY, RanZ);

        // 飞碟的初始位置
        RanX = RanX * UnityEngine.Random.Range(4.5f, 6f);
        RanY = 2.6f - RanY;
        RanZ = -RanZ * 2;
        newDisk.GetComponent<Disk>().position = new Vector3(RanX, RanY, RanZ);

        //飞碟在发射完之后放入待删除队列
        toDelete.Add(newDisk.GetComponent<Disk>());
        newDisk.SetActive(false);
        newDisk.name = newDisk.GetInstanceID().ToString();
        return newDisk;
    }

    public void FreeDisk(GameObject disk) {
        Disk cycledDisk = null;//每次只处理一个飞碟
        foreach (Disk toCycle in toDelete) {
            if (disk.GetInstanceID() == toCycle.gameObject.GetInstanceID()) {
                cycledDisk = toCycle;//赋值过去给处理
            }
        }
        if (cycledDisk != null) {
            cycledDisk.gameObject.SetActive(false);
            toUse.Add(cycledDisk);
            toDelete.Remove(cycledDisk);
        }
    }
}

三,飞碟的运动管理

飞碟进入处理队列,开始运动时,就需要运动的管理。鼠标的点击命中了飞碟时,分数(在后文会定义)增加。代码实现如下:

//飞碟的运动管理
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;

public class ActionManager : MonoBehaviour {

    public Vector3 direction;       // 运动方向
    public float speed;             // 初速度
    public GameObject cam;

    public void diskFly(Vector3 direction,float speed) {    // 赋予飞碟初速度和方向
        this.direction = direction;
        this.speed = speed;
    }

    void Start() {
        cam = GameObject.Find("Main Camera");
    }

    //每一帧都检测是否点击到了物体
    void Update() {
        this.gameObject.transform.position += speed * direction * Time.deltaTime;
        if (Input.GetButtonDown("Fire1")) {     // 鼠标集中飞碟的结果
            Debug.Log("Fired Pressed");
            Debug.Log(Input.mousePosition);
            Vector3 mp = Input.mousePosition;
            Camera ca;
            if (cam != null) 
                ca = cam.GetComponent<Camera>();
            else
                ca = Camera.main;

            Ray ray = ca.ScreenPointToRay(Input.mousePosition);

            RaycastHit[] hits = Physics.RaycastAll(ray);

            foreach (RaycastHit hit in hits) {
                print(hit.transform.gameObject.name);
                Debug.Log("hit " + hit.collider.gameObject.name + "!");
                if(hit.collider.gameObject.name == "Sphere")
                {
                    Director.getInstance().currentSceneController.getSceneController().addHit(); // 击中数加一
                }
                Singleton<DiskFactory>.Instance.FreeDisk(hit.transform.gameObject);             // 飞碟消失
                //Debug.Log(Director.getInstance().currentSceneController.getSceneController().getScore());
                Director.getInstance().currentSceneController.getSceneController().addScore(); // 分数加一
                continue;
            }
        }

    }
}

四,游戏全局的控制

根据实验要求,在此处使用场景单实例模式,用于游戏全局的管理

//用于控制全局的controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;

//运用场景单实例(Singleton)的方法
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;
        }
    }
}

//其中的ISceneController继承于BaseCode中,用于加载场景中的UFO
public class FirstSceneController : MonoBehaviour, ISceneController {
    public int diskFlyTimes;    // 已经发射的飞碟个数,每回合10个,最多30个
    public float time;          // 时间,用于控制飞碟发射间隔
    public int round;           // 当前回合数
    public Queue<GameObject> diskQueue = new Queue<GameObject>();   // 飞碟队列
    public SceneController  sceneCtrl;//

    void Start() {
        // 当前场景控制器
        Director.getInstance().currentSceneController = this;
        this.gameObject.AddComponent<DiskFactory>();
        this.gameObject.AddComponent<UserGUI>();
        Director.getInstance().currentSceneController.Init();       // 初始化FirstSceneController相关数据
    }

    // 初始化每个回合的飞碟队列,每个回合的飞碟属性不同,总数都为10个
    void initQueue() {
        for(int i = 0; i < 10; i++)
            diskQueue.Enqueue(Singleton<DiskFactory>.Instance.GetDisk(round));
    }

    void Update() {
        // round = sceneCtrl.getRound();
        time += Time.deltaTime;
        // 发射飞碟的间隔回合数成反比
        if(time >= 2.0f-0.25*round) {
            if(diskFlyTimes >= 30) {                //游戏结束
                //Invoke("Reset", 1.5f);
                Reset();
            } else if ((diskFlyTimes % 10) == 0 ) { //更新回合(此步骤必须在发射飞碟前面)
                round++;                            //在initQueue()之前
                sceneCtrl.addRound();               //回合数增加
                initQueue();                        //初始化新的飞盘队列
            }

            if (diskFlyTimes < 30) {
                time = 0;
                ThrowDisk();                        //发射飞盘
                diskFlyTimes++;                     //飞盘数增加
                sceneCtrl.addTotal();               //综费盘数增加
            }
        }
    }

    public void ThrowDisk() {
        if(diskQueue.Count > 0) {
            GameObject disk = diskQueue.Dequeue();
            disk.GetComponent<Renderer>().material.color = disk.GetComponent<Disk>().color;
            disk.transform.position = disk.GetComponent<Disk>().position;
            disk.transform.localScale = disk.GetComponent<Disk>().size * disk.transform.localScale;
            disk.SetActive(true);
            disk.AddComponent<ActionManager>();
            disk.GetComponent<ActionManager>().diskFly(disk.GetComponent<Disk>().direction, disk.GetComponent<Disk>().speed);
        }
    }
    public void Init() {//声明于接口ISceneController中
        time = 0;
        diskFlyTimes = 0;
        round = 0;
        diskQueue.Clear();                          //清空飞盘队列
        sceneCtrl = new SceneController();          //SceneController元素归0
    }
    public SceneController  getSceneController() {  //返回SceneController
        return sceneCtrl;
    }
    void Reset() {                                  //游戏重置。该函数会在飞碟数大于30时由update()调用,修改UserGUI中的参数reset
        this.gameObject.GetComponent<UserGUI>().reset = 1;
    }

}

五,游戏得分统计

游戏得分的本质即为有多少次点击命中了飞碟。在该段代码中,仅定义了方法,而具体调用则由前文的三和后文的六调用。

//专门用于定义方法,控制分数变化的controller
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;

//用于记录分数、回合等信息
public class SceneController {
    public int round;   // 回合数
    public int total ;  // 总飞碟数
    public int score ;  // 得到的分数
    public int hit;

    private static SceneController sceneCtrl;

    public SceneController() {  // 用于SceneController归0
        score = 0;
        total = 0;
        score = 0;
        hit = 0;
    }
    public static SceneController getInstance() {
        if (sceneCtrl == null) {
            sceneCtrl = new SceneController();
        }
        return sceneCtrl;
    }

    public void addRound() {
        round++;
    }
    public void addHit()
    {
        hit++;
    }
    public void addTotal() {
        total++;
    }
    public void addScore() {
        score++;
    }
    
    public int getRound() {
        return round;
    }
    public int getHit()
    {
        return hit;
    }
    public int getTotal() {
        return total;
    }
    public int getScore() {
        return score;
    }
}

六,用户交互界面

这段代码主要负责与用户交互的一切行为,包括显示分数、游戏重新开始等。

//用户交互界面,主要负责分数和按钮显示
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UFO;

public class UserGUI : MonoBehaviour {
    public int reset;//记录是否需要重置游戏
    GUIStyle style;
	GUIStyle buttonStyle;

    void Start() {
        reset = 0;
        style = new GUIStyle();
		style.fontSize = 45;
		style.normal.textColor = Color.green;

		buttonStyle = new GUIStyle("button");
		buttonStyle.fontSize = 40;
		buttonStyle.normal.textColor = Color.green;
    }


    void Update() {}

    private void OnGUI() {
        if(reset == 1) {
            if(GUI.Button(new Rect(280, 200, 150, 90), "Reset", buttonStyle)) {
                Director.getInstance().currentSceneController.Init();
                reset = 0;
            }
        }

        int round = Director.getInstance().currentSceneController.getSceneController().getRound();
        int total = Director.getInstance().currentSceneController.getSceneController().getTotal();
        int score = Director.getInstance().currentSceneController.getSceneController().getScore();
        int hit = Director.getInstance().currentSceneController.getSceneController().getHit();
        string text = "Round: " + round.ToString() + "\nTotal:  " + total.ToString() + "\nHit:  " + hit.ToString() + "\nScores:  " + score.ToString() ;
        GUI.Label(new Rect(10, 10, Screen.width, 50),text,style);      
    }

}

七,游戏游玩实录与源文件

注:我的录制软件没法录入鼠标,因此点击效果并不明显。

视频链接:hit the UFO_哔哩哔哩_bilibili

源文件如下:3d-game-design

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值