Unity3D-鼠标打飞碟

游戏规则

  1. 游戏包括了n个round(回合),每个回合有十次trial。
  2. 通过按空格开始每个trial,同时会发射飞碟(UFO),发射飞碟的数量会逐渐递增,为了减低游戏难度我把发射飞碟的数量限制为最多3个(因为自己体验的时候发现5个的话要全命中难度较大)。颜色/角度/速度均随机产生。
  3. 用户在每一个trial必须在飞碟落地前击毙所有UFO才能成功进入下一个trial,当通关十个trial的时候会自动进入下一轮。
  4. 用户每击中一个飞碟加100分,但如果无法通过trail会使分数清0。

游戏设计

UML图

飞碟游戏UML

游戏开发

接口

这里IUserAction接口定义了与用户交互相关的动作,用户可以通过按下空格发射飞碟,也可以点击鼠标打飞碟。而IGameStatusOp接口定义了与游戏状态相关的工作,由场记(SceneController)控制。

public interface IUserAction
    {
        void launchUFO();
        void hitUFO(Vector3 mousePos);
    }

    public interface IGameStatusOp
    {
        int getRoundNum();
        void addScore();
        void subScore();
    }

具体的用户接口实现如下:

public class UserInterface : MonoBehaviour {
    private IUserAction action;

    // Use this for initialization
    void Start () {
        action = SceneController.getInstance() as IUserAction;
    }

    // Update is called once per frame
    void Update () {
        //检测鼠标点击
        if (Input.GetMouseButtonDown(0))
        {
            Vector3 mousePos = Input.mousePosition;
            action.hitUFO(mousePos);
        }
        //检测用户按下空格
        if (Input.GetKeyDown(KeyCode.Space))
        {
            action.launchUFO();
        }
    }
}

场记

场记采用了单例模式,这样在其他地方调用getInstance()获取到的就是同一个SceneController对象。不同于前面几个星期,游戏由导演、场记、动作管理员、演员构成;在这个打飞碟游戏中,场记(由于突然有钱)请来了记分员和飞碟管理者,当用户按下空格,场记监控到只需要大喊一声“嘿!飞碟管理者,大胆放出你的飞碟吧~~”,具体怎么发射飞碟由飞碟管理者控制,场记就可以躺下来好好休息了~当然,记分相关的工作也都分发给了记分员,有钱真好~

public class SceneController : System.Object, IUserAction, IGameStatusOp
    {
        private static SceneController instance;    //单例模式
        private UFOController myUFOCtrl;          //飞碟管理者
        private StatusController myStatusCtrl;        //记分员

        public static SceneController getInstance()
        {
            if (instance == null) instance = new SceneController();
            return instance;
        }

        internal void setUFOController(UFOController ufoCtrl)
        {
            if (myUFOCtrl == null) myUFOCtrl = ufoCtrl;
        }

        internal void setStatusController(StatusController gameStatus)
        {
            if (myStatusCtrl == null) myStatusCtrl = gameStatus;
;        }

        //记分员负责的工作
        public void addScore()
        {
            myStatusCtrl.addScore();
        }

        public int getRoundNum()
        {
            return myStatusCtrl.getRoundNum();
        }

        public void subScore()
        {
            myStatusCtrl.subScore();
        }

        //飞碟管家的任务
        public void launchUFO()
        {
            //每次发射之前清0分数
            myStatusCtrl.resetScore();
            myUFOCtrl.launchUFO();
        }

        public void hitUFO(Vector3 mousePos)
        {
            myUFOCtrl.hitUFO(mousePos);
        }
    }

记分员

记分员负责记录回合数以及当前玩家的得分情况,它持有几个用来显示分数及回合的GameObject对象,然后等待场记的命令。当它收到一个扣分命令的时候,就执行subScore()函数,给用户减分直到不能再减(0);当收到加分命令,就为用户添加100分,如果分数达到了当前关卡上限,会自动进入下一个trial,若trial超过10则成功进入了下一回合。同时,场记也可以命令记分员清0分数,以便重新开始。

public class StatusController : MonoBehaviour
{
    //各种预设资源
    public GameObject canvasItem, roundTextItem, scoreTextItem, TipsTextItem;
    //回合数
    private int roundNum = 1;
    //trial数
    private int trialNum = 1;
    //得分
    private int score = 0;
    //每一个trial的得分上界
    private int scoreUpBound = 100;
    //Tips显示的时间
    private const float TIPS_TEXT_SHOW_TIME = 0.8f;

