Unity 3D 游戏编程设计g05

编程实践——编写一个简单的鼠标打飞碟(Hit UFO)游戏

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

UserGUI.cs

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

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    readonly float roundTime = 40f;

    GUIStyle style;
    GUIStyle buttonStyle;
    GUIStyle pauseStyle;
    GUIStyle resultStyle;

    void Start()
    {
        action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
        style = new GUIStyle();
        style.fontSize = 20;

        buttonStyle = new GUIStyle("button");
        buttonStyle.fontSize = 30;

        pauseStyle = new GUIStyle("button");
        pauseStyle.alignment = TextAnchor.MiddleCenter;
        pauseStyle.fontSize = 15;

        resultStyle = new GUIStyle();
        resultStyle.fontSize = 50;
        resultStyle.alignment = TextAnchor.MiddleCenter;
        resultStyle.normal.textColor = Color.white;
    }

    void OnGUI()
    {
        GUI.Box(new Rect(15, 15, 120, 50), "");
        if (Input.GetButtonDown("Fire1") && action.GetGameState() == GameState.RUNNING)
        {

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

        }
        if (action.GetGameState() != GameState.START)
        {
            GUI.Label(new Rect(0, 0, 80, 20), "Round " + (action.GetRound() + 1).ToString(), style);
            GUI.Label(new Rect(0, 20, 80, 20), "Time: " + (roundTime - (int)action.GetTime()).ToString(), style);
            GUI.Label(new Rect(0, 40, 80, 20), "Score: " + action.GetScore().ToString(), style);
        }

        if (action.GetGameState() == GameState.START && GUI.Button(new Rect(320, 280, 130, 55), "Start", buttonStyle))
        {
            action.SetGameState(GameState.RUNNING);
        }
        else if (action.GetGameState() == GameState.RUNNING && GUI.Button(new Rect(695, 5, 55, 30), "pause", pauseStyle))
        {
            action.SetGameState(GameState.PAUSE);
            action.Pause();
        }

        else if (action.GetGameState() == GameState.PAUSE && GUI.Button(new Rect(695, 5, 55, 30), "run", pauseStyle))
        {
            action.SetGameState(GameState.RUNNING);
            action.Run();
        }
        else if (action.GetGameState() == GameState.OVER)
        {
            if (GUI.Button(new Rect(320, 280, 130, 55), "Restart", buttonStyle))
                action.Restart();

            GUI.Label(new Rect(285, 130, 200, 50), "Your score is " + action.GetScore().ToString() + "!", resultStyle);
        }

    }
}

IUserAction.cs

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

public enum GameState { START, RUNNING, OVER, PAUSE }

public interface IUserAction
{
    void Restart();
    void Pause();
    void Run();
    void Hit(Vector3 pos);
    int GetScore();
    float GetTime();
    int GetRound();
    void SetGameState(GameState state);
    GameState GetGameState();
}

Singleton.cs

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

DiskData.cs

public class DiskData : MonoBehaviour
{
    public Vector3 size;
    public Color color;
    public float speed;
    public Vector3 direction;
    public int score;
}

DiskFactory.cs

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

public class DiskFactory : MonoBehaviour
{
    public GameObject disk;                 //飞碟预制体
    private List<DiskData> used = new List<DiskData>();   //正在被使用的飞碟列表
    private List<DiskData> free = new List<DiskData>();   //空闲的飞碟列表

