3d遊戲專案實訓一週總結
近期學校有一個3d遊戲的專案實訓,要求學生組成專案組共同完成一個3d遊戲的製作。當然,遊戲製作過程中的所有任務,包括設計、策劃、美工、編碼、測試等等,都將由我們自己完成。我們的專案組決定製作一款類似球球大作戰以“吞食”為核心玩法的休閒遊戲,遊戲的主角是鯤(?),另外我們的遊戲場景要做成3d版本。我們的專案組採用Unity引擎進行遊戲開發,並且打算實現線上多人對戰的功能。專案實訓開始到今天已經經過約一週時間,在這一週時間內我們其實主要還是以學習為主,不過也完成了遊戲指標過程中的一些任務。我在這一週內的學習成果和工作成果主要包括兩方面:
一:系統總體設計文件的編寫:
這一部分的內容還是比較多的,雖然有一份文件模板,但由於之前沒有類似的經驗,所以一開始寫起來還是覺得有點困難。我在編寫文件之前也在網上查閱了一些關於文件編寫的資料,同時結合實際的文件編寫過程,作出了下面的一些個人總結,包括內容和編寫技巧兩個方面。
在文件的內容方面:
1.總體設計:主要對整個遊戲進行一些概要性的說明,包括功能模組、關卡設計、介面設計、互動事件設計等等。
功能模組可以理解為遊戲開發時所需要的一些主要的元件,如場景定義、事件處理等等(功能模組從使用者的角度來說也可以遊戲提供提供給使用者的一些子系統,如好友系統、排名系統等等);
關卡設計中定義主遊戲場景中的一些規則;
另外互動事件不只包括玩家輸入,也包括AI等元素觸發的其他事件。
2.遊戲狀態轉換設計:遊戲中的狀態包括主介面、遊戲進行中等狀態。
3.總體架構設計:整個遊戲開發過程採用的架構模式。網路多人對戰遊戲一般採用客戶端/伺服器的架構模式。
在文件的編寫技巧方面:
1.注意文件的內容一定要清晰準確,不能出現“可以這麼做”“可能要”之類的話,否則別人看了文件怎麼知道是要做還是不要做呢?
2.避免相同的內容重複。一個內容只在文件出現一次,除了避免閱讀的人讀到相同的內容降低效率,更重要的是可以避免修改文件時帶來的麻煩:如果文件中有內容重複,那麼一改則全改,而且萬一不小心漏了一兩處沒改呢?(感覺上避免重複的意義跟程式設計概念中的“函式”的意義有種異曲同工之妙)
3.把握好內容細分的粒度。文件的內容不能太籠統也不能太詳細...
二:遊戲角色控制指令碼的編寫:
這一部分算是一個不難不易的技術點,主要是實現通過使用者輸入操控遊戲角色和遊戲視角的功能。我們的角色要能夠在3D的場景中的3個自由度中移動,所以遊戲角色的操控方法其實也不太好設計。而且,我們需要設計一個跟隨角色移動的攝像機,這個攝像機本身也有一個活動的範圍。經過本人的探索和測試,目前算是初步實現了這樣的控制功能,但效能還有待優化。
遊戲角色的控制功能:
1.滑鼠在遊戲畫面上移動時,改變遊戲角色的朝向。
定義一個Vector3變數儲存角色的朝向資訊,在每一幀中,獲取滑鼠的偏移並對應更新其朝向資訊,我這裡主要通過向量的球面插值(Vector3.Slerp)實現。
2.按下WSAD時,角色移動(移動方向根據其朝向方向決定)。按下W鍵時向其朝向的方向移動。
每一幀根據輸入獲取角色移動方向的向量(和其朝向資訊有關),根據這個向量更改角色的位置即可。
遊戲攝像機的控制功能:
1.跟隨遊戲角色的移動。
攝像機有一個遊戲角色的引用,表示其跟隨的物件。攝像機的指令碼每一幀獲取其跟隨遊戲角色的朝向方向,確定攝像機的朝向方向(通過球面插值),再根據遊戲角色的位置,確定攝像機的位置,然後讓攝像機看向遊戲角色即可。
2.按下右鍵,滑鼠移動改變攝像機視角(此時不改變遊戲角色本身)。
攝像機的指令碼本身儲存自身的偏移角度資料,當右鍵按下,滑鼠移動改變攝像機偏移角度,由遊戲角色的朝向和攝像機的偏移角度共同決定攝像機的最終朝向。
3.鬆開右鍵,攝像機歸位,要求有平滑過渡。
攝像機歸位只需把攝像機的偏移角度置零即可,但要實現平滑效果則不能在一幀之內馬上把偏移角度置零,我的方案是讓偏移角度逐幀按比例減小直至歸零。
//PlayerController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PlayerController : MonoBehaviour
{
public float mouseSensitivity = 1.0f;
private Vector3 towards;//角色朝向的方向
private Vector3 up;//向上向量
private Vector3 right;//規定向右向量的y方向分量為0,避免鏡頭“翻滾”
private float speed;
private enum MOVE_DIRECTION { FRONT, FRONT_LEFT, FRONT_RIGHT, LEFT, RIGHT };
private MOVE_DIRECTION moveDirection;
void Start()
{
speed = gameObject.GetComponent<Player>().speed;
towards = new Vector3(0.0f, 0.0f, 1.0f);
up = new Vector3(0.0f, 1.0f, 0.0f);
right = new Vector3(1.0f, 0.0f, 0.0f);
moveDirection = MOVE_DIRECTION.FRONT;
}
private void FixedUpdate()
{
if (!Input.GetMouseButton(1))//如果滑鼠右鍵被按下,則不需要對角色的朝向方向作任何修改
{
float mouseX = -Input.GetAxis("Mouse X") * mouseSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
//ctrlX和ctrlY可置為1或-1,控制角色朝向的操控方式
float ctrlX = -1.0f, ctrlY = -1.0f;
right = Vector3.Cross(towards, up);
towards = Vector3.SlerpUnclamped(towards, up, ctrlY * mouseY / 90.0f);
towards.Normalize();
//限制最大“俯仰角”不超過45度
if (towards.y * towards.y > towards.x * towards.x + towards.z * towards.z)
{
float sqXXZZ = Mathf.Sqrt(towards.x * towards.x + towards.z * towards.z);
towards.y = towards.y > 0 ? 0.707f : -0.707f;
towards.x = 0.707f * towards.x / sqXXZZ;
towards.z = 0.707f * towards.z / sqXXZZ;
}
towards.Normalize();
towards = Vector3.SlerpUnclamped(towards, right, ctrlX * mouseX / 90.0f);
towards.Normalize();
right = new Vector3(towards.z, 0.0f, -towards.x).normalized;
up = Vector3.Cross(right, towards);
up.Normalize();
}
//根據朝向和移動方向最終決定角色的“旋轉”
float moveHorizontal = Input.GetAxis("Horizontal");
float moveVertical = Input.GetAxis("Vertical");
Vector3 lookAt = new Vector3();
if (moveHorizontal > 0.0f)
if (moveVertical > 0.0f)
moveDirection = MOVE_DIRECTION.FRONT_RIGHT;
else
moveDirection = MOVE_DIRECTION.RIGHT;
else if (moveHorizontal < 0.0f)
if (moveVertical > 0.0f)
moveDirection = MOVE_DIRECTION.FRONT_LEFT;
else
moveDirection = MOVE_DIRECTION.LEFT;
else if (moveVertical >= 0.0f)
moveDirection = MOVE_DIRECTION.FRONT;
switch (moveDirection)
{
case MOVE_DIRECTION.FRONT:
lookAt = towards;
break;
case MOVE_DIRECTION.FRONT_LEFT:
lookAt = Vector3.Slerp(towards, -right, 0.5f);
break;
case MOVE_DIRECTION.LEFT:
lookAt = -right;
break;
case MOVE_DIRECTION.FRONT_RIGHT:
lookAt = Vector3.Slerp(towards, right, 0.5f);
break;
case MOVE_DIRECTION.RIGHT:
lookAt = right;
break;
}
transform.LookAt(gameObject.transform.position + lookAt);
//限制不能向後移動
if (moveVertical < 0.0f)
moveVertical = 0.0f;
//更新位置
Vector3 move = towards * moveVertical + right * moveHorizontal;
gameObject.transform.position += (move.normalized * speed);
}
public Vector3 GetTowards()
{
return towards;
}
}
//CameraController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraController : MonoBehaviour
{
public GameObject player;
public float mouseSensitivity = 3.0f;
public float scrollSensitivity = 3.0f;
private float distanceToPlayer;
public float distanceToPlayerInit = 7.5f;
public float maxDistanceInit = 10.0f;
public float minDistanceInit = 5.0f;
private Vector3 direction;//方向向量,玩家角色指向攝像機
//向右向量和向上向量是相對“角色朝向”的
private Vector3 up;
private Vector3 right;//規定向右向量的y方向分量為0,避免鏡頭“翻滾”
//攝像機本身的偏移角度
private float offsetAngleHorizontal;
private float offsetAngleVertical;
void Start()
{
distanceToPlayer = distanceToPlayerInit;
offsetAngleHorizontal = 0.0f;
offsetAngleVertical = 0.0f;
}
private void FixedUpdate()
{
if (Input.GetMouseButton(1))//滑鼠右鍵被按下,調整視角
{
float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;
offsetAngleHorizontal += mouseX;
offsetAngleVertical += mouseY;
}
else//視角迴歸原位,對視角進行平滑過渡
{
if (Mathf.Abs(offsetAngleHorizontal) < 1e-6)
offsetAngleHorizontal = 0.0f;
else
offsetAngleHorizontal *= 0.6f;
if (Mathf.Abs(offsetAngleVertical) < 1e-6)
offsetAngleVertical = 0.0f;
else
offsetAngleVertical *= 0.6f;
}
//計算攝像機朝向方向
Vector3 playerTowards = player.gameObject.GetComponent<PlayerController>().GetTowards();
right = new Vector3(playerTowards.z, 0.0f, -playerTowards.x).normalized;
up = Vector3.Cross(playerTowards, right).normalized;
direction = Vector3.SlerpUnclamped(playerTowards, right, offsetAngleHorizontal / 90.0f);
direction = Vector3.SlerpUnclamped(direction, up, 1.6f + offsetAngleVertical / 90.0f).normalized;
//攝像機距離角色的距離根據角色大小來變化
float playerSize = player.GetComponent<Player>().GetPlayerSize().x;
distanceToPlayer = playerSize * distanceToPlayerInit;
float scroll = Input.GetAxis("Mouse ScrollWheel");
distanceToPlayerInit -= scroll * scrollSensitivity * playerSize;//滾輪往“上”滾時,攝像機距離拉近
distanceToPlayerInit = Mathf.Clamp(distanceToPlayerInit, minDistanceInit, maxDistanceInit);
}
//攝像機位置的更新一般要放在其跟隨物體的更新之後
void LateUpdate()
{
transform.position = player.gameObject.transform.position + direction * distanceToPlayer;
transform.LookAt(player.transform);
}
}