Unity3D課程學習筆記(四)
阿新 • • 發佈:2018-12-20
遊戲:滑鼠打飛碟遊戲
本週的作業是寫一個滑鼠打飛碟的遊戲,具體的實現用工廠模式和前兩次作業中用到的MVC模式來實現,從而實現人機互動與遊戲模型分離的作用。
遊戲總共有3個回合,每個回合的難度依次遞增(雖然這次遊戲設計的回合有點傻.....飛碟出現的速度也比較慢,軌道比較單一),但是最主要的作用是通過這次作業來充分了解工廠模式,和更加熟練的操作以前的MVC模式。
主要涉及到的新知識如下:
- 建立空物件並新增元件
- 建立基礎型別遊戲物件
- 從已知物件或預製克隆
建立空物件並新增元件的方法如下:
new GameObject();
new GameObject(string name);
new GameObject(string name, params Type[] components);
建立基礎型別遊戲物件的方法如下:
GameObject CreatePrimitive(PrimitiveType type);
從已知物件或預製克隆:
Instantiate<Transform>(brick, new Vector3(x,y,0),Quaterinion.identity);
檢查一個物件是否擁有資料屬性
GetComponent<T>()
遊戲工廠模式設計
- 遊戲物件的建立和銷燬高成本,必須減少銷燬次數。如:遊戲中的子彈
- 遮蔽建立與銷燬的業務邏輯,使程式易於拓展
工廠模式的UML圖如下所示:
下面就是本次遊戲的程式碼,頭幾個類基本上都是照搬前幾次作業的類,工廠和飛碟等幾個類則是參照老師的PPT,關鍵的場記和動作則是參照老師給的優秀部落格的師兄的程式碼: 師兄部落格傳送門
導演類的程式碼:
public class Director : System.Object {
public ISceneController currentSceneController { get; set; }
private static Director director;
private Director() {}
public static Director getInstance() {
if (director == null) {
director = new Director();
}
return director;
}
}
實現單例項的模版程式碼:
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 ScoreRecorder : MonoBehaviour {
private int Score;
private Dictionary<Color, int> ScoreDictionary = new Dictionary<Color,int>();
void Start() {
Score = 0;
ScoreDictionary.Add(Color.yellow,1);
ScoreDictionary.Add(Color.red,2);
ScoreDictionary.Add(Color.black,4);
}
public void Record(GameObject disk) {
Score += ScoreDictionary[disk.GetComponent<DiskData>().getDiskColor()];
Score += 2;
}
public void MinRecord() {
Score -= 2;
}
public int getScore(){
return Score;
}
public void Reset() {
Score = 0;
}
}
動作基類程式碼:
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 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>();
// 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<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);
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();
}
}
UI程式碼
public class UserGUI : MonoBehaviour {
private IUserAction action;
public CCActionManager actionManager;
GUIStyle style;
void Start() {
action = Director.getInstance().currentSceneController as IUserAction;
}
private void OnGUI() {
style = new GUIStyle();
style.fontSize = 40;
style.alignment = TextAnchor.MiddleCenter;
if (Input.GetButtonDown("Fire1")) {
Vector3 position = Input.mousePosition;
action.hit(position);
}
GUI.Button(new Rect(Screen.width / 15, Screen.height / 15, 140, 70), "Score: " + action.getScore().ToString(), style);
GUI.Button(new Rect(Screen.width / 15, Screen.height / 7, 140, 70), "Round: " + action.getCurrentRound().ToString(), style);
GUI.Label(new Rect(Screen.width / 2 + 500, Screen.height / 15, 180, 110), "Rule:\nYellow: 1 point\nRed: 2 points\nBlack: 3 points\nDrop: -2 points\nFail: Score < 0");
//µ±ÓÎÏ·»¹Ã»¿ªÊ¼£¬»òÕßÓÎÏ·ÒѾ½áÊø£¬¿ÉÒÔÑ¡Ôñ¿ªÊ¼
if (!action.checkWhetherIsStart() && GUI.Button(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 55, 180,110),"Start")) {
action.setGameStart();
action.setGameState(GameState.ROUND_START);
}
//µ±ÓÎϷûÓнáÊø£¬²¢ÇÒÓÎÏ·ÒѾ¿ªÊ¼£¬²¢ÇÒµ±Ç°»ØºÏÒѾ½áÊø£¬³öÏָð´Å¥£¬Ñ¡Ôñ½øÈëÏÂÒ»»ØºÏ
if (!action.isGameOver() && action.checkWhetherIsStart() && action.getGameState() == GameState.ROUND_FINISH &&
GUI.Button(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 55, 180,110), "Next Round")) {
action.setFirstCurrent();
action.setGameState(GameState.ROUND_START);
}
}
}
介面與場景控制器的介面程式碼:
public enum GameState { GAME_START, ROUND_START, ROUND_FINISH, GAME_RUNNING, GAME_OVER}
public interface IUserAction {
int getScore();
int getCurrentRound();
void setFirstCurrent();
void setGameState(GameState _gameState);
void setGameStart();
void hit(Vector3 pos);
bool checkWhetherIsStart();
bool isGameOver();
GameState getGameState();
}
動作與動作管理器之間通訊的介面程式碼:
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);
}
管理具體動作的動作管理器CCActionManager程式碼:
public class CCActionManager : SSActionManager, ISSActionCallback {
public FirstSceneController sceneController;
public List<CCFlyAction> Fly;
private int DiskNumber = 0;
private List<SSAction> Used = new List<SSAction>();
private List<SSAction> Free = new List<SSAction>();
public void setDiskNumber(int _diskNumber) {
DiskNumber = _diskNumber;
}
public int getDiskNumber() {
return DiskNumber;
}
//GetSSAction,首先從工廠裡面找,如果沒有的話就創造
SSAction GetSSAction() {
SSAction action = null;
if (Free.Count > 0) {
action = Free[0];
Free.Remove(Free[0]);
} else {
action = ScriptableObject.Instantiate<CCFlyAction>(Fly[0]);
}
Used.Add(action);
return action;
}
//FreeSSAction
public void FreeSSAction(SSAction action) {
SSAction temp = null;
foreach (SSAction disk in Used) {
if (action.GetInstanceID() == disk.GetInstanceID()) {
temp = disk;
}
}
if (temp != null) {
temp.reset();
Free.Add(temp);
Used.Remove(temp);
}
}
protected new void Start() {
sceneController = (FirstSceneController)Director.getInstance().currentSceneController;
sceneController.actionManager = this;
Fly.Add(CCFlyAction.GetSSAction());
}
public void SSActionEvent(SSAction source,
SSActionEventType events = SSActionEventType.Competeted,
int Param = 0,
string strParam = null,
UnityEngine.Object objectParam = null) {
if (source is CCFlyAction) {
DiskNumber--;
DiskFactory factory = Singleton<DiskFactory>.Instance;
factory.FreeDisk(source.gameobject);
FreeSSAction(source);
}
}
public void StartThrow(Queue<GameObject> diskQueue) {
foreach (GameObject temp in diskQueue) {
//if (GetSSAction() != null)
RunAction(temp, GetSSAction(), (ISSActionCallback)this);
}
}
}
飛碟的動作程式碼:
public class CCFlyAction : SSAction {
public ScoreRecorder scoreRecorder { get; set; }
private float acceleration = 3.0f;
private float horizontalSpeed;
private float lowerBound = -4;
private float flyTime;
private Vector3 direction;
public override void Start(){
enable = true;
flyTime = 0;
horizontalSpeed = gameobject.GetComponent<DiskData>().getDiskSpeed();
direction = gameobject.GetComponent<DiskData>().getDiskDirection();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
}
public override void Update(){
if (gameobject.activeSelf) {
flyTime += Time.deltaTime;
transform.Translate(Vector3.down * acceleration * flyTime * Time.deltaTime);
transform.Translate(direction * horizontalSpeed * Time.deltaTime);
if(checkWhetherShouldRecycle()){
scoreRecorder.MinRecord();
}
}
}
private bool checkWhetherShouldRecycle() {
if (this.transform.position.y < lowerBound){
this.destroy = true;
this.enable = false;
this.callback.SSActionEvent(this);
return true;
}
return false;
}
public static CCFlyAction GetSSAction(){
CCFlyAction action = ScriptableObject.CreateInstance<CCFlyAction>();
return action;
}
}
飛碟的資料程式碼:
public class DiskData : MonoBehaviour {
private Vector3 disk_size;
private Vector3 disk_direction;
private Color disk_color;
private float disk_speed;
public void setDiskSize(Vector3 _size) { disk_size = _size; }
public void setDiskColor(Color _color) { disk_color = _color; }
public void setDiskSpeed(float _speed) { disk_speed = _speed; }
public void setDiskDirection(Vector3 _direction) { disk_direction = _direction; }
public Vector3 getDiskSize() { return disk_size; }
public Vector3 getDiskDirection() { return disk_direction; }
public Color getDiskColor() { return disk_color; }
public float getDiskSpeed() { return disk_speed; }
}
飛碟工廠的程式碼:
public class DiskFactory : MonoBehaviour {
public GameObject Disk_Product;
//Used用來儲存被啟用的飛碟,Free用來儲存空閒的飛碟
private List<DiskData> Used = new List<DiskData>();
private List<DiskData> Free = new List<DiskData>();
//Awake
private void Awake() {
Disk_Product = GameObject.Instantiate<GameObject>(Resources.Load<GameObject>("Prefabs/DiskModel"),
Vector3.zero, Quaternion.identity);
Disk_Product.SetActive(false);
}
//GetDisk
public GameObject GetDisk(int Game_Round) {
GameObject NewDiskProduct = null;
if(Free.Count > 0) {
NewDiskProduct = Free[0].gameObject;
Free.Remove(Free[0]);
} else {
NewDiskProduct = GameObject.Instantiate<GameObject>(Disk_Product, Vector3.zero,
Quaternion.identity);
NewDiskProduct.AddComponent<DiskData>();
}
//控制飛碟產生的頻率
int From = 0;
if (Game_Round == 1) {
From = 100;
}
if (Game_Round == 2) {
From = 250;
}
int TheDiskColor = Random.Range(From, Game_Round*499);
if (TheDiskColor > 500) {
Game_Round = 2;
} else if (TheDiskColor > 300) {
Game_Round = 1;
} else {
Game_Round = 0;
}
//根據回合控制生成飛碟的屬性
if (Game_Round == 0) {
setDiskProp(NewDiskProduct,Color.yellow,2.0f);
} else if (Game_Round == 1) {
setDiskProp(NewDiskProduct,Color.red,3.0f);
} else if (Game_Round == 2) {
setDiskProp(NewDiskProduct,Color.black,4.0f);
}
Used.Add(NewDiskProduct.GetComponent<DiskData>());
NewDiskProduct.name = NewDiskProduct.GetInstanceID().ToString();
return NewDiskProduct;
}
//setDiskProp.
public void setDiskProp(GameObject Disk, Color _Color, float _Speed) {
Disk.GetComponent<DiskData>().setDiskColor(_Color);
Disk.GetComponent<DiskData>().setDiskSpeed(_Speed);
float RanX = UnityEngine.Random.Range(-1.0f,1.0f) < 0 ? -1 : 1;
Disk.GetComponent<DiskData>().setDiskDirection(new Vector3(RanX, 1, 0));
Disk.GetComponent<Renderer>().material.color = _Color;
}
//FreeDisk.
public void FreeDisk(GameObject Disk) {
DiskData temp = null;
foreach (DiskData disk in Used) {
if (Disk.GetInstanceID() == disk.gameObject.GetInstanceID()) {
temp = disk;
}
}
if (temp != null) {
temp.gameObject.SetActive(false);
Free.Add(temp);
Used.Remove(temp);
}
}
}
場記程式碼,管理具體的場景的控制器:
public interface ISceneController {}
public class FirstSceneController : MonoBehaviour, ISceneController, IUserAction {
public CCActionManager actionManager { get; set; }
public ScoreRecorder scoreRecorder { get; set; }
private bool isStart = false;
private bool FirstCurrent = true;
GUIStyle gameOverStyle;
public Queue<GameObject> diskQueue = new Queue<GameObject>();
private int RoundCount = 1;
private int diskNumber;
private int currentRound = 0;
UserGUI IUserGUI;
public int maxRound = 3;
private float diskGapTime = 0;
private GameState gameState = GameState.GAME_START;
void Awake () {
Director director = Director.getInstance();
director.currentSceneController = this;
diskNumber = 10;
this.gameObject.AddComponent<ScoreRecorder>();
this.gameObject.AddComponent<DiskFactory>();
scoreRecorder = Singleton<ScoreRecorder>.Instance;
IUserGUI = gameObject.AddComponent<UserGUI>() as UserGUI;
}
private void Start() {
gameOverStyle = new GUIStyle();
gameOverStyle.fontSize = 40;
gameOverStyle.alignment = TextAnchor.MiddleCenter;
diskNumber = 10;
}
private void Update() {
if (gameState == GameState.ROUND_START && isStart){
Debug.Log("Hello");
if (actionManager.getDiskNumber() == 0) {
actionManager.setDiskNumber(10);
NextRound();
gameState = GameState.GAME_RUNNING;
}
if (isStart) {
currentRound = (currentRound + 1) % maxRound;
if (!FirstCurrent) RoundCount = (RoundCount + 1) % maxRound;
}
}
if (actionManager.getDiskNumber() == 0 && gameState == GameState.GAME_RUNNING) {
isGameOver();
gameState = GameState.ROUND_FINISH;
Debug.Log("In Two!");
}
if (diskGapTime > 1.5f){
if (diskQueue.Count != 0) {
GameObject disk = diskQueue.Dequeue();
Vector3 position = new Vector3(0, 0, 0);
float y = UnityEngine.Random.Range(0f, 4f);
position = new Vector3(-disk.GetComponent<DiskData>().getDiskDirection().x * 7, y, 0);
disk.transform.position = position;
disk.SetActive(true);
}
diskGapTime = 0;
}
else{
diskGapTime += Time.deltaTime;
}
}
private void OnGUI(){
if(isGameOver()){
GUI.Label(new Rect(Screen.width / 2 - 90, Screen.height / 2 - 200, 180, 130), "Game Over!", gameOverStyle);
}
}
/*µ±·ÖÊýСÓÚ0£¬ÓÎÏ·½áÊø*/
public bool isGameOver(){
if (scoreRecorder.getScore() < 0) {
//scoreRecorder.Reset();
gameState = GameState.GAME_OVER;
isStart = false;
return true;
}
return false;
}
private void NextRound() {
DiskFactory df = Singleton<DiskFactory>.Instance;
for (int i = 0; i < diskNumber; i++) {
diskQueue.Enqueue(df.GetDisk(currentRound));
}
actionManager.StartThrow(diskQueue);
}
/*SetÀà·½·¨*/
public void setGameStart() {
scoreRecorder.Reset();
isStart = true;
}
public bool checkWhetherIsStart() {
return isStart;
}
public void setGameState(GameState _gameState) {
gameState = _gameState;
}
public void setFirstCurrent(){
FirstCurrent = false;
}
/*GetÀà·½·¨*/
public int getCurrentRound(){
return RoundCount ;
}
public GameState getGameState(){
return gameState;
}
public int getScore() {
return scoreRecorder.getScore();
}
/*ÉäÏß*/
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];
Physics.Raycast(ray, out hit);
Debug.DrawLine(ray.origin, hit.point);
if (hit.collider.gameObject.GetComponent<DiskData>() != null) {
scoreRecorder.Record(hit.collider.gameObject);
hit.collider.gameObject.transform.position = new Vector3(0, -5, 0);
//DiskFactory df = Singleton<DiskFactory>.Instance;
//df.FreeDisk(hit.collider.gameObject);
}
}
}
}
最後實現的效果如下圖所示:
可以參照演示視訊:演示視訊