3D游戏设计与分析-打飞碟

3D游戏设计与分析-打飞碟


此次作业是要编写一个简单的鼠标打飞碟(Hit UFO)游戏

游戏内容要求

  1. 游戏有 n 个 round,每个 round 都包括10 次 trial;
  2. 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
  3. 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  4. 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。

游戏的要求

  1. 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  2. 近可能使用前面 MVC 结构实现人机交互与游戏模型分离

各部分代码解释

Singleton.cs

/**
 * 这是一个实现单例模式的模板,所有的MonoBehaviour对象都用这个模板来实现单实例
 */

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

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

场景单实例类,当所需的实例第一次被需要时,在场景内搜索该实例,下一次使用时不需要搜索直接返回。

DiskObj.cs

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

public class DiskObj : MonoBehaviour {

    public Vector3 size;
    public Color color;
    public float speed;
    public Vector3 direction;
}

飞碟数据,携带飞碟的尺寸、颜色、飞行速度以及飞行方向。

DiskFactory.cs

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

public class DiskFactory : MonoBehaviour {

	public GameObject diskPrefab;

	private List<DiskObj> used = new List<DiskObj>();
	private List<DiskObj> free = new List<DiskObj>();

	private void Awake()
	{
		diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
		diskPrefab.SetActive(false);
	}

	public GameObject GetDisk(int round)
	{
		GameObject newDisk = null;
		if (free.Count > 0)
		{
			newDisk = free[0].gameObject;
			free.Remove(free[0]);
		}
		else
		{
			newDisk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
			newDisk.AddComponent<DiskObj>();
		}

		int start = 0;
		if (round == 1) start = 100;
		if (round == 2) start = 250;
		int selectedColor = Random.Range(start, round * 499);

		if (selectedColor > 500)
		{
			round = 2;
		}
		else if (selectedColor > 300)
		{
			round = 1;
		}
		else
		{
			round = 0;
		}

		switch (round)
		{

		case 0:
			{
				newDisk.GetComponent<DiskObj>().color = Color.yellow;
				newDisk.GetComponent<DiskObj>().speed = 4.0f;
				float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
				newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
				newDisk.GetComponent<Renderer>().material.color = Color.yellow;
				break;
			}
		case 1:
			{
				newDisk.GetComponent<DiskObj>().color = Color.red;
				newDisk.GetComponent<DiskObj>().speed = 6.0f;
				float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
				newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
				newDisk.GetComponent<Renderer>().material.color = Color.red;
				break;
			}
		case 2:
			{
				newDisk.GetComponent<DiskObj>().color = Color.green;
				newDisk.GetComponent<DiskObj>().speed = 8.0f;
				float RanX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
				newDisk.GetComponent<DiskObj>().direction = new Vector3(RanX, 1, 0);
				newDisk.GetComponent<Renderer>().material.color = Color.black;
				break;
			}
		}

		used.Add(newDisk.GetComponent<DiskObj>());
		//newDisk.SetActive(true);
		newDisk.name = newDisk.GetInstanceID().ToString();
		return newDisk;
	}

	public void FreeDisk(GameObject disk)
	{
        DiskObj tmp = null;
		foreach (DiskObj i in used)
		{
			if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
			{
				tmp = i;
			}
		}
		if (tmp != null) {
			tmp.gameObject.SetActive(false);
			free.Add(tmp);
			used.Remove(tmp);
		}
	}
}

飞碟工厂,负责生产与释放飞碟。
GetDisk:生产飞碟,首先从free空闲队列中查找是否有可用的飞碟,如果没有则新建一个飞碟。
FreeDisk:释放飞碟,将飞碟从used队列中移除并添加到free队列中。

SSAction.cs

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

public class SSAction : ScriptableObject
{

    public bool enable = false;
    public bool destroy = false;

    public GameObject gameobject { get; set; }
    public Transform transform { get; set; }
    public ISSActionCallback callback { get; set; }

    protected SSAction() { }

    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

    // Update is called once per frame
    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }

    public void reset()
    {
        enable = false;
        destroy = false;
        gameobject = null;
        transform = null;
        callback = null;
    }
}

动作基类。

CCFlyAction.cs

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

public class CCFlyAction : SSAction
{

    /**
     * acceleration是重力加速度,为9.8
     */

    float acceleration;

    /**
     * horizontalSpeed是飞碟水平方向的速度
     */

    float horizontalSpeed;

    /**
     * direction是飞碟的初始飞行方向
     */

    Vector3 direction;

    /**
     * time是飞碟已经飞行的时间
     */

    float time;

    public override void Start()
    {
        enable = true;
        acceleration = 9.8f;
        time = 0;
        horizontalSpeed = gameobject.GetComponent<DiskObj>().speed;
        direction = gameobject.GetComponent<DiskObj>().direction;
    }

