Unity3D实现打飞碟小游戏

游戏相关简介

游戏介绍与制作要求

  • 游戏内容要求:

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

    • 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类
    • 近可能使用前面 MVC 结构实现人机交互与游戏模型分离
    • 必须使用对象池管理飞碟对象。
    • 按adapter模式设计图修改飞碟游戏,使它同时支持物理运动与运动学(变换)运动。

        计分规则:

加分项加分
飞碟为红色加1分
飞碟为绿色加2分
飞碟为蓝色加3分
飞碟的size为1(小飞碟)加3分
飞碟的size为2(中飞碟)加2分
飞碟的size为3(大飞碟)加1分
飞碟的速度为x加x分

项目地址

Wesley/HitUFO

游戏展示

unity打飞碟小游戏_演示

项目详解

总览:UML图

单实例设计

导入Singleton单实例模板类:

        首先,定义了一个名为Singleton的泛型类,它继承自MonoBehaviour。这意味着可以将这个类作为组件挂载到Unity场景中的游戏对象上。在这个类中,定义了一个静态的instance变量,用来保存类的唯一实例。然后,定义了一个公共的静态属性Instance,用于获取类的实例。在这个属性的get访问器中,首先检查instance是否为空。如果为空,就调用FindObjectOfType(typeof(T))来查找场景中是否已经存在类型为T的实例。如果没有找到,就会输出一个错误信息。最后,返回instance变量,以便外部代码可以访问到该类的实例。使用这个泛型单例模式,可以确保在整个Unity场景中只有一个指定类型的实例存在。其他脚本可以通过Singleton<T>.Instance来获取该实例,并进行操作。

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

导演类的实现:

        首先,定义了一个名为Director的类。在这个类中,定义了一个私有的静态变量instance,用来保存Director类的唯一实例。接下来,定义了一个名为mainController的属性,用于保存MainController类型的实例,并可以进行读取和设置。然后,定义了一个公共的静态方法GetInstance(),用于获取Director类的实例。在这个方法中,首先检查instance是否为空。如果为空,就创建一个新的Director实例,并将其赋值给instance。最后,返回instance变量。

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

public class Director : System.Object {
    private static Director instance;
    public MainController mainController { get; set; }
    public static Director GetInstance() {
        if (instance == null) {
            instance = new Director();
        }
        return instance;
    }
}

飞碟创建与销毁

单实例,使用了对象池,实现了缓存功能

飞碟工厂类(DiskFactory.cs

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

public class DiskFactory : MonoBehaviour {
    public GameObject diskPrefab; // 飞碟游戏对象,创建新的飞碟游戏对象的复制对象
    private List<DiskData> used; // 正在被游戏使用的飞碟对象
    private List<DiskData> free; // 没有被使用的空闲飞碟对象

    public void Start() {
        diskPrefab = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Disk"), Vector3.zero, Quaternion.identity);
        diskPrefab.SetActive(false);
        used = new List<DiskData>();
        free = new List<DiskData>();
    }

    // 飞碟获取方法,根据ruler获取相应飞碟
    public GameObject GetDisk(Ruler ruler) {
        GameObject disk;

        // 从缓存中获取飞碟,没有则先创建
        int diskNum = free.Count;
        if (diskNum == 0) {
            disk = GameObject.Instantiate(diskPrefab, Vector3.zero, Quaternion.identity);
            disk.AddComponent(typeof(DiskData));
        }
        else {
            disk = free[diskNum - 1].gameObject;
            free.Remove(free[diskNum - 1]);
        }

        // 根据ruler设置disk的速度、颜色、大小、飞入方向
        disk.GetComponent<DiskData>().speed = ruler.speed;
        disk.GetComponent<DiskData>().color = ruler.color;
        disk.GetComponent<DiskData>().size = ruler.size;
        
        // 给飞碟上颜色
        if (ruler.color == "red") {
            disk.GetComponent<Renderer>().material.color = Color.red;
        }
        else if (ruler.color == "green") {
            disk.GetComponent<Renderer>().material.color = Color.green;
        }
        else {
            disk.GetComponent<Renderer>().material.color = Color.blue;
        }

        // 绘制飞碟大小
        disk.transform.localScale = new Vector3(1.2f, 0.1f * (float)ruler.size, 1.2f);
        
        // 选择飞碟飞入屏幕的起始位置
        disk.transform.position = ruler.beginPos;
        
        // 设置飞碟显示
        disk.SetActive(true);
    
        // 将飞碟加入使用队列
        used.Add(disk.GetComponent<DiskData>());

        return disk;
    }

    // 飞碟回收方法,将不使用的飞碟从使用队列放到空闲队列中
    public void FreeDisk(GameObject disk) {
        foreach (DiskData d in used) {
            if (d.gameObject.GetInstanceID() == disk.GetInstanceID()) {
                disk.SetActive(false);
                used.Remove(d);
                free.Add(d);
                break;
            }

        }
    }
}

        首先,在类中定义了一个公共的游戏对象变量diskPrefab,用于存储飞碟游戏对象的预制体。在Start()方法中,通过实例化Resources.Load<GameObject>("Prefabs/Disk")来加载飞碟预制体,并将其赋值给diskPrefab。然后将diskPrefab设置为不激活状态,以便稍后使用。接下来,定义了两个私有的List<DiskData>类型的变量used和free,分别用于存储正在被游戏使用的飞碟对象和没有被使用的空闲飞碟对象。

        在GetDisk()方法中,首先根据free列表中的飞碟数量判断是否有可用的飞碟对象。如果free列表为空,说明没有空闲的飞碟对象,需要创建一个新的飞碟对象。通过实例化diskPrefab并添加DiskData组件来创建新的飞碟对象。否则,从free列表中取出最后一个飞碟对象,并将其从free列表中移除。

        然后,根据传入的ruler参数设置飞碟对象的速度、颜色和大小。通过访问飞碟对象的GetComponent<DiskData>()方法获取到DiskData组件,并设置其speed、color和size属性。接下来,根据ruler的颜色设置飞碟对象的渲染器的颜色。根据ruler的大小设置飞碟对象的缩放。根据ruler的beginPos设置飞碟对象的位置。最后,激活飞碟对象并将其加入used列表中。最后,在FreeDisk()方法中,遍历used列表,找到与传入的disk对象实例相匹配的DiskData对象。将该飞碟对象设置为不激活状态,然后从used列表中移除,并将其添加到free列表中。

        这样,通过调用GetDisk()方法可以获取一个飞碟对象,通过调用FreeDisk()方法可以回收一个不再使用的飞碟对象。

飞碟预制(disk:GameObject

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

飞碟各方面数据(DiskData.cs

在这个类中,定义了三个公共变量:size、color和speed。它们分别表示飞碟的大小、颜色和发射速度。这些变量是公共的,因此可以从其他脚本中访问和修改它们的值,以便根据需要设置每个飞碟的属性。

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

public class DiskData : MonoBehaviour {
    public int size; // 大小
    public string color; // 颜色
    public int speed; // 发射速度
}

飞碟获取规则(ruler)

// 飞碟获取规则
public struct Ruler {
    public int trialNum; // 当前trial的编号
    public int roundNum; // 当前round的编号
    public int roundSum; // 一共round的总数目
    public int [] roundDisksNum; // 每一轮对于trial的飞碟数量
    public float sendTime; // 发射间隔时间

    public int size; // 飞碟大小
    public int speed; // 飞碟速度
    public string color; // 飞碟颜色
    public Vector3 direction; // 飞碟飞入方向
    public Vector3 beginPos; // 飞碟飞入位置
};

动作分离

IActionManager.cs:

        定义了一个名为IActionManager的接口。在这个接口中,定义了一个名为PlayDisk的方法,它接受三个参数:disk(类型为GameObject)、speed(类型为float)和direction(类型为Vector3)。该方法用于播放飞碟的动作,根据给定的速度和方向使飞碟进行移动或执行其他动作。这个接口由CCActionManagerPhysisActionManager两个适配器类分别实现,分别描述运动学运动与物理运动的运动方式。

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

public interface IActionManager {
    void PlayDisk(GameObject disk, float speed, Vector3 direction);
}

ISSActionCallback.cs:

        定义了一个名为ISSActionCallback的接口,用于定义动作的回调函数。在这个接口中,定义了一个名为SSActionEvent的方法,它接受五个可选参数:source(类型为SSAction)、events(类型为SSActionEventType,默认值为SSActionEventType.Competed)、intParam(类型为int,默认值为0)、strParam(类型为string,默认值为null)和objectParam(类型为Object,默认值为null)。当飞碟飞行完成之后,就会调用这个接口里的SSActionEvent方法,完成对飞碟对象的销毁,由CCActionManagerPhysisActionManager两个适配器类继承:

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

public enum SSActionEventType : int { Started, Competed }
public interface ISSActionCallback {
    // 回调函数
    void SSActionEvent(SSAction source,
        SSActionEventType events = SSActionEventType.Competed,
        int intParam = 0,
        string strParam = null,
        Object objectParam = null);
}

CCActionManager.cs和PhysisActionManager.cs:

        CCActionManager类主要用于管理飞碟的运动学运动的动作,通过继承自SSActionManager类来管理动作的执行,并通过实现IActionManager接口来提供飞碟的播放方法。通过实现ISSActionCallback接口的SSActionEvent方法来处理动作事件的回调。实现了IActionManager接口中的PlayDisk方法,通过创建一个CCPlayDiskAction的实例来设置飞碟的空中动作,并通过调用父类的RunAction方法来执行该动作。实现了ISSActionCallback接口中的SSActionEvent方法,用作动作事件的回调函数。在这个方法中,当飞碟动作完成时,回收该飞碟对象。

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

public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager {
    CCPlayDiskAction PlayDiskAction; // 飞碟空中动作

    public void PlayDisk(GameObject disk, float speed, Vector3 direction) {
        PlayDiskAction = CCPlayDiskAction.GetSSAction(direction, speed);
        RunAction(disk, PlayDiskAction, this);
    }

    // 回调函数
    public void SSActionEvent(SSAction source,
    SSActionEventType events = SSActionEventType.Competed,
    int intParam = 0,
    string strParam = null,
    Object objectParam = null)
    {
        // 结束飞行后回收飞碟
        Singleton<RoundController>.Instance.FreeFactoryDisk(source.gameObject);
    }
}

        PhysisActionManager类主要用于管理飞碟的物理运动的动作,通过继承自SSActionManager类来管理动作的执行,并通过实现IActionManager接口来提供飞碟的播放方法。通过实现ISSActionCallback接口的SSActionEvent方法来处理动作事件的回调。实现了IActionManager接口中的PlayDisk方法,通过创建一个PhysisPlayDiskAction的实例来设置飞碟的空中动作,并通过调用父类的RunAction方法来执行该动作。实现了ISSActionCallback接口中的SSActionEvent方法,用作动作事件的回调函数。在这个方法中,当飞碟动作完成时,回收该飞碟对象。

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

public class PhysisActionManager : SSActionManager, ISSActionCallback, IActionManager {
    PhysisPlayDiskAction PlayDiskAction; // 飞碟空中动作

    public void PlayDisk(GameObject disk, float speed, Vector3 direction) {
        PlayDiskAction = PhysisPlayDiskAction.GetSSAction(direction, speed);
        RunAction(disk, PlayDiskAction, this);
    }

    // 回调函数
    public void SSActionEvent(SSAction source,
    SSActionEventType events = SSActionEventType.Competed,
    int intParam = 0,
    string strParam = null,
    Object objectParam = null)
    {
        // 结束飞行后回收飞碟
        Singleton<RoundController>.Instance.FreeFactoryDisk(source.gameObject);
    }
}

SSAction.cs和SSActionManager.cs:

在这个类中,定义了以下成员变量和属性:

  • enable(bool类型):表示动作是否可进行。
  • destroy(bool类型):表示动作是否已完成可被销毁。
  • gameObject(GameObject类型,只有get方法):表示动作附着的游戏对象。
  • transform(Transform类型,只有get方法):表示游戏对象的运动。
  • callback(ISSActionCallback类型,只有get和set方法):表示回调函数。

这个类还定义了两个虚拟方法:

  • Start():用于在动作开始时执行的逻辑。需要在子类中进行重写实现。
  • Update():用于在每一帧更新时执行的逻辑。同样需要在子类中进行重写实现。

SSAction类提供了一些基本的属性和方法,用于管理游戏对象的动作。其他类可以继承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 ISSActionCallback callback { get; set; } // 回调函数

    public virtual void Start() {} // Start()重写方法

    public virtual void Update() {} // Update()重写方法
}

在这个类中,定义了以下成员变量:

  • actions(Dictionary<int, SSAction>类型):用于存储当前正在进行的动作,以动作的实例ID作为键。
  • waitingAdd(List<SSAction>类型):用于存储即将开始的动作,待加入actions字典中。
  • waitingDelete(List<int>类型):用于存储已完成的动作,待从actions字典中删除。

这个类的Update方法在每一帧更新时执行以下逻辑:

  • 将waitingAdd列表中的动作加入到actions字典中。
  • 清空waitingAdd列表。
  • 遍历actions字典,对每个动作进行处理。如果动作标记为destroy,则将其实例ID添加到waitingDelete列表中;如果动作标记为enable,则调用其Update方法。
  • 遍历waitingDelete列表,将其中的动作从actions字典中移除,并销毁动作的实例。
  • 清空waitingDelete列表。

SSActionManager类还定义了一个RunAction方法,用于初始化动作并加入到等待加入队列中:

  • 设置动作的gameObject、transform和callback属性。
  • 将动作加入到waitingAdd列表中。
  • 调用动作的Start方法。

SSActionManager类用于管理游戏对象的动作,可以通过调用RunAction方法来添加和执行动作。它负责将动作加入到适当的队列中,并在适当的时机执行和删除动作。

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

CCPlayDiskAction.cs和PhysisPlayDiskAction.cs:

CCPlayDiskAction类重写了SSAction类中的Start和Update方法:

  • 在Start方法中,将飞碟的刚体组件设置为isKinematic,即不受物理引擎的影响。
  • 在Update方法中,根据时间的流逝,更新飞碟的位置和速度。
  • 在每一帧更新时,飞碟会向下以gravity的速度下落,并以direction和speed的速度水平移动。
  • 如果飞碟的位置低于-5,表示飞碟到达底部,设置destroy为true表示动作已完成,将enable设置为false停止动作的继续执行,并通过回调函数的SSActionEvent方法通知动作管理器。

CCPlayDiskAction类用于表示飞碟的飞行动作,它根据设定的速度和方向进行垂直和水平的移动,同时检测飞碟是否到达底部,并通知动作管理器处理动作完成的事件。

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

public class CCPlayDiskAction : SSAction {
    float gravity; // 垂直速度
    float speed; // 水平速度
    Vector3 direction;  // 方向
    float time; // 时间

    public static CCPlayDiskAction GetSSAction(Vector3 direction, float speed) {
        CCPlayDiskAction action = ScriptableObject.CreateInstance<CCPlayDiskAction>();
        action.gravity = 9.8f;
        action.time = 0;
        action.speed = speed;
        action.direction = direction;
        return action;
    }

    public override void Start() {
        gameObject.GetComponent<Rigidbody>().isKinematic = true;
    }

    public override void Update() {
        time += Time.deltaTime;
        transform.Translate(Vector3.down * gravity * time * Time.deltaTime);
        transform.Translate(direction * speed * Time.deltaTime);
        // 飞碟到达底部动作结束,回调
        if (this.transform.position.y < -5) {
            this.destroy = true;
            this.enable = false;
            this.callback.SSActionEvent(this);
        }
    }
}

PhysisPlayDiskAction类重写了SSAction类中的Start和Update方法:

  • 在Start方法中,将飞碟的刚体组件的isKinematic属性设置为false,即受物理引擎的影响。
  • 同时,将飞碟的刚体组件的velocity属性设置为speed * direction,即设置飞碟的水平初速度。
  • 这样飞碟就会受到物理引擎的力和碰撞等影响,进行真实的物理运动。

在Update方法中,只检测飞碟是否到达底部:

  • 如果飞碟的位置低于-5,表示飞碟到达底部,设置destroy为true表示动作已完成,将enable设置为false停止动作的继续执行,并通过回调函数的SSActionEvent方法通知动作管理器。

PhysisPlayDiskAction类用于表示飞碟的真实物理运动动作,它根据设定的速度和方向进行水平运动,并通过物理引擎模拟真实的物理效果。当飞碟到达底部时,通知动作管理器处理动作完成的事件。

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

public class PhysisPlayDiskAction : SSAction {
    float speed; // 水平速度
    Vector3 direction; // 飞行方向

    public static PhysisPlayDiskAction GetSSAction(Vector3 direction, float speed) {
        PhysisPlayDiskAction action = ScriptableObject.CreateInstance<PhysisPlayDiskAction>();
        action.speed = speed;
        action.direction = direction;
        return action;
    }

    public override void Start() {
        gameObject.GetComponent<Rigidbody>().isKinematic = false;
        // 水平初速度
        gameObject.GetComponent<Rigidbody>().velocity = speed * direction;
    }

    public override void Update() {
        // 飞碟到达底部动作结束,回调
        if (this.transform.position.y < -5) {
            this.destroy = true;
            this.enable = false;
            this.callback.SSActionEvent(this);
        }
    }
}

各类控制器与视图

MainController.cs:

        MainController类是游戏的核心控制器,负责管理游戏的状态、回合控制、用户界面的显示和用户交互的处理。它与RoundController和View进行交互,协调游戏逻辑和界面展示。

在Start方法中,进行了以下初始化:

  • 获取Director单例实例,并将MainController赋值给Director的mainController属性,以便其他对象可以通过Director访问MainController。
  • 创建RoundController和View的实例,并将其作为组件添加到游戏对象上。
  • 将游戏状态设置为准备状态。
  • 将游戏界面样式传递给View。

GetN方法返回N的值。

Restart方法用于重新开始游戏:

  • 调用view的Init方法,重新初始化游戏界面。
  • 调用roundController的Reset方法,重置回合控制器。

SetGameState方法用于设置游戏状态。

GetGameState方法返回游戏状态。

ShowPage方法根据游戏状态显示不同的页面:

  • 根据游戏状态的值,调用view的不同方法显示主页、游戏页面或重新开始页面。

SetRoundSum方法用于设置游戏的回合数,将其传递给roundController。

SetPlayDiskModeToPhysis方法用于设置飞碟的物理模式,将其传递给roundController。

SetViewTip、SetViewScore、SetViewRoundNum和SetViewTrialNum方法用于设置游戏视图中的提示、得分、回合数和尝试次数。

Hit方法用于处理点击事件:

  • 通过主摄像机将点击位置转换为射线。
  • 使用射线检测,获取射线碰撞到的所有对象。
  • 遍历碰撞到的对象,如果是飞碟对象,将其移出屏幕,并记录飞碟的得分。
  • 更新游戏视图中的当前得分。

FreeAllFactoryDisk方法用于释放所有工厂创建的飞碟,将其传递给roundController。

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

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

public class MainController : MonoBehaviour {
    private RoundController roundController; // 回合控制器
    private View view; // 游戏视图
    private int N = 2; // 默认游戏回合
    private int gameState;
    public GUISkin gameSkin;

    void Start() {
        Director.GetInstance().mainController = this;
        roundController = gameObject.AddComponent<RoundController>();
        view = gameObject.AddComponent<View>();
        gameState = (int)GameState.Ready;
        view.gameSkin = gameSkin;
    }

    public int GetN() {
        return N;
    }

    public void Restart() {
        view.Init();
        roundController.Reset();
    }

    public void SetGameState(int state) {
        gameState = state;
    }

    public int GetGameState() {
        return gameState;
    }

    public void ShowPage() {
        switch(gameState) {
            case 0:
                view.ShowHomePage();
                break;
            case 1:
                view.ShowGamePage();
                break;
            case 2:
                view.ShowRestart();
                break;
        }
    }

    public void SetRoundSum(int roundSum) {
        roundController.SetRoundSum(roundSum);
    }

    public void SetPlayDiskModeToPhysis(bool isPhysis) {
        roundController.SetPlayDiskModeToPhysis(isPhysis);
    }

    public void SetViewTip(string tip) {
        view.SetTip(tip);
    }

    public void SetViewScore(int score) {
        view.SetScore(score);
    }

    public void SetViewRoundNum(int round) {
        view.SetRoundNum(round);
    }

    public void SetViewTrialNum(int trial) {
        view.SetTrialNum(trial);
    }

    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.GetComponent<DiskData>() != null) {
                // 把击中的飞碟移出屏幕,触发回调释放
                hit.collider.gameObject.transform.position = new Vector3(0, -6, 0);
                // 记录飞碟得分
                roundController.Record(hit.collider.gameObject.GetComponent<DiskData>());
                // 显示当前得分
                view.SetScore(roundController.GetScores());
            }
        }
    }

    // 释放所有工厂飞碟
    public void FreeAllFactoryDisk() {
        roundController.FreeAllFactoryDisk();
    }
}

Roundcontroller.cs:

RoundController类负责控制飞碟的生成和发射并控制其运动逻辑,在游戏运行时根据设定的规则发射飞碟,并处理游戏状态的更新和提示信息的显示。

在Start方法中,进行了以下初始化:

  • 将飞碟的运动类型默认设置为运动学运动。
  • 创建记分器和主控制器的实例。
  • 创建飞碟工厂的实例。
  • 调用InitRuler方法初始化飞碟获取规则。

InitRuler方法用于初始化飞碟获取规则,将trialNum、roundNum、sendTime和roundDisksNum等属性重置为默认值。

generateRoundDisksNum方法用于生成每个trial同时发出的飞碟数量的数组,每个trial中同时发出的飞碟数量不超过4。

Reset方法用于重置飞碟获取规则和记分器。

Record方法用于记录飞碟的得分。

GetScores方法用于获取当前的得分。

SetRoundSum方法用于设置游戏的回合数。

SetPlayDiskModeToPhysis方法用于设置游戏模式,支持物理运动模式和动力学运动模式。

LaunchDisk方法用于发射飞碟,根据飞碟获取规则生成飞碟,并设置飞碟的飞行动作。

FreeFactoryDisk方法用于释放工厂中的飞碟。

FreeAllFactoryDisk方法用于释放所有工厂中的飞碟。

Update方法用于更新游戏状态,根据当前游戏状态判断是否发射飞碟,当达到设定的回合数或游戏结束时,提示重新开始游戏,并更新回合数和trial数目的显示。

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

// 飞碟获取规则
public struct Ruler {
    public int trialNum; // 当前trial的编号
    public int roundNum; // 当前round的编号
    public int roundSum; // 一共round的总数目
    public int [] roundDisksNum; // 每一轮对于trial的飞碟数量
    public float sendTime; // 发射间隔时间

    public int size; // 飞碟大小
    public int speed; // 飞碟速度
    public string color; // 飞碟颜色
    public Vector3 direction; // 飞碟飞入方向
    public Vector3 beginPos; // 飞碟飞入位置
};

public class RoundController : MonoBehaviour {
    private IActionManager actionManager; // 选择飞碟的运动类型
    private ScoreRecorder scoreRecorder; // 记分器
    private MainController mainController;
    private Ruler ruler; // 飞碟获取规则

    void Start() {
        // 一开始飞碟的运动类型默认为运动学运动
        actionManager = gameObject.AddComponent<CCActionManager>();
        gameObject.AddComponent<PhysisActionManager>();
        scoreRecorder = new ScoreRecorder();
        mainController = Director.GetInstance().mainController;
        gameObject.AddComponent<DiskFactory>();
        InitRuler();
    }

