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

Unity3D課程學習筆記(二)

1.1.遊戲物件運動的本質是什麼?

遊戲物件運動的本質是遊戲物件座標位置,方向的變換。

1.2.三種方法實現物體的拋物線運動

實現基本拋物線的原理為:

水平方向上\(x=v_{0}t\)

豎直方向上\(y=at^{2}\)

1.2.1.用Position實現

實現的程式碼如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveOne : MonoBehaviour {
	private float speed = 0.25f;
	private float acceleration = 0.20f;
	// Use this for initialization
	void Start () {
		
	}

	// Update is called once per frame
	void Update () {
		this.transform.position += Vector3.right * speed * Time.time;
		this.transform.position += Vector3.up * acceleration * Time.time * Time.time;
	}
}

效果圖如下所示:


1.2.2.用Translate實現

實現程式碼如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveOne : MonoBehaviour {
	private float speed = 0.25f;
	private float acceleration = 0.20f;
	// Use this for initialization
	void Start () {
		
	}

	// Update is called once per frame
	void Update () {
		transform.Translate (transform.right * speed * Time.fixedTime, Space.World);
		transform.Translate (transform.up * acceleration * Time.fixedTime * Time.fixedTime, Space.World);
	}
}

該程式碼的效果和1.2.1的效果圖是一樣的,只是實現的方法不一樣。

1.2.3.用Vector3.Moveforward實現

首先建立兩個物件,Target1和Target2,兩個物件的位置放置在很遠的地方,然後讓物體朝著兩個目標個體移動,即可得到拋物線的運動軌跡。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveOne : MonoBehaviour {
	private float speed = 2.0f;
	public Transform target1;
	public Transform target2;
	// Use this for initialization
	void Start () {	
	}

	// Update is called once per frame
	void Update () {
		transform.position = Vector3.MoveTowards (transform.position, target1.position, Time.time);
		transform.position = Vector3.MoveTowards (transform.position, target2.position, speed * Time.time * Time.time);
	}
}

該程式碼的實現效果和1.2.1的效果一樣

1.3.實現一個完整的太陽系

第三個步驟是通過實現一個太陽系來熟悉遊戲物件運動的操作

首先我們先製作好樣式,就是八大行星的桌布貼圖等等,相關的素材可以在網上找到,直接將其貼到相應創造出來的球體即可以


然後我們開始寫公轉和自轉的指令碼,因為八大行星都是繞太陽公轉,而月球是繞地球公轉的,所有八大行星都以太陽為中心點旋轉,而月球則以地球為中心點旋轉。

實現公轉的程式碼如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Revolution : MonoBehaviour {
	public Transform point;
	public float speed;
	private float ry,rz;

	void Start () {
		ry = Random.Range (0, 360);
		rz = Random.Range (0, 360);
	}

	void Update () {
		Vector3 axis = new Vector3(0,ry,rz);
		this.transform.RotateAround(point.position,axis,speed * Time.deltaTime);
	}
}

為了保證各大行星繞地球公轉不在同一個法平面內,在開始程式的時候應該要隨機產生一個向量,使得該行星能夠繞這個軸公轉

解決了公轉的問題後,就要解決行星的自轉問題了,行星的自轉的指令碼程式碼如下所示:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Rotation : MonoBehaviour {
	private float speed = 0.5f;
	// Use this for initialization
	void Start () {
		
	}
	
	// Update is called once per frame
	void Update () {
		this.transform.RotateAround (this.transform.position, Vector3.up, speed);
	}
}

之後我們就基本能夠製作出太陽系的輪廓了,為了能夠增加效果,我們給行星運動的時候加上一些軌跡

Add Component => Trail Renderer


在這個框內調好Element 0的樣式,Width寬度,Color顏色後就能夠在行星運動的時候顯示軌跡了

完成這步以後我們要給這個空間加上一個背景,加上背景的話,我們可以先創造一個立方體,然後把立方體的樣式設定為我們的背景圖案,然後把它的y和z屬性設定得很大,而x屬性設定得比較小,就能創造出一個背景來了


然後我們就能得到大致的效果圖


2.牧師與惡魔

遊戲中提及的事物:牧師,惡魔,船,河流

遊戲的規則表如下所示:


遊戲的設計採用MVC模式,遊戲中的所有GameObject就是Model,View包括UserGUI和ClickGUI,Controller包括FirstController->SceneController->MyCharcaterController,BoatController,CoastController.

導演Director即相應場景控制器的宣告程式碼如下:

namespace Game {
	/*單例模式*/
	public class Director : System.Object {
		private static Director _instance;
		public SceneController currentSceneController { get; set; }

		public static Director getInstance() {
			if (_instance == null) {
				_instance = new Director ();
			}
			return _instance;
		}
	}

    /*場景控制器,生成物件,背景等等*/
	public interface SceneController {
		void loadResources ();
        void loadBackground();
	}

    /*使用者行為,即使用者能夠執行的操作*/
	public interface UserAction {
		void moveBoat();
		void characterIsClicked(MyCharacterController characterCtrl);
		void restart();
        bool isGameOver();
	}

	/*The Function of move, 能夠使物體運動的類*/
	public class Moveable: MonoBehaviour {
		
		readonly float move_speed = 20;

		int moving_status;	
        // 0->not moving, 1->moving to middle, 2->moving to dest
		Vector3 middle;
        Vector3 dest;

        void Update() {
			if (moving_status == 1) {
				transform.position = Vector3.MoveTowards (transform.position, middle, move_speed * Time.deltaTime);
				if (transform.position == middle) {
					moving_status = 2;
				}
			} else if (moving_status == 2) {
				transform.position = Vector3.MoveTowards (transform.position, dest, move_speed * Time.deltaTime);
				if (transform.position == dest) {
					moving_status = 0;
				}
			}
		}

        /*通過設定目的地可以達到物體移動的效果*/
		public void setDestination(Vector3 _dest) {
			dest = _dest;
			middle = _dest;
			if (_dest.y == transform.position.y) {	
                // boat moving
				moving_status = 2;
			}
			else if (_dest.y < transform.position.y) {	
                // character from coast to boat
				middle.y = transform.position.y;
			} else {								
                // character from boat to coast
				middle.x = transform.position.x;
			}
			moving_status = 1;
		}

		public void reset() {
			moving_status = 0;
		}
	}
}

相應的物件由相應的Controller實現,下面建立了一個namespace,用於聚集各類Controller類

