简单的鼠标打飞碟(Hit UFO)游戏--unity

一、简介

        该游戏是使用unity编写的鼠标打飞碟游戏。飞碟会从屏幕外的多个方向飞入屏幕中,玩家只要用鼠标点击飞行中的飞碟,即可将其击落(对象消失),并因此得到积分;对于每一只飞碟而言,都有自己的颜色、大小和速度,击落后的得分也会根据这三个属性值的不同而不同。该游戏一共有多个回合(round),且游戏的难度也会随着回合数的增加而增加。

游戏需求:

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

        在该案例中使用对象池技术实现对游戏对象的创建与回收。

游戏视频

简单的鼠标打飞碟(Hit UFO)游戏--unity

二、对象池原理及游戏框架

1. 对象池原理

        对象池是一种常用的设计模式,其目的是为了在需要创建和销毁大量相似对象的场景中提高性能。对象池维护着一组可重用的对象,在需要使用时从池中获取对象,并在使用完成后将其放回池中,而不是每次都创建新对象和销毁旧对象。使用对象池操作可以减少创建对象和销毁对象带来的高成本。

        对象池设计主要包含两个部分:

        1. 对象池的初始化:在程序启动时,创建一定数量的对象实例,并将它们保存到一个列表中,这些对象被称为“池对象”。

        2. 对象池的使用:当需要一个对象时,从对象池(列表)中取出一个可用的池对象,完成相应操作后再将其返回池中。如果池中没有可用的对象,则可以创建新的对象加入池中。

2. 游戏框架

        该游戏使用 工厂方法 + 单实例 + 对象池 的设计方法,其UML图如下:

  • RoundController类是该游戏的场景控制器,用于控制飞碟各个飞行回合(round)的运行,是一个单实例类。(类似于牧师与魔鬼中的FirstController)
  • DiskFactory 类是一个单实例类,用前面场景单实例RoundController创建。
  • DiskFactory 类中维护了对象池,有工厂方法 GetDisk 产生飞碟,有回收方法 FreeDisk
  • ScoreRecorder类作为“记分员”,用于统计玩家的得分。
  • 游戏对象disk拥有一个组件用于保存自己的多个属性(后面介绍),同时每次创建对象时从游戏预制中获取对象。

3. 对象池实现的伪代码

getDisk(ruler) 
BEGIN
	IF (free list has disk) THEN
	a_disk = remove one from list
	ELSE
	a_disk = clone from Prefabs
	ENDIF
	Set DiskData of a_disk with the ruler
	Add a_disk to used list
	Return a_disk
END
FreeDisk(disk)
BEGIN
	Find disk in used list
	IF (not found) THEN THROW exception
	Move disk from used to free list
END

三、游戏设计

1. 游戏对象

        该游戏只有一种游戏对象,即飞碟(UFO)。飞碟有三种属性值,颜色、大小和速度。这三种属性值每种都有三种可能的取值。属性不同飞碟最后的得分也会不一样。对于颜色而言,黄色为3分,红色2分,蓝色1分;对于大小而言,小的3分,中2分,大1分;对于速度而言,快3分,中2分,慢1分。这三种属性值对于得分的贡献是线性的,因此飞碟得分的取值为3分(蓝色大体积慢速)到9分(黄色小体积快速)。

        每个飞碟的发射位置也是不一样的。一共有三个方向,分别是屏幕的左边、中间和右边。对于左右两边而言,可能在随机的高度发射。

2. 文件组织形式:

Assets中目录如下:

Resources中包含了游戏对象的材质和预制:

 

Scenes中包含了游戏的场景:

        其中场景中除了主相机、灯光外,只有一个空的、挂载了场景控制器的空对象。

        

Scripts中包含了游戏的脚本,该脚本的组织形式采用了MVC框架和动作分离的框架。其中Actions中包含了动作相关的代码(即飞碟移动),Controllers包含了场景控制相关的代码,Views中包含了图形用户交互界面的代码:

四、代码介绍

1. 动作部分Actions

        采用了动作分离的设计方法:

(下图为动作分离的示例,并不完全适用于该游戏)

        在该游戏中,没有了上述的CCSquenceAction类和CCMoveToAction类,取而代之的是飞碟飞行的动作CCUFOAction。