    void InitRuler() {
        ruler.trialNum = 0;
        ruler.roundNum = 0;
        ruler.sendTime = 0;
        ruler.roundDisksNum = new int [10];
        generateRoundDisksNum();
    }

    // 生成每trial同时发出的飞碟数量的数组,同时发出飞碟个数不超过4
    public void generateRoundDisksNum() {
        for (int i = 0; i < 10; ++i) {
            ruler.roundDisksNum[i] = Random.Range(0, 4) + 1;
        }
    }

    public void Reset() {
        InitRuler();
        scoreRecorder.Reset();
    }

    public void Record(DiskData disk) {
        scoreRecorder.Record(disk);
    }

    public int GetScores() {
        return scoreRecorder.score;
    }

    public void SetRoundSum(int roundSum) {
        ruler.roundSum = roundSum;
    }

    // 设置游戏模式,同时支持物理运动模式和动力学运动模式
    public void SetPlayDiskModeToPhysis(bool isPhysis) {
        if (isPhysis) {
            actionManager = Singleton<PhysisActionManager>.Instance as IActionManager;
        }
        else {
            actionManager = Singleton<CCActionManager>.Instance as IActionManager;
        }
    }

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

        for (int i = 0; i < ruler.roundDisksNum[ruler.trialNum]; ++i) {
            // 获取随机数
            int randomNum = Random.Range(0, 3) + 1;
            // 飞碟速度随回合数增加而变快,这样难度增加
            ruler.speed = randomNum * (ruler.roundNum + 4);

            // 重新选取随机数,并根据随机数选择飞碟颜色
            randomNum = Random.Range(0, 3) + 1;
            if (randomNum == 1) {
                ruler.color = "red";
            }
            else if (randomNum == 2) {
                ruler.color = "green";
            }
            else {
                ruler.color = "blue";
            }

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

            // 重新选取随机数,并根据随机数选择飞碟飞入的方向
            randomNum = Random.Range(0, 2);
            if (randomNum == 1) {
                ruler.direction = new Vector3(3, 1, 0);
            }
            else {
                ruler.direction = new Vector3(-3, 1, 0);
            }

            // 重新选取随机数,并使不同飞碟的飞入位置尽可能分开
            do {
                randomNum = Random.Range(0, 4);
            } while (beginPosY[randomNum] != 0);
            beginPosY[randomNum] = 1;
            ruler.beginPos = new Vector3(-ruler.direction.x * 4, -0.5f * randomNum, 0);

            // 根据ruler从工厂中生成一个飞碟
            GameObject disk = Singleton<DiskFactory>.Instance.GetDisk(ruler);
        
            // 设置飞碟的飞行动作
            actionManager.PlayDisk(disk, ruler.speed, ruler.direction);
        }
    }

    // 释放工厂飞碟
    public void FreeFactoryDisk(GameObject disk) {
        Singleton<DiskFactory>.Instance.FreeDisk(disk);
    }

    // 释放所有工厂飞碟
    public void FreeAllFactoryDisk() {
        GameObject[] obj = FindObjectsOfType(typeof(GameObject)) as GameObject[];
        foreach (GameObject g in obj) {
            if (g.gameObject.name == "Disk(Clone)(Clone)") {
                Singleton<DiskFactory>.Instance.FreeDisk(g);
            }
        }
    }

    void Update() {
        if (mainController.GetGameState() == (int)GameState.Playing) {
            ruler.sendTime += Time.deltaTime;
            // 每隔2s发送一次飞碟(trial)
            if (ruler.sendTime > 2) {
                ruler.sendTime = 0;
                // 如果为无限回合或还未到设定回合数
                if (ruler.roundSum == -1 || ruler.roundNum < ruler.roundSum) {
                    // 发射飞碟,次数trial增加
                    mainController.SetViewTip("");
                    LaunchDisk();
                    ruler.trialNum++;
                    // 当次数trial等于10时,说明一个回合已经结束,回合加一,重新生成飞碟数组
                    if (ruler.trialNum == 10) {
                        ruler.trialNum = 0;
                        ruler.roundNum++;
                        generateRoundDisksNum();
                    }
                }
                // 否则游戏结束,提示重新进行游戏
                else {
                    mainController.SetViewTip("Click Restart and Play Again!");
                    mainController.SetGameState((int)GameState.GameOver);
                }
                // 设置回合数和trial数目的提示
                if (ruler.trialNum == 0) mainController.SetViewRoundNum(ruler.roundNum);
                else mainController.SetViewRoundNum(ruler.roundNum + 1);
                mainController.SetViewTrialNum(ruler.trialNum);
            }
        }
    }
}