    public GameObject GetDisk(int round)
    {
        disk = null;
        if (free.Count > 0)
        {
            disk = free[0].gameObject;
            free.Remove(free[0]);
        }
        else
        {
            disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
            disk.AddComponent<DiskData>(); 
        }
        // round 1: 全是黄色disk,最慢 
        // round 2: 40%可能出现黄色,60%可能出现红色(更快)
        // round 3: 20%可能出现黄色,30%可能出现红色,50%可能出现黑色(更快 or 同时出现?)
        int start = 0;
        int selectedColor = Random.Range(start, round * 500);

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


        switch (round)
        {

            case 0:
                {
                    disk.GetComponent<DiskData>().color = Color.yellow;
                    disk.GetComponent<DiskData>().speed = Random.Range(10f, 12f);
                    float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                    disk.GetComponent<DiskData>().score = 1;
                    disk.GetComponent<Renderer>().material.color = Color.yellow;
                    break;
                }
            case 1:
                {
                    disk.GetComponent<DiskData>().color = Color.red;
                    disk.GetComponent<DiskData>().speed = Random.Range(15f, 18f);
                    float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                    disk.GetComponent<DiskData>().score = 2;
                    disk.GetComponent<Renderer>().material.color = Color.red;
                    break;
                }
            case 2:
                {
                    disk.GetComponent<DiskData>().color = Color.blue;
                    disk.GetComponent<DiskData>().speed = Random.Range(10f, 15f);
                    float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                    disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                    disk.GetComponent<DiskData>().score = 3;
                    disk.GetComponent<Renderer>().material.color = Color.blue;
                    break;
                }
        }

        used.Add(disk.GetComponent<DiskData>());
        return disk;
    }

    //回收飞碟
    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;
            }
        }
    }
}

 FirstController.cs

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

public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
    readonly float roundTime = 40f;
    private float speed = 1.5f;                                                //发射一个飞碟的时间间隔
    readonly int[] passScore = { 20, 50 };

    private List<GameObject> disks = new List<GameObject>();          //飞碟队列
    private int currentRound = 0;                                                   //回合
    private float time = 0f;                                                 //记录时间间隔
    private float currrentTime = 0f;
    private GameState gameState = GameState.START;

    public UserGUI userGUI;
    public ScoreRecorder scoreRecorder;      //计分   
    public DiskFactory diskFactory;          //生成和回收飞碟
    public FlyActionManager actionManager;   //动作管理


    // Start is called before the first frame update
    void Start()
    {
        SSDirector director = SSDirector.GetInstance();
        director.CurrentScenceController = this;
        diskFactory = Singleton<DiskFactory>.Instance;
        scoreRecorder = Singleton<ScoreRecorder>.Instance;
        actionManager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
        userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;

        gameState = GameState.START;
        time = 0f;
        currentRound = 0;
        currrentTime = 0;

        LoadResources();
    }

    void Update()
    {
        if (gameState == GameState.RUNNING ) 
        {
            for (int i = 0; i < disks.Count; i++)
            {
                //飞碟飞出摄像机视野也没被打中
                if ((disks[i].transform.position.y <= -4.5) && disks[i].gameObject.activeSelf == true)
                {
                    diskFactory.FreeDisk(disks[i]);
                    disks.Remove(disks[i]);
                    scoreRecorder.Miss();
                }
            }
            if (time > speed)
            {
                time = 0;
                SendDisk();
            }
            else
            {
                time += Time.deltaTime;
            }

            if (currrentTime > roundTime)
            {
                currrentTime = 0;
                if (currentRound < 2 && GetScore() >= passScore[currentRound])
                {
                    currentRound++;
                    time = 0f;
                }
                else
                {
                    GameOver();
                } 
            }
            else
            {
                currrentTime += Time.deltaTime;
            }
        }
    }

    private void GameOver()
    {
        gameState = GameState.OVER;
        currrentTime = 40;
    }

    public void LoadResources()              
    {
        //不需要加载,飞碟由diskFactory生产了
    }

    public void Hit(Vector3 pos)
    {
        Ray ray = Camera.main.ScreenPointToRay(pos);
        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);
        bool isHit = 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 < disks.Count; j++)        //射中的物体要在飞碟列表中
                {
                    if (hit.collider.gameObject.GetInstanceID() == disks[j].gameObject.GetInstanceID())
                    {
                        isHit = true;
                    }
                }
                if (!isHit)        //如果没有打中,返回
                {
                    return;
                }

                disks.Remove(hit.collider.gameObject);          //从队列中移除

                scoreRecorder.Record(hit.collider.gameObject);      //记分员记录分数
                
                StartCoroutine(WaitingParticle(0.08f, hit, diskFactory, hit.collider.gameObject));      //等0.08秒后执行回收飞碟,这一步很关键
                break;
            }
        }
    }
    //暂停几秒后回收飞碟
    IEnumerator WaitingParticle(float waitTime, RaycastHit hit, DiskFactory diskFactory, GameObject obj)
    {
        yield return new WaitForSeconds(waitTime);
        //等待之后执行的动作  
        hit.collider.gameObject.transform.position = new Vector3(0, -9, 0);
        diskFactory.FreeDisk(obj);
    }

    //发送飞碟
    private void SendDisk()
    {
        
        GameObject disk = diskFactory.GetDisk(currentRound);
        disks.Add(disk);
        disk.SetActive(true);
        //设置被隐藏了或是新建的飞碟的位置
        float positionX = 16;
        float ranY = Random.Range(1f, 4f);
        float ranX = Random.Range(-1f, 1f) < 0 ? -1 : 1;
        disk.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
        Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * positionX, ranY, 0);
        disk.transform.position = position;
        //设置飞碟初始角度
        float angle = Random.Range(15f, 20f);
        actionManager.UFOFly(disk, angle);
        if (disk.GetComponent<DiskData>().color == Color.blue)
        {
            GameObject disk1 = Instantiate(disk);
            GameObject disk2 = Instantiate(disk);
            disks.Add(disk1);
            disk1.SetActive(true);
            disk1.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
            disk1.transform.position = position;
            actionManager.UFOFly(disk1, Random.Range(15f, 28f));

            disks.Add(disk2);
            disk2.SetActive(true);
            disk2.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
            disk2.transform.position = position;
            actionManager.UFOFly(disk2, Random.Range(15f, 28f));

        }
    }

    public void Restart()
    {
        time = 0f;
        currentRound = 0;
        currrentTime = 0;
        scoreRecorder.Reset();
        gameState = GameState.RUNNING;
    }

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

    public void SetGameState(GameState state)
    {
        gameState = state;
    }

    public GameState GetGameState()
    {
        return gameState;
    }

    public float GetTime()
    {
        return currrentTime;
    }

    public int GetRound()
    {
        return currentRound;
    }

    public void Pause()
    {
        actionManager.Pause();
    }

    public void Run()
    {
        actionManager.Run();
    }
}   

