Unity3D学习——射飞碟游戏(工厂模式)

成品展示


鼠标点中飞碟后,飞碟爆炸并计分,达到500分后难度提升,1000分则胜利

游戏制作

脚本挂载与预制

注意设置脚本的执行顺序,否则可能出现创建对象为空的情况!
脚本挂载如下:

Unity3D学习——射飞碟游戏(工厂模式)_第1张图片 Unity3D学习——射飞碟游戏(工厂模式)_第2张图片 Unity3D学习——射飞碟游戏(工厂模式)_第3张图片
Unity3D学习——射飞碟游戏(工厂模式)_第4张图片

预制包括一个飞碟和一个爆炸的粒子效果
注意:要把Diskdata脚本挂到预制的Disk上

Unity3D学习——射飞碟游戏(工厂模式)_第5张图片 Unity3D学习——射飞碟游戏(工厂模式)_第6张图片

下面是爆炸粒子的设置:

Unity3D学习——射飞碟游戏(工厂模式)_第7张图片 Unity3D学习——射飞碟游戏(工厂模式)_第8张图片 Unity3D学习——射飞碟游戏(工厂模式)_第9张图片

游戏设计以及代码分析

在上一次牧师与魔鬼游戏的基础上,新添加了工厂模式生产飞碟。

工厂类的UML图如下:


Unity3D学习——射飞碟游戏(工厂模式)_第10张图片

全部类的UML图如下:


Unity3D学习——射飞碟游戏(工厂模式)_第11张图片

各类说明

  • 导演类:管理游戏全局状态,获取当前游戏的场景,协调各个类之间的通讯,是单实例类,不被Unity内存管理所管理
  • 界面:负责与用户交互
  • 场记:场记只需要管理出飞碟规则与管理碰撞就可以了
  • 动作管理者:被场记调用,为场景中的对象设计具体动作并执行
  • 记分员:记分员按飞碟的数据计分,记分员拥有计分规则
  • 飞碟工厂:负责生产和回收飞碟

各类具体代码
这次除了引入工厂模式,还用了单实例的模板Singleton

Singleton模板代码如下:

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

public class Singleton where T : MonoBehaviour
{
    private static T instance;

    public static T Instance
    {
        get
        {
            if (instance == null)
            {
                instance = (T)Object.FindObjectOfType(typeof(T));
                if (instance == null)
                {
                    Debug.LogError("Can't find instance of " + typeof(T));
                }
            }
            return instance;
        }
    }
}

使用起来很简单,场景单实例要求场景中至少有一个 T 类型的 Mono 子类,然后在需要的地方,如在场记中,调用如下即可获得对应类的单实例对象:

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

除了飞碟工厂和记分员的代码是新写的之外,其他类如场记、动作管理者、界面都是在上一篇牧师与魔鬼游戏的代码基础上修改的,工作量非常少

  • 飞碟数据 Diskdata
    记录每个飞碟自身的数据
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 used = new List();//存储正在使用的飞碟
    private List free = new List();//存储使用完了被回收的飞碟

    //颜色数组用于随机分配颜色
    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().size = UnityEngine.Random.Range(0, 6);//随机大小
                a_disk.GetComponent().color = color[UnityEngine.Random.Range(0, 4)];//随机颜色
                a_disk.GetComponent().speed = UnityEngine.Random.Range(10, 15);//不同关卡速度不同,同一关卡速度在一定范围内

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

                a_disk.transform.localScale = new Vector3(a_disk.GetComponent().size * 2, a_disk.GetComponent().size * 0.1f, a_disk.GetComponent().size * 2);
                a_disk.GetComponent().material.color = a_disk.GetComponent().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);
            }
        }
    }
}
  • 计分员
    单纯根据飞碟大小、速度和颜色计分
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().size *(20 - disk.GetComponent().speed));

        //根据颜色加分
        Color c = disk.GetComponent().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;
    }
}
  • 场记 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 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.Instance;
        scoreRecorder = Singleton.Instance;
        actionManager = Singleton.Instance;

        leaveSeconds = 60;
        count = leaveSeconds;

        state = State.PAUSE;

        disks = new List();
    }


    void Start () {

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

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

    public void LoadResources()
    {
        Camera.main.transform.position = new Vector3(0, 0, -15);
        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().material = hit.collider.gameObject.GetComponent().material;
                    explosion.GetComponent().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();
        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 sequence;
    public int repeat = -1; //-1表示无限循环,0表示只执行一遍,repeat> 0 表示重复repeat遍
    public int currentAction = 0;//当前动作列表里,执行到的动作序号

    public static SequenceAction getAction(int repeat, int currentActionIndex, List sequence)
    {
        SequenceAction action = ScriptableObject.CreateInstance();
        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 waitingToAdd = new List();
    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().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
    和牧师与魔鬼游戏的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();
    }

}
  • 导演 SSDirector
    与上一篇牧师与魔鬼游戏一样,没有改动
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;



public class SSDirector : System.Object
{
    public static SSDirector _instance;
    public ISceneController currentScenceController { get; set; }
    public bool running { get; set; }


    public static SSDirector getInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }

    public int getFPS()
    {
        return Application.targetFrameRate;
    }

    public void setFPS(int fps)
    {
        Application.targetFrameRate = fps;
    }

    public void NextScene()
    {
        Debug.Log("抱歉,没下一个场景了");
    }
}

菜鸟一个,不好的地方欢迎大家吐槽TAT。

你可能感兴趣的:(Unity3d)