1. 程式人生 > >Unity3D課程學習筆記(四)

Unity3D課程學習筆記(四)

遊戲:滑鼠打飛碟遊戲

    本週的作業是寫一個滑鼠打飛碟的遊戲,具體的實現用工廠模式和前兩次作業中用到的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);
            }  
  
        }  
    }  
}

最後實現的效果如下圖所示:



可以參照演示視訊:演示視訊