3d学习笔记(四)——打飞碟小游戏

题目

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

游戏内容要求:

  • 游戏有 n 个 round,每个 round 都包括10 次 trial;
  • 每个 trial的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
  • 每个 trial的飞碟有随机性,总体难度随 round 上升;

游戏的要求:

  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  • 近可能使用前面MVC 结构实现人机交互与游戏模型分离
  • 如果你的使用工厂有疑问,参考:弹药和敌人:减少,重用和再利用

实践内容

游戏架构

RoundController:游戏的导演,总控制器,其中的shoot负责检查是否击中飞碟
RoundActionManager:动作管理者,负责管理动作的产生
UserGUI:负责渲染整个页面的布局,主要是功能按钮的实现
ScoreRecorder:负责分数的计算,根据飞碟的大小,速度,颜色,计算打中的得分
DiskDate:挂在飞碟预制上的组件,规定了飞碟的属性
DiskFactory:负责生产不同大小,速度,颜色的飞碟

具体的源码如下:

  • RoundController
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum State { WIN, LOSE, PAUSE, CONTINUE, START };

public interface ISceneController
{
    State state { get; set; }
    void LoadResources();
    void Pause();
    void Resume();
    void Restart();
}

public class RoundController : MonoBehaviour, IUserAction, ISceneController
{

    public DiskFactory diskFactory;
    public RoundActionManager actionManager;
    public ScoreRecorder scoreRecorder;
    private List<GameObject> disks;
    private int round;//第几个回合
    private GameObject shootAtSth;
    GameObject explosion;

    //游戏状态
    public State state { get; set; }

    //计时器, 每关60秒倒计时
    public int leaveSeconds;

    //用来计数,每秒自动发射一次飞碟
    public int count;

    IEnumerator DoCountDown()
    {
        while (leaveSeconds >= 0)
        {
            yield return new WaitForSeconds(1);
            leaveSeconds--;
        }
    }

    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.setFPS(60);
        director.currentScenceController = this;

        LoadResources();

        diskFactory = Singleton<DiskFactory>.Instance;
        scoreRecorder = Singleton<ScoreRecorder>.Instance;
        actionManager = Singleton<RoundActionManager>.Instance;

        leaveSeconds = 60;
        count = leaveSeconds;

        state = State.PAUSE;