(1)回调接口ISSCallback

        包含了一个枚举动作事件类型和一个回调函数。作为一个接口,由动作对象实现并用于通信:

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

public enum SSActionEventType:int {Started, Completed}   // 枚举动作事件类型
public interface ISSCallback
{
    //回调函数
    void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null);
}

(2)动作基类SSAction

        动作的基类,所有动作继承于此:

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

public class SSAction : ScriptableObject
{
    public bool enable = true;
    public bool destroy = false;
    public GameObject gameObject { get; set;}
    public Transform transform {get; set;}
    public ISSCallback callback {get; set;}

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

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

(3)具体动作CCUFOAction

        该类是飞碟的具体运动过程,作为一个组件加入到飞碟对象中。该类继承自动作基类,同时还定义了一些与飞碟飞行有关的参数值。对于飞碟的运动轨迹,这里使用了运动学的公式,使得飞碟的飞行轨迹呈现抛物线的形式。其中x_t = x_0 + v_x * t,   y_t = y_0 + v_y * t - \frac{1}{2} * g * t^2 (为了避免下落太快,这里取g=6)。该类有个GetSSAction方法用于获取当前的动作交给动作管理器处理。Start方法用于参数的初始化,nextPosition输入当前的时间(从飞碟开始飞行算起)后得到下一个移动的位置,Update方法用于判断飞碟是否出界以及改变飞碟的位置。

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

public class CCUFOAction : SSAction
{
    public float speedX;
    public float speedY;
    public float movedTime;
    public Vector3 originPosition;
    public static CCUFOAction GetSSAction(float x, float y) {
        CCUFOAction action = ScriptableObject.CreateInstance<CCUFOAction>();
        action.speedX = x;
        action.speedY = y;
        return action;
    }

    Vector3 nextPosition(float time){
        Vector3 position;

        // 根据抛物线轨迹公式计算位置
        position.x = originPosition.x + speedX * time;
        position.y = originPosition.y + speedY * time - 0.5f * 6f * time * time;
        position.z = originPosition.z;

        return position;
    }

    public override void Start(){
        movedTime = 0;        // 初始化开始运动的时间
        originPosition = this.transform.position;    // 初始位置
    }

    public override void Update()
    {
        Vector3 vec3 = Camera.main.WorldToScreenPoint (this.transform.position); 
        // 如果飞碟已经被"销毁" 或者 超出屏幕的一定范围内
        if (!this.transform.gameObject.activeSelf || vec3.x < -100 || vec3.x > Camera.main.pixelWidth + 100 || vec3.y < -100 || vec3.y > Camera.main.pixelHeight + 100) { // 超出屏幕范围,销毁
            this.destroy = true;
            this.callback.SSActionEvent(this);
            return;
        }
        this.transform.position = nextPosition(movedTime);
        movedTime += Time.deltaTime;
    }
}

(4)动作管理器基类SSActionManager

        动作管理器的基类。用于处理动作的添加和删除操作。

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>(); 

    protected void Start(){
        
    }

	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 (); // update action
			}
		}

		foreach (int key in waitingDelete) {  // 等待删除的动作
			SSAction ac = actions[key]; 
			actions.Remove(key);    // 从字典中删除动作
			Object.Destroy(ac);      // 销毁动作
		}
		waitingDelete.Clear (); // 等到所有动作执行完毕后,清空等待删除的动作列表
	}

	// 执行动作
	public void RunAction(GameObject gameobject, SSAction action, ISSCallback manager) {
		action.gameObject = gameobject;     // 设置动作的游戏对象
		action.transform = gameobject.transform;    // 设置动作的游戏对象的 Transform 组件
		action.callback = manager;  // 设置动作的回调接口
		waitingAdd.Add (action);    // 添加到等待执行的动作列表中
		action.Start ();       // 开始执行动作
	}


    public int RemainActionCount() {     // 剩余动作的数量
        return actions.Count;
    }
}

(5)动作管理器CCActionManager

        在start方法中将自己(即动作管理器)传递给场景控制器,并创建一个单实例的工厂对象。该类实现了ISSCallback的接口函数SSActionEvent,用于将使用过的飞碟回收。在MoveDisk方法中创建一个动作并且执行该动作。

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

public class CCActionManager : SSActionManager, ISSCallback
{
    public RoundController roundCtrl;
    public CCUFOAction action;
    public DiskFactory factory;

    protected new void Start()
    {
        roundCtrl = (RoundController)SSDirector.getInstance().currentSceneController;
        roundCtrl.actionManager = this;
        factory = Singleton<DiskFactory>.Instance;
    }

    public void SSActionEvent(SSAction source,  // 使用回调函数,当动作完成时,将该飞碟回收
        SSActionEventType events = SSActionEventType.Completed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null) {
            factory.FreeDisk(source.transform.gameObject);   // 将使用过的飞碟回收
    }

    public void MoveDisk(GameObject disk) {
        action = CCUFOAction.GetSSAction(disk.GetComponent<DiskAttributes>().speedX, disk.GetComponent<DiskAttributes>().speedY);
        RunAction(disk, action, this);
    }
}

2. 控制器部分Controllers

        使用了MVC框架:

(下图为MVC框架的示例,并不完全适用于该游戏)

        在该游戏中,FirstController即场景控制器变成了RoundController,并且不再单独创建游戏对象的类了,因为游戏对象被工厂创建并维护在对象池中。其他部分保持不变。

(1)单实例类SSDirector

        继承自System.object,可以获取当前的场景。

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

public class SSDirector : System.Object
{
    private static SSDirector _instance;
    public ISceneController currentSceneController {get; set;}
    
    public static SSDirector getInstance() {
        if (_instance == null) {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

(2)场景控制器接口ISceneController

        包含了场景控制器与游戏场景相关的函数,等待由场景控制器RoundController实现。

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

public interface ISceneController
{
    void LoadResource();
}

(3)场景单实例模板Singleton

        运用该模板,可以为每个 MonoBehaviour子类创建一个对象的实例。使用单实例时,只需要将 MonoBehaviour 子类对象挂载任何一个游戏对象上即可,然后在任意位置使用代码Singleton<T>获得该对象。

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 = (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;  
		}  
	}
}
// 场景单实例的使用很简单,你仅需要将 MonoBehaviour 子类对象挂载任何一个游戏对象上即可。然后在任意位置使用代码 Singleton<YourMonoType>.Instance 获得该对象。
// 运用模板,可以为每个 MonoBehaviour子类 创建一个对象的实例。

(4)工厂类DiskFactory

        该文件中包含了三部分代码(三个类):

第一部分:自定义报错类MyException

        定义了一个继承自System.Exception的异常检测类型。当出现异常操作时,会显示报错的信息。

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

public class MyException : System.Exception
{
    public MyException() { }
    public MyException(string message) : base(message) { }
}

第二部分:飞碟对象的属性DiskAttributes

        定义了一些与飞碟有关的属性值,在后面作为组件添加到飞碟对象中。

public class DiskAttributes : MonoBehaviour     // 飞碟的属性
{
    public int score;
    public float speedX;
    public float speedY;
}

第三部分:飞碟“工厂”类DiskFactory

        该工厂具有三个成员变量,random是随机数对象,在后面用于生成随机数来决定飞碟的各个属性;userd是一个列表,用于保存使用过的飞碟对象(对象的回收);free是一个列表,用于保存可以使用的飞碟(对象的创建)。在Start函数中实现上述三个变量的初始化。

        该工厂定义了两个重要的函数。对于GetDisk函数,接收回合数作为参数(因为要根据回合数增加难度),然后返回一个游戏对象(即飞碟)。在该函数中,首先从对象池free中取出一个游戏对象(如果没有则从预制中获取新的游戏对象并为其添加属性组件)。然后,根据上面的游戏设计部分,随机设置飞碟对象的颜色、大小、速度。在这里,采用随机分配“档位”(即random.Next(1,4))的设计方法,根据“颜色档位”color_w的不同为飞碟附上颜色,根据“大小档位”size_w的不同为飞碟设置大小(即缩放),根据“速度档位”和回合数(随着回合数增加,速度也会加快)的线性叠加为飞碟设置速度。飞碟的得分则根据这三个档位相加即可。同时,为飞碟设置的发射位置(左边、中间、右边)和飞碟的角度(朝向)。在完成所有的设置后,将该飞碟加入到使用过的列表used,并设置为激活状态。

