用Unity3D实现打飞碟游戏
项目地址
打飞碟游戏
完成效果图
类图
要求
游戏规则
- 游戏有 n 个 round,每个 round 都包括10 次 trial。
- 每个 trial 的飞碟的色彩、大小、发射位置、速度、角度、同时出现的个数都可能不同。它们由该 round 的 ruler 控制。
- 每个 trial 的飞碟有随机性,总体难度随 round 上升。
- 鼠标点中得分,得分规则按色彩、大小、速度不同计算,规则可自由设定。
实现要求
- 使用带缓存的工厂模式管理不同飞碟的生产与回收,该工厂必须是场景单实例的!具体实现见参考资源 Singleton 模板类。
- 尽可能使用前面 MVC 结构实现人机交互与游戏模型分离。
- 按adapter模式设计图修改飞碟游戏,使它同时支持物理运动与运动学(变换)运动。
实现心得
首先要设计出计分规则,比如:
加分项 | 加分 |
---|
飞碟为红色 | 加1分 | 飞碟为绿色 | 加2分 | 飞碟为蓝色 | 加3分 | 飞碟的size为1(小飞碟) | 加3分 | 飞碟的size为2(中飞碟) | 加2分 | 飞碟的size为3(大飞碟) | 加1分 | 飞碟的速度为x | 加x分 |
然后要设计单实例类:
因为要求场景单实例,所以可以导入一个Singleton 单实例模板类:
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;
}
}
}
或者使用导演类,使主控制器实现唯一:
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:
飞碟工厂类负责飞碟对象的创建和销毁,其在场景中是单实例的,且使用了对象池,实现了缓存功能,当一个飞碟对象被创建时,会首先在对象池中寻找没有被使用的空闲飞碟对象,有的话就根据规则设置飞碟对象相应属性后直接使用,没有再创建,这样就省下了一部分开销,销毁则直接将飞碟对象加入对象池的空闲部分,下次要使用时直接拿出即可,实现如下:
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;
}
}
}
}
disk:GameObject 是飞碟的预制,需要提前制作好,并加上刚体组件Rigidbody ,将Use Gravity 项勾选上:
DiskData.cs:
DiskData 是飞碟的模型,包含了飞碟相应的数据:
public class DiskData : MonoBehaviour {
public int size; // 大小
public string color; // 颜色
public int speed; // 发射速度
}
这样就使用工厂方法 + 单实例 + 对象池完成了飞碟的创建和销毁,实现要求。
ScoreRecorder.cs:
记分类根据记分规则,对被点击中的飞碟得分进行记录,实现如下:
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;
}
}
RoundController.cs:
回合控制器控制飞碟对象的运动类型为运动学运动或物理运动,并根据一定规则请求飞碟工厂生成相应的飞碟对象,控制游戏飞碟对象的发射和轮次的进行,实现如下:
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);
}
}
}
}
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 ,由CCActionManager 和PhysisActionManager 两个适配器类分别实现,描述飞碟的不同运动过程,实现如下:
public interface IActionManager {
void PlayDisk(GameObject disk, float speed, Vector3 direction);
}
ISSActionCallback.cs:
ISSActionCallback 是飞碟运动完成后的回调接口,当飞碟飞行完成之后,就会调用这个接口里的SSActionEvent 方法,完成对飞碟对象的销毁,由CCActionManager 和PhysisActionManager 两个适配器类继承:
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:
这两个类是运动学运动动作和物理运动动作的管理类,分别实现了继承的IActionManager 接口中的PlayDisk 方法,也实现了继承的ISSActionCallback 这个接口中的回调方法SSActionEvent ,实现如下:
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);
}
}
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:
SSAction 是动作基类,SSActionManager 是动作管理者的基类,其实现分别为:
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()重写方法
}
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:
这两个类是运动学运动动作和物理运动动作的类,分别实现了继承的SSAction 接口中的Start 和Update 方法,实现如下:
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);
}
}
}
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 监测游戏事件发生之后,通知主控制器MainController ,然后主控制器控制游戏视图View 类显示相应内容,通过设置主控制类中的成员变量N,可以调整普通模式的默认回合,代码如下:
/* 游戏状态,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();
}
}
View.cs:
视图类经过主控制器的控制,显示相应内容:
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();
}
}
核心算法
在RoundController 的LaunchDisk 方法中,飞碟以随机方式进行发射,因为要求随着回合往后难度增大,所以这里设置了飞碟的速度随回合的增加而增大,表现难度增加:
// 发射飞碟
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);
}
}
在RoundController 的FreeAllFactoryDisk 方法中,因为运动模式变化时飞碟的运动属性可能会不一样,点击返回按钮或重新开始按钮时也有可能有飞碟没有被销毁,影响下一次进行游戏,所以需要在点击这些按钮时,直接将所有飞碟都销毁,再去进行游戏:
// 释放所有工厂飞碟
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);
}
}
}
游戏截图:
游戏界面:
普通模式飞碟按运动学模式飞行,不会碰在一起:
普通模式飞碟按物理模式飞行,有可能相撞:
普通模式按照设置的N回合,这里设为2,游戏结束:
无尽模式,没有回合限制,可以一直进行下去:
|