Hit UFO

Hit Disk

1. 游戏内容与要求:
  1. 游戏有n个round,每个round包括10次trial
  2. 每个trial的飞碟色彩、大小,发射位置,速度、角度、同时出现的个数可能不同。它们由该round的ruler控制
  3. 每个trial的飞碟具有随机性,总体难度随round上升
  4. 鼠标点中得分,得分规则按照色彩、大小、速度不同计算,规则可以自由设定
  5. 使用带缓存的工厂管理模式管理不同的飞碟的生产与回收,该工厂必须是场景单实例的
  6. 尽可能使用MVC结构实现人机交互与游戏模型分离
2. 游戏设计

在这里插入图片描述

3. 细节实现
  1. 单个disk的参数设置,预制的Disk资源的参数可以被修改//

    public class DiskData : MonoBehaviour
    {
        // Start is called before the first frame update
        public Vector3 size;  //大小
        public Color color; //颜色,绿、红、蓝。
        public float speed; //速度随回合数升高递增
        public Vector3 direction; //方向
        public int score;//代表的分数随回合数升高递增
    }
    
  2. DiskFactory类管理所有的disk,实现对象池的设置,被点击的飞碟不会被销毁,而是进入回收队列,当需要时再次被使用,减少游戏对象的创建与销毁的开销。

    //飞碟创建和回收队列
    public GameObject disk;                 //飞碟预制体
    private List<DiskData> used = new List<DiskData>();   //正在被使用的飞碟列表
    private List<DiskData> free = new List<DiskData>();   //空闲的飞碟列表
    

    获取飞碟GetDisk()方法:

    判断空闲飞碟队列是否为空,不为空直接使用,否则clone一个新的Disk对象,之后通过round参数判断当前的回合数,对Disk的参数(初始速度,颜色,方向等)进行修改并把飞碟加入等待队列。

    public GameObject GetDisk(int round){
            disk = null;
            if (free.Count > 0){
                //空闲列表存在飞碟,直接使用
                disk = free[0].gameObject;
                free.Remove(free[0]);
            }
            else{
                //不存在,进行预制
                disk = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/disk"), Vector3.zero, Quaternion.identity);
                disk.AddComponent<DiskData>(); 
            }
            
            //round代表回合数
            switch (round){
                case 0:
                    {
                        disk.GetComponent<DiskData>().color = Color.green;
                        disk.GetComponent<DiskData>().size = new Vector3(5,0,5);                  
                        disk.GetComponent<DiskData>().speed = Random.Range(10f, 12f);
                        float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                        disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                        disk.GetComponent<DiskData>().score = 1;
                        disk.GetComponent<Renderer>().material.color = Color.green;
                        break;
                    }
    
                case 1:
                    {
                        disk.GetComponent<DiskData>().color = Color.red;
                        disk.GetComponent<DiskData>().speed = Random.Range(15f, 18f);
                        float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                        disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                        disk.GetComponent<DiskData>().score = 2;
                        disk.GetComponent<Renderer>().material.color = Color.red;
                        break;
                    }
    
                case 2:
                    {
                        disk.GetComponent<DiskData>().color = Color.blue;
                        disk.GetComponent<DiskData>().speed = Random.Range(10f, 15f);
                        float startX = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
                        disk.GetComponent<DiskData>().direction = new Vector3(startX, 1, 0);
                        disk.GetComponent<DiskData>().score = 3;
                        disk.GetComponent<Renderer>().material.color = Color.blue;
                        break;
                    }
            }
    
            used.Add(disk.GetComponent<DiskData>());
            return disk;
    
        }
    

    回收飞碟FreeDisk()方法:

    检查所有Used队列中的Disk,如果不在工作状态则回收到Free队列

    public void FreeDisk(GameObject disk){
            for (int i = 0; i < used.Count; i++){
                if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID()){
                    used[i].gameObject.SetActive(false);
                    free.Add(used[i]);
                    used.Remove(used[i]);
                    break;
                }
            }
        }
    
  3. 运用模板Singleten,可以为每个 MonoBehaviour子类 创建一个对象的实例

    模板已在课件中实现,直接使用

    调用方法:任意位置使用Singleton<YourMonoType>.Instance 获得该对象。

  4. FirstController类继承了MonoBehaviour, ISceneController, IUserAction类,控制游戏资源的加载,游戏的开始,暂停,结束,Disk对象的发送,打击,收回等,用户交互的计分,回合控制。

    资源加载:

    readonly float roundTime = 40f;
        private float speed = 1.5f;                                                //发射一个飞碟的时间间隔
        readonly int[] passScore = { 20, 50 };
    
        private List<GameObject> disks = new List<GameObject>();          //飞碟队列
        private int currentRound = 0;                                                   //回合
        private float time = 0f;                                                 //记录时间间隔
        private float currrentTime = 0f;
        private GameState gameState = GameState.START;
    
        public UserGUI userGUI;
        public ScoreRecorder scoreRecorder;      //计分   
        public DiskFactory diskFactory;          //工厂
        public FlyActionManager actionManager;   //动作管理
    
    

    通过Singleten模板实现游戏对象的实例化

    void Start()
        {
            SSDirector director = SSDirector.GetInstance();
            director.CurrentScenceController = this;
            diskFactory = Singleton<DiskFactory>.Instance;
            scoreRecorder = Singleton<ScoreRecorder>.Instance;
            actionManager = gameObject.AddComponent<FlyActionManager>() as FlyActionManager;
            userGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
            gameState = GameState.START;
            time = 0f;
            currentRound = 0;
            currrentTime = 0;
            LoadResources();
    
        }
    

    判断并修改场景中Disk的状态:

    void Update()
        {
            if (gameState == GameState.RUNNING ) 
            {
                for (int i = 0; i < disks.Count; i++)
                {
                    //飞碟飞出摄像机视野也没被打中
                    if ((disks[i].transform.position.y <= -4.5) && disks[i].gameObject.activeSelf == true)
                    {
                        diskFactory.FreeDisk(disks[i]);
                        disks.Remove(disks[i]);
                        scoreRecorder.Miss();
                    }
                }
                if (time > speed)
                {
                    time = 0;
                    SendDisk();
                }
                else
                {
                    time += Time.deltaTime;
                }
                if (currrentTime > roundTime)
                {
                    currrentTime = 0;
                    if (currentRound < 2 && GetScore() >= passScore[currentRound])
                    {
                        currentRound++;
                        time = 0f;
                    }
                    else
                    {
                        GameOver();
                    } 
                }
                else
                {
                    currrentTime += Time.deltaTime;
                }
            }
        }
    
    

    飞碟打击动作的实现:Hit()方法

    public void Hit(Vector3 pos)
        {
            Ray ray = Camera.main.ScreenPointToRay(pos);
            RaycastHit[] hits;
            hits = Physics.RaycastAll(ray);
            bool isHit = false;
            for (int i = 0; i < hits.Length; i++)
            {
                RaycastHit hit = hits[i];
    
                if (hit.collider.gameObject.GetComponent<DiskData>() != null)       
                {
                    for (int j = 0; j < disks.Count; j++)        //射中的物体在used列表中
                    {
                        if (hit.collider.gameObject.GetInstanceID() == disks[j].gameObject.GetInstanceID())
                        {
                            isHit = true;
                        }
                    }
                    if (!isHit)        //如果没有打中,返回
                    {
                        return;
                    }
                    disks.Remove(hit.collider.gameObject);          //从队列中移除
                    scoreRecorder.Record(hit.collider.gameObject);      //记分员记录分数
                    StartCoroutine(WaitingParticle(0.08f, hit, diskFactory, hit.collider.gameObject));      //等几秒后回收Disk
                    break;
                }
            }
        }
    

    飞碟发射实现:sendDisk()方法

    private void SendDisk()
        {
            GameObject disk = diskFactory.GetDisk(currentRound);
            disks.Add(disk);
            disk.SetActive(true);
    
            float positionX = 16;
            float ranY = Random.Range(1f, 4f);
            float ranX = Random.Range(-1f, 1f) < 0 ? -1 : 1;
            disk.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
            Vector3 position = new Vector3(-disk.GetComponent<DiskData>().direction.x * positionX, ranY, 0);
            disk.transform.position = position;
    
            float angle = Random.Range(15f, 20f);
            actionManager.UFOFly(disk, angle);
    
            if (disk.GetComponent<DiskData>().color == Color.blue)
    
            {
                GameObject disk1 = Instantiate(disk);
                GameObject disk2 = Instantiate(disk);
                disks.Add(disk1);
                disk1.SetActive(true);
                disk1.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
                disk1.transform.position = position;
                actionManager.UFOFly(disk1, Random.Range(15f, 28f));
    
                disks.Add(disk2);
                disk2.SetActive(true);
                disk2.GetComponent<DiskData>().direction = new Vector3(ranX, ranY, 0);
                disk2.transform.position = position;
                actionManager.UFOFly(disk2, Random.Range(15f, 28f));
            }
    }
    

    还实现了Restart()、GetScore()、Gettime()、GetRound()、Pause()方法等,不再列出。

  5. FlyAction()实现飞碟的飞行管理

    public class FlyActionManager : SSActionManager  //本游戏管理器
    {
        private UFOFlyAction fly;     //飞行动作,这次只有单独动作,没有组合动作
        public FirstController sceneController;
        protected void Start()
        {
            sceneController = (FirstController)SSDirector.GetInstance().CurrentScenceController;
            sceneController.actionManager = this;
        }
        public void UFOFly(GameObject disk, float angle)
        {
            fly = UFOFlyAction.GetSSAction(disk.GetComponent<DiskData>().direction, angle, disk.GetComponent<DiskData>().speed);
            this.RunAction(disk, fly, this);
        }
    
        public void Pause()
        {
            fly.run = false;
        }
    
        public void Run()
        {
            fly.run = true;
        }
    }
    
  6. ScoreRecorder类实现计分管理

    规则:

    round1:每个Disk一分,Miss之后总分减一

    round2:每个Disk两分,Miss之后总分减二

    round3:每个Disk三分,Miss之后总分减三

    游戏结束后总分复位

    public class ScoreRecorder : MonoBehaviour{
        // Start is called before the first frame update
        public int score;                   //分数
        void Start(){
            score = 0;
        }
        //记录分数
        public void Record(GameObject disk)
        {
            score = disk.GetComponent<DiskData>().score + score;
        }
        public void Miss()
        {
            if (score >= 2)
                score -= 2;
            else
                score = 0;
        }
        //重置分数
    
        public void Reset()
        {
            score = 0;
        }
    }
    
    
  7. SequenceAction类实现Disk队列的组合动作

    public class SequenceAction : SSAction, ISSActionCallback{
        public List<SSAction> sequence;    //动作的列表
        public int repeat = -1;            //-1就是无限循环做组合中的动作
        public int start = 0;              //当前做的动作的索引
    
        public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence){
            SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
            action.repeat = repeat;
            action.sequence = sequence;
            action.start = start;
            return action;
        }
    
        public override void Update(){
            if (sequence.Count == 0) return;
            if (start < sequence.Count){
                sequence[start].Update();     
            }
        }
    
        public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
    
            int intParam = 0, string strParam = null, Object objectParam = null)
    
        {
            source.destroy = false;          //先保留这个动作,如果是无限循环动作组合之后还需要使用
            this.start++;
            if (this.start >= sequence.Count){
                this.start = 0;
                if (repeat > 0) repeat--;
                if (repeat == 0)
                {
                    this.destroy = true;               //整个组合动作就删除
                    this.callback.SSActionEvent(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(){
        }
    }
    
  8. 其它SSAction、SSDirection等类的实现较为简单,不再给出。

4. 效果展示:

在这里插入图片描述

5. 不足与改进
  1. 不同的回合内飞碟的大小未改变。
  2. 可以增加点击后的爆炸效果等。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值