Action.cs

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

public class SSAction : ScriptableObject            //动作
{

    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁

    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //回调函数

    protected SSAction() { }                        //保证SSAction不会被new

    public virtual void Start()                    //子类可以使用这两个函数
    {
        throw new System.NotImplementedException();
    }

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

public class UFOFlyAction : SSAction                       
{

    public float gravity = -5;                                 //向下的加速度
    private Vector3 startVector;                              //初速度向量
    private Vector3 gravityVector = Vector3.zero;             //加速度的向量,初始时为0
    private float time;                                        //已经过去的时间
    private Vector3 currentAngle = Vector3.zero;               //当前时间的欧拉角
    public bool run = true;

    private UFOFlyAction() { }
    public static UFOFlyAction GetSSAction(Vector3 direction, float angle, float power)
    {
        //初始化物体将要运动的初速度向量
        UFOFlyAction action = CreateInstance<UFOFlyAction>();
        if (direction.x == -1)
        {
            action.startVector = Quaternion.Euler(new Vector3(0, 0, -angle)) * Vector3.left * power;
        }
        else
        {
            action.startVector = Quaternion.Euler(new Vector3(0, 0, angle)) * Vector3.right * power;
        }
        return action;
    }

    public override void Update()
    {
        if (run)
        {
            //斜抛运动
            //计算物体的向下的速度,v=at
            time += Time.fixedDeltaTime;
            gravityVector.y = gravity * time;

            //位移模拟
            transform.position += (startVector + gravityVector) * Time.fixedDeltaTime;
            currentAngle.z = Mathf.Atan((startVector.y + gravityVector.y) / startVector.x) * Mathf.Rad2Deg;
            transform.eulerAngles = currentAngle;

            //如果物体y坐标小于-10,动作就做完了
            if (this.transform.position.y < -10)
            {
                this.destroy = true;
                this.callback.SSActionEvent(this);
            }
        } 
    }
    public override void Start() { }
}

public class SequenceAction : SSAction, ISSActionCallback
{
    public List<SSAction> sequence;    //动作的列表
    public int repeat = -1;            //-1就是无限循环做组合中的动作
    public int start = 0;              //当前做的动作的索引