ScoreRecorder.cs:

ScoreRecorder类负责记录游戏的得分,根据飞碟的大小、速度和颜色计算得分,并提供重置得分的功能。

在构造函数ScoreRecorder中,将得分score初始化为0。

Record方法用于根据点击中的飞碟的大小、速度和颜色计算得分。根据飞碟的大小,分数计算规则如下:大小为1的飞碟得3分,大小为2的飞碟得2分,大小为3的飞碟得1分。然后根据飞碟的速度,将速度加到得分上。最后根据飞碟的颜色,分数计算规则如下:红色的飞碟得1分,黄色的飞碟得2分,蓝色的飞碟得3分。

Reset方法用于重置得分,将score设为0,重新开始计分。

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

public class ScoreRecorder {
    public int score; // 游戏分数

    public ScoreRecorder() {
        score = 0;
    }

    /* 记录分数,根据点击中的飞碟的大小,速度,颜色计算得分 */
    public void Record(DiskData disk) {
        // 飞碟越小分就越高,大小为1得3分,大小为2得2分,大小为3得1分
        int diskSize = disk.size;
        switch (diskSize) {
            case 1:
                score += 3;
                break;
            case 2:
                score += 2;
                break;
            case 3:
                score += 1;
                break;
            default: break;
        }

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

        // 颜色为红色得1分,颜色为黄色得2分,颜色为蓝色得3分
        string diskColor = disk.color;
        if (diskColor.CompareTo("red") == 0) {
            score += 1;
        }
        else if (diskColor.CompareTo("green") == 0) {
            score += 2;
        }
        else if (diskColor.CompareTo("blue") == 0) {
            score += 3;
        }
    }

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

View.cs:

游戏的视图控制器View类负责游戏界面的显示和用户界面的交互,它通过与主控制器进行交互来更新视图和处理用户输入。

在Start方法中,进行了以下初始化:

