Unity3D course study notes (4)

Game: mouse and flying saucer game

    This week's homework is to write a game where the mouse plays a flying saucer. The specific implementation is implemented using the factory pattern and the MVC pattern used in the previous two assignments, so as to realize the separation of human-computer interaction and game model.

   The game has a total of 3 rounds, and the difficulty of each round increases in turn (although the rounds designed in this game are a bit silly... The speed of the flying saucer is also relatively slow, and the track is relatively simple), but the main function is to pass this game. This homework to fully understand the factory pattern, and more proficient operation of the previous MVC pattern.

   The new knowledge mainly involved is as follows:

 

  • Create empty object and add component
  • Create base type game objects
  • clone from known object or prefab

    Here's how to create an empty object and add a component:

new GameObject();
new GameObject(string name);
new GameObject(string name, params Type[] components);

     Here's how to create a base-type game object:

GameObject CreatePrimitive(PrimitiveType type);

      Clone from a known object or prefab:

Instantiate<Transform>(brick, new Vector3(x,y,0),Quaterinion.identity);

      Check if an object has data properties

GetComponent<T>()

      Game Factory Pattern Design

 

  • The creation and destruction of game objects are expensive, and the number of destructions must be reduced. Such as: bullets in the game
  • Shields the business logic of creation and destruction, making the program easy to expand    

     The UML diagram of the factory pattern is as follows:

 

The following is the code of this game. The first few classes are basically copied from the previous assignments. The factory and flying saucer classes are based on the teacher's PPT. The key scene notes and actions are based on the teacher's excellent work. Blog brother's code: brother blog portal

 

Director class code:

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;  
    }  
}  

 

Template code to implement single instance:

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;  
        }  
    }  
} 

 

Scorer code:

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;
	}
}

 

Action base class code:

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;  
    }  
}

 

Action manager code:

 

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 code

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);
		}
	}
}

 

Interface code of interface and scene controller:

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();
}

 

Interface code for communication between action and action manager:

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);  
}  

 

Action manager CCActionManager code that manages specific actions:

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);
		}
	}
}

 

UFO action code:

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;
    }
}

 

UFO data code:

 

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; }
}  

 

UFO Factory's code:

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);
		}
	}
}

 

The field record code, the controller that manages the specific scene:

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);
            }  
  
        }  
    }  
}

The final effect is shown in the following figure:

You can refer to the demo video: demo video

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=324098249&siteId=291194637