unity:打飞碟小游戏

游戏开发要求

  • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
  • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
  • 必须使用对象池管理飞碟对象。
  • 建议使用 ScriptableObject 配置不同的飞碟(方便配置修改)
  • 建议使用物理引擎管理飞碟飞行路径。

使用 UML 图、伪代码辅助表达对象池的原理,对象配置方法,以及你的设计

游戏规则

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

工厂与对象池理解

工厂方法,即类一个方法能够得到一个对象实例,使用者不需要知道该实例如何构建、初始化等细节。

好处:

  • 游戏对象的创建与销毁高成本,必须减少销毁次数。
  • 屏蔽创建与销毁的业务逻辑,使程序易于扩展

在这里插入图片描述

UML图

在这里插入图片描述

代码介绍

代码分为下面几个模块

遵循MVC架构和cocos2d的设计思想

action

action不多做讲解,可以参考之前博客的牧师与魔鬼动作分离版的讲解

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
    /// <summary>
    /// when an action has been excuted, call the callback function
    /// </summary>
    /// <param name="source">action has been excuted</param>
    /// <param name="events"></param>
    /// <param name="intParam"></param>
    /// <param name="strParam"></param>
    /// <param name="objectParam"></param>
    public void SSActionEvent(SSAction source,
                              SSActionEventType events = SSActionEventType.Competeted,
                              int intParam = 0,
                              string strParam = null,
                              GameObject objectParam = null);
    
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSAction : ScriptableObject
{
    
    /// <summary>
    /// if enable, then will call update per frame
    /// </summary>
    public bool enable = true;
    /// <summary>
    /// if destory, will be removed after a frame
    /// </summary>
    public bool destroy = false;


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


    public ISSActionCallback callback { get; set; }
    protected SSAction() { }


    // Start is called before the first frame update
    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }

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

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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>();
    // Start is called before the first frame update
    protected void Start()
    {
        
    }

    // Update is called once per frame
    protected void Update()
    {
        // add to actions
        foreach(SSAction ac in waitingAdd){
            actions[ac.GetInstanceID()] = ac;
        }
        waitingAdd.Clear();

        //run actions
        foreach(KeyValuePair<int, SSAction> kv in actions){
            SSAction ac = kv.Value;
            if(ac.destroy){
                waitingDelete.Add(ac.GetInstanceID());
            }
            else if(ac.enable){
                ac.Update();//call action update manually
            }
        }
        //delete actions
        foreach(int key in waitingDelete){
            SSAction ac = actions[key];
            actions.Remove(key);
            Destroy(ac);
        }
        waitingDelete.Clear();
    }

    public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager){
        action.gameobject = gameobject;//add action to gameobject
        action.transform = gameobject.transform;//ser action's transform
        action.callback = manager;//set action's callback
        waitingAdd.Add(action);
        action.Start();//not monobehavior thus call start mannually
    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NPhysicalDiskAction : SSAction
{
    float gravity;
    float time;
    float speed;
    Vector3 direction;

    // builder, get specific actions success SSAction
    public static NPhysicalDiskAction GetSSAction(Vector3 direction, float speed)
    {
        NPhysicalDiskAction action = ScriptableObject.CreateInstance<NPhysicalDiskAction>();
        action.gravity = 9.8f;
        action.time = 0;
        action.speed = speed;
        action.direction = direction;
        return action;
    }
    // Start is called before the first frame update
    public override void Start()
    {
        gameobject.GetComponent<Rigidbody>().isKinematic = true;
    }

    // Update is called once per frame
    public override void Update()
    {
        // disk move 运动合成,transform is the one of scriptable object,
        // which will be set equal to gameobject which excecutes this action. 
        transform.Translate(Vector3.down * gravity * time * Time.deltaTime);
        transform.Translate(direction * speed * Time.deltaTime);
        time += Time.deltaTime;
        //Debug.Log("postion of the disk"+gameobject.name+" is "+gameobject.transform.position);
        // 飞碟到达屏幕底部回调
        Camera mainCamera = Camera.main; // 获取主摄像机
        float cameraHeight = mainCamera.orthographicSize * 2f; // 获取相机的视图高度

        // 计算相机视图的上下边界坐标
        float upperBoundary = mainCamera.transform.position.y + cameraHeight / 2f;
        float lowerBoundary = mainCamera.transform.position.y - cameraHeight / 2f;

        //Debug.Log("Upper Boundary: " + upperBoundary);
        //Debug.Log("Lower Boundary: " + lowerBoundary);
        if (this.transform.position.y < lowerBoundary-100 || this.transform.position.y > upperBoundary+100)
        {
            // action destory
            this.destroy = true;
            this.enable = false;
            // action callback
            this.callback.SSActionEvent(this);//source as this
        }

    }
}

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PhysicalDiskAction : SSAction
{
    private float speed;
    private Vector3 direction;

    // builder
    public static PhysicalDiskAction GetSSAction(Vector3 direction, float speed)
    {
        PhysicalDiskAction ac = ScriptableObject.CreateInstance<PhysicalDiskAction>();
        ac.speed = speed;
        ac.direction = direction;
        return ac;
    }

    // Start is called before the first frame update
    public override void Start()
    {
        // add clash pysical effect
        gameobject.GetComponent<Rigidbody>().isKinematic = false;
        // add init speed
        gameobject.GetComponent<Rigidbody>().velocity = speed * direction;
    }

    // Update is called once per frame
    public override void Update()
    {
        //Debug.Log("postion of the disk"+gameobject.name+" is "+gameobject.transform.position);
        // 飞碟到达屏幕底部回调
        Camera mainCamera = Camera.main; // 获取主摄像机
        float cameraHeight = mainCamera.orthographicSize * 2f; // 获取相机的视图高度

        // 计算相机视图的上下边界坐标
        float upperBoundary = mainCamera.transform.position.y + cameraHeight / 2f;
        float lowerBoundary = mainCamera.transform.position.y - cameraHeight / 2f;

        //Debug.Log("Upper Boundary: " + upperBoundary);
        //Debug.Log("Lower Boundary: " + lowerBoundary);
        if (this.transform.position.y < lowerBoundary - 100 || this.transform.position.y > upperBoundary + 100)
        {
            // action destory
            this.destroy = true;
            this.enable = false;
            // action callback
            this.callback.SSActionEvent(this);//source as this
        }

    }
}

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class DiskActionManager : SSActionManager, ISSActionCallback
{
    public FirstController firstController;

    public void ShootDisk(GameObject disk, float speed, Vector3 direction,PhysiclaType type)
    {
        SSAction ac;
        // run action
        if (type==  PhysiclaType.noPhysical)
        {
            ac = NPhysicalDiskAction.GetSSAction(direction, speed);
        }
        else if (type == PhysiclaType.pysical)
        {
            ac = PhysicalDiskAction.GetSSAction(direction, speed);
        }
        else
        {
            throw new NotImplementedException();
        }
        
        RunAction(disk, ac, this);
    }

    protected new void Start()
    {
        // get fisrt controller
        firstController = (FirstController)Director.GetInstance().CurrentSceneController;
        // set action manager as this
        firstController.actionManager = this;

    }

    // specific manager do callback
    public void SSActionEvent(SSAction source,
                    SSActionEventType events = SSActionEventType.Competeted,
                    int intParam = 0,
                    string strParam = null,
                    GameObject objectParam = null)
    {
        //回收飞碟
        //source is the class success the SSAction, in this case is obj of NphysicalDiskAction Class
        Singleton<RoundController>.Instance.FreeDisk(source.gameobject);
    }
}

config


using System;
using UnityEngine;

[CreateAssetMenu(menuName ="create ruler config")]
public class Ruler : ScriptableObject
{
    // 对局属性
    public int trailIndex;
    public int roundIndex;
    
    public int SumRoundNum;
    public int SumTrailNum;

    public int[] diskNumPerRound;//avg disk num per round
    public float[] shootDeltaTimePerRound;//发射间隔

    // 飞碟属性
    public DiskFeature diskFeature;

    [NonSerialized]
    public Vector3 startPos;
    [NonSerialized]
    public Vector3 startDir;

}

controller

Director 导演类,管理场记

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;


public class Director : System.Object
{
    static Director _instance;
    // 关联场记实例
    public ISceneController CurrentSceneController { get; set; }
    // 获取当前的场记
    public static Director GetInstance()
    {
        if (_instance == null)
        {
            _instance = new Director();
        }
        return _instance;
    }

    public static void ReloadCurrentScene()
    {
        int currentSceneIndex = SceneManager.GetActiveScene().buildIndex;
        SceneManager.LoadScene(currentSceneIndex);
    }
}


ISceneController场记类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISceneController
{
    void LoadResources();
}

IUserAction定义了用户的动作

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

// 抽象接口,定义用户的行为
public interface IUserAction
{
    
    void Restart();
}

FirstController 第一个场记,继承ISceneController

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/* 游戏状态,0为准备进行,1为正在进行游戏,2为结束 */
public enum GameState
{
    Ready = 0, Playing = 1, GameOver = 2
};



public class FirstController : MonoBehaviour, ISceneController
{
    
    public DiskActionManager actionManager;
    public RoundController roundController;
    public UserGUI userGUI;

    public GameState gameState;

    // ruler config
    public Ruler rulerConfig;

     


    public void LoadResources()
    {
        Director.GetInstance().CurrentSceneController = this;
        
        roundController = gameObject.AddComponent<RoundController>();
        Debug.Log("add roundController Component");

        userGUI = gameObject.AddComponent<UserGUI>();
        Debug.Log("add UserGUI");

        actionManager = gameObject.AddComponent<DiskActionManager>();
        Debug.Log("add DiskActionManager");

        gameState = (int)GameState.Ready;
    }

    
    public void Restart()
    {
        Director.ReloadCurrentScene();
    }


    public void JudgeResultCallBack(string result)
    {

    }

    void Awake()
    {
        LoadResources();
    }

    void Start()
    {
        
    }

    void Update()
    {

    }

    public void GameOver()
    {
        gameState = GameState.GameOver;
        userGUI.SetGameState(GameState.GameOver);
    }

    public void SetGameMode(PhysiclaType type)
    {
        roundController.phyType= type;
    }
}

RoundController 控制飞碟对象的运动类型为运动学运动或物理运动,并根据一定规则请求飞碟工厂生成相应的飞碟对象,控制游戏飞碟对象的发射

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public enum PhysiclaType
{
    pysical,
    noPhysical
}

public class RoundController : MonoBehaviour
{
    DiskActionManager actionManager;
    ScoreRecorder scoreRecorder;
    FirstController firstController;
    Ruler ruler;
    float time = 0;

    public PhysiclaType phyType;

    public void LaunchDisk()
    {
        // 使飞碟飞入位置尽可能分开,从不同位置飞入使用的数组
        int[] beginPosY = new int[10];
        float beginPosX;

        // shoot the disks for each trail
        int diskNum = ruler.diskNumPerRound[ruler.roundIndex] / ruler.SumTrailNum;
        diskNum += Random.Range(0, 5);// random num per trail


        for (int i = 0; i < diskNum; ++i)
        {
            // 获取随机数
            int randomNum = Random.Range(1, 4);
            // 飞碟速度随回合数增加而变快
            ruler.diskFeature.speed = randomNum * (ruler.roundIndex + 2);

            // 根据随机数选择飞碟颜色
            randomNum = Random.Range(1, 4);
            if (randomNum == 1)
            {
                ruler.diskFeature.color = DiskFeature.colors.red;
            }
            else if (randomNum == 2)
            {
                ruler.diskFeature.color = DiskFeature.colors.green;
            }
            else
            {
                ruler.diskFeature.color = DiskFeature.colors.blue;
            }

            // 重新选取随机数,并根据随机数选择飞碟的大小
            ruler.diskFeature.size = Random.Range(1, 4);

            // 重新选取随机数,并根据随机数选择飞碟飞入的方向
            randomNum = Random.Range(0, 2);
            Camera mainCamera = Camera.main;
            float cameraW = mainCamera.orthographicSize * 2f * Camera.main.aspect;

            if (randomNum == 1)
            {
                ruler.startDir = new Vector3(3, 1, 0);
                beginPosX= mainCamera.transform.position.x - cameraW / 2f;
            }
            else
            {
                ruler.startDir = new Vector3(-3, 1, 0);
                beginPosX = mainCamera.transform.position.x + cameraW / 2f;
            }

            // 重新选取随机数,并使不同飞碟的飞入位置尽可能分开
            int iterNum = 0;
            
            do
            {
                // case if infinite iter
                iterNum++;
                if (iterNum>=100)
                {
                    int ii;
                    for (ii = 0; ii < beginPosY.Length; ii++)
                    {
                        beginPosY.SetValue(0, ii);
                    }
                }
                randomNum = Random.Range(0, 10);
            } while (beginPosY[randomNum] != 0);
            beginPosY[randomNum] = 1;
            ruler.startPos = new Vector3(beginPosX, -0.2f * randomNum, 0);

            // 根据ruler从工厂中生成一个飞碟
            GameObject disk = Singleton<DiskFactory>.Instance.GetDisk(ruler);

            // 设置飞碟的飞行动作
            actionManager.ShootDisk(disk, ruler.diskFeature.speed, ruler.startDir,phyType);
        }
    }

    /// <summary>
    /// free the gameobject that loaded the action
    /// </summary>
    /// <param name="obj">obj is the one load the action</param>
    public void FreeDisk(GameObject disk)
    {
        // call factory to free the disk
        Singleton<DiskFactory>.Instance.FreeDisk(disk);
    }

    // 用户点击碰撞
    public void Hit(Vector3 position)
    {
        Camera camera = Camera.main;
        Ray ray = camera.ScreenPointToRay(position);

        RaycastHit[] hits;
        hits = Physics.RaycastAll(ray);

        for (int i = 0; i < hits.Length; i++)
        {
            RaycastHit hit = hits[i];
            if (hit.collider.gameObject != null)
            {
                // 把击中的飞碟移出屏幕,触发回调释放
                hit.collider.gameObject.transform.position = new Vector3(0, -6, 0);
                // 记录飞碟得分
                scoreRecorder.Record(hit.collider.gameObject.GetComponent<Disk>());
                // 显示当前得分
                UserGUI userGUI = Singleton<UserGUI>.Instance;
                userGUI.SetScore(scoreRecorder.GetScore());
            }
        }
    }

    // Start is called before the first frame update
    void Start()
    {
        Debug.Log("gameobject of roundcontroller is:" + this.gameObject);

        actionManager = gameObject.AddComponent<DiskActionManager>();
        scoreRecorder = gameObject.AddComponent<ScoreRecorder>();
        firstController = Director.GetInstance().CurrentSceneController as FirstController;
        ruler = firstController.rulerConfig;
        gameObject.AddComponent<DiskFactory>();
        
        
        
        ruler.roundIndex= 0;
        ruler.trailIndex= 0;

        Debug.Log("ruler config:");
        Debug.Log(JsonUtility.ToJson(ruler));

        phyType = PhysiclaType.noPhysical;
        Debug.Log("round controller's phytype is:"+phyType);
  
    }

    // Update is called once per frame
    void Update()
    {
        // when playing
        if (firstController.gameState == GameState.Playing)
        {
            //update usergui round index
            UpdateRoundInfo();
            //Debug.Log("round cont is playing update--");
            //Debug.Log("roundIndex" + ruler.roundIndex + "trailIndex" + ruler.trailIndex);
            time += Time.deltaTime;
            float shootDeltaTime = ruler.shootDeltaTimePerRound[ruler.roundIndex];

            // 发射一次飞碟(trial)
            if (time > shootDeltaTime)
            {
                // reset time
                time = 0;

                if (ruler.trailIndex<ruler.SumTrailNum && ruler.roundIndex<ruler.SumRoundNum)
                {
                    // 发射飞碟
                    Debug.LogFormat("call LaunchDisk at round controller--at round {0} trail {1}"
                        , ruler.roundIndex, ruler.trailIndex);
                    LaunchDisk();
                    ruler.trailIndex++;
                    // 回合加一,重新生成飞碟数组
                    if (ruler.trailIndex == ruler.SumTrailNum)
                    {
                        ruler.trailIndex = 0;
                        ruler.roundIndex++;
                    }
                }
                // 否则游戏结束,提示重新进行游戏
                else
                {
                    firstController.GameOver();
                }
                
            }
        }
    }

    private void UpdateRoundInfo()
    {
        Singleton<UserGUI>.Instance.SetIndex(ruler.roundIndex, ruler.trailIndex);
        Singleton<UserGUI>.Instance.SetRTNum(ruler.SumRoundNum, ruler.SumTrailNum);
        Singleton<UserGUI>.Instance.SetPhyMode(phyType);
    }

    
}

记分类根据记分规则,对被点击中的飞碟得分进行记录

using UnityEngine;



public class ScoreRecorder : MonoBehaviour
{
    private int score;

    public ScoreRecorder()
    { score = 0; }

    public ScoreRecorder(int score)
    { this.score = score; }

    public int GetScore()
    {
        return score;
    }

    // record the score
    public void Record(Disk disk)
    {
        // 飞碟大小为1得3分,大小为2得2分,大小为3得1分
        int diskSize = disk.diskFeature.size;
        switch (diskSize)
        {
            case 1:
                score += 3;
                break;
            case 2:
                score += 2;
                break;
            case 3:
                score += 1;
                break;
            default: break;
        }

        // 速度越快分就越高
        score += disk.diskFeature.speed;

        // 颜色为红色得1分,颜色为黄色得2分,颜色为蓝色得3分
        DiskFeature.colors diskColor = disk.diskFeature.color;
        if (diskColor == DiskFeature.colors.red)
        {
            score += 1;
        }
        else if (diskColor == DiskFeature.colors.yellow)
        {
            score += 2;
        }
        else if (diskColor == DiskFeature.colors.blue)
        {
            score += 3;
        }
    }

    /* 重置分数,设为0 */
    public void Reset()
    {
        score = 0;
    }

    // Use this for initialization
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }
}

