改進飛碟(Hit UFO)遊戲:
遊戲內容要求:
按 adapter模式 設計圖修改飛碟遊戲
使它同時支持物理運動與運動學(變換)運動
Adapter設計模式
定義:將某個類的接口轉換成客戶端期望的另一個接口表示。使得原本由於接口不兼容而不能一起工作的那些類能在一起工作。
適用的情況:接口與接口之間繼承存在矛盾。
作用:使用抽象類分離了接口與接口實現;抽象類分攤接口中的方法;使得接口可以隨意的選擇接口中的方法來實現。
題目要求我們使飛碟同時支持物理運動與運動學(變換)運動,在之前的作業中我們已經完成了運動學運動模型的設計(CCActionManager),現在要加入一個物理運動的管理類(PhysisActionManager),但原本的接口並不兼容這兩種運動模式,所以就要使用一個適配器來使得兩個接口都能與整個系統融合在一起。
這裏的適配器類是IActionManager,提供了一個StartThrow()函數供兩個運動模式類實現,並在FirstController中調用。
public interface IActionManager
{
void StartThrow(GameObject disk);
}
此外,還需要添加一個枚舉函數ActionMode,用於對運動模式的選擇:
public enum ActionMode { PHYSIC, KINEMATIC, NOTSET }
FirstController中增加ActionMode的getter和setter函數用於其設置和調用:
public ActionMode GetMode() { return mode; }
public void setMode(ActionMode m)
{
if (m == ActionMode.KINEMATIC) this.gameObject.AddComponent<CCActionManager>();
else this.gameObject.AddComponent<PhysicActionManager>();
mode = m;
}
CCActionManager
實現運動學動作的管理器,基於運動學的變換模型。
public class CCActionManager : SSActionManager, ISSActionCallback, IActionManager
{
public FirstController sceneController;
public List<CCFlyAction> Fly;
public int DiskNumber = 0;
protected new void Start()
{
sceneController = (FirstController)SSDirector.GetInstance().CurrentSceneController;
sceneController.actionManager = this;
}
protected new void Update()
{
base.Update();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted, int intParam = 0, string strParam = null, UnityEngine.Object objectParam = null)
{
if (source is CCFlyAction)
{
sceneController.setGameState(GameState.FLYING);
DiskFactory df = Singleton<DiskFactory>.Instance;
df.FreeDisk(source.gameobject);
}
}
public void StartThrow(GameObject disk)
{
RunAction(disk, CCFlyAction.GetSSAction(disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction), (ISSActionCallback)this);
}
}
PhysicActionManager
基於物理學模型的動作管理器。
public class PhysicActionManager : MonoBehaviour, ISSActionCallback, IActionManager
{
public FirstController sceneController;
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> toAdd = new List<SSAction>();
private List<int> toDelete = new List<int>();
protected void Start()
{
sceneController = (FirstController)SSDirector.GetInstance().CurrentSceneController;
sceneController.actionManager = this;
}
// Update is called once per frame
protected void FixedUpdate()
{
foreach (SSAction ac in toAdd) actions[ac.GetInstanceID()] = ac;
toAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
toDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.FixedUpdate();
}
}
foreach (int key in toDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
toDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
toAdd.Add(action);
action.Start();
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0,
string strParam = null,
UnityEngine.Object objectParam = null)
{
if (source is CCFlyAction)
{
sceneController.setGameState(GameState.FLYING);
DiskFactory df = Singleton<DiskFactory>.Instance;
df.FreeDisk(source.gameobject);
}
}
public void StartThrow(GameObject disk)
{
RunAction(disk, CCFlyAction.GetSSAction(disk.GetComponent<DiskData>().speed, disk.GetComponent<DiskData>().direction), (ISSActionCallback)this);
}
}
SSActionManager
運動管理器的基類。
public class SSActionManager : MonoBehaviour
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>();
private List<SSAction> toAdd = new List<SSAction>();
private List<int> toDelete = new List<int>();
// Use this for initialization
protected void Start()
{
}
// Update is called once per frame
protected void Update()
{
foreach (SSAction ac in toAdd) actions[ac.GetInstanceID()] = ac;
toAdd.Clear();
foreach (KeyValuePair<int, SSAction> kv in actions)
{
SSAction ac = kv.Value;
if (ac.destroy)
{
toDelete.Add(ac.GetInstanceID());
}
else if (ac.enable)
{
ac.Update();
}
}
foreach (int key in toDelete)
{
SSAction ac = actions[key];
actions.Remove(key);
DestroyObject(ac);
}
toDelete.Clear();
}
public void RunAction(GameObject gameobject, SSAction action, ISSActionCallback manager)
{
action.gameobject = gameobject;
action.transform = gameobject.transform;
action.callback = manager;
toAdd.Add(action);
action.Start();
}
}
SSAction
運動的基類:
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 virtual void FixedUpdate()
{
throw new System.NotImplementedException();
}
public void reset()
{
enable = false;
destroy = false;
gameobject = null;
transform = null;
callback = null;
}
}
CCFlyAction
飛碟飛行運動的類,控制了飛碟運動的變換以及飛行的軌跡。
public class CCFlyAction : SSAction
{
float acceleration;
float horizontalSpeed;
Vector3 direction;
float time;
public static CCFlyAction GetSSAction(float speed, Vector3 d)
{
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
action.acceleration = 9.8f;
action.horizontalSpeed = speed;
action.time = 0;
action.direction = d;
action.enable = true;
return action;
}
public override void Start()
{
}
// Update is called once per frame
public override void Update()
{
if (gameobject.activeSelf)
{
time += Time.deltaTime; //total time
transform.Translate(Vector3.down * acceleration * time * Time.deltaTime); //y speed
transform.Translate(direction * horizontalSpeed * Time.deltaTime); //x speed
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
public override void FixedUpdate()
{
if (gameobject.activeSelf)
{
time += Time.deltaTime;
transform.Translate(Vector3.down * acceleration * time * Time.deltaTime);
transform.Translate(direction * horizontalSpeed * Time.deltaTime);
if (this.transform.position.y < -4)
{
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
}
}
}
}
其他運動管理的類和接口:
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);
}
DiskFactory
飛碟工廠類,與之前的設計基本相同,用來管理飛碟的產生和相關屬性。
public class DiskFactory : MonoBehaviour
{
public GameObject disk_prefab; //disk prefab
private List<DiskData> used = new List<DiskData>(); //list of using disks
private List<DiskData> free = new List<DiskData>(); //list of free disks
public GameObject GetDisk(int round) //get disks
{
disk_prefab = null;
if (free.Count > 0){
disk_prefab = free[0].gameObject;
free.Remove(free[0]);
}else{
disk_prefab = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/UFO"), Vector3.zero, Quaternion.identity);
disk_prefab.AddComponent<DiskData>();
}
// change speed and size according to round
if (round == 1)
{
disk_prefab.GetComponent<DiskData>().size = 10f;
disk_prefab.GetComponent<DiskData>().speed = 4f;
disk_prefab.GetComponent<DiskData>().color = Color.blue;
}
if (round == 2)
{
disk_prefab.GetComponent<DiskData>().size = 8f;
disk_prefab.GetComponent<DiskData>().speed = 8f;
disk_prefab.GetComponent<DiskData>().color = Color.green;
}
if (round == 3)
{
disk_prefab.GetComponent<DiskData>().size = 5f;
disk_prefab.GetComponent<DiskData>().speed = 12f;
disk_prefab.GetComponent<DiskData>().color = Color.red;
}
//direction
float ran_x = UnityEngine.Random.Range(-1f, 1f) < 0 ? -1 : 1;
disk_prefab.GetComponent<DiskData>().direction = new Vector3(ran_x, Random.Range(-2f, 2f), 0);
disk_prefab.GetComponent<DiskData>().position = new Vector3(ran_x, Random.Range(-2f, 2f), UnityEngine.Random.Range(-1f, 1f));
disk_prefab.SetActive(false);
disk_prefab.name = disk_prefab.GetInstanceID().ToString();
used.Add(disk_prefab.GetComponent<DiskData>());
return disk_prefab;
}
public void FreeDisk(GameObject disk)
{
for (int i = 0; i < used.Count; i++)
{
if (disk.GetInstanceID() == used[i].gameObject.GetInstanceID())
{
used[i].gameObject.SetActive(false);
free.Add(used[i]);
used.Remove(used[i]);
break;
}
}
}
}
SSDirector、ISceneController、DiskData和Singleton類也都與上次作業基本一致,不再贅述。
ScoreRecorder
記分員類,進行分數的記錄。
public class ScoreRecorder : MonoBehaviour
{
public int score;
void Start(){score = 0;}
public void AddRecord(int round){score += round;}
public void Reset(){ score = 0;}
}
IUserAction
接口類,實現控制器和UserGUI之間信息的交互,具體實現在FirstController中。
public interface IUserAction
{
GameState getGameState();
void setGameState(GameState gs);
int GetScore();
int GetRound();
int GetNum();
ActionMode GetMode();
void setMode(ActionMode mode);
void hit(Vector3 pos);
}
枚舉類函數
GameState來代表遊戲的進行狀態,相比上次作業使用總飛盤數來做計算的方式簡潔明瞭了不少。
public enum GameState { START, NEXT_ROUND, GAME_OVER, RUNNING, ROUND_END, FLYING }
public enum ActionMode { PHYSIC, KINEMATIC, NOTSET }
FirstController
總控制器,在上一次作業的基礎上進行了修改,實現了IUserAction接口,並且通過GameState來判斷遊戲狀態並進行相應的操作。
public class FirstController : MonoBehaviour, ISceneController, IUserAction
{
public IActionManager actionManager { get; set; }
public ScoreRecorder scoreRecorder { get; set; }
public Queue<GameObject> diskQueue = new Queue<GameObject>();
public ActionMode mode { get; set; }
private int diskNumber;
public int round;
private int num;
private GameState gameState = GameState.START;
void Awake()
{
SSDirector director = SSDirector.GetInstance();
director.CurrentSceneController = this;
diskNumber = 10;
gameState = GameState.START;
this.gameObject.AddComponent<ScoreRecorder>();
this.gameObject.AddComponent<DiskFactory>();
this.gameObject.AddComponent<CCActionManager>();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
mode = ActionMode.NOTSET;
director.CurrentSceneController.LoadResources();
}
public void LoadResources() { round = 0; }
public int GetScore() { return scoreRecorder.score; }
public int GetRound() { return round; }
public int GetNum() { return num; }
public GameState getGameState() { return gameState; }
public void setGameState(GameState gamestate) { gameState = gamestate; }
public ActionMode GetMode() { return mode; }
public void setMode(ActionMode m)
{
if (m == ActionMode.KINEMATIC) this.gameObject.AddComponent<CCActionManager>();
else this.gameObject.AddComponent<PhysicActionManager>();
mode = m;
}
void init() //disk queue initial
{
for (int i = 0; i < 10; i++) //15 disks per round
diskQueue.Enqueue(Singleton<DiskFactory>.Instance.GetDisk(round));
}
private void Update()
{
if (gameState == GameState.START)
{
scoreRecorder.Reset();
num = 10;
round = 1;
init();
ThrowDisk();
gameState = GameState.RUNNING;
}
if (gameState == GameState.NEXT_ROUND)
{
round++;
gameState = GameState.RUNNING;
num = 10;
init();
ThrowDisk();
}
if (gameState == GameState.FLYING)
{
gameState = GameState.RUNNING;
num--;
if (num == 0)
{
if (round == 3)
gameState = GameState.GAME_OVER;
else
gameState = GameState.ROUND_END;
}
else
{
ThrowDisk();
}
}
}
void ThrowDisk()
{
if (diskQueue.Count != 0)
{
Debug.Log("current round: " + round);
GameObject disk = diskQueue.Dequeue();
disk.GetComponent<Renderer>().material.color = disk.GetComponent<DiskData>().color;
disk.transform.position = disk.GetComponent<DiskData>().position;
disk.SetActive(true);
actionManager.StartThrow(disk);
}
}
public void hit(Vector3 pos)
{
Ray ray = Camera.main.ScreenPointToRay(pos);
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)
{
scoreRecorder.AddRecord(round);
DiskFactory df = Singleton<DiskFactory>.Instance;
df.FreeDisk(hit.collider.gameObject);
gameState = GameState.FLYING;
}
}
}
}
UserGUI
與用戶的交互頁面,新增了兩個運動模式選擇按鈕,供用戶進行模式的切換。
public class UserGUI : MonoBehaviour
{
private IUserAction action;
private int score;
private int num;
private int round;
GUIStyle style;
GUIStyle buttonStyle;
// Use this for initialization
void Start()
{
action = SSDirector.GetInstance().CurrentSceneController as IUserAction;
style = new GUIStyle();
style.fontSize = 20;
style.normal.textColor = Color.white;
buttonStyle = new GUIStyle("button");
buttonStyle.fontSize = 20;
buttonStyle.normal.textColor = Color.white;
}
private void Update()
{
score = action.GetScore();
num = action.GetNum();
round = action.GetRound();
}
void OnGUI()
{
if (GUI.Button(new Rect(110, 0, 100, 50), "Kinematic"))
{
action.setMode(ActionMode.KINEMATIC);
}
if (GUI.Button(new Rect(0, 0, 100, 50), "Physic"))
{
action.setMode(ActionMode.PHYSIC);
}
string text = "Round: " + round.ToString();
GUI.Label(new Rect(Screen.width / 2 - 75, 10, 150, 55), text, style);
string text2 = "Your Score: " + score.ToString();
GUI.Label(new Rect(Screen.width / 2 - 75, 30, 150, 55), text2, style);
string text3 = "Remain Disk Number: " + num.ToString();
GUI.Label(new Rect(Screen.width / 2 - 75, 50, 150, 55), text3, style);
if (action.getGameState() == GameState.ROUND_END)
{
action.setGameState(GameState.NEXT_ROUND);
}
if (action.getGameState() == GameState.GAME_OVER)
{
string t = "GAME OVER!";
GUI.Label(new Rect(Screen.width / 2 - 30, 100, 50, 50), t, style);
if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.height / 2 - 50, 200, 100), "Restart", buttonStyle))
{
action.setGameState(GameState.START);
}
}
if (Input.GetButtonDown("Fire1"))
{
Vector3 pos = Input.mousePosition;
action.hit(pos);
}
}
}
實現效果: