3D遊戲-作業三-空間與運動

weixin_46092070發表於2020-10-04

一、簡答並用程式驗證

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

遊戲物件運動的本質就是它的空間屬性的變化,包括空間位置,旋轉角度,放縮大小等等

2. 請用三種方法以上方法,實現物體的拋物線運動。(如,修改Transform屬性,使用向量Vector3的方法…)

方法一 直接修改Transform屬性

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

public class method1 : MonoBehaviour
{   
    private float vX = 5;
    private float vY = 0;
    private float g = 10;
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        vY += g * Time.deltaTime;
        transform.position += Vector3.right * vX * Time.deltaTime;
        transform.position += Vector3.down * vY * Time.deltaTime;
    }
}

方法二 通過建立Vector3來修改Transform屬性
此時只需要修改Update函式部分就可以了

void Update()
    {
        vY += g * Time.deltaTime;
        Vector3 temp = new Vector3(vX * Time.deltaTime, - vY * Time.deltaTime, 0);
        transform.position += temp;
    }

方法三 transform的Translate方法
同樣修改Update函式,如下

void Update()
    {
        vY += g * Time.deltaTime;
        Vector3 temp = new Vector3(vX * Time.deltaTime, - vY * Time.deltaTime, 0);
        transform.Translate(temp);
    }

方法四 通過修改物體的重力屬性
這個方法是我搜尋得知物件的重力屬性,然後修改這個屬性實現的

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

public class method4 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        gameObject.AddComponent<Rigidbody>();
        gameObject.GetComponent<Rigidbody>().velocity = new Vector3(2, 0, 0);
    }

    // Update is called once per frame
    void Update() { }
}

3. 寫一個程式,實現一個完整的太陽系, 其他星球圍繞太陽的轉速必須不一樣,且不在一個法平面上。

首先建立太陽系
在這裡插入圖片描述
建立好各個行星,並調整它們的位置和大小,貼上相應的圖片

編寫程式碼,實現自轉和公轉
在這裡,自轉通過函式Rotate來實現,公轉通過函式RotateAround來實現,程式碼如下

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

public class solarSystem : MonoBehaviour
{
    public Transform sun;
    public Transform mercury;
    public Transform venus;
    public Transform earth;
    public Transform mars;
    public Transform jupiter;
    public Transform saturn;
    public Transform uranus;
    public Transform neptune;

    // Start is called before the first frame update
    void Start(){ }

    // Update is called once per frame
    void Update()
    {
        //RotateAround 實現公轉
        //Rotate實現自轉
        sun.Rotate(Vector3.up * Time.deltaTime * 5);

        mercury.RotateAround(this.transform.position, new Vector3(3, 15, 0), 47 * Time.deltaTime);
		mercury.Rotate(Vector3.up * Time.deltaTime * 300);
		
        venus.RotateAround(this.transform.position, new Vector3(2,10, 0), 35 * Time.deltaTime);
		venus.Rotate(Vector3.up * Time.deltaTime * 280);
		
        earth.RotateAround(this.transform.position, new Vector3(1, 10, 0), 30 * Time.deltaTime);
		earth.Rotate(Vector3.up * Time.deltaTime * 250);
		
        mars.RotateAround(this.transform.position, new Vector3(2, 15, 0), 24 * Time.deltaTime);
		mars.Rotate(Vector3.up * Time.deltaTime * 220);
		
        jupiter.RotateAround(this.transform.position, new Vector3(2, 10, 0), 13 * Time.deltaTime);
		jupiter.Rotate(Vector3.up * Time.deltaTime * 180);
		
        saturn.RotateAround(this.transform.position, new Vector3(1, 10, 0), 9 * Time.deltaTime);
		saturn.Rotate(Vector3.up * Time.deltaTime * 160);
		
        uranus.RotateAround(this.transform.position, new Vector3(2, 10, 0), 6 * Time.deltaTime);
		uranus.Rotate(Vector3.up * Time.deltaTime * 150);
		
        neptune.RotateAround(this.transform.position, new Vector3(3, 15, 0), 5 * Time.deltaTime);
		neptune.Rotate(Vector3.up * Time.deltaTime * 140);
    }
}

此時還有一個問題,就是怎麼把地月系統加到現在的這個太陽系裡,因為上面這個系統中地球是在自轉的,直接把月球繞它轉的話,就必須要考慮到自轉對月球公轉的影響,所以在這裡我們採用另一種八法來解決這個問題。
那就是建立一個新物件,他的位置大小都與地球相同,同樣繞太陽公轉,但是不自轉,這樣的話,就可以讓月球來繞它自轉,這樣就解決了這個問題。

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

public class solarSystem : MonoBehaviour
{
    public Transform moon;
    public Transform earthclone;

    // Start is called before the first frame update
    void Start(){ }

    // Update is called once per frame
    void Update()
    {
        //實現地月系統
        earthclone.RotateAround(this.transform.position, new Vector3(1, 10, 0), 30 * Time.deltaTime);
	    moon.transform.RotateAround (earthclone.transform.position, new Vector3(0, 12, 0), 500 * Time.deltaTime);
    }
}

這樣就完成了太陽系模擬。

顯示軌跡
雖然已經完成了太陽系模擬的過程,但是在執行的時候可以發現,我們看著這樣雜亂無章的效果很頭疼,完全看不到行星運動軌跡,這壓根的話就不能體現這個太陽系的行星軌道之間的區別等等,所以通過搜尋,我找到了一個解決辦法。
那就是通過Trail Render來顯示運動軌跡。
這裡是參考部落格
在這裡插入圖片描述
以上就是最後實現的效果。

二、 程式設計實踐-牧師與魔鬼

Priests and Devils
Priests and Devils is a puzzle game in which you will help the Priests and Devils to cross the river within the time limit. There are 3 priests and 3 devils at one side of the river. They all want to get to the other side of this river, but there is only one boat and this boat can only carry two persons each time. And there must be one person steering the boat from one side to the other side. In the flash game, you can click on them to move them and click the go button to move the boat to the other direction. If the priests are out numbered by the devils on either side of the river, they get killed and the game is over. You can try it in many > ways. Keep all priests alive! Good luck!

程式需要滿足的要求

