其实这次的作业结构和牧师过河的差不多,Director和动作管理器部分都基本上结构都是差不多的,只需要修改用到不同的函数就行了,类与类之间的关系没有发生变化。就是添加了计分的类scoreRecorder和管理飞碟的类DiskFactory
UML图
我做的这个游戏一共有4关,每一关的飞碟种类会增加,然后间隔飞出飞碟的时间也会缩短,飞碟的数目也会增加,即每一关的难度都会增加,而且最后一关出现的紫色飞碟打到是要扣分的。
一.以下是场记的接口类ISceneControl, 和用户动作的接口类IUserAction,还有导演Director类,基本和牧师过河的没什么区别,就是把在IUserAction 接口的函数改成现在UserGUI要用的函数就行。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Disk.action;
using Disk.factory;
namespace Disk.controller
{
public interface ISceneControl
{
void LoadResources();
}
public interface IUserAction
{
GameState get_state();
void set_state(GameState gs);
int get_score();
void click(Vector3 pos);
int get_round();
void reset_score();
}
public class Director : System.Object
{
public ISceneControl currentSceneController { get; set; }
private static Director director;
private Director()
{
}
public static Director getInstance()
{
if (director == null)
{
director = new Director();
}
return director;
}
}
二.以下则是UserGUI类
action就是记录不同场记的变量,就是等于Director类的currentSceneControl,由于现在这个游戏只有一个场记,所以就是指FirstController了, 然后就是调用OnGUI函数渲染出我们想要的界面,Input.GetButtonDown("Fire1))就是用来判断是不是点击了左键,Fire1是可以在ProjectSetting的Input里面改的,点击了就调用action的click函数。然后就是根据action的游戏状态分别渲染不同的页面。
using Disk.action;
using Disk.controller;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class UserGUI : MonoBehaviour
{
private IUserAction action;
bool start;
// Use this for initialization
void Start()
{
action = Director.getInstance().currentSceneController as IUserAction;
start = true;
}
private void OnGUI()
{
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.click(pos);
}
GUI.color = Color.red;
GUI.Label(new Rect(2 * Screen.width / 3, 0, 400, 400), "score: " + action.get_score().ToString());
if (action.get_state() == GameState.OVER)
{
GUI.color = Color.red;
GUI.Label(new Rect(Screen.width / 2 - 100, Screen.height / 2 - 200, 400, 400), "GAMEOVER" + " your score is " + action.get_score().ToString());
if (GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Restart"))
{
action.reset_score();
action.set_state(GameState.ROUND_START);
}
}
else
{
GUI.color = Color.red;
GUI.Label(new Rect(3 * Screen.width / 4 , 0, 400, 400), "round: " + (action.get_round() + 1).ToString());
}
if (start && GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Start"))
{
start = false;
action.set_state(GameState.ROUND_START);
}
if (!start && action.get_state() == GameState.ROUND_FINISH && GUI.Button(new Rect(Screen.width / 2 - 45, Screen.height / 2 - 45, 90, 90), "Next Round"))
{
action.set_state(GameState.ROUND_START);
}
}
}
三。以下就是FirstSceneControl,就是FirstController,由于是一个场记,所以就要继承场记的接口类还有UserAction接口类,在Update函数中根据不同的游戏状态,调整current_round来改变正在的回合,rount_time来改变飞碟飞出的间隔时间(调用show_disk)函数,还有改变disk_number来改变没回合的飞碟数,达到改变回合难度的目的。而飞碟是存在diskQueue队列中的,在进入下一个回合时我们就要把当前回合发给飞碟工厂类,来获取足够的飞碟,然后才能扔出飞碟。其余的就是实现接口类中的函数以供UserGUI调用
public class FirstSceneControl : MonoBehaviour, ISceneControl, IUserAction
{
public CCActionManager ac_manager { get; set; }
public ScoreRecorder score_recorder { get; set; }
public Queue disks = new Queue();
private int disk_number;
private int current_round = -1;
public int round = 4;
private float time = 0; //计时
private float roundtime = 0; //每个回合隔多久飞一次飞碟
private GameState gameState = GameState.START;
UserGUI user_gui;
void Awake()
{
Director director = Director.getInstance();
ac_manager = gameObject.AddComponent() as CCActionManager;
user_gui = gameObject.AddComponent() as UserGUI;
director.currentSceneController = this;
disk_number = 5;
this.gameObject.AddComponent();
this.gameObject.AddComponent();
score_recorder = Singleton.Instance;
director.currentSceneController.LoadResources();
}
private void Update()
{
if (gameState == GameState.ROUND_FINISH && current_round == round)
{
gameState = GameState.OVER;
current_round = -1;
}
else
{
if (ac_manager.disk_number == 0 && gameState == GameState.RUNNING && score_recorder.score < (current_round + 1)* (current_round + 1) * 4)
{
gameState = GameState.OVER;
current_round = -1;
}
if (ac_manager.disk_number == 0 && gameState == GameState.RUNNING)
{
gameState = GameState.ROUND_FINISH;
}
if (ac_manager.disk_number == 0 && gameState == GameState.ROUND_START)
{
current_round++;
ac_manager.disk_number = (current_round + 1) * disk_number;
roundtime = 1 - (current_round + 1) * 0.15F; //每一关时间减少0.15秒
nextRound();
gameState = GameState.RUNNING;
}
if (time > roundtime)
{
show_disk();
time = 0;
}
else
{
time += Time.deltaTime;
}
}
}
private void nextRound()
{
DiskFactory df = Singleton.Instance;
for (int i = 0; i < ac_manager.disk_number; i++)
{
disks.Enqueue(df.GetDisk(current_round));
}
ac_manager.loadAction(disks);
}
void show_disk()
{
if (disks.Count != 0)
{
GameObject disk = disks.Dequeue();
disk.SetActive(true);
}
}
public void LoadResources()
{
GameObject greensward = GameObject.Instantiate(Resources.Load("Prefabs/bg"));
}
public int get_score()
{
return score_recorder.score;
}
public void reset_score()
{
score_recorder.Reset();
}
public GameState get_state()
{
return gameState;
}
public void set_state(GameState state)
{
gameState = state;
}
public int get_round()
{
return current_round;
}
public void click(Vector3 pos)
{
Ray ray = Camera.main.ScreenPointToRay(pos);
RaycastHit[] clicks;
clicks = Physics.RaycastAll(ray);
for (int i = 0; i < clicks.Length; i++)
{
RaycastHit hit = clicks[i];
if (hit.collider.gameObject.GetComponent() != null) //撞击的是飞碟
{
score_recorder.add_score(hit.collider.gameObject);
hit.collider.gameObject.transform.position = new Vector3(0, -10, 0);
}
}
}
}
四。以下就是飞碟工厂类,由于游戏对象的创建和删除要花费较多的时间,所以要创建这个类来对飞碟进行管理,不能用完就删,要用又创建,把飞碟存在used和free中以供调用,一开始先创建一个原始的飞碟来给后面进行克隆(注意克隆和prefab创建的区别),大体的逻辑在老师的PPT上都有,我们需要改的就是怎么得到不同的飞碟,首先是先看看有没有空余的飞碟,然后根据不同的回合,然后每个回合不同颜色飞碟的概率不同,根据Random的数字落在哪个区间来进行判断得到color_index,再把飞碟的属性改一改就可以。
public class DiskData : MonoBehaviour
{
public Color color;
public float speed;
public Vector3 direction;
}
public class DiskFactory : MonoBehaviour
{
public GameObject diskPrefab;
private List used = new List();
private List free = new List();
private void Awake()
{
diskPrefab = GameObject.Instantiate(Resources.Load("Prefabs/disk"), Vector3.zero, Quaternion.identity);
diskPrefab.SetActive(false);
}
public GameObject GetDisk(int current_round)
{
GameObject newDisk = null;
if (free.Count > 0)
{
newDisk = free[0].gameObject;
free.Remove(free[0]);
}
else
{
newDisk = GameObject.Instantiate(diskPrefab, Vector3.zero, Quaternion.identity);
newDisk.AddComponent();
}
int color_index = 0;
if(current_round == 0)
{
color_index = 0;
}
else if (current_round == 1)
{
float red_rate = 0.7F;
int random = Random.Range(0, 10);
if (random < red_rate * 10)
color_index = 1;
else
color_index = 0;
}
else if (current_round == 2)
{
float black_rate = 0.5F;
float red_rate = 0.3F;
int random = Random.Range(0, 10);
if (random < black_rate * 10)
color_index = 2;
else if (random - black_rate * 10 < red_rate * 10)
color_index = 1;
else
color_index = 0;
}
else if(current_round == 3)
{
float black_rate = 0.5F;
float red_rate = 0.2F;
float purple_rate = 0.1F;
int random = Random.Range(0, 10);
if (random < black_rate * 10)
color_index = 2;
else if (random - black_rate * 10 < red_rate * 10)
color_index = 1;
else if (random - black_rate * 10 - red_rate * 10 < purple_rate * 10)
color_index = 3;
else
color_index = 0;
}
switch (color_index)
{
case 0:
{
newDisk.GetComponent().color = Color.yellow;
newDisk.GetComponent().speed = 4.0F;
float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
newDisk.GetComponent().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent().material.color = Color.yellow;
break;
}
case 1:
{
newDisk.GetComponent().color = Color.red;
newDisk.GetComponent().speed = 6.0F;
float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
newDisk.GetComponent().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent().material.color = Color.red;
break;
}
case 2:
{
newDisk.GetComponent().color = Color.black;
newDisk.GetComponent().speed = 8.0F;
float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
newDisk.GetComponent().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent().material.color = Color.black;
break;
}
case 3:
{
newDisk.GetComponent().color = Color.magenta;
newDisk.GetComponent().speed = 6.0F;
float RanX = UnityEngine.Random.Range(-1F, 1F) < 0 ? -1 : 1;
newDisk.GetComponent().direction = new Vector3(RanX, 1, 0);
newDisk.GetComponent().material.color = Color.magenta;
break;
}
}
used.Add(newDisk.GetComponent());
newDisk.name = newDisk.GetInstanceID().ToString();
return newDisk;
}
public void FreeDisk(GameObject disk)
{
DiskData target = null;
foreach (DiskData i in used)
{
if (disk.GetInstanceID() == i.gameObject.GetInstanceID())
{
target = i;
}
}
if (target != null)
{
target.gameObject.SetActive(false);
free.Add(target);
used.Remove(target);
}
}
}
五。以下是计分类
就把击中飞碟的分数属性加上就可以了
public class ScoreRecorder : MonoBehaviour
{
public int score;
// Use this for initialization
void Start()
{
score = 0;
}
public void add_score(GameObject disk)
{
score += disk.GetComponent().score;
}
public void Reset()
{
score = 0;
}
}
五。动作管理器的抽象类,SSACTION,SSACTIONMANAGER,和CALLBACK类,一般的动作都是继承SSAction类,然后Manager都是继承SSManager类,就是CallBack的类比较繁琐,就是用于具体的MANAGER类来管理发生不同事件的动作,SSAction中的callback就是这个类的对象,当其完成的时候就调用SSActionEvent来告诉它的manager,所以在runaction的时候就要设置好manager是谁。
public class SSAction : ScriptableObject
{
public bool enable = false;
public bool destroy = false;
public GameObject gameobject { get; set; }
public Transform transform { get; set; }
public ISSActionCallback callback { get; set; }
protected SSAction() { }
public virtual void Start()
{
throw new System.NotImplementedException();
}
// Update is called once per frame
public virtual void Update()
{
throw new System.NotImplementedException();
}
public void reset()
{
enable = false;
destroy = false;
gameobject = null;
transform = null;
callback = null;
}
}
public enum SSActionEventType : int { Started, Competeted }
public interface ISSActionCallback
{
void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
Object objectParam = null);
}
public enum GameState { ROUND_START, ROUND_FINISH, RUNNING, START, OVER }
public class SSActionManager : MonoBehaviour
{
private Dictionary actions = new Dictionary();
private List waitingAdd = new List();
private List waitingDelete = new List();
// Use this for initialization
protected void Start()
{
}
// Update is called once per frame
protected void Update()
{
foreach (SSAction ac in waitingAdd) actions[ac.GetInstanceID()] = ac;
waitingAdd.Clear();
foreach (KeyValuePair 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);
DestroyObject(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();
}
}
六。具体的Manager类和Action类
Fly的运动分为两个动作垂直的减速和一个斜方向的匀速运动。通过获取飞碟上的分数属性进行判断赋予不同的速度和方向,还有位置信息
public class CCFlyAction : SSAction
{
float g;
float begin_speed;
Vector3 direction; //斜方向的运动方向,只需要是一个单位向量就可以了
float time;
public override void Start()
{
enable = true;
g = 9.8F;
time = 0;
int index = System.Math.Abs(gameobject.GetComponent().score);
float random_y = UnityEngine.Random.Range(-2F, 5F);
begin_speed = index * 1.5F + 2;
direction = new Vector3(-1, 1, 0);
Vector3 position = new Vector3(4, random_y, 0);
this.gameobject.transform.position = position;
}
// Update is called once per frame
public override void Update()
{
if (gameobject.activeSelf)
{
time += Time.deltaTime;
transform.Translate(Vector3.down * g * time * Time.deltaTime); //重力的减速
transform.Translate(direction * begin_speed * Time.deltaTime); //初速度,包括竖直和水平的运动
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
}
具体的Manager类都是继承callback类的,他的SSActionEvent函数就用来处理动作完成的结果。Loaction函数是用来给每个飞碟配置Fly的动作的,但是由于飞碟的active是false,所以一开始并不会飞,要等到active为true才会开始运动
public class CCActionManager : SSActionManager, ISSActionCallback
{
public FirstSceneControl sceneController;
public int disk_number = 0;
protected new void Start()
{
sceneController = (FirstSceneControl)Director.getInstance().currentSceneController;
sceneController.ac_manager = this;
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
UnityEngine.Object objectParam = null)
{
if (source is CCFlyAction)
{
disk_number--;
DiskFactory df = Singleton.Instance;
df.FreeDisk(source.gameobject);
}
}
public void loadAction(Queue diskQueue)
{
foreach (GameObject disk in diskQueue)
{
RunAction(disk, ScriptableObject.CreateInstance(), (ISSActionCallback)this);
}
}
}
七。单例实现类,是一个模板,可以直接套用
public class Singleton : 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;
}
}
}