Unity打飞碟小游戏

本文介绍了一个使用Unity开发的3D飞碟射击小游戏,游戏包含多个难度递增的round,每个round有10次trial,飞碟具有随机性。游戏要求使用工厂模式管理飞碟,并遵循MVC结构。详细讨论了Model属性、类工厂设计、Controller组件规划、View视图设计以及动作管理。关键代码包括DiskFactory.cs,GameView.cs和Controller.cs。
摘要由CSDN通过智能技术生成

游戏内容要求

  • 游戏有 n 个 round,每个 round 都包括10 次 trial;
  • 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制;
  • 每个 trial 的飞碟有随机性,总体难度随 round 上升;
  • 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。

游戏的要求

  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类。
  • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离。

游戏实现

Model属性分析

飞碟本身有大小、颜色、形状、运动属性,其中运动属性又包括发射位置、速度、角度属性。

类工厂属性方法

类工厂负责提供飞碟实例,为了节省内存空间耗费,可以采用Pool的方法,保存两个实例队列,一个用于保存正在使用的实例,另一个保存实例化完成但没有使用的实例,当有需要提供一个飞碟实例时,调用GetOneDisk()方法从未使用实例队列或者重新实例化一个Disk实例。

关键在于对Disk实例属性的赋值必须具有随机性。C#语言System库中有Random类,可以提供随机数,通过对随机数的判断,即可实现属性赋值的随机性。

此外,考虑每一回合的难度不同,回合结束或者游戏重新开始,所以需要清空正在使用的实例队列中的所有实例。

Controller组件规划

基本上是由Director类统筹整个游戏,由于需要使用单例模式,所以为导演类声明静态的实例化方法getInstance()来确保同一时刻只有一个游戏运行。同时游戏的具体实现由场记类实现,所以在导演类中需要添加一个场记接口,为游戏场景运行提供支持。

场记类负责调度各种事件,实现场景的加载、事件的执行。

用户行为和用户行为管理类作为一套,UserAction类负责实现各种可能的用户行为,定义行为对应的执行事件,ActionManager类负责对行为的调度,实现行为。在本次项目中,

View视图规划

视图的设计时层次化的,考虑到游戏是回合制的,所以有几个游戏状态:游戏未开始,游戏开始,游戏中回合前,游戏中回合间,游戏中回合后。
针对不同状态需要进行状态转移,而且对象的实例化在不同回合需要也会不同。
因为添加了血量,红色的飞碟被击中将会减少血量,当血量为0游戏结束。所以需要在View中完成对血量的判断和显示。
针对不同的状态,视图必须不同。

动作管理类

动作管理类主要负责对各个动作的统筹实现,SSActionSSActionManager基本结构具有普适性,只需要完成其子类的Update方法,即可使场记类(controller)对场景的调度过程中根据事件执行Action
本次项目中主要是SSActionFly方法Update中使用Translate对游戏对象施加运动,包括向下的重力加速度,即可实现抛物线运动。SSActionManagerloadAction方法调用RunAction将实例对象与动作绑定,实现运动。

状态转移的判断

主要通过三个参数,isOver确定游戏是否开始,inRound确定在回合前、回合内、回合后间隔,actionManager.disk_number确定回合内的状态,若等于零就代表所有飞碟都已抛出完毕,回合结束,当然也可能是游戏并未开始,所以需要与isRound==1共同使用,是否能进入下一回合。

关键代码

DiskFactory.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Data;
//using System;
using DiskClass;

namespace DiskFactoryClass
{
    public class DiskFactory : MonoBehaviour
    {
        public GameObject diskModel = null;                   /* 飞碟游戏对象 */
        private List<Disk> used = new List<Disk>();           /* 类似pool,用于减少内存消耗 */
        private List<Disk> free = new List<Disk>();

        public GameObject GetOneDisk(int roundNow)
        {
            diskModel = null;
            //System.Random randomNum = new System.Random(1);
            //Debug.Log("DiskFactory:" + free.Count +" "+ used.Count);
            if (free.Count > 0)
            {
                diskModel = free[0].gameObject;
                free.Remove(free[0]);
            }
            else
            {
                int id = Random.Range(-10, 10);
                if (id > roundNow/2)
                {
                    //Debug.Log("white");
                    diskModel = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Plate1"), Vector3.zero, Quaternion.identity);
                    diskModel.AddComponent<Disk>();                       /* 将GameObject加入Scene */
                    diskModel.GetComponent<Disk>().id = true;
                }
                else
                {
                    //Debug.Log("red");
                    diskModel = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/Plate2"), Vector3.zero, Quaternion.identity);
                    diskModel.AddComponent<Disk>();
                    diskModel.GetComponent<Disk>().id = false;                
                    diskModel.GetComponent<Disk>().value = 1;
                }
                diskModel.GetComponent<Disk>().speed = Random.Range(0,5);
                float runX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
                //diskModel.GetComponent<Renderer>().material.color = diskModel.GetComponent<Disk>().color;            /* 设置Scene中GameObject的显示颜色,Renderer是显示类 */
                diskModel.GetComponent<Disk>().direction = new Vector3(runX, 1, 0);
            }

            used.Add(diskModel.GetComponent<Disk>());
            diskModel.name = diskModel.GetInstanceID().ToString();
            return diskModel;
        }

        public void FreeUsingDisk(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;
                }
            }
        }
    }
}