play the game ( http://www.flash-game.net/game/2535/priests-and-devils.html )
列出遊戲中提及的事物(Objects)
用表格列出玩家動作表(規則表),注意,動作越少越好
請將遊戲中物件做成預製
在場景控制器 LoadResources 方法中載入並初始化 長方形、正方形、球 及其色彩代表遊戲中的物件。
使用 C# 集合型別 有效組織物件
整個遊戲僅 主攝像機 和 一個 Empty 物件, 其他物件必須程式碼動態生成!!! 。 整個遊戲不許出現 Find 遊戲物件, SendMessage 這類突破程式結構的 通訊耦合 語句。 違背本條準則,不給分
請使用課件架構圖程式設計,不接受非 MVC 結構程式
注意細節,例如:船未靠岸,牧師與魔鬼上下船運動中,均不能接受使用者事件!

遊戲中提及的事物

牧師(3個)、魔鬼(3個)、船、河流、河岸(兩側)

玩家動作表(規則表)

玩家動作條件執行結果
點選岸上的牧師或魔鬼當前船在岸邊,且船未滿點選物件上船
點選船上的牧師或魔鬼當前船在岸邊點選物件上岸
點選船當前船在岸邊,且船上至少有一個牧師或魔鬼船移動到對岸

遊戲物件預製

預製結果如下
在這裡插入圖片描述

根據MVC架構程式設計

MVC架構
在這裡插入圖片描述

實現過程

首先實現SSDirector
它的功能是控制場景切換,在這裡它繼承於System.Object,保持導演類一直存在,不被Unity記憶體管理而管理
實現程式碼如下:

public class SSDirector : System.Object
{
	private static SSDirector _instance;
	public ISceneController CurrentScenceController { get; set; }
	public static SSDirector GetInstance()
	{
		if (_instance == null)
		{
			_instance = new SSDirector();
		}
		return _instance;
	}
}

接下來就是建立介面
介面的實現,方便了其他模組呼叫此名稱空間。分別是場景控制器的介面,利用這個介面,得知當前場景是由哪個控制,然後向場景控制器傳達要求,以及使用者動作的介面,使用者通過鍵盤、滑鼠等對遊戲發出指令,這個指令會觸發遊戲中的一些行為,這一部分由IUserAction來宣告。

namespace interfacecon{
	public interface ISceneController                      
	{
		void LoadResources();
	}
	public interface IUserAction                           
	{
		void MoveBoat();                                   
		void Restart();                                    
		void MoveRole(RoleModel role);                     
		int Check();                                       
	}
}

建立使用者介面
在這裡,包括左上角的遊戲規則介紹按鈕,以及在遊戲結束之後的彈出按鈕和標籤等等

public class UserGUI : MonoBehaviour {

	private IUserAction action;
	public int sign = 0;

	bool isShow = false;
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnGUI()
	{
		GUIStyle text_style;
		GUIStyle button_style;
		text_style = new GUIStyle()
		{
			fontSize = 30
		};
		button_style = new GUIStyle("button")
		{
			fontSize = 15
		};
		if (GUI.Button(new Rect(10, 10, 60, 30), "Rule", button_style))
		{
			if (isShow)
				isShow = false;
			else
				isShow = true;
		}
		if(isShow)
		{
			GUI.Label(new Rect(Screen.width / 2 - 85, 10, 200, 50), "讓全部牧師和惡魔都渡河");
			GUI.Label(new Rect(Screen.width / 2 - 120, 30, 250, 50), "每一邊惡魔數量都不能多於牧師數量");
			GUI.Label(new Rect(Screen.width / 2 - 85, 50, 250, 50), "點選牧師、惡魔、船移動");
		}
		if (sign == 1)
		{
			GUI.Label(new Rect(Screen.width / 2-90, Screen.height / 2-120, 100, 50), "Gameover!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
		else if (sign == 2)
		{
			GUI.Label(new Rect(Screen.width / 2 - 80, Screen.height / 2 - 120, 100, 50), "You Win!", text_style);
			if (GUI.Button(new Rect(Screen.width / 2 - 70, Screen.height / 2, 100, 50), "Restart", button_style))
			{
				action.Restart();
				sign = 0;
			}
		}
	}
}

實現控制器
它對場景中的具體物件進行操作,是遊戲物件的行為改變的核心。

public class Controller : MonoBehaviour, ISceneController, IUserAction
{
	public LandModel start_land;            
	public LandModel end_land;              
	public BoatModel boat;                  
	private RoleModel[] roles;              
	UserGUI user_gui;
	void Start ()
	{
		SSDirector director = SSDirector.GetInstance();
		director.CurrentScenceController = this;
		user_gui = gameObject.AddComponent<UserGUI>() as UserGUI;
		LoadResources();
	}

	public void LoadResources()              
	{
		GameObject water = Instantiate(Resources.Load("Prefabs/Water", typeof(GameObject)), new Vector3(0,-10,-2), Quaternion.identity) as GameObject;
		water.name = "water";       
		start_land = new LandModel("start");
		end_land = new LandModel("end");
		boat = new BoatModel();
		roles = new RoleModel[6];

		for (int i = 0; i < 3; i++)
		{
			RoleModel role = new RoleModel("priest");
			role.SetName("priest" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}

		for (int i = 3; i < 6; i++)
		{
			RoleModel role = new RoleModel("devil");
			role.SetName("devil" + i);
			role.SetPosition(start_land.GetEmptyPosition());
			role.GoLand(start_land);
			start_land.AddRole(role);
			roles[i] = role;
		}
	}

	public void MoveBoat()                  
	{
		if (boat.IsEmpty() || user_gui.sign != 0) return;
		boat.BoatMove();
		user_gui.sign = Check();
	}

	public void MoveRole(RoleModel role)    
	{
		if (user_gui.sign != 0) return;
		if (role.IsOnBoat())
		{
			LandModel land;
			if (boat.GetBoatSign() == -1)
				land = end_land;
			else
				land = start_land;
			boat.DeleteRoleByName(role.GetName());
			role.Move(land.GetEmptyPosition());
			role.GoLand(land);
			land.AddRole(role);
		}
		else
		{                                
			LandModel land = role.GetLandModel();
			if (boat.GetEmptyNumber() == -1 || land.GetLandSign() != boat.GetBoatSign()) return;   
			land.DeleteRoleByName(role.GetName());
			role.Move(boat.GetEmptyPosition());
			role.GoBoat(boat);
			boat.AddRole(role);
		}
		user_gui.sign = Check();
	}

	public void Restart()
	{
		SceneManager.LoadScene(0);
	}

	public int Check()
	{
		int start_priest = (start_land.GetRoleNum())[0];
		int start_devil = (start_land.GetRoleNum())[1];
		int end_priest = (end_land.GetRoleNum())[0];
		int end_devil = (end_land.GetRoleNum())[1];

		if (end_priest + end_devil == 6)     
			return 2;

		int[] boat_role_num = boat.GetRoleNumber();
		if (boat.GetBoatSign() == 1)         
		{
			start_priest += boat_role_num[0];
			start_devil += boat_role_num[1];
		}
		else                                  
		{
			end_priest += boat_role_num[0];
			end_devil += boat_role_num[1];
		}
		if (start_priest > 0 && start_priest < start_devil) 
		{      
			return 1;
		}
		if (end_priest > 0 && end_priest < end_devil)        
		{
			return 1;
		}
		return 0;                                             
	}
}

接下來就是ClickController
它的功能是檢測船和牧師與魔鬼是否被點選,然後進行操作。

public class Click : MonoBehaviour
{
	IUserAction action;
	RoleModel role = null;
	BoatModel boat = null;
	public void SetRole(RoleModel role)
	{
		this.role = role;
	}
	public void SetBoat(BoatModel boat)
	{
		this.boat = boat;
	}
	void Start()
	{
		action = SSDirector.GetInstance().CurrentScenceController as IUserAction;
	}
	void OnMouseDown()
	{
		if (boat == null && role == null) return;
		if (boat != null)
			action.MoveBoat();
		else if(role != null)
			action.MoveRole(role);
	}
}

最後是對具體遊戲物件的操作
首先是對船的操作,包括船的運動,以及牧師與魔鬼上下船的繫結

public class BoatModel
	{
		GameObject boat;                                          
		Vector3[] start_empty_pos;                                    
		Vector3[] end_empty_pos;                                      
		Move move;                                                    
		Click click;
		int boat_sign = 1;                                                     
		RoleModel[] roles = new RoleModel[2];                                  

		public BoatModel()
		{
			boat = Object.Instantiate(Resources.Load("Prefabs/Boat", typeof(GameObject)), new Vector3(25, -2.5F, 0), Quaternion.identity) as GameObject;

			boat.name = "boat";
			move = boat.AddComponent(typeof(Move)) as Move;
			click = boat.AddComponent(typeof(Click)) as Click;
			click.SetBoat(this);
			start_empty_pos = new Vector3[] { new Vector3(18, 4, 0), new Vector3(32,4 , 0) };
			end_empty_pos = new Vector3[] { new Vector3(-32, 4, 0), new Vector3(-18,3 , 0) };
		}

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

		public void BoatMove()
		{
			if (boat_sign == -1)
			{
				move.MovePosition(new Vector3(25, -2.5F, 0));
				boat_sign = 1;
			}
			else
			{
				move.MovePosition(new Vector3(-25, -2.5F, 0));
				boat_sign = -1;
			}
		}

		public int GetBoatSign(){ return boat_sign;}

		public RoleModel DeleteRoleByName(string role_name)
		{
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

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

		public Vector3 GetEmptyPosition()
		{
			Vector3 pos;
			if (boat_sign == -1)
				pos = end_empty_pos[GetEmptyNumber()];
			else
				pos = start_empty_pos[GetEmptyNumber()];
			return pos;
		}

		public void AddRole(RoleModel role)
		{
			roles[GetEmptyNumber()] = role;
		}

		public GameObject GetBoat(){ return boat; }

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

然後是對牧師與魔鬼(即role)的操作
包括上船、上岸

public class RoleModel
	{
		GameObject role;
		int role_sign;             
		Click click;
		bool on_boat;              
		Move move;
		LandModel land_model = (SSDirector.GetInstance().CurrentScenceController as Controller).start_land;
		public RoleModel(string role_name)
		{
			if (role_name == "priest")
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Priests", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -70, 0)) as GameObject;
				role_sign = 0;
			}
			else
			{
				role = Object.Instantiate(Resources.Load("Prefabs/Devils", typeof(GameObject)), Vector3.zero, Quaternion.Euler(0, -70, 0)) as GameObject;
				role_sign = 1;
			}
			move = role.AddComponent(typeof(Move)) as Move;
			click = role.AddComponent(typeof(Click)) as Click;
			click.SetRole(this);
		}
		public int GetSign() { return role_sign;}
		public LandModel GetLandModel(){return land_model;}
		public string GetName() { return role.name; }
		public bool IsOnBoat() { return on_boat; }
		public void SetName(string name) { role.name = name; }
		public void SetPosition(Vector3 pos) { role.transform.position = pos; }
		public void Move(Vector3 vec)
		{
			move.MovePosition(vec);
		}
		public void GoLand(LandModel land)
		{  
			role.transform.parent = null;
			land_model = land;
			on_boat = false;
		}
		public void GoBoat(BoatModel boat)
		{
			role.transform.parent = boat.GetBoat().transform;
			land_model = null;          
			on_boat = true;
		}
	}

對陸地的操作
用於控制角色上下岸,船的離開和停靠

public class LandModel
	{
		GameObject land;                                
		Vector3[] positions;                            
		int land_sign;                                  
		RoleModel[] roles = new RoleModel[6];           
		public LandModel(string land_mark)
		{
			positions = new Vector3[] {new Vector3(46F,14.73F,-4), new Vector3(55,14.73F,-4), new Vector3(64F,14.73F,-4),
				new Vector3(73F,14.73F,-4), new Vector3(82F,14.73F,-4), new Vector3(91F,14.73F,-4)};
			if (land_mark == "start")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = 1;
			}
			else if(land_mark == "end")
			{
				land = Object.Instantiate(Resources.Load("Prefabs/Land", typeof(GameObject)), new Vector3(-70, 1, 0), Quaternion.identity) as GameObject;
				land_sign = -1;
			}
		}

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

		public int GetLandSign() { return land_sign; }

		public Vector3 GetEmptyPosition()               
		{
			Vector3 pos = positions[GetEmptyNumber()];
			pos.x = land_sign * pos.x;                  
			return pos;
		}

		public void AddRole(RoleModel role)             
		{
			roles[GetEmptyNumber()] = role;
		}

		public RoleModel DeleteRoleByName(string role_name)      
		{ 
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null && roles[i].GetName() == role_name)
				{
					RoleModel role = roles[i];
					roles[i] = null;
					return role;
				}
			}
			return null;
		}

		public int[] GetRoleNum()
		{
			int[] count = { 0, 0 };                    
			for (int i = 0; i < roles.Length; i++)
			{
				if (roles[i] != null)
				{
					if (roles[i].GetSign() == 0)
						count[0]++;
					else
						count[1]++;
				}
			}
			return count;
		}	
	}

最後,只需要將Controller掛載到新建的空物件上,就可以執行遊戲了,示例一局的結果如下:
在這裡插入圖片描述

在這裡插入圖片描述

思考題

使用向量與變換,實現並擴充套件 Tranform 提供的方法,如 Rotate、RotateAround 等

法一 直接通過公式計算
根據旋轉後點的位置的變換公式,可以直接計算得到旋轉後的位置,然後修改物體的空間屬性就可以實現旋轉

void Update () {
		float delta = Mathf.Atan (this.transform.position.y / this.transform.position.x);
		float temp_x = this.transform.position.x;
		float temp_y = this.transform.position.y;
		this.transform.position.x = Mathf.Cos (delta) * temp_x - Mathf.Sin (delta) * temp_y;
		this.transform.position.y = Mathf.Sin (delta) * temp_x + Mathf.Cos (delta) * temp_y;
}

法二 通過vector3來實現

int angle;
void Update () {
		angle -= Mathf.Atan (this.transform.position.y / this.transform.position.x);
		transform.Translate(new Vector3(Time.deltaTime * 10*Mathf.Cos(angle), Time.deltaTime * 10*Mathf.Sin(angle), 0));  
}

相關文章