factory

Singleton单实例类,用来获取单实例的类的对象

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T instance;

    public static T Instance
    {
        get
        { if (instance == null)
            {
                instance = FindObjectOfType(typeof(T)) as T;
                if (instance == null)
                {
                    Debug.LogError("no instance of type " + typeof(T));
                }
            } 
            return instance;
        }
    }

    // Start is called before the first frame update
    void Start()
    {
           
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

飞碟工厂类负责飞碟对象的创建和销毁,在场景中是单实例的,且使用了对象池,实现了缓存功能。

当一个飞碟对象被创建时,会首先在对象池中寻找没有被使用的空闲飞碟对象,有的话就根据规则设置飞碟对象相应属性后直接使用,没有再创建。

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using Unity.VisualScripting.Antlr3.Runtime;
using UnityEngine;
using UnityEngine.Experimental.GlobalIllumination;

public class DiskFactory : MonoBehaviour
{
    public GameObject diskPrefab;

    private List<GameObject> used;
    private List<GameObject> free;
    private int diskIndex;

    public GameObject GetDisk(Ruler ruler)
    {
        GameObject disk;

        // 如果free没有空闲就创建
        if (free.Count==0)
        {
            disk = GameObject.Instantiate(diskPrefab, Vector3.zero, Quaternion.identity);
            disk.name = "UFO" + diskIndex;
            Debug.LogFormat("factory create a disk, index is {0}",diskIndex);
            disk.AddComponent(typeof(Disk));
            diskIndex++;
        }
        else
        {
            int freeNum = free.Count;
            disk = free[freeNum-1];
            free.Remove(free[freeNum-1]);
        }

        // cache
        used.Add(disk);

        // 初始化disk feature
        disk.GetComponent<Disk>().diskFeature = ruler.diskFeature;

        // initial disk color
        Renderer render = disk.GetComponent<Renderer>();
        if (ruler.diskFeature.color == DiskFeature.colors.white)
        {
            render.material.color = Color.red;
        }
        else if (ruler.diskFeature.color == DiskFeature.colors.blue)
        {
            render.material.color = Color.blue;
        }
        else if (ruler.diskFeature.color == DiskFeature.colors.black)
        {
            render.material.color = Color.black;
        }
        else if (ruler.diskFeature.color == DiskFeature.colors.yellow)
        {
            render.material.color = Color.yellow;
        }
        else if (ruler.diskFeature.color == DiskFeature.colors.green)
        {
            render.material.color= Color.green;
        }

        // set disk position and scale
        disk.transform.localScale = new Vector3(1f, (float)ruler.diskFeature.size, 1f);
        disk.transform.position = ruler.startPos;
        disk.SetActive(true);

        

        return disk;
    }

    public void FreeDisk(GameObject dd)
    {
        foreach (GameObject d in used)
        {
            if (d.GetInstanceID()==dd.GetInstanceID())
            {
                Debug.LogFormat("factory free a disk, name is {0}", d.name);
                d.SetActive(false);
                used.Remove(d);
                free.Add(d);
                break;
            }
        }
    }
    
    // Start is called before the first frame update
    void Start()
    {
        diskPrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/UFO"));
        diskPrefab.SetActive(false);
        Debug.Log("DiskFactory initial the diskPrefab");
        used = new List<GameObject>();
        free = new List<GameObject>();
        diskIndex = 0;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

model

飞碟的model类

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Disk : MonoBehaviour
{

    public DiskFeature diskFeature;


    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

飞碟的特征类,有大小,颜色,速度等属性


public struct DiskFeature
{
    public enum colors
    {
        green,
        blue,
        red,
        purple,
        black,
        white,
        yellow,
    }


    public int size;
    public colors color;
    public int speed;
}

view

视图类,游戏有三种状态:就绪,游戏中,游戏结束
根据游戏状态不同显示不同的gui界面

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

struct Status
{
    public int score;
    public string tip;
    public int roundNum;
    public int trialNum;
    public int roundIndex;
    public int trialIndex;
    public PhysiclaType type;
    public GameState gameState;
}

public class UserGUI : MonoBehaviour
{
    private Status status;
    private ISceneController currentSceneController;
    private GUIStyle playInfoStyle;




    // Start is called before the first frame update
    void Start()
    {
        Init();
        currentSceneController = Director.GetInstance().CurrentSceneController;

        // set style
        playInfoStyle = new GUIStyle();
        playInfoStyle.normal.textColor = Color.black;
        playInfoStyle.fontSize= 25;
    }

    private void Init()
    {
        status.score= 0;
        status.tip = "";
        status.roundNum = 0;
        status.trialNum = 0;
        status.gameState = GameState.Ready;
    }

    // Update is called once per frame
    void Update()
    {

    }

    private void OnGUI()
    {
        // title
        GUIStyle titleStyle = new GUIStyle();
        titleStyle.normal.textColor = Color.red;
        titleStyle.fontSize = 50;
        GUI.Label(new Rect(Screen.width / 2 - 80, 10, 60, 100), "Hit UFO", titleStyle);
        
        // show user page
        ShowPage();

    }

    public void SetIndex(int roundIndex, int  trailIndex)
    {
        status.roundIndex = roundIndex;
        status.trialIndex = trailIndex;
    }

    public void SetRTNum(int roundNum, int trailNum)
    {
        status.roundNum = roundNum;
        status.trialNum= trailNum;
    }

    public void SetGameState(GameState gameState)
    {
        status.gameState = gameState;
    }

    /*
     set property of status
     */

    public void SetScore(int score)
    {
        status.score = score;
    }

    /*
     base on game status to show different user view
     */

    private void ShowPage()
    {
        switch (status.gameState)
        {
            case GameState.Ready:
                ShowHomePage();
                break;
            case GameState.Playing:
                ShowPlayingPage();
                break;
            case GameState.GameOver:
                ShowGameoverPage();
                break;
        }
    }

    private void ShowGameoverPage()
    {
        GUI.Label(new Rect(Screen.width / 2 - 40, 60, 60, 100), "游戏结束!", playInfoStyle);
        if (GUI.Button(new Rect(420, 200, 100, 60), "重新开始"))
        {
            FirstController f = (FirstController)currentSceneController;
            // set game status
            f.Restart();
        }
    }

    private void ShowPlayingPage()
    {
        
        GUI.Label(new Rect(10, 10, 60, 100), "正在游戏",playInfoStyle);
        GUI.Label(new Rect(Screen.width  - 200, 10, 60, 100), 
            "round:" +(status.roundIndex+1)+"  trail:"+ (status.trialIndex+1), playInfoStyle);
        GUI.Label(new Rect(10, 40, 60, 100), "得分:"+status.score, playInfoStyle);
        GUI.Label(new Rect(Screen.width - 200, 35, 60, 100),
            "总轮数:" + status.roundNum + "\n每轮射击数:" + status.trialNum, playInfoStyle);
        GUI.Label(new Rect(10, 70, 60, 100),
            "当前模式:"+(status.type==PhysiclaType.noPhysical?"运动学模式":"物理模式"), playInfoStyle);
        if (Input.GetButtonDown("Fire1"))
        {
            Singleton<RoundController>.Instance.Hit(Input.mousePosition);
        }

        // chose mode
        if (GUI.Button(new Rect(50, 450, 80, 50), "运动学模式"))
        {
            FirstController f = (FirstController)currentSceneController;
            f.SetGameMode(PhysiclaType.noPhysical);
        }
        if (GUI.Button(new Rect(150, 450, 80, 50), "物理学模式"))
        {
            FirstController f = (FirstController)currentSceneController;
            f.SetGameMode(PhysiclaType.pysical);
        }
    }

    private void ShowHomePage()
    {
        // add game mode
        if (GUI.Button(new Rect(Screen.width/2-50, 100, 100, 60), "开始游戏式\n(默认为运动学模式)"))
        {
            FirstController f = (FirstController)currentSceneController;
            // set game status
            f.gameState = GameState.Playing;
            status.gameState = GameState.Playing;
        }
    }

    internal void SetPhyMode(PhysiclaType phyType)
    {
        status.type= phyType;
    }
}


创建预制&&配置ruler&&配置天空盒&&挂载脚本

飞碟的预制,需要提前制作好,并加上刚体组件Rigidbody,将Use Gravity项勾选上

在这里插入图片描述

配置游戏对局数据,这样就不用要修改都在代码里修改了

在这里插入图片描述

先创建一个config,之后配置我们的对局数据

在这里插入图片描述
在这里插入图片描述

再给camera配上一个天空盒

在这里插入图片描述

最后挂载firstcontroller和ruler,config脚本到一个空gameobject上

在这里插入图片描述

游戏就可以运行了

视频演示

unity 打飞碟小游戏

代码仓库

GitHub地址

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值