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

最后实现的效果如下图所示:

可以参照演示视频:演示视频

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值