BackgroundController用於控制生成背景圖案

    public class BackgroundController
    {
        readonly GameObject background;
        public BackgroundController()
        {
            background = Object.Instantiate(Resources.Load("Perfabs/Background", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
        }
        public void setName(string _name)
        {
            background.name = _name;
        }
        public void setPosition(Vector3 background_position)
        {
            background.transform.position = background_position;
        }
        public string getName()
        {
            return background.name;
        }
    }

MyCharacterController用於控制牧師和魔鬼物件

    public class MyCharacterController
    {
        readonly GameObject character;
        readonly Moveable moveableScript;
        readonly ClickGUI clickGUI;
        readonly int characterType; 
        // 0->priest, 1->devil

        bool _isOnBoat;
        CoastController coastController;


        public MyCharacterController(string which_character)
        {

            if (which_character == "priest")
            {
                character = Object.Instantiate(Resources.Load("Perfabs/Priest", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                characterType = 0;
            }
            else
            {
                character = Object.Instantiate(Resources.Load("Perfabs/Devil", typeof(GameObject)), Vector3.zero, Quaternion.identity, null) as GameObject;
                characterType = 1;
            }
            moveableScript = character.AddComponent(typeof(Moveable)) as Moveable;

            clickGUI = character.AddComponent(typeof(ClickGUI)) as ClickGUI;
            clickGUI.setController(this);
        }

        public void setName(string name)
        {
            character.name = name;
        }

        public void setPosition(Vector3 pos)
        {
            character.transform.position = pos;
        }

        public void moveToPosition(Vector3 destination)
        {
            moveableScript.setDestination(destination);
        }

        public int getType()
        { 
            return characterType;
        }

        public string getName()
        {
            return character.name;
        }

        public void getOnBoat(BoatController boatCtrl)
        {
            coastController = null;
            character.transform.parent = boatCtrl.getGameobj().transform;
            _isOnBoat = true;
        }

        public void getOnCoast(CoastController coastCtrl)
        {
            coastController = coastCtrl;
            character.transform.parent = null;
            _isOnBoat = false;
        }

        public bool isOnBoat()
        {
            return _isOnBoat;
        }

        public CoastController getCoastController()
        {
            return coastController;
        }

        public void reset()
        {
            moveableScript.reset();
            coastController = (Director.getInstance().currentSceneController as FirstController).fromCoast;
            getOnCoast(coastController);
            setPosition(coastController.getEmptyPosition());
            coastController.getOnCoast(this);
        }
    }

BoatController用於控制船的物件

    public class BoatController
    {
        readonly GameObject boat;
        readonly Moveable moveableScript;
        readonly Vector3 fromPosition = new Vector3(3.0f, 0.8f, 0);
        readonly Vector3 toPosition = new Vector3(-3.0f, 0.8f, 0);
        readonly Vector3[] from_positions;
        readonly Vector3[] to_positions;

        int to_or_from; 
        // to->-1; from->1
        MyCharacterController[] passenger = new MyCharacterController[2];

        public BoatController()
        {
            to_or_from = 1;

            from_positions = new Vector3[] { new Vector3(2.5F, 2.0F, 0), new Vector3(3.5F, 2.0F, 0) };
            to_positions = new Vector3[] { new Vector3(-2.5F, 2.0F, 0), new Vector3(-3.5F, 2.0F, 0) };

            boat = Object.Instantiate(Resources.Load("Perfabs/Boat", typeof(GameObject)), fromPosition, Quaternion.identity, null) as GameObject;
            boat.name = "boat";

            moveableScript = boat.AddComponent(typeof(Moveable)) as Moveable;
            boat.AddComponent(typeof(ClickGUI));
        }


        public void Move()
        {
            if (to_or_from == -1)
            {
                moveableScript.setDestination(fromPosition);
                to_or_from = 1;
            }
            else
            {
                moveableScript.setDestination(toPosition);
                to_or_from = -1;
            }
        }

        public int getEmptyIndex()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null)
                {
                    return i;
                }
            }
            return -1;
        }

        public bool isEmpty()
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] != null)
                {
                    return false;
                }
            }
            return true;
        }

        public Vector3 getEmptyPosition()
        {
            Vector3 pos;
            int emptyIndex = getEmptyIndex();
            if (to_or_from == -1)
            {
                pos = to_positions[emptyIndex];
            }
            else
            {
                pos = from_positions[emptyIndex];
            }
            return pos;
        }

        public void GetOnBoat(MyCharacterController characterCtrl)
        {
            int index = getEmptyIndex();
            passenger[index] = characterCtrl;
        }

        public MyCharacterController GetOffBoat(string passenger_name)
        {
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] != null && passenger[i].getName() == passenger_name)
                {
                    MyCharacterController charactorCtrl = passenger[i];
                    passenger[i] = null;
                    return charactorCtrl;
                }
            }
            return null;
        }

        public GameObject getGameobj()
        {
            return boat;
        }

        public int get_to_or_from()
        {
            return to_or_from;
        }

        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for (int i = 0; i < passenger.Length; i++)
            {
                if (passenger[i] == null)
                    continue;
                if (passenger[i].getType() == 0)
                { 
                    count[0]++;
                }
                else
                {
                    count[1]++;
                }
            }
            return count;
        }

        public void reset()
        {
            moveableScript.reset();
            if (to_or_from == -1)
            {
                Move();
            }
            passenger = new MyCharacterController[2];
        }
    }

