Unity3d-打飞碟工厂模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/hellowangld/article/details/79980641

Unity3d打飞碟学习记录

花了九牛二虎之力,终于完成了打飞碟的游戏,写个博客记录一下。

游戏涉及的类:

DiskData();飞碟数据
DiskFactory();飞碟的生产和销毁的工厂类;
RoundActionManager();动作管理类;
RoundController();场景控制类;
ScoreRecorder();分数记录;
SSDirector();导演类;
UserGUI();用户交互类;

类的解析:

关于导演类和用户交互类我就不做过多解释了,毕竟都是老师上课的代码和之前的一些模板。

动作管理类

我们就从动作管理类中的管理实例RoundActionManager()开始解析:

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

在这个类中唯一值得注意的函数是addRandomAction(),该函数设置了起点和终点的坐标,然后就像是牧师与魔鬼动作分离版 中的船的动作从起点到终点一样设置飞碟的运动轨迹,没有抛物线,只因本人太菜不懂怎么设计。

飞碟数据类:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DiskData : MonoBehaviour {
    public float size;
    public Color color;
    public float speed;
}

没什么好说的,就是给飞碟设置必要时可以改变的属性。

飞碟工厂类:
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);
        }

        a_disk.GetComponent<DiskData>().size = UnityEngine.Random.Range(0, 7-ruler);
        a_disk.GetComponent<DiskData>().color = color[UnityEngine.Random.Range(0, 4)];
        a_disk.GetComponent<DiskData>().speed = UnityEngine.Random.Range(10+ruler, 18+ruler);

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

这个也没什么好说的,记录击中的飞碟类型给予相应的分数。关键是这个给分也是随机的,会根据飞碟的速度和大小对应相应分数。

场景当单实例模板:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> 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;
        }
    }
}

为飞碟工厂,分数以及动作提供实例。

diskFactory = Singleton<DiskFactory>.Instance;
scoreRecorder = Singleton<ScoreRecorder>.Instance;
actionManager = Singleton<RoundActionManager>.Instance;
场景控制类:

重头戏来了!!!!!
在这个类中,我先实现了在ISceneController接口中定义的函数和枚举类型:

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 Pause()
{
    state = State.PAUSE;
    CoolTimes = 3;
    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()
{
    CoolTimes = 3;
    scoreRecorder.Reset();
    Application.LoadLevel(Application.loadedLevelName);
    SSDirector.getInstance().currentScenceController.state = State.START;
}

在Resume和Pause两个函数中,我是用了协程的知识,简单来说协程就类似于Python中的挂起,关键字是yield。指的是在一个线程内,一个程序中断去执行另一个程序,有点类似于CPU中断。这样减少了切换线程带来的负担,同时不需要多线程中的锁机制,因为不存在同时写的问题。当程序执行到StopAllCoroutines()函数时,程序执行中断并挂起,这时执行的yield return语句,停止计时,等到StartCoroutines()时再从当前中断的地方继续执行。这就要在StartCoroutines()中加入另一个返回IEnumerator(协程)的函数作为参数:

IEnumerator DoCountDown()
{
    while (leaveSeconds >= 0)
    {
        if (leaveSeconds >= 60) {
            GameText.text = (leaveSeconds - 60).ToString ();
        } else {
            GameText.text = "";
        }
        yield return new WaitForSeconds(1);
        leaveSeconds--;
    }
}

该函数采用秒来跳帧便于计时,在最开始的3秒我用于游戏开始之前的倒数,因此每一回合的游戏时间实际为60秒。每一秒生产一个飞碟。

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 == 4) 
    {
        StopAllCoroutines();
        state = State.WIN;
    }
    else if (leaveSeconds == 0 && scoreRecorder.getScore() < 500 * round) //时间到,分数不够,输了
    {
        StopAllCoroutines();
        state = State.LOSE;
    } 
    else
        state = State.CONTINUE;

}

判断游戏进程。
在以上每个函数中都有枚举类型:

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

用来记录游戏状态。
这里写图片描述
这里写图片描述
传送门

阅读更多

没有更多推荐了,返回首页