打飞碟小游戏

    其实这次的作业结构和牧师过河的差不多,Director和动作管理器部分都基本上结构都是差不多的,只需要修改用到不同的函数就行了,类与类之间的关系没有发生变化。就是添加了计分的类scoreRecorder和管理飞碟的类DiskFactory

UML图


我做的这个游戏一共有4关,每一关的飞碟种类会增加,然后间隔飞出飞碟的时间也会缩短,飞碟的数目也会增加,即每一关的难度都会增加,而且最后一关出现的紫色飞碟打到是要扣分的。


一.以下是场记的接口类ISceneControl, 和用户动作的接口类IUserAction,还有导演Director类,基本和牧师过河的没什么区别,就是把在IUserAction 接口的函数改成现在UserGUI要用的函数就行。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Disk.action;
using Disk.factory;

namespace Disk.controller
{
    public interface ISceneControl
    {
        void LoadResources();
    }

    public interface IUserAction
    {
        GameState get_state();
        void set_state(GameState gs);
        int get_score();
        void click(Vector3 pos);
        int get_round();
        void reset_score();
    }

    public class Director : System.Object
    {

        public ISceneControl currentSceneController { get; set; }

        private static Director director;

        private Director()
        {

        }

        public static Director getInstance()
        {
            if (director == null)
            {
                director = new Director();
            }
            return director;
        }
    }

二.以下则是UserGUI类

action就是记录不同场记的变量,就是等于Director类的currentSceneControl,由于现在这个游戏只有一个场记,所以就是指FirstController了, 然后就是调用OnGUI函数渲染出我们想要的界面,Input.GetButtonDown("Fire1))就是用来判断是不是点击了左键,Fire1是可以在ProjectSetting的Input里面改的,点击了就调用action的click函数。然后就是根据action的游戏状态分别渲染不同的页面。 

using Disk.action;
using Disk.controller;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;




public class UserGUI : MonoBehaviour
{
    private IUserAction action;
    bool start;


    // Use this for initialization  
    void Start()
    {
        action = Director.getInstance().currentSceneController as IUserAction;
        start = true;
    }


    private void OnGUI()
    {
        if (Input.GetButtonDown("Fire1"))
        {


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


        }
        GUI.color = Color.red;
        GUI.Label(new Rect(2 * Screen.width / 3, 0, 400, 400), "score: " + action.get_score().ToString());
        if (action.get_state() == GameState.OVER)
        {
            GUI.color = Color.red;
            
            GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 200, 400, 400), "GAMEOVER" + " your score is " + action.get_score().ToString());
            if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Restart"))
            {
                action.reset_score();
                action.set_state(GameState.ROUND_START);
            }
        }
        else
        {
            GUI.color = Color.red;
            GUI.Label(new Rect(3 * Screen.width / 4 , 0, 400, 400), "round: " + (action.get_round() + 1).ToString());
        }
        if (start && GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Start"))
        {
            start = false;
            action.set_state(GameState.ROUND_START);
        }


       
        if (!start && action.get_state() == GameState.ROUND_FINISH && GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Next Round"))
        {
            action.set_state(GameState.ROUND_START);
        }


        
    }




}

三。以下就是FirstSceneControl,就是FirstController,由于是一个场记,所以就要继承场记的接口类还有UserAction接口类,在Update函数中根据不同的游戏状态,调整current_round来改变正在的回合,rount_time来改变飞碟飞出的间隔时间(调用show_disk)函数,还有改变disk_number来改变没回合的飞碟数,达到改变回合难度的目的。而飞碟是存在diskQueue队列中的,在进入下一个回合时我们就要把当前回合发给飞碟工厂类,来获取足够的飞碟,然后才能扔出飞碟。其余的就是实现接口类中的函数以供UserGUI调用

 public class FirstSceneControl : MonoBehaviour, ISceneControl, IUserAction
    {




        public CCActionManager ac_manager { get; set; }




        public ScoreRecorder score_recorder { get; set; }




        public Queue<GameObject> disks = new Queue<GameObject>();


        private int disk_number;


        private int current_round = -1;




        public int round = 4;


        private float time = 0;     //计时
        private float roundtime = 0;    //每个回合隔多久飞一次飞碟




        private GameState gameState = GameState.START;


        UserGUI user_gui;


        void Awake()
        {
            Director director = Director.getInstance();
            ac_manager = gameObject.AddComponent<CCActionManager>() as CCActionManager;
            user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
            director.currentSceneController = this;
            disk_number = 5;
            this.gameObject.AddComponent<ScoreRecorder>();
            this.gameObject.AddComponent<DiskFactory>();
            score_recorder = Singleton<ScoreRecorder>.Instance;
            director.currentSceneController.LoadResources();
        }


        private void Update()
        {


            if (gameState == GameState.ROUND_FINISH && current_round == round)
            {
                gameState = GameState.OVER;
                current_round = -1;
            }
            else
            {
                if (ac_manager.disk_number == 0 && gameState == GameState.RUNNING && score_recorder.score < (current_round + 1)* (current_round + 1) * 4)
                {
                    gameState = GameState.OVER;
                    current_round = -1;
                }
                if (ac_manager.disk_number == 0 && gameState == GameState.RUNNING)
                {


                    gameState = GameState.ROUND_FINISH;
                }


               
                if (ac_manager.disk_number == 0 && gameState == GameState.ROUND_START)
                {
                    current_round++;
                    ac_manager.disk_number = (current_round + 1) * disk_number;
                    roundtime = 1 - (current_round + 1) * 0.15F;     //每一关时间减少0.15秒
                    nextRound();
                    
                    gameState = GameState.RUNNING;
                }


                if (time > roundtime)
                {
                    show_disk();
                    time = 0;
                }
                else
                {
                    time += Time.deltaTime;
                }
            }




        }




        private void nextRound()
        {
            DiskFactory df = Singleton<DiskFactory>.Instance;
            for (int i = 0; i < ac_manager.disk_number; i++)
            {
                disks.Enqueue(df.GetDisk(current_round));
            }


            ac_manager.loadAction(disks);


        }


        void show_disk()
        {
            if (disks.Count != 0)
            {
                GameObject disk = disks.Dequeue();
                disk.SetActive(true);
            }


        }


        public void LoadResources()
        {
            
            GameObject greensward = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/bg"));
        }




     


        public int get_score()
        {
            return score_recorder.score;
        }


        public void reset_score()
        {
            score_recorder.Reset();
        }


        public GameState get_state()
        {
            return gameState;
        }


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


        public int get_round()
        {
            return current_round;
        }


        public void click(Vector3 pos)
        {
            Ray ray = Camera.main.ScreenPointToRay(pos);


            RaycastHit[] clicks;
            clicks = Physics.RaycastAll(ray);
            for (int i = 0; i < clicks.Length; i++)
            {
                RaycastHit hit = clicks[i];


                if (hit.collider.gameObject.GetComponent<DiskData>() != null)   //撞击的是飞碟
                {
                    score_recorder.add_score(hit.collider.gameObject);


                    hit.collider.gameObject.transform.position = new Vector3(0, -10, 0);
                }


            }
        }
    }

四。以下就是飞碟工厂类,由于游戏对象的创建和删除要花费较多的时间,所以要创建这个类来对飞碟进行管理,不能用完就删,要用又创建,把飞碟存在used和free中以供调用,一开始先创建一个原始的飞碟来给后面进行克隆(注意克隆和prefab创建的区别),大体的逻辑在老师的PPT上都有,我们需要改的就是怎么得到不同的飞碟,首先是先看看有没有空余的飞碟,然后根据不同的回合,然后每个回合不同颜色飞碟的概率不同,根据Random的数字落在哪个区间来进行判断得到color_index,再把飞碟的属性改一改就可以。

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

    public class DiskFactory : MonoBehaviour
    {

        public GameObject diskPrefab;

        private List<DiskData> used = new List<DiskData>();
        private List<DiskData> free = new List<DiskData>();

        private void Awake()
        {
            diskPrefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
            diskPrefab.SetActive(false);
        }

        public GameObject GetDisk(int current_round)
        {
            GameObject newDisk = null;
            if (free.Count > 0)
            {
                newDisk = free[0].gameObject;
                free.Remove(free[0]);
            }
            else
            {
                newDisk = GameObject.Instantiate<GameObject>(diskPrefab, Vector3.zero, Quaternion.identity);
                newDisk.AddComponent<DiskData>();
            }

            int color_index = 0;
            if(current_round == 0)
            {
                color_index = 0;
            }
            else if (current_round == 1)
            {
                float red_rate = 0.7F;
                int random = Random.Range(0, 10);
                if (random < red_rate * 10)
                    color_index = 1;
                else
                    color_index = 0;
            }
            else if (current_round == 2)
            {
                float black_rate = 0.5F;
                float red_rate = 0.3F;
                int random = Random.Range(0, 10);
                if (random < black_rate * 10)
                    color_index = 2;
                else if (random - black_rate * 10 < red_rate * 10)
                    color_index = 1;
                else
                    color_index = 0;
            }

            else if(current_round == 3)
            {
                float black_rate = 0.5F;
                float red_rate = 0.2F;
                float purple_rate = 0.1F;
                int random = Random.Range(0, 10);
                if (random < black_rate * 10)
                    color_index = 2;
                else if (random - black_rate * 10 < red_rate * 10)
                    color_index = 1;
                else if (random - black_rate * 10 - red_rate * 10 < purple_rate * 10)
                    color_index = 3;
                else
                    color_index = 0;
            }

            switch (color_index)
            {

                case 0:
                    {
                        newDisk.GetComponent<DiskData>().color = Color.yellow;
                        newDisk.GetComponent<DiskData>().speed = 4.0F;
                        float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
                        newDisk.GetComponent<DiskData>().direction = new Vector3(RanX, 1, 0);
                        newDisk.GetComponent<Renderer>().material.color = Color.yellow;
                        break;
                    }
                case 1:
                    {
                        newDisk.GetComponent<DiskData>().color = Color.red;
                        newDisk.GetComponent<DiskData>().speed = 6.0F;
                        float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
                        newDisk.GetComponent<DiskData>().direction = new Vector3(RanX, 1, 0);
                        newDisk.GetComponent<Renderer>().material.color = Color.red;
                        break;
                    }
                case 2:
                    {
                        newDisk.GetComponent<DiskData>().color = Color.black;
                        newDisk.GetComponent<DiskData>().speed = 8.0F;
                        float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
                        newDisk.GetComponent<DiskData>().direction = new Vector3(RanX, 1, 0);
                        newDisk.GetComponent<Renderer>().material.color = Color.black;
                        break;
                    }
                case 3:
                    {
                        newDisk.GetComponent<DiskData>().color = Color.magenta;
                        newDisk.GetComponent<DiskData>().speed = 6.0F;
                        float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
                        newDisk.GetComponent<DiskData>().direction = new Vector3(RanX, 1, 0);
                        newDisk.GetComponent<Renderer>().material.color = Color.magenta;
                        break;
                    }
            }

            used.Add(newDisk.GetComponent<DiskData>());  
            newDisk.name = newDisk.GetInstanceID().ToString();
            return newDisk;
        }

        public void FreeDisk(GameObject disk)
        {
            DiskData target = null;
            foreach (DiskData i in used)
            {
                if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
                {
                    target = i;
                }
            }
            if (target != null)
            {
                target.gameObject.SetActive(false);
                free.Add(target);
                used.Remove(target);
            }
        }

    }


五。以下是计分类

就把击中飞碟的分数属性加上就可以了



public class ScoreRecorder : MonoBehaviour
{


    public int score;




    // Use this for initialization  
    void Start()
    {
        score = 0;
    }


    public void add_score(GameObject disk)
    {
        score += disk.GetComponent<DiskData>().score;
    }


    public void Reset()
    {
        score = 0;
    }
}
五。动作管理器的抽象类,SSACTION,SSACTIONMANAGER,和CALLBACK类,一般的动作都是继承SSAction类,然后Manager都是继承SSManager类,就是CallBack的类比较繁琐,就是用于具体的MANAGER类来管理发生不同事件的动作,SSAction中的callback就是这个类的对象,当其完成的时候就调用SSActionEvent来告诉它的manager,所以在runaction的时候就要设置好manager是谁。
public class SSAction : ScriptableObject
    {


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


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


        protected SSAction() { }


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


        // Update is called once per frame  
        public virtual void Update()
        {
            throw new System.NotImplementedException();
        }


        public void reset()
        {
            enable = false;
            destroy = false;
            gameobject = null;
            transform = null;
            callback = null;
        }
    }




    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 enum GameState { ROUND_START, ROUND_FINISH, RUNNING, START, OVER }


    public class SSActionManager : MonoBehaviour
    {


        private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();        
        private List<SSAction> waitingAdd = new List<SSAction>();                           
        private List<int> waitingDelete = new List<int>();                                   




        // Use this for initialization  
        protected void Start()
        {


        }


        // Update is called once per frame  
        protected void Update()
        { 
            foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
            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();
        }
    }

六。具体的Manager类和Action类

Fly的运动分为两个动作垂直的减速和一个斜方向的匀速运动。通过获取飞碟上的分数属性进行判断赋予不同的速度和方向,还有位置信息



    public class CCFlyAction : SSAction
    {
        float g;


        float begin_speed;


        Vector3 direction; //斜方向的运动方向,只需要是一个单位向量就可以了


        float time;


        public override void Start()
        {
            enable = true;
            g = 9.8F;
            time = 0;
            int index = System.Math.Abs(gameobject.GetComponent<DiskData>().score);


            float random_y = UnityEngine.Random.Range(-2F, 5F);


            begin_speed = index * 1.5F + 2;
            direction = new Vector3(-1, 1, 0);
            Vector3 position = new Vector3(4, random_y, 0);
            this.gameobject.transform.position = position;
        }


        // Update is called once per frame  
        public override void Update()
        {
            if (gameobject.activeSelf)
            {
                time += Time.deltaTime;


                transform.Translate(Vector3.down * g * time * Time.deltaTime); //重力的减速


                transform.Translate(direction * begin_speed * Time.deltaTime); //初速度,包括竖直和水平的运动


                if (this.transform.position.y < -4)
                {
                    this.destroy = true;
                    this.enable = false;
                    this.callback.SSActionEvent(this);
                }
            }


        }
    }
具体的Manager类都是继承callback类的,他的SSActionEvent函数就用来处理动作完成的结果。Loaction函数是用来给每个飞碟配置Fly的动作的,但是由于飞碟的active是false,所以一开始并不会飞,要等到active为true才会开始运动
 
public class CCActionManager : SSActionManager, ISSActionCallback
    {


        public FirstSceneControl sceneController;
        public int disk_number = 0;


        protected new void Start()
        {
            sceneController = (FirstSceneControl)Director.getInstance().currentSceneController;
            sceneController.ac_manager = this;


        }


        public void SSActionEvent(SSAction source,
            SSActionEventType events = SSActionEventType.Competeted,
            int intParam = 0,
            string strParam = null,
            UnityEngine.Object objectParam = null)
        {
            if (source is CCFlyAction)
            {
                disk_number--;
                DiskFactory df = Singleton<DiskFactory>.Instance;
                df.FreeDisk(source.gameobject);
            }
        }


        public void loadAction(Queue<GameObject> diskQueue)
        {
            foreach (GameObject disk in diskQueue)
            {  
                RunAction(disk, ScriptableObject.CreateInstance<CCFlyAction>(), (ISSActionCallback)this);
            }
        }
    }






七。单例实现类,是一个模板,可以直接套用

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



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值