    private GameObject canvas, roundText, scoreText, TipsText;
    private SceneController scene;

    // Use this for initialization
    void Start()
    {
        scene = SceneController.getInstance();
        scene.setStatusController(this);

        canvas = Instantiate(canvasItem);
        roundText = Instantiate(roundTextItem, canvas.transform);
        roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;

        scoreText = Instantiate(scoreTextItem, canvas.transform);
        scoreText.GetComponent<Text>().text = "Score: " + score + " / " + (roundNum * 100);

        TipsText = Instantiate(TipsTextItem, canvas.transform);
        showTipsText();
    }

    public int getRoundNum() { return roundNum; }

    void addRoundNum()
    {
        roundNum++;
        roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
    }

    public int getScore()
    {
        return score;
    }

    public int getTrialNum() { return trialNum; }

    public void addScore()
    {
        score += 100;
        scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
        Debug.Log(scoreUpBound);

        //当分数达到当前回合最大分数自动进入下一轮
        if (score >= scoreUpBound)
        {
            trialNum++;
            updateScoreUpBound();
            roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
            resetScore();
            showTipsText();
        }
        if (trialNum > 10)
        {
            addRoundNum();
            trialNum = 1;
            roundText.GetComponent<Text>().text = "Round: " + roundNum + " Trial: " + trialNum;
            updateScoreUpBound();
            resetScore();
            showTipsText();
        }
    }

    private void updateScoreUpBound()
    {
        scoreUpBound = trialNum > 3 ? 300 : trialNum * 100;
    }

    private void showTipsText()
    {
        TipsText.GetComponent<Text>().text = "Round " + roundNum + ": Trial " + trialNum + "!";
        TipsText.SetActive(true);
        StartCoroutine(waitForSomeTimeAndDisappearTipsText());
    }

    IEnumerator waitForSomeTimeAndDisappearTipsText()
    {
        yield return new WaitForSeconds(TIPS_TEXT_SHOW_TIME);
        TipsText.SetActive(false);
    }

    public void resetScore()
    {
        score = 0;
        scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
    }


    public void subScore()
    {
        score = score >= 100 ? score - 100 : 0;
        scoreText.GetComponent<Text>().text = "Score: " + score + " / " + scoreUpBound;
    }
}

飞碟管理者

飞碟管理者主要管理发射飞碟动作及判断用户是否击中飞碟。当场记发出一个发射飞碟信号时,飞碟管理者会判断当前是否有飞碟处于发射状态,如果有便不再发射新的飞碟,否则根据当前回合数来发射出一定数量的飞碟,飞碟的颜色也由回合数来决定。而当用户击中飞碟的时候,飞碟管理者会通知场记需要进行加分动作,然后把被击中的飞碟回收。添加了击中爆炸效果(createExplosion)使得看起来更加酷炫。

public class UFOController : MonoBehaviour {
    //预设文件
    public GameObject PlaneItem, LauncherItem, ExplosionItem;
    public Material greenMat, redMat, blueMat;

    private GameObject plane, launcher, explosion;
    private SceneController scene;

    //发射每一个飞碟的时间间隔
    private const float LAUNCH_GAP = 0.1f;

    // Use this for initialization
    void Start () {
        scene = SceneController.getInstance();
        scene.setUFOController(this);
        plane = Instantiate(PlaneItem);
        launcher = Instantiate(LauncherItem);
        explosion = Instantiate(ExplosionItem);
    }

    // Update is called once per frame
    void Update () {
        UFOFactory.getInstance().detectLandingUFOs();
    }

    public void launchUFO()
    {
        //限制最大发射飞碟的数量为3
        int trailNum = scene.getTrailNum() > 3 ? 3 : scene.getTrailNum();
        Debug.Log("发射!");
        if (!UFOFactory.getInstance().isLaunching())
        {
            StartCoroutine(launchUFOs(trailNum));
        }
    }

    IEnumerator launchUFOs(int roundNum)
    {
        for(int i = 0; i<roundNum; i++)
        {
            GameObject UFO = UFOFactory.getInstance().getUFO();
            UFO.transform.position = launcher.transform.position;
            UFO.GetComponent<MeshRenderer>().material = getMaterial(scene.getTrailNum());

            Vector3 force = getRandomForce();
            UFO.GetComponent<Rigidbody>().AddForce(force, ForceMode.Impulse);

            //每隔LAUNCH_GAP时间发射一个UFO
            yield return new WaitForSeconds(LAUNCH_GAP);
        }
    }