        对于FreeDisk函数,该函数用于释放飞碟对象,在接收一个飞碟对象作为输入后,将其状态设置为不可见,同时将飞碟预制恢复到原来的状态(因为伸缩变化是针对原预制进行的,需要恢复到原来的状态以免飞碟越来越小)。至此,完成了飞碟的释放,将飞碟从used列表中移出(移出前需要检测userd列表是否为空),然后加入回free列表中(代表该对象回到对象池中,可以被再次“创建”使用)。


// 工厂:飞碟管理员
public class DiskFactory : MonoBehaviour
{

    System.Random random;  // 随机数
    List<GameObject> used;        // 使用过的飞碟
    List<GameObject> free;        // 可以被使用的飞碟

    void Start()
    {
        used = new List<GameObject>();
        free = new List<GameObject>();
        random = new System.Random();         // 初始化随机数
    }

    void Update(){
        
    }

    public GameObject GetDisk(int round) {
        GameObject disk;
        if (free.Count != 0) {
            disk = free[0];   //  从列表中取出一个飞碟
            free.Remove(disk);
        }
        else {
            disk = GameObject.Instantiate(Resources.Load("Prefabs/UFO", typeof(GameObject))) as GameObject; // 从预设中创建飞碟
            disk.AddComponent<DiskAttributes>();
        }

        // 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。
        // 每个 trial 的飞碟有随机性,总体难度随 round 上升;
        // 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。

        int color_w = random.Next(1,4);          // 颜色有三个档位
        int size_w = random.Next(1,4);           // 大小有三个档位
        int speed_w = random.Next(1,4);          // 速度有三个档位

        switch(color_w){   // 根据颜色的档位为UFO上色
            case 1:       // 蓝色档位最低
                disk.GetComponent<Renderer>().material.color = Color.blue;
                break;
            case 2:
                disk.GetComponent<Renderer>().material.color = Color.red;
                break;
            case 3:     //  黄色档位最高
                disk.GetComponent<Renderer>().material.color = Color.yellow;
                break;
        }

        switch(size_w){    // 根据大小的档位调节UFO的尺寸
            case 1:   // 档位1尺寸不变
                break;
            case 2:
                disk.transform.localScale = new Vector3(1.8f,0.1f,1.8f);
                break;
            case 3:     // 档位越高,尺寸越小
                disk.transform.localScale = new Vector3(1.5f,0.1f,1.5f);
                break;
        }

        // 设置UFO的角度
        disk.transform.localEulerAngles = new Vector3(-random.Next(20,40),0,0);

        DiskAttributes atbt = disk.GetComponent<DiskAttributes>();
        atbt.score = color_w + size_w + speed_w;                // 根据颜色、大小、速度来决定分数
        atbt.speedX = (speed_w * 3 + round) * 0.5f;                   
        atbt.speedY = (speed_w * 3 + round) * 0.8f;
        
        int dir = random.Next(1,4);   // 随机一个方向
        switch(dir){
            case 1:
                disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(0, random.Next(0,Camera.main.pixelHeight/2), 8)));
                break;
            case 2:
                disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth, random.Next(0,Camera.main.pixelHeight/2), 8)));
                atbt.speedX *= -1;   // 从右下角时,方向需要调整
                break;
            case 3:
                disk.transform.Translate(Camera.main.ScreenToWorldPoint(new Vector3(Camera.main.pixelWidth/2, 0, 8)));
                atbt.speedX = 0;            // 竖直向上飞
                break;
        }
        used.Add(disk);    // 标记飞碟为用过
        disk.SetActive(true);  // 设置为激活状态
        return disk;
    }

    public void FreeDisk(GameObject disk) {  // 释放飞碟
        disk.SetActive(false);
        //将位置和大小恢复到预制的初始状态
        disk.transform.position = new Vector3(0, 0,0);
        disk.transform.localScale = new Vector3(2f,0.1f,2f);
        if (!used.Contains(disk)) {
            throw new MyException("尝试从不包含项目的列表中删除该项目。");
        }
        used.Remove(disk);          // 从使用过的飞碟中移除
        free.Add(disk);       // 添加到等待释放的飞碟中
    }
}