GameView.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UserActions;
using Director;

namespace GameView
{
    public class pageImage : MonoBehaviour
    {
        IUserAction userAction;
        
        public bool isOver = true;

        // Start is called before the first frame update
        void Start()
        {
            userAction = SSDirector.getInstance().currentSceneController as IUserAction;
        }

        private void OnGUI()
        {
            if (!isOver)//游戏正在进行中
            {
                /* 点击事件 */
                if (Input.GetButtonDown("Fire1"))
                {
                    Vector3 pos = Input.mousePosition;
                    userAction.click(pos);
                }

                /* 分数和生命值标识 */
                GUI.color = Color.red;
                GUI.Label(new Rect(2 * Screen.width / 3, 0, 400, 400), "Score: " + userAction.getScore().ToString());
                GUI.Label(new Rect(Screen.width-120, 0, 400,400), "Life:" + userAction.getLife().ToString());


                GUI.color = Color.red;
                if (userAction.getLife() == 0)//游戏失败
                {
                    GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 200, 400, 400), "GAMEOVER\n" + " your score is " + userAction.getScore().ToString());
                    if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Restart"))
                    {
                        userAction.resetScore();
                        userAction.setLife();
                        //isOver = false;
                    }
                }

                else//游戏正常执行中,确定当前回合数
                {
                    GUI.color = Color.red;
                    GUI.Label(new Rect(3 * Screen.width / 4, 0, 400, 400), "Round: " + (userAction.getRound() + 1).ToString());
                    if (userAction.isInRound()==2 && GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Next Round"))//在回合间隔中,按钮进入下一回合
                    {
                        userAction.setRoundState(0);
                    }

                }
            }
            else if (userAction.getRound() == 5)
            {
                GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 200, 400, 400), "YOU WIN!!!\n" + " your score is " + userAction.getScore().ToString());
                if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Restart"))
                {
                    userAction.resetScore();
                    userAction.setLife();
                    userAction.setRound(-1);
                    //isOver = false;
                }
            }
            else//游戏未开始
            {
                if(GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Start"))
                {
                    isOver = false;
                    userAction.setRoundState(0);
                }
            }
        }
    }
}

Controller.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UserActions;
using Director;
using DiskClass;
using GameView;
using DiskFactoryClass;
using ActionManager;
using SceneController;
using SingletonClass;

/* 场记类实现:需要继承场记接口 */
public class Controller : MonoBehaviour, ISceneController, IUserAction
{
    public pageImage gameGUI;
    public CCActionManager actionManager;

    public Queue<GameObject> disks = new Queue<GameObject>();
    public int score = 0;
    private int diskNumber=5;
    private int currentRound = -1;
    public int life = 5;
    public int finalRound = 5;
    public int inRound = 0;

    private float time = 0;     //计时
    private float roundTime = 1;    //每个回合隔多久飞一次飞碟
    /* 场记接口方法的实现,主要完成场景载入 */
    public void LoadResource()
    {
        //无需载入初始场景
    }

    public int isInRound()
    {
        return inRound;
    }
    public void setRoundState(int state)
    {
        inRound = state;
    }
    public int getScore()
    {
        return score;
    }
    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<Disk>() != null)
            {
                if (hit.collider.gameObject.GetComponent<Disk>().id)
                {
                    score += hit.collider.gameObject.GetComponent<Disk>().value;
                    hit.collider.gameObject.transform.position = new Vector3(0, -10, 0);
                }
                else
                {
                    life--;
                    hit.collider.gameObject.transform.position = new Vector3(0, -10, 0);
                }
            }
        }
    }
    public int getRound()
    {
        return currentRound;
    }
    public void setRound(int num)
    {
        currentRound = num;
    }
    public void resetScore()
    {
        score = 0;
    }
    public int getLife()
    {
        return life;
    }
    public void setLife()
    {
        life = 5;
    }


    /* 控制器的生命周期 */
    void Awake()
    {
        SSDirector director = SSDirector.getInstance();
        director.currentSceneController = this;
        director.currentSceneController.LoadResource();
    }

    void Start()
    {
        Debug.Log("First SceneController Start!");
        gameGUI = gameObject.AddComponent<pageImage>() as pageImage;
        actionManager = gameObject.AddComponent<CCActionManager>() as CCActionManager;
        this.gameObject.AddComponent<DiskFactory>();
    }

    private void Update()
    {
        if (inRound==2 && currentRound==finalRound)
        {
            gameGUI.isOver = true;
        }
        else
        {
            
            if (actionManager.disk_number == 0 && inRound==1&&!gameGUI.isOver)
            {
                inRound = 2;
            }

            if (actionManager.disk_number == 0 && inRound==0&&!gameGUI.isOver)
            {
                currentRound++;
                actionManager.disk_number = (currentRound + 1) * diskNumber;
                Debug.Log("Controller's disk number:" + actionManager.disk_number);
                inRound = 1;
            }
            Debug.Log(actionManager.disk_number);
            if (time > roundTime)
            {
                if (actionManager.disk_number>0)
                { 
                    DiskFactory df = Singleton<DiskFactory>.Instance;
                    time = 0;
                    actionManager.loadAction(df.GetOneDisk(currentRound));
                }
            }
            else
            {
                time += Time.deltaTime;
            }
        }

    }

    
}

具体内容

代码传送门
演示视频传送门

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值