    private Vector3 getRandomForce()
    {
        int x = UnityEngine.Random.Range(-30, 31);
        int y = UnityEngine.Random.Range(30, 41);
        int z = UnityEngine.Random.Range(20, 31);
        float t = 0.7f + scene.getTrailNum() / 20;
        return new Vector3(x, y, z) * t;
    }

    public void hitUFO(Vector3 mousePos)
    {
        Ray ray = Camera.main.ScreenPointToRay(mousePos);
        RaycastHit hit;
        if(Physics.Raycast(ray, out hit))
        {
            if (hit.collider.gameObject.tag.Equals("UFO"))
            {
                createExplosion(hit.collider.gameObject.transform.position);
                scene.addScore();
                UFOFactory.getInstance().RecyclingUFO(hit.collider.gameObject);
            }
        }
    }

    private void createExplosion(Vector3 position)
    {
        explosion.transform.position = position;
        explosion.GetComponent<ParticleSystem>().GetComponent<Renderer>().material = getMaterial(scene.getTrailNum());
        explosion.GetComponent<ParticleSystem>().Play();
    }

    private Material getMaterial(int roundNum)
    {
        switch(roundNum % 3)
        {
            case 0:
                return redMat;
            case 1:
                return greenMat;
            case 2:
                return blueMat;
            default:
                return redMat; 
        }
    }
}

飞碟工厂

由于飞碟对象需要大量地创建和销毁,为了降低开销使用了工厂。工厂里维护了两个列表,一个用来记录正在运行的飞碟,一个用来记录空闲的飞碟。对于使用过的飞碟,当它飞出边界或者被用户击中的时候能够被工厂回收。当需要提供飞碟对象的时候就从空闲列表里面找,如果列表里没有飞碟则新建一个。使用工厂的好处还可以屏蔽创建与销毁的业务逻辑,从而使程序易于扩展。


public class UFOFactory : System.Object {
    private static UFOFactory instance;                                        //单例工厂
    private List<GameObject> usedUFOList = new List<GameObject>();            //使用中的飞碟
    private List<GameObject> freeUFOList = new List<GameObject>();           //空闲的飞碟

    private GameObject UFOItem;        //飞碟预设

    public static UFOFactory getInstance()
    {
        if (instance == null) instance = new UFOFactory();
        return instance;
    }

    public GameObject getUFO()
    {
        if(freeUFOList.Count == 0)
        {
            GameObject newUFO = Camera.Instantiate(UFOItem);
            usedUFOList.Add(newUFO);
            return newUFO;
        }
        else
        {
            GameObject oldUFO = freeUFOList[0];
            freeUFOList.RemoveAt(0);
            oldUFO.SetActive(true);
            usedUFOList.Add(oldUFO);
            return oldUFO;
        }
    }

    //检测飞镖落地,回收。由飞碟管理者update()调用
    public void detectLandingUFOs()
    {
        for(int i = 0; i < usedUFOList.Count; i++)
        {
            if(usedUFOList[i].transform.position.y <= -8)
            {
                usedUFOList[i].GetComponent<Rigidbody>().velocity = Vector3.zero;
                usedUFOList[i].SetActive(false);
                freeUFOList.Add(usedUFOList[i]);
                usedUFOList.Remove(usedUFOList[i]);
                i--;
                //SceneController.getInstance().subScore();
            }
        }
    }

    //飞镖被击中,回收 
    public void RecyclingUFO(GameObject UFOObject)
    {
        UFOObject.GetComponent<Rigidbody>().velocity = Vector3.zero;
        UFOObject.SetActive(false);
        freeUFOList.Add(UFOObject);
        usedUFOList.Remove(UFOObject);
    }


    //是否处于发射飞碟阶段,即空中是否有飞碟在飞
    public bool isLaunching()
    {
        return (usedUFOList.Count > 0);
    }

    public void initItems(GameObject ufoItem)
    {
        UFOItem = ufoItem;
    }
}

public class UFOFactoryBC: MonoBehaviour
{
    public GameObject ufoItem;

    private void Awake()
    {
        UFOFactory.getInstance().initItems(ufoItem);
    }
}

演示

演示视频移步B站:https://www.bilibili.com/video/av22190043/

完整源码见github:https://github.com/CarolSum/Unity3d-Learning/tree/master/hw4

猜你喜欢

转载自blog.csdn.net/bkjs626/article/details/79956368