  • 将得分、提示信息、回合数和尝试次数初始化为默认值。
  • 获取Director单例实例,并将MainController赋值给mainController属性,以便与主控制器进行交互。

SetTip、SetScore、SetRoundNum和SetTrialNum方法用于设置提示信息、得分、回合数和尝试次数。

Init方法用于初始化视图,将得分、提示信息、回合数和尝试次数重置为默认值。

AddTitle方法用于添加游戏标题的显示。

AddChooseModeButton方法用于添加选择模式的按钮,根据选择的模式设置回合数,并触发游戏重新开始的操作。

ShowHomePage方法用于显示主页,调用AddChooseModeButton方法展示模式选择按钮。

AddActionModeButton方法用于添加操作模式的按钮,根据选择的模式设置飞碟的运动学模式或物理模式。

AddBackButton方法用于添加返回按钮,触发返回主页的操作。

AddGameLabel方法用于添加游戏标签的显示,包括得分、提示信息、回合数和尝试次数。

AddRestartButton方法用于添加重新开始按钮,触发游戏重新开始的操作。

ShowGamePage方法用于显示游戏页面,调用AddGameLabel、AddBackButton和AddActionModeButton方法展示游戏界面的各个元素,并监听鼠标点击事件。

ShowRestart方法用于显示重新开始页面,调用ShowGamePage方法展示游戏页面,并调用AddRestartButton方法添加重新开始按钮。

OnGUI方法用于处理GUI事件,在其中调用AddTitle方法添加游戏标题,并调用mainController的ShowPage方法根据游戏状态显示不同的页面。

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

public class View : MonoBehaviour {
    private MainController mainController;
    private int score;
    private string tip;
    private string roundNum;
    private string trialNum;
    public GUISkin gameSkin;  // 游戏控件的皮肤风格