    // Update is called once per frame
    public override void Update()
    {
        if (gameobject.activeSelf)
        {
            /**
             * 计算飞碟的累计飞行时间
             */
            time += Time.deltaTime;

            /**
             * 飞碟在竖直方向的运动
             */

            transform.Translate(Vector3.down * acceleration * time * Time.deltaTime);

            /**
             * 飞碟在水平方向的运动
             */

            transform.Translate(direction * horizontalSpeed * Time.deltaTime);

            /**
             * 当飞碟的y坐标比-4小时,飞碟落地
             */

            if (this.transform.position.y < -4)
            {
                this.destroy = true;
                this.enable = false;
                this.callback.SSActionEvent(this);
            }
        }

    }

    public static CCFlyAction GetSSAction()
    {
        CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
        return action;
    }
}

飞行动作。

SSActionManager.cs

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

public class SSActionManager : MonoBehaviour {

    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();        //保存所以已经注册的动作
    private List<SSAction> waitingAdd = new List<SSAction>();                           //动作的等待队列,在这个对象保存的动作会稍后注册到动作管理器里
    private List<int> waitingDelete = new List<int>();                                  //动作的删除队列,在这个对象保存的动作会稍后删除


    // Update is called once per frame
    protected void Update()
    {
        //把等待队列里所有的动作注册到动作管理器里
        foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
        waitingAdd.Clear();

        //管理所有的动作,如果动作被标志为删除,则把它加入删除队列,被标志为激活,则调用其对应的Update函数
        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                waitingDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        //把删除队列里所有的动作删除
        foreach (int key in waitingDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        waitingDelete.Clear();
    }

    //初始化一个动作
    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
    {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();
    }
}

public enum SSActionEventType : int { Started, Competeted }

public interface ISSActionCallback
{
    void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null);

}

动作管理者的基类

CCActionManager.cs

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

public class CCActionManager : SSActionManager, ISSActionCallback
{

    public FirstSceneControl sceneController;
    public List<CCFlyAction> Fly;
    public int DiskNumber = 0;

    /**
     * used是用来保存正在使用的动作
     * free是用来保存还未被激活的动作
     */

    private List<SSAction> used = new List<SSAction>();
    private List<SSAction> free = new List<SSAction>();

    /**
     * GetSSAction这个函数是用来获取CCFlyAction这个动作的,
     * 每次首次判断free那里还有没有未使用的CCFlyActon这个动作,
     * 有就从free那里获取,没有就生成一个CCFlyAction
     */

    SSAction GetSSAction()
    {
        SSAction action = null;
        if (free.Count > 0)
        {
            action = free[0];
            free.Remove(free[0]);
        }
        else
        {
            action = ScriptableObject.Instantiate<CCFlyAction>(Fly[0]);
        }

        used.Add(action);
        return action;
    }

    public void FreeSSAction(SSAction action)
    {
        SSAction tmp = null;
        foreach (SSAction i in used)
        {
            if (action.GetInstanceID() == i.GetInstanceID())
            {
                tmp = i;
            }
        }
        if (tmp != null)
        {
            tmp.reset();
            free.Add(tmp);
            used.Remove(tmp);
        }
    }

    protected void Start()
    {
        sceneController = (FirstSceneControl)Director.getInstance().currentSceneControl;
        sceneController.actionManager = this;
        Fly.Add(CCFlyAction.GetSSAction());

    }

    public void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0,
        string strParam = null,
        UnityEngine.Object objectParam = null)
    {
        if (source is CCFlyAction)
        {
            DiskNumber--;
            DiskFactory df = Singleton<DiskFactory>.Instance;
            df.FreeDisk(source.gameobject);
            FreeSSAction(source);
        }
    }

    public void StartThrow(Queue<GameObject> diskQueue)
    {
        foreach (GameObject tmp in diskQueue)
        {
            RunAction(tmp, GetSSAction(), (ISSActionCallback)this);
        }
    }
}

飞行动作管理者

Director.cs

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

public class Director : System.Object {

    public ISceneControl currentSceneControl { get; set; }

    /**
     * Director这个类是采用单例模式
     */

    private static Director director;

    private Director()
    {

    }

    public static Director getInstance()
    {
        if (director == null)
        {
            director = new Director();
        }
        return director;
    }
}

导演类。

FirstSceneController.cs

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

public interface ISceneControl
{

    void LoadResources();
}

public class FirstSceneControl : MonoBehaviour ,ISceneControl , IUserAction
{

    public CCActionManager actionManager { get; set; }
    public ScoreRecorder scoreRecorder { get; set; }
    public Queue<GameObject> diskQueue = new Queue<GameObject>();

    private int diskNumber;
    private int currentRound = -1;
    public int round = 3;
    private float time = 0;
    private GameState gameState = GameState.START;