(5)计分器ScoreRecorder

        该类用于统计获得的分数并将分数信息传送给用户交互对象(UserGUI)显示。该类具有一个变量score用于保存得到的总分,同时创建了一个回合控制对象(场景控制器)roundCtrl和一个用户交互对象userGUI。在start函数中,将当前的计分器对象赋值给场景控制器,同时获得当前的用户交互对象。在record函数中,将单次得分的信息传输给用户交互对象,并且将单词得分累积到总分中,并将总分也传输给用户交互对象。

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

public class ScoreRecorder : MonoBehaviour
{
    int score;
    public RoundController roundCtrl;
    public UserGUI userGUI;

    void Start()
    {
        roundCtrl = (RoundController)SSDirector.getInstance().currentSceneController;
        roundCtrl.scoreRecorder = this;
        userGUI = this.gameObject.GetComponent<UserGUI>();
    }

    public void Record(GameObject disk) {
        int add = disk.GetComponent<DiskAttributes>().score;
        userGUI.gameMessage = "+"+add;   // 显示单次得分
        score += add;
        userGUI.score = score;
    }
}

(6)回合控制器(场景控制器)RoundController

        定义了当前回合数,最大回合数,单个回合时间等变量,同时包含了动作管理器对象、计分器对象、工厂对象、用户交互对象等。start函数中对参数进行了初始化(令round为-1是为了确保游戏池中游戏对象的正常加载)。在update函数中,先确保游戏已经开始,然后调用getHit函数处理鼠标点击事件,调用gameover函数判断游戏是否结束。接着,对于每一个回合,time代表单个回合的时间,actionManager.RemainActionCount()代表剩余的动作数(对应于剩余的飞碟数),直到时间结束并且飞碟数为0时该回合结束。对于每个回合,从工厂创建10个飞碟对象,并给它们添加动作,然后将回合数传递给用户交互界面显示。

        在awake函数中,实现回合控制器的初始化,包括了当前场景的创建、为游戏对象添加组件、以及工厂对象、用户交互对象的获取。由于加载预制的任务在工厂对象中完成,因此场景控制器不再需要编写LordResource函数。在gameover函数中,如果回合数大于最大回合数并且所有的飞碟动作执行完毕后,游戏结束,并将结束的信息传递给用户交互对象。在getHit函数中,检测鼠标左键的动作。当鼠标点击飞碟对象后,使用计数器对象获得其分数,并将其状态设置为false(即不可见状态)。

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

// 回合控制器(场景控制器)
public class RoundController : MonoBehaviour, ISceneController, IUserAction
{
    int round,maxRound;
    float time;
    GameObject disk;
    DiskFactory factory;
    public CCActionManager actionManager;
    public ScoreRecorder scoreRecorder;
    public UserGUI userGUI;
    
    void Start(){
        round = -1;
        maxRound = 10;
        time = 1.0f;
    }

    void Update()
    {
        if (!userGUI.isStart) return;           // 等待游戏开始
        GetHit();                             // 鼠标点击事件
        gameOver();                           // 判断游戏是否结束
        if (round > maxRound) {              // 回合结束
            return;
        }
        time -= Time.deltaTime;            // 单个回合的时间
        if (time <= 0 && actionManager.RemainActionCount() == 0) { // 一个回合结束
            //从工厂中得到10个飞碟,为其加上动作
            for (int i = 0; i < 10; i++) {
                disk = factory.GetDisk(round);
                actionManager.MoveDisk(disk);
            }
            round += 1;
            if (round <= maxRound) {
                userGUI.round = round;  // 更新显示的回合数
            }
            if(round>0) time = 4.0f;
            userGUI.gameMessage = "";
        }
    }