        disks = new List<GameObject>();
    }


    void Start()
    {

        round = 1;//从第一关开始
        LoadResources();
    }

    void Update()
    {
        LaunchDisk();
        Judge();
        RecycleDisk();
    }

    public void LoadResources()
    {
        Camera.main.transform.position = new Vector3(0, 0, -30);
        //explosion = Instantiate(Resources.Load("Prefabs/ParticleSys"), new Vector3(-40, 0, 0), Quaternion.identity) as GameObject;

    }

    public void shoot()//用户在游戏状态为开始或者继续时,才能左键射击
    {
        if (Input.GetMouseButtonDown(0) && (state == State.START || state == State.CONTINUE))
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            RaycastHit hit;
            if (Physics.Raycast(ray, out hit))
            {
                if ((SSDirector.getInstance().currentScenceController.state == State.START || SSDirector.getInstance().currentScenceController.state == State.CONTINUE))
                {
                    shootAtSth = hit.transform.gameObject;

                    //explosion.transform.position = hit.collider.gameObject.transform.position;
                    //explosion.GetComponent<Renderer>().material = hit.collider.gameObject.GetComponent<Renderer>().material;
                    //explosion.GetComponent<ParticleSystem>().Play();
                }
            }
        }
    }

    public void LaunchDisk()//每秒自动发射飞碟
    {
        if (count - leaveSeconds == 1)
        {
            count = leaveSeconds;
            GameObject disk = diskFactory.GetDisk(round);//从飞碟工厂得到飞碟
            Debug.Log(disk);
            disks.Add(disk);//飞碟进入场景
            actionManager.addRandomAction(disk);//让动作管理者设计轨迹
        }
    }

    public void RecycleDisk()//检查需不需要回收飞碟
    {
        for (int i = 0; i < disks.Count; i++)
        {
            if (disks[i].transform.position.z < -18)
            {
                diskFactory.FreeDisk(disks[i]);//让飞碟工厂回收
                disks.Remove(disks[i]);
            }
        }
    }



    public void Judge()//判断游戏状态,是否射中以及够不够分数进入下一回合
    {

        if (shootAtSth != null && shootAtSth.transform.tag == "Disk" && shootAtSth.activeInHierarchy)//射中飞碟
        {
            scoreRecorder.Record(shootAtSth);//计分
            diskFactory.FreeDisk(shootAtSth);//回收飞碟
            shootAtSth = null;//点击的物体重置为空,避免计分出错
        }

        if (scoreRecorder.getScore() > 500 * round)//每关500分才能进入下一关,重新倒数60秒
        {
            round++;
            leaveSeconds = count = 60;
        }

        if (round == 3) //只设计了两关, 所以赢了
        {
            StopAllCoroutines();
            state = State.WIN;
        }
        else if (leaveSeconds == 0 && scoreRecorder.getScore() < 500 * round) //时间到,分数不够,输了
        {
            StopAllCoroutines();
            state = State.LOSE;
        }
        else
            state = State.CONTINUE;

    }

    public void Pause()
    {
        state = State.PAUSE;
        StopAllCoroutines();
        for (int i = 0; i < disks.Count; i++)
        {
            disks[i].SetActive(false);//暂停后飞碟不可见
        }
    }

    public void Resume()
    {
        StartCoroutine(DoCountDown());         //开启协程计时
        state = State.CONTINUE;
        for (int i = 0; i < disks.Count; i++)
        {
            disks[i].SetActive(true);//恢复后飞碟可见
        }
    }

    public void Restart()
    {
        scoreRecorder.Reset();
        Application.LoadLevel(Application.loadedLevelName);
        SSDirector.getInstance().currentScenceController.state = State.START;
    }

}
  • RoundActionManager
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISSActionCallback
{
    void actionDone(SSAction source);
}

public class SSAction : ScriptableObject
{

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

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

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

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

public class MoveToAction : SSAction
{
    public Vector3 target;
    public float speed;

    private MoveToAction() { }
    public static MoveToAction getAction(Vector3 target, float speed)
    {
        MoveToAction action = ScriptableObject.CreateInstance<MoveToAction>();
        action.target = target;
        action.speed = speed;
        return action;
    }

    public override void Update()
    {
        this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed * Time.deltaTime);
        if (this.transform.position == target)
        {
            this.destroy = true;
            this.callback.actionDone(this);
        }
    }

    public override void Start() { }

}

public class SequenceAction : SSAction, ISSActionCallback
{
    public List<SSAction> sequence;
    public int repeat = -1; //-1表示无限循环,0表示只执行一遍,repeat> 0 表示重复repeat遍
    public int currentAction = 0;//当前动作列表里,执行到的动作序号

    public static SequenceAction getAction(int repeat, int currentActionIndex, List<SSAction> sequence)
    {
        SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
        action.sequence = sequence;
        action.repeat = repeat;
        action.currentAction = currentActionIndex;
        return action;
    }

    public override void Update()
    {
        if (sequence.Count == 0) return;
        if (currentAction < sequence.Count)
        {
            sequence[currentAction].Update();
        }
    }