    void Start() {
        score = 0;
        tip = "";
        roundNum = "";
        trialNum = "";
        mainController = Director.GetInstance().mainController;
    }

    public void SetTip(string tip) {
        this.tip = tip;
    }

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

    public void SetRoundNum(int round) {
        roundNum = "回合: " + round;
    }

    public void SetTrialNum(int trial) {
        if (trial == 0) trial = 10;
        trialNum = "Trial: " + trial;
    }

    public void Init() {
        score = 0;
        tip = "";
        roundNum = "";
        trialNum = "";
    }

    public void AddTitle() {
        GUIStyle titleStyle = new GUIStyle();
        titleStyle.normal.textColor = Color.black;
        titleStyle.fontSize = 50;

        GUI.Label(new Rect(Screen.width / 2 - 80, 20, 60, 100), "Hit UFO", titleStyle);
    }

    public void AddChooseModeButton() {
        GUI.skin = gameSkin;
        if (GUI.Button(new Rect(280, 100, 160, 80), "普通模式\n(默认为" + mainController.GetN() + "回合)")) {
            mainController.SetRoundSum(mainController.GetN());
            mainController.Restart();
            mainController.SetGameState((int)GameState.Playing);
        }
        if (GUI.Button(new Rect(280, 210, 160, 80), "无尽模式\n(回合数无限)")) {
            mainController.SetRoundSum(-1);
            mainController.Restart();
            mainController.SetGameState((int)GameState.Playing);
        }
    }

    public void ShowHomePage() {
        AddChooseModeButton();
    }

    public void AddActionModeButton() {
        GUI.skin = gameSkin;
        if (GUI.Button(new Rect(10, Screen.height - 100, 110, 40), "运动学模式")) {
            mainController.FreeAllFactoryDisk();
            mainController.SetPlayDiskModeToPhysis(false);
        }
        if (GUI.Button(new Rect(10, Screen.height - 50, 110, 40), "物理模式")) {
            mainController.FreeAllFactoryDisk();
            mainController.SetPlayDiskModeToPhysis(true);
        }
    }

    public void AddBackButton() {
        GUI.skin = gameSkin;
        if (GUI.Button(new Rect(10, 10, 60, 40), "Back")) {
            mainController.FreeAllFactoryDisk();
            mainController.Restart();
            mainController.SetGameState((int)GameState.Ready);
        }
    }

    public void AddGameLabel() {
        GUIStyle labelStyle = new GUIStyle();
        labelStyle.normal.textColor = Color.black;
        labelStyle.fontSize = 30;

        GUI.Label(new Rect(570, 10, 100, 50), "得分: " + score, labelStyle);
        GUI.Label(new Rect(170, 80, 50, 200), tip, labelStyle);
        GUI.Label(new Rect(570, 60, 100, 50), roundNum, labelStyle);
        GUI.Label(new Rect(570, 110, 100, 50), trialNum, labelStyle);
    }

    public void AddRestartButton() {
        if (GUI.Button(new Rect(300, 150, 100, 60), "Restart")) {
            mainController.FreeAllFactoryDisk();
            mainController.Restart();
            mainController.SetGameState((int)GameState.Playing);
        }
    }

    public void ShowGamePage() {
        AddGameLabel();
        AddBackButton();
        AddActionModeButton();
        if (Input.GetButtonDown("Fire1")) {
            mainController.Hit(Input.mousePosition);
        }
    }

    public void ShowRestart() {
        ShowGamePage();
        AddRestartButton();
    }

    void OnGUI() {
        AddTitle();
        mainController.ShowPage();
    }
}

 特别感谢博客http://t.csdnimg.cn/npy2j

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

We3le7

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值