    void Awake() {  // 初始化
        SSDirector director = SSDirector.getInstance();
        director.currentSceneController = this;
        director.currentSceneController.LoadResource();
        gameObject.AddComponent<UserGUI>();  // 添加用户界面
        gameObject.AddComponent<CCActionManager>();  // 添加动作管理器
        gameObject.AddComponent<ScoreRecorder>();  // 添加分数记录器
        gameObject.AddComponent<DiskFactory>();   // 添加飞碟工厂
        factory = Singleton<DiskFactory>.Instance;  // 创建工厂单实例
        userGUI = gameObject.GetComponent<UserGUI>();
    }

    public void LoadResource() {  // 加载资源
        // 加载资源的任务交给工厂
    }

    public void gameOver(){
        if(round > maxRound && actionManager.RemainActionCount() == 0){
            userGUI.gameMessage = "游戏结束";
        }
    }

    public void GetHit() {   // 鼠标点击事件???
        if (Input.GetButtonDown("Fire1")) {     // 鼠标左键点击
			Camera ca = Camera.main;  
			Ray ray = ca.ScreenPointToRay(Input.mousePosition);
    		RaycastHit hit;
			if (Physics.Raycast(ray, out hit)) {
                scoreRecorder.Record(hit.transform.gameObject);
                hit.transform.gameObject.SetActive(false);
			}
		}
    }

}

3. 视图部分Views

(1)用户动作接口IUserAction

        包含了场景控制器与动作相关的函数。由于采用了动作分离的方式,因此虽然下面的两个函数由场景控制器实现,但实际调用了动作类CCUFOAction的函数来实现。

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

public interface IUserAction
{
    void gameOver();
    void GetHit();
}

(2)用户交互类UserGUI

        包含了接收用户输入的按钮以及显示给用户看的信息。在Start函数中对部分变量进行初始化,同时从场景控制器处获得用户动作接口,设置了用于显示的两种字体格式。在OnGUI函数中,根据bool值判断是否开始游戏。没开始游戏时处于主页面MainMenu,会显示游戏标题和“开始游戏”的按钮;当按下该按钮后,切换到游戏界面,显示的得分信息和回合信息,等到游戏结合后显示结束信息和最终游戏得分。

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

public class UserGUI : MonoBehaviour
{
    public bool isStart;        // 是否开始游戏
    public int score;         // 得分
    public int round;       // 回合数
    public string gameMessage;  // 游戏信息
    private IUserAction action;
    public GUIStyle bigStyle, smallStyle;//自定义字体格式
    private int menu_width = Screen.width / 5, menu_height = Screen.width / 10;//主菜单每一个按键的宽度和高度

    void Start()
    {
        isStart = false;
        gameMessage = "";
        action = SSDirector.getInstance().currentSceneController as IUserAction;

        bigStyle = new GUIStyle();
        bigStyle.normal.textColor = Color.black;
        bigStyle.fontSize = 50;

        smallStyle = new GUIStyle();
        smallStyle.normal.textColor = Color.black;
        smallStyle.fontSize = 30;
    }

    void OnGUI() {
        GUI.skin.button.fontSize = 35;
        if(isStart) {
            GameStart();
        } else {
            MainMenu();
        }
    }

    void MainMenu() {
        GUI.Label(new Rect(Screen.width / 2 - menu_width * 0.5f  - 50, Screen.height * 0.1f, menu_width, menu_height), "打飞碟小游戏", bigStyle);
        bool button = GUI.Button(new Rect(Screen.width / 2 - menu_width * 0.5f, Screen.height * 3 / 7, menu_width, menu_height), "开始游戏");
        if (button) isStart = true;
    }
    void GameStart() {
        GUI.Label(new Rect(300, 60, 50, 200), gameMessage, bigStyle);
        if(gameMessage=="游戏结束"){
            GUI.Label(new Rect(150, 120, 50, 200), "你最终的分数是:"+score, bigStyle);
        } 
        GUI.Label(new Rect(0,0,100,50), "分数: " + score, smallStyle);
        GUI.Label(new Rect(560,0,100,50), "回合数: " + round, smallStyle);
    }
}

至此,所有的代码介绍完毕。

五、代码链接

lab8: 3D游戏编程实验8---鼠标打飞碟小游戏 (gitee.com)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值