CoastController用於控制河岸物件

    public class CoastController
    {
        readonly GameObject coast;
        readonly Vector3 from_pos = new Vector3(9, -2, 0);
        readonly Vector3 to_pos = new Vector3(-9, -2, 0);
        readonly Vector3[] positions;
        readonly int to_or_from;    // to->-1, from->1

        // change frequently
        MyCharacterController[] passengerPlaner;

        public CoastController(string _to_or_from)
        {
            positions = new Vector3[] {
                new Vector3(5.4F,2.4F,0),
                new Vector3(6.6F,2.4F,0),
                new Vector3(7.8F,2.4F,0),
                new Vector3(9.0F,2.4F,0),
                new Vector3(10.2F,2.4F,0),
                new Vector3(11.4F,2.4F,0)};

            passengerPlaner = new MyCharacterController[6];

            if (_to_or_from == "from")
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/Stone", typeof(GameObject)), from_pos, Quaternion.identity, null) as GameObject;
                coast.name = "from";
                to_or_from = 1;
            }
            else
            {
                coast = Object.Instantiate(Resources.Load("Perfabs/Stone", typeof(GameObject)), to_pos, Quaternion.identity, null) as GameObject;
                coast.name = "to";
                to_or_from = -1;
            }
        }

        public int getEmptyIndex()
        {
            for (int i = 0; i < passengerPlaner.Length; i++)
            {
                if (passengerPlaner[i] == null)
                {
                    return i;
                }
            }
            return -1;
        }

        public Vector3 getEmptyPosition()
        {
            Vector3 pos = positions[getEmptyIndex()];
            pos.x *= to_or_from;
            return pos;
        }

        public void getOnCoast(MyCharacterController characterCtrl)
        {
            int index = getEmptyIndex();
            passengerPlaner[index] = characterCtrl;
        }

        public MyCharacterController getOffCoast(string passenger_name)
        {   // 0->priest, 1->devil
            for (int i = 0; i < passengerPlaner.Length; i++)
            {
                if (passengerPlaner[i] != null && passengerPlaner[i].getName() == passenger_name)
                {
                    MyCharacterController charactorCtrl = passengerPlaner[i];
                    passengerPlaner[i] = null;
                    return charactorCtrl;
                }
            }
            return null;
        }

        public int get_to_or_from()
        {
            return to_or_from;
        }

        public int[] getCharacterNum()
        {
            int[] count = { 0, 0 };
            for (int i = 0; i < passengerPlaner.Length; i++)
            {
                if (passengerPlaner[i] == null)
                    continue;
                if (passengerPlaner[i].getType() == 0)
                {   // 0->priest, 1->devil
                    count[0]++;
                }
                else
                {
                    count[1]++;
                }
            }
            return count;
        }

        public void reset()
        {
            passengerPlaner = new MyCharacterController[6];
        }
    }

最後是FirstController和UserGUI和ClickGUI的程式碼

public class FirstController : MonoBehaviour, SceneController, UserAction {
    /*動態常量 readonly*/
	readonly Vector3 water_pos = new Vector3(0,-2.0F,0);
    readonly Vector3 background_pos = new Vector3(0, 0, 20);


	UserGUI userGUI;
    public BackgroundController Background;
	public CoastController fromCoast;
	public CoastController toCoast;
	public BoatController boat;
	private MyCharacterController[] characters;

	void Awake() {
		Director director = Director.getInstance ();
		director.currentSceneController = this;
		userGUI = gameObject.AddComponent <UserGUI>() as UserGUI;
		characters = new MyCharacterController[6];
        loadBackground();
		loadResources ();
	}

    public void loadBackground()
    {
        /*創造背景*/
        Background = new BackgroundController();
        Background.setPosition(background_pos);
    }

	public void loadResources() {
        /*在Resources資料夾中載入Water,只有GameObject型別的才被載入,載入到water_pos的位置,無旋轉,完全對齊於世界*/
		GameObject water = Instantiate (Resources.Load ("Perfabs/Water", typeof(GameObject)), water_pos, Quaternion.identity, null) as GameObject;
		water.name = "water";

        /*創造兩個對岸*/
		fromCoast = new CoastController ("from");
		toCoast = new CoastController ("to");

        /*創造船*/
		boat = new BoatController ();

        /*創造牧師與魔鬼*/
		loadCharacter ();
	}

	private void loadCharacter() {
		for (int i = 0; i < 3; i++) {
			MyCharacterController Character = new MyCharacterController ("priest");
            Character.setName("priest" + i);
            Character.setPosition (fromCoast.getEmptyPosition ());
            Character.getOnCoast (fromCoast);
			fromCoast.getOnCoast (Character);

			characters [i] = Character;
		}

		for (int i = 0; i < 3; i++) {
			MyCharacterController Character = new MyCharacterController ("devil");
            Character.setName("devil" + i);
            Character.setPosition (fromCoast.getEmptyPosition ());
            Character.getOnCoast (fromCoast);
			fromCoast.getOnCoast (Character);

			characters [i+3] = Character;
		}
	}