    void Awake()
    {
        Director director = Director.getInstance();
        director.currentSceneControl = this;
        diskNumber = 10;
        this.gameObject.AddComponent<ScoreRecorder>();
        this.gameObject.AddComponent<DiskFactory>();
        scoreRecorder = Singleton<ScoreRecorder>.Instance;
        //director.currentSceneControl.LoadResources();
    }

    private void Update()
    {

        if (actionManager.DiskNumber == 0 && gameState == GameState.RUNNING)
        {
            gameState = GameState.ROUND_FINISH;

        }

        if (actionManager.DiskNumber == 0 && gameState == GameState.ROUND_START)
        {
            currentRound = (currentRound + 1) % round;
            NextRound();
            actionManager.DiskNumber = 10;
            gameState = GameState.RUNNING;
        }

        if (time > 1)
        {
            ThrowDisk();
            time = 0;
        }
        else
        {
            time += Time.deltaTime;
        }


    }

    private void NextRound()
    {
        DiskFactory df = Singleton<DiskFactory>.Instance;
        for (int i = 0; i < diskNumber; i++)
        {
            diskQueue.Enqueue(df.GetDisk(currentRound));
        }

        actionManager.StartThrow(diskQueue);

    }

    void ThrowDisk()
    {
        if (diskQueue.Count != 0)
        {
            GameObject disk = diskQueue.Dequeue();

            /**
             * 以下几句代码是随机确定飞碟出现的位置
             */

            Vector3 position = new Vector3(0, 0, 0);
            float y = UnityEngine.Random.Range(0f, 4f);
            position = new Vector3(-disk.GetComponent<DiskObj>().direction.x * 7, y, 0);
            disk.transform.position = position;

            disk.SetActive(true);
        }

    }

    public void LoadResources()
    {
        //DiskFactory df = Singleton<DiskFactory>.Instance;
        //df.init(diskNumber);
        //GameObject greensward = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/greensward"));
    }


    public void GameOver()
    {
        GUI.color = Color.red;
        GUI.Label(new Rect(700, 300, 400, 400), "GAMEOVER");

    }

    public int GetScore()
    {
        return scoreRecorder.score;
    }

    public GameState getGameState()
    {
        return gameState;
    }

    public void setGameState(GameState gs)
    {
        gameState = gs;
    }

    public void hit(Vector3 pos)
    {
        Ray ray = Camera.main.ScreenPointToRay(pos);

        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        for (int i = 0; i < hits.Length; i++)
        {
            RaycastHit hit = hits[i];

            if (hit.collider.gameObject.GetComponent<DiskObj>() != null)
            {
                scoreRecorder.Record(hit.collider.gameObject);

                /**
                 * 如果飞碟被击中,那么就移到地面之下,由工厂负责回收
                 */

                hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
            }

        }
    }
}

场景控制器,负责游戏主要逻辑。

ScoreRecorder.cs

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

public class ScoreRecorder : MonoBehaviour
{

    /**
     * score是玩家得到的总分
     */

    public int score;

    /**
     * scoreTable是一个得分的规则表,每种飞碟的颜色对应着一个分数
     */

    private Dictionary<Color, int> scoreTable = new Dictionary<Color, int>();

    // Use this for initialization
    void Start()
    {
        score = 0;
        scoreTable.Add(Color.yellow, 10);
        scoreTable.Add(Color.red, 20);
        scoreTable.Add(Color.green, 40);
    }

    public void Record(GameObject disk)
    {
        score += scoreTable[disk.GetComponent<DiskObj>().color];
    }

    public void Reset()
    {
        score = 0;
    }
}

记录分数。

UserGUI.cs

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

public enum GameState { ROUND_START, ROUND_FINISH, RUNNING, PAUSE, START }

public interface IUserAction
{
    void GameOver();
    GameState getGameState();
    void setGameState(GameState gs);
    int GetScore();
    void hit(Vector3 pos);
}


public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    bool isFirst = true;
    // Use this for initialization
    void Start()
    {
        action = Director.getInstance().currentSceneControl as IUserAction;

    }


    private void OnGUI()
    {
        if (Input.GetButtonDown("Fire1"))
        {

            Vector3 pos = Input.mousePosition;
            action.hit(pos);

        }

        GUIStyle myStyle = new GUIStyle();
        myStyle.fontSize = 30;

        GUI.Label(new Rect(710, 5, 400, 400), "Score:" + action.GetScore().ToString(), myStyle);

        if (isFirst && GUI.Button(new Rect(700, 100, 90, 90), "Start"))
        {
            isFirst = false;
            action.setGameState(GameState.ROUND_START);
     
        }

        if (!isFirst && action.getGameState() == GameState.ROUND_FINISH && GUI.Button(new Rect(700, 100, 90, 90), "Next Round"))
        {
            action.setGameState(GameState.ROUND_START);
          
        }

    }


}

界面类,构建UI并捕捉用户动作。

传送门

gitee

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值