    public void actionDone(SSAction source)
    {
        source.destroy = false;
        this.currentAction++;
        if (this.currentAction >= sequence.Count)
        {
            this.currentAction = 0;
            if (repeat > 0) repeat--;
            if (repeat == 0)
            {
                this.destroy = true;
                this.callback.actionDone(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()
    {
        foreach (SSAction action in sequence)
        {
            DestroyObject(action);
        }
    }
}


public class SSActionManager : MonoBehaviour
{
    private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
    private List<SSAction> waitingToAdd = new List<SSAction>();
    private List<int> watingToDelete = new List<int>();

    protected void Update()
    {
        foreach (SSAction ac in waitingToAdd)
        {
            actions[ac.GetInstanceID()] = ac;
        }
        waitingToAdd.Clear();

        foreach (KeyValuePair<int, SSAction> kv in actions)
        {
            SSAction ac = kv.Value;
            if (ac.destroy)
            {
                watingToDelete.Add(ac.GetInstanceID());
            }
            else if (ac.enable)
            {
                ac.Update();
            }
        }

        foreach (int key in watingToDelete)
        {
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        watingToDelete.Clear();
    }

    public void RunAction(GameObject gameObject, SSAction action, ISSActionCallback whoToNotify)
    {
        action.gameObject = gameObject;
        action.transform = gameObject.transform;
        action.callback = whoToNotify;
        waitingToAdd.Add(action);
        action.Start();
    }

}

public class RoundActionManager : SSActionManager, ISSActionCallback
{
    public RoundController scene;
    public MoveToAction action1, action2;
    public SequenceAction saction;
    float speed;


    public void addRandomAction(GameObject gameObj)
    {
        int[] X = { -20, 20 };
        int[] Y = { -5, 5 };
        int[] Z = { -20, -20 };

        // 随机生成起始点和终点
        Vector3 starttPos = new Vector3(
              UnityEngine.Random.Range(-20, 20),
              UnityEngine.Random.Range(-5, 5),
              UnityEngine.Random.Range(50, 10)
             );

        gameObj.transform.position = starttPos;

        Vector3 randomTarget = new Vector3(
             X[UnityEngine.Random.Range(0, 2)],
             Y[UnityEngine.Random.Range(0, 2)],
             Z[UnityEngine.Random.Range(0, 2)]
             );

        MoveToAction action = MoveToAction.getAction(randomTarget, gameObj.GetComponent<DiskData>().speed);

        RunAction(gameObj, action, this);
    }

    protected void Start()
    {
        scene = (RoundController)SSDirector.getInstance().currentScenceController;
        scene.actionManager = this;
    }

    protected new void Update()
    {
        base.Update();
    }

    public void actionDone(SSAction source)
    {
        Debug.Log("Done");
    }
}
  • UserGUI
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IUserAction
{
    void shoot();//射击动作
}

public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    private float width, height;
    private string countDownTitle;

    void Start()
    {
        countDownTitle = "Start";
        action = SSDirector.getInstance().currentScenceController as IUserAction;
    }

    float castw(float scale)
    {
        return (Screen.width - width) / scale;
    }

    float casth(float scale)
    {
        return (Screen.height - height) / scale;
    }

    void OnGUI()
    {
        width = Screen.width / 12;
        height = Screen.height / 12;

        //倒计时
        GUI.Label(new Rect(castw(2f) + 20, casth(6f) - 20, 50, 50), ((RoundController)SSDirector.getInstance().currentScenceController).leaveSeconds.ToString());

        //分数
        GUI.Button(new Rect(580, 10, 80, 30), ((RoundController)SSDirector.getInstance().currentScenceController).scoreRecorder.getScore().ToString());

        if (SSDirector.getInstance().currentScenceController.state != State.WIN && SSDirector.getInstance().currentScenceController.state != State.LOSE
            && GUI.Button(new Rect(10, 10, 80, 30), countDownTitle))
        {

            if (countDownTitle == "Start")
            {
                //恢复场景
                countDownTitle = "Pause";
                SSDirector.getInstance().currentScenceController.Resume();
            }
            else
            {
                //暂停场景
                countDownTitle = "Start";
                SSDirector.getInstance().currentScenceController.Pause();
            }
        }

        if (SSDirector.getInstance().currentScenceController.state == State.WIN)//胜利
        {
            if (GUI.Button(new Rect(castw(2f), casth(6f), width, height), "Win!"))
            {
                //选择重来
                SSDirector.getInstance().currentScenceController.Restart();
            }
        }
        else if (SSDirector.getInstance().currentScenceController.state == State.LOSE)//失败
        {
            if (GUI.Button(new Rect(castw(2f), casth(6f), width, height), "Lose!"))
            {
                SSDirector.getInstance().currentScenceController.Restart();
            }
        }
    }

    void Update()
    {
        //监测用户射击
        action.shoot();
    }

}
  • ScoreRecorder
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ScoreRecorder : MonoBehaviour
{
    private float score;

    public float getScore()
    {
        return score;
    }

    public void Record(GameObject disk)
    {
        //size越小、速度越快,分越高
        score += (100 - disk.GetComponent<DiskData>().size * (20 - disk.GetComponent<DiskData>().speed));

        //根据颜色加分
        Color c = disk.GetComponent<DiskData>().color;
        switch (c.ToString())
        {
            case "red":
                score += 50;
                break;
            case "green":
                score += 40;
                break;
            case "blue":
                score += 30;
                break;
            case "yellow":
                score += 10;
                break;
        }
    }

    public void Reset()
    {
        score = 0;
    }
}
  • DiskDate
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskData : MonoBehaviour
{
    public float size;
    public Color color;
    public float speed;
}
  • DiskFactory
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class DiskFactory : MonoBehaviour
{

    private List<GameObject> used = new List<GameObject>();//存储正在使用的飞碟
    private List<GameObject> free = new List<GameObject>();//存储使用完了被回收的飞碟

    //颜色数组用于随机分配颜色
    private Color[] color = { Color.red, Color.green, Color.blue, Color.yellow };

    //生产飞碟,先从回收部分取,若回收的部分为空,才从资源加载新的飞碟
    public GameObject GetDisk(int ruler)
    {
        GameObject a_disk;
        if (free.Count > 0)
        {
            a_disk = free[0];
            free.Remove(free[0]);
        }
        else
        {
            a_disk = GameObject.Instantiate(Resources.Load("Prefabs/Disk")) as GameObject;
            Debug.Log(a_disk);
        }
        switch (ruler)
        {
            case 1:
                a_disk.GetComponent<DiskData>().size = UnityEngine.Random.Range(0, 6);//随机大小
                a_disk.GetComponent<DiskData>().color = color[UnityEngine.Random.Range(0, 4)];//随机颜色
                a_disk.GetComponent<DiskData>().speed = UnityEngine.Random.Range(10, 15);//不同关卡速度不同,同一关卡速度在一定范围内

                a_disk.transform.localScale = new Vector3(a_disk.GetComponent<DiskData>().size * 2, a_disk.GetComponent<DiskData>().size * 0.1f, a_disk.GetComponent<DiskData>().size * 2);
                a_disk.GetComponent<Renderer>().material.color = a_disk.GetComponent<DiskData>().color;
                break;
            case 2:
                a_disk.GetComponent<DiskData>().size = UnityEngine.Random.Range(0, 4);
                a_disk.GetComponent<DiskData>().color = color[UnityEngine.Random.Range(0, 4)];
                a_disk.GetComponent<DiskData>().speed = UnityEngine.Random.Range(15, 20);

                a_disk.transform.localScale = new Vector3(a_disk.GetComponent<DiskData>().size * 2, a_disk.GetComponent<DiskData>().size * 0.1f, a_disk.GetComponent<DiskData>().size * 2);
                a_disk.GetComponent<Renderer>().material.color = a_disk.GetComponent<DiskData>().color;
                break;
        }
        a_disk.SetActive(true);
        used.Add(a_disk);
        return a_disk;
    }

    //回收飞碟
    public void FreeDisk(GameObject disk)
    {
        for (int i = 0; i < used.Count; i++)
        {
            if (used[i] == disk)
            {
                disk.SetActive(false);
                used.Remove(used[i]);
                free.Add(disk);
            }
        }
    }
}

难点说明

  • 工厂模式:主要是为了节省内存,提高效率,因此每次加载预制,在被击中后不是销毁,而且用一个链表储存起来,在生产新的飞碟时,判断该链表中是否存在可以用的飞碟,如果有则直接用,如果无才重新加载一个新的预制
  • 另外一个是利用射线组件,来实现点击打中目标,具体实现代码在RoundController文件中的shoot函数里面

猜你喜欢

转载自blog.csdn.net/qq_36312878/article/details/79982755