	public void moveBoat() {
		if (boat.isEmpty ()) return;
		boat.Move ();
		userGUI.status = check_game_over ();
	}

	public void characterIsClicked(MyCharacterController characterCtrl) {
		if (characterCtrl.isOnBoat ()) {
			CoastController whichCoast;
			if (boat.get_to_or_from () == -1) { // to->-1; from->1
				whichCoast = toCoast;
			} else {
				whichCoast = fromCoast;
			}

			boat.GetOffBoat (characterCtrl.getName());
			characterCtrl.moveToPosition (whichCoast.getEmptyPosition ());
			characterCtrl.getOnCoast (whichCoast);
			whichCoast.getOnCoast (characterCtrl);

		} else {
			CoastController whichCoast = characterCtrl.getCoastController ();

			if (boat.getEmptyIndex () == -1) {
				return;
			}

			if (whichCoast.get_to_or_from () != boat.get_to_or_from ())	
                // boat is not on the side of character
				return;

			whichCoast.getOffCoast(characterCtrl.getName());
			characterCtrl.moveToPosition (boat.getEmptyPosition());
			characterCtrl.getOnBoat (boat);
			boat.GetOnBoat (characterCtrl);
		}
		userGUI.status = check_game_over ();
	}

    public bool isGameOver()
    {
        return check_game_over() != 0;
    }

	int check_game_over() {
		int from_priest = 0;
		int from_devil = 0;
		int to_priest = 0;
		int to_devil = 0;

		int[] fromCount = fromCoast.getCharacterNum ();
		from_priest += fromCount[0];
		from_devil += fromCount[1];

		int[] toCount = toCoast.getCharacterNum ();
		to_priest += toCount[0];
		to_devil += toCount[1];

		if (to_priest + to_devil == 6)
			return 2;

		int[] boatCount = boat.getCharacterNum ();
		if (boat.get_to_or_from () == -1)
        {	
			to_priest += boatCount[0];
			to_devil += boatCount[1];
		}
        else
        {	
			from_priest += boatCount[0];
			from_devil += boatCount[1];
		}
		if (from_priest < from_devil && from_priest > 0)
        {
			return 1;
		}
		if (to_priest < to_devil && to_priest > 0)
        {
			return 1;
		}
		return 0;
	}

	public void restart() {
		boat.reset ();
		fromCoast.reset ();
		toCoast.reset ();
		for (int i = 0; i < characters.Length; i++) {
			characters [i].reset ();
		}
	}
}

ClickGUI和UserGUI的程式碼如下:

public class UserGUI : MonoBehaviour {
	private UserAction Player;
	public int status = 0;
	GUIStyle style;
	GUIStyle Button;

	void Start() {
		Player = Director.getInstance ().currentSceneController as UserAction;

		style = new GUIStyle();
		style.fontSize = 40;
		style.alignment = TextAnchor.MiddleCenter;

        Button = new GUIStyle("button");
        Button.fontSize = 30;
	}
	void OnGUI() {
		if (status == 1) {
			GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "Gameover!", style);
			if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", Button)) {
				status = 0;
				Player.restart ();
			}
		} else if(status == 2) {
			GUI.Label(new Rect(Screen.width/2-50, Screen.height/2-85, 100, 50), "You win!", style);
			if (GUI.Button(new Rect(Screen.width/2-70, Screen.height/2, 140, 70), "Restart", Button)) {
				status = 0;
				Player.restart ();
			}
		}

        if (GUI.Button(new Rect(0, 0, 150, 50), "Reset"))
        {
            Player.restart();
        }
	}
}
public class ClickGUI : MonoBehaviour {
	UserAction Player;
	MyCharacterController characterController;

	public void setController(MyCharacterController characterCtrl) {
		characterController = characterCtrl;
	}

	void Start() {
        Player = Director.getInstance ().currentSceneController as UserAction;
	}

    void OnMouseDown() {
        if(!Player.isGameOver())
        {
            if (gameObject.name == "boat")
            {
                Player.moveBoat();
            }
            else
            {
                Player.characterIsClicked(characterController);
            }
        }

	}
}

最後的效果圖如下所示,雖然大部分程式碼都是參考師兄的....但是感覺還是在搬磚過程中瞭解到不少東西....