    public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
    {
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();//让unity自己创建一个SequenceAction实例
        action.repeat = repeat;
        action.sequence = sequence;
        action.start = start;
        return action;
    }

    public override void Update()
    {
        if (sequence.Count == 0) return;
        if (start < sequence.Count)
        {
            sequence[start].Update();     //一个组合中的一个动作执行完后会调用接口,所以这里看似没有start++实则是在回调接口函数中实现
        }
    }

    public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {
        source.destroy = false;          //先保留这个动作,如果是无限循环动作组合之后还需要使用
        this.start++;
        if (this.start >= sequence.Count)
        {
            this.start = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0)
            {
                this.destroy = true;               //整个组合动作就删除
                this.callback.SSActionEvent(this); //告诉组合动作的管理对象组合做完了
            }
        }
    }

    public override void Start()
    {
        foreach (SSAction action in sequence)
        {
            action.gameobject = this.gameobject;
            action.transform = this.transform;
            action.callback = this;                //组合动作的每个小的动作的回调是这个组合动作
            action.Start();
        }
    }

    void OnDestroy()
    {
        //如果组合动作做完第一个动作突然不要它继续做了,那么后面的具体的动作需要被释放
    }
}

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

public class SSActionManager : MonoBehaviour, ISSActionCallback                      //action管理器
{

    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();    //将执行的动作的字典集合,int为key,SSAction为value
    private List<SSAction> waitingAdd = new List<SSAction>();                       //等待去执行的动作列表
    private List<int> waitingDelete = new List<int>();                              //等待删除的动作的key                

    protected void Update()
    {
        foreach (SSAction ac in waitingAdd)
        {
            actions[ac.GetInstanceID()] = ac;                                      //获取动作实例的ID作为key
        }
        waitingAdd.Clear();

        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 void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
        int intParam = 0, string strParam = null, Object objectParam = null)
    {

    }
}

public class FlyActionManager : SSActionManager  //本游戏管理器
{

    private UFOFlyAction fly;     //飞行动作,这次只有单独动作,没有组合动作

    public FirstController sceneController;

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

    public void UFOFly(GameObject disk, float angle)
    {
        fly = UFOFlyAction.GetSSAction(disk.GetComponent<DiskData>().direction, angle, disk.GetComponent<DiskData>().speed);
        this.RunAction(disk, fly, this);
    }

    public void Pause()
    {
        fly.run = false;
    }

    public void Run()
    {
        fly.run = true;
    }

}

SSDirector.cs

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

public class SSDirector : System.Object
{
    //singlton instance
    private static SSDirector _instance;

    public ISceneController CurrentScenceController { get; set; }

    //get instance
    public static SSDirector GetInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

ScoreRecorder.cs

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

public class ScoreRecorder : MonoBehaviour
{
    public int score;                   //分数

    void Start()
    {
        score = 0;
    }

    //记录分数
    public void Record(GameObject disk)
    {
        score = disk.GetComponent<DiskData>().score + score;
    }

    public void Miss()
    {
        if (score >= 2)
            score -= 2;
        else
            score = 0;
    }

    //重置分数
    public void Reset()
    {
        score = 0;
    }
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值