3D Game Programming & Design:遊戲對象與圖形基礎
遊戲對象與圖形基礎
1、基本操作演練【建議做】
- 下載 Fantasy Skybox FREE, 構建自己的遊戲場景
首先在Asset Store中搜索Fantasy Skybox FREE,點擊download下載然後import導入。
我在導入的時候遇到了一點小問題,就是點import怎麼都沒有反應。然後解決方法是重新啓動了一次unity,然後在最上面的菜單欄找到asset->import unity package,會出現如下圖所示的界面,然後導入即可。
在下面asset裏可以看到Fantasy Skybox FREE導入成功。
然後我們可以在Materials裏選擇一個的天空盒子樣式:
在Assets 上下文菜單 -> create -> Material 給新建的skybox起名 mysky
在 Inspector 視圖中選擇 Shader -> Skybox -> 6Sided,給各個面分別貼上我們下載好的資源貼圖。
這樣一個skybox就構建好了,然後我們把它應用於當前的遊戲場景中。
之後我們開始加terrain地形系統。
加入terrain有很多可以調的參數,如下圖所示,Terrain有五個圖標,左邊第一個是用來增加terrain的鄰居terrain的,也就是擴充地形,第二個是繪製terrain,有增加地形高低,上顏色或紋理等等功能,第三個是種樹,選中樹木的預製件然後在要種的地方點擊即可,第四個是繪製細節,可以改筆刷的屬性,最後一個是settings,就是設置整塊terrain的參數的。
上色後的terrain如下圖所示:
因爲這個的baking過程比較長,爲了節省時間,沒有等整個種樹效果出來就關掉了(省點電),大概遊戲場景製作效果如下圖所示:
- 寫一個簡單的總結,總結遊戲對象的使用
GameObject 菜單上游戲元素是最基礎的。儘管實際遊戲生產中我們更多依賴模型、預製,但它們都是由這些元素構成。只有理解它們才能更好的控制它們。
Unity 中常用的遊戲對象主要有以下幾類:
- Empty :不顯示卻是最常用對象之一,通常我們掛載腳本都是用empty對象
作爲子對象的容器
創建一個新的對象空間- 3D 物體:3D遊戲中的重要組成部分,可以改變其網格和材質,三角網格是遊戲物體表面的唯一形式。
· 基礎 3D 物體(Primitive Object):立方體(Cube)、球體(Sphere)、膠囊體(Capsule)、圓柱體(Sylinder)、平面(Plane)、四邊形(Quad)
· 構造 3D 物體:由三角形網格構建的物體:如地形可用於創建陸地、山川、河流、小徑等地理結構,打造用戶的遊戲場景。- 2D物體:遊戲中的二維物體。
- Camera 攝像機,觀察遊戲世界的窗口
- Light 光線,遊戲世界的光源,光與影是讓遊戲世界富有魅力。燈光是每個場景的重要組成部分。網格和紋理定義了場景的形狀和外觀,而燈光定義了3D環境的顏色和情緒。
- Audio 聲音:3D遊戲中的聲音來源。
- UI 基於事件的 new UI 系統
- Particle System 粒子系統與特效
2、編程實踐
牧師與魔鬼 動作分離版
本次作業需要在上次作業的基礎上,將場記中的動作分離出來,首先要創建動作管理器。
上次的牧師與惡魔中,惡魔、牧師、船這三個遊戲對象,用了獨立的類來實現操作,分別創建類對象後,通過用戶與用戶界面的交互調用不同對象的函數獨立實現相對應的遊戲對象的運動。而本週的動作分離版,需要用到動作管理器和動作控制器,使用動作控制器控制所有遊戲對象的運動樣式,而用戶通過用戶界面交互通知動作管理器調用傳入的控制器並實現其內的遊戲對象的動作。即動作分離後由管理器、控制器兩個類管理動作。
動作管理是遊戲內容的重要內容,這次作業要搞清楚簡單動作的組合問題,實現動作管理器,實現基礎動作類,回調實現動作完成通知(經典方法),其規劃與設計的UML圖如下所示:
動作基類:
基類創建了動作管理器的基本元素,它繼承了Unity的ScriptableObject類,該類是不需要綁定GameObject對象的可編程類。其中的屬性包括enable(動作是否進行),destroy(動作是否該被銷燬)。同時利用接口實現遊戲的通信:callback。
public class SSAction : ScriptableObject
{
public bool enable = true;
public bool destroy = false;
public GameObject gameobject;
public Transform transform;
public ISSActionCallback callback;
protected SSAction() {}
public virtual void Start()
{
throw new System.NotImplementedException();
}
public virtual void Update()
{
throw new System.NotImplementedException();
}
}
簡單動作實現類:
實現具體動作,將一個物體移動到目標位置,並通知任務完成。其中GetSSAction函數通過獲取物體運動的目的地和速度,讓unity自己建立一個動作類。當動作完成時銷燬該動作並告訴管理者動作已經完成,這以功能在UpDate中實現,畢竟要通過每一幀來判斷是否該結束動作。
public class SSMoveToAction : SSAction //移動
{
public Vector3 target;
public float speed;
private SSMoveToAction(){}
public static SSMoveToAction GetSSAction(Vector3 target, float speed)
{
SSMoveToAction action = ScriptableObject.CreateInstance<SSMoveToAction>();
action.target = target;
action.speed = speed;
return action;
}
public override void Update()
{
this.transform.position = Vector3.MoveTowards(this.transform.position, target, speed*Time.deltaTime);
if (this.transform.position == target)
{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
public override void Start()
{
}
}
組合動作實現類:
實現一個動作組合序列,順序播放動作。讓動作組合繼承抽象動作,能夠被進一步組合;實現回調接受,能接收被組合動作的事件。創建一個動作順序執行序列,-1 表示無限循環,start 開始動作。Update方法執行執行當前動作。SSActionEvent 收到當前動作執行完成,推下一個動作,如果完成一次循環,減次數。如完成,通知該動作的管理者。Start 執行動作前,爲每個動作注入當前動作遊戲對象,並將自己作爲動作事件的接收者。OnDestory 如果自己被註銷,應該釋放自己管理的動作。在UpDate中每一幀完成當前動作,當這一序列的動作完成時,停止並通過回調函數告訴管理者該序列動作已完成,並刪除序列。在執行動作前,我們需要爲每一個動作注入遊戲對象,在Start中實現。
public class SequenceAction: SSAction, ISSActionCallback
{
public List<SSAction> sequence;
public int repeat = -1;
public int start = 0;
public static SequenceAction GetSSAcition(int repeat, int start, List<SSAction> sequence)
{
SequenceAction action = ScriptableObject.CreateInstance<SequenceAction>();
action.repeat = repeat;
action.sequence = sequence;
action.start = start;
return action;
}
public override void Update()
{
if (sequence.Count == 0) return;
if (start < sequence.Count)
{
sequence[start].Update();
}
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null)
{
source.destroy = false; //先保留這個動作,如果是無限循環動作組合之後還需要使用
this.start++;
if (this.start >= sequence.Count)
{
this.start = 0;
if (repeat > 0) repeat--;
if (repeat == 0)
{
this.destroy = true;
this.callback.SSActionEvent(this);
}
}
}
public override void Start()
{
foreach(SSAction action in sequence)
{
action.gameobject = this.gameobject;
action.transform = this.transform;
action.callback = this;
action.Start();
}
}
void OnDestroy()
{
}
}
動作管理基類:
首先建立MonoBehavior管理動作,動作做完自動回收,在這之中必然有需要等待的動作和已經做完的動作,顯然,需要等待的動作加入等待列表,需要刪除的動作加入刪除列表。提供了運行一個新動作的方法RunAction。該方法把遊戲對象與動作綁定,並綁定該動作事件的消息接收者。
public class SSActionManager: MonoBehaviour, ISSActionCallback //action管理器
{
private Dictionary<int, SSAction> actions = new Dictionary<int, SSAction>(); //將執行的動作的字典集合,int爲key,SSAction爲value
private List<SSAction> waitingAdd = new List<SSAction>(); //等待去執行的動作列表
private List<int> waitingDelete = new List<int>(); //等待刪除的動作的key
protected void Update()
{
foreach(SSAction ac in waitingAdd)
{
actions[ac.GetInstanceID()] = ac; //獲取動作實例的ID作爲key
}
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();
}
public void SSActionEvent(SSAction source, SSActionEventType events = SSActionEventType.Competeted,
int intParam = 0, string strParam = null, Object objectParam = null)
{
}
}
judge:
private int checkGameOver(){
if (Director.cn_move == 1)
return 0;
int from_priest = 0;
int from_devil = 0;
int to_priest = 0;
int to_devil = 0;
int[] from_count = fromCoast.getCharacterNum ();
from_priest = from_count [0];
from_devil = from_count [1];
int[] to_count = toCoast.getCharacterNum ();
to_priest = to_count [0];
to_devil = to_count [1];
if (to_devil + to_priest == 6)
return 1;//you win
int[] boat_count = boat.getCharacterNum();
if (boat.getTFflag () == 1) {
from_priest += boat_count [0];
from_devil += boat_count [1];
} else {
to_priest += boat_count [0];
to_devil += boat_count [1];
}
if (from_priest < from_devil && from_priest > 0)
return -1;//you lose
if(to_priest < to_devil && to_priest > 0)
return -1;//you lose
return 0;//not yet finish
}
ISSActionCallback接口
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);
}
定義了事件處理接口,所有事件管理者都必須實現這個接口來實現事件調度。利用接口(ISSACtionCallback)實現消息通知,避免與動作管理者直接依賴。
- 最後
完整代碼見GitHub,就不貼了。
視頻鏈接-lose?
視頻鏈接-win?
我的Github代碼傳送門