? ? ? ?在寫學習筆記之前,我先說說為什么會做這么一個游戲DEMO吧,目前是在一家互聯網廣告公司任職3D設計組長一職,主要內容是CG這塊,因為對AR和VR很感興趣,并且暫時選擇了Unity這款游戲引擎做為我的敲門磚,因為CG是我的優勢,所以對Unity這款軟件不陌生,上手很快,唯一比較陌生的是程序代碼這塊,但因為大學的時候學過C和游戲編程,所以倒不止于看到程序就放棄,只是工作一兩年完全沒接觸程序了。因此我決定在上手了Unity軟件后,準備啃下Unity中編程這塊硬骨頭。我選擇了C#這門語言來學習,因為市面上大多數AR和VR教程都是用C#來寫的,這樣學習起來方便一些。
? ? ? ?我先在在網上學習了一些Unity的C#基礎知識,比如GameObject,Transform,枚舉,方法等,對Unity中編程的基礎知識有了一些了解,接著我覺得還得跟著一些教程做練習,理解消化,跟著官方教程是一個很不錯的選擇,接下來就進入我的Spaceshooter筆記。? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
一:教程的前5個課時講述的是3D知識,比如相機燈光背景和特效等,表示毫無壓力跳過
二:角色控制、發射子彈
代碼如下:
using UnityEngine;
using System.Collections;
public class spaceshooter : MonoBehaviour {
? ? ? ? ? ? ?public float speed;
? ? ? ? ? ? ?public float rt;
? ? ? ? ? ? ?public float xMin;
? ? ? ? ? ? ?public float xMax;
? ? ? ? ? ? ?public float zMin;
? ? ? ? ? ? public float zMax;
? ? publicGameObjectshot;
? ? publicTransformshotspawn;
? ? publicfloatfirerate;
? ? privatefloatnextfire=0.0f;
voidUpdate(){
? ? ? if(Input.GetButton("Fire1")&&Time.time>nextfire){
? ? ? nextfire=Time.time+firerate;
? ? ?Instantiate(shot,shotspawn.position,shotspawn.rotation);
}
}
? ?void FixedUpdate() ? {
? ? ? ? ? ?float mh = Input.GetAxis ("Horizontal");
? ? ? ? ? ? float mv =? Input.GetAxis ("Vertical");
? ? ? ? ? ? Rigidbody rigidbody =GetComponent();
? ? ? ? ? ? Vector3 movement = new Vector3 (mh, 0.0f, mv);
? ? ? ? ? ? ?rigidbody.velocity = movement*speed;
? ? ? ? ? ? ?transform.rotation= Quaternion.Euler (0.0f, 0.0f, rigidbody.velocity.x* rt);
? ? ? ? ? ? ? transform.position = new Vector3 (
? ? ? ? ? ? ?Mathf.Clamp (transform.position.x,xMin,xMax),
? ? ? ? ? ? ?0.0F,
? ? ? ? ? ? ?Mathf.Clamp(transform.position.z,zMin,zMax)
? ? ? ? ? ? ?);
? ? }
}
這段代碼主要實現了以下功能
1:這段代碼首先沒有寫在Unity默認的Update函數中,而是寫在了FixedUpdate()函數中,在網上搜了一下Update和FixedUpdate的區別,Update()和FixedUpdate()在游戲中都會在更新的時候自動循環調用。但是Update是在每次渲染新的一幀的時候才會調用,也就是說,這個函數的更新頻率和設備的性能有關以及被渲染的物體(可以認為是三角形的數量)。在性能好的機器上可能fps 30,差的可能小些。這會導致同一個游戲在不同的機器上效果不一致,有的快有的慢。因為Update的執行間隔不一樣了。而FixedUpdate,是在固定的時間間隔執行,不受游戲幀率的影響。所以處理Rigidbody的時候最好用FixedUpdate。
PS:FixedUpdate的時間間隔可以在項目設置中更改,Edit->Project Setting->time找到Fixed timestep。就可以修改了。
2:關于Rigidbody調用的問題,官方案例是4.6版本,可以直接調用rigidbody,但5.0以后的版本中,Unity將C#規范了,要調用rigidbody必須要定義,比如Rigidbody r=GetComponent<Rigidebody>()來定義。
3:通過鍵盤上的"W" "A" "S" "D"或者方向鍵來控制飛船的前后左右的移動,左右是通過Input.GetAxis ("Horizontal")和Input.GetAxis ("Vertical")來實現,它們都是Vector3類型并且值范圍都是在-1到1之間 ,此時定義一個訪問修飾符為public且float類型的speed來控制飛船的速度;
4:用Math.Clamp方法來控制飛船移動邊界,通過查閱官方文檔知道,Math.Clamp返回的是一個float類型value的值,寫法是Math.Clamp(float value,float min,float max)
5:設定飛船的旋轉邊界值,rotation是一個Quaternion類型的值,用Quaternion.Euler來表示物體的旋轉,物體在x和y方向上的旋轉為0 ,在z方向上的旋轉跟物體的速度正負值掛鉤,所以用rigidbody.velovity.x來表示,這樣,按下方向鍵,向左或者向右時,飛船就能夠發生“側翻”效果.
6:Instantiat方法的使用,參照官方文檔,Instancitate是用來實例化物體的,使用方法是Instantiate (Object Original,Vector3 position,Quaternion rotation),Object是要實例化的對象,Vector是要復制到的位置,Quaternion是設置物體的旋轉。
7:Time.time方法用來獲取游戲運行的時間,在本例中用來設置按下鼠標左鍵后間隔多少時間發射下一發子彈。
8:Input.GetButton("FIre1")代碼塊中,Fire1表示鼠標左鍵,Fire0表示鼠標中間,Fire2表示鼠標右鍵,而且Input.GetButton表示持續按下Fire1,同類型的還有Input.GetButtonDown和Input.GetButtonUp也表示按下按鍵,但是只能計算一次按下或者彈起。
二:邊界
voidOnTriggerExit(Colliderother){
Destroy(other.gameObject);
}
調用了Trigger中的一個方法,即離開這個邊界的物體都會被“殺死”,條件就是只要觸碰到了邊界物體的Collider就會調用這個方法,Destroy中的參數是other.gameobjet,表明只會殺死其他的物體,而不會把自身給銷毀掉,跟后面要用到的碰撞方法有區別。
三 創建激光
void Start()?
{
Rigidbodyrigidbody=GetComponent();
rigidbody.velocity=transform.forward*speed;
rigidbody.angularVelocity=Random.insideUnitSphere*tumble;
}
1:這個代碼是用來表示行星的產生方向和起始隨機的旋轉速度,transform.forward用來表示z軸方向上的速度,為(0,0,1)
2:rigidbody.angularVelocity用來表示物體的旋轉角速度,是一個Vector3類型的,而隨機的起始速度則由Random類中的insideUnitSphere來表示,它是一個半徑為1的球,方向是-1到1,并且這個方法只能放在Start函數中只能調用一次,自己在做的時候,放在了Update()函數中,導致這個產生的行星都像發了瘋似的旋轉,因為每一幀都會更新它的角速度,而這個角速度又是隨機產生的。
四 爆炸?
代碼如下
publicGameObjectzidanbaozha;
publicGameObjectfeichuanbaozha;
publicGameObjectzidanbaozhashengyin;
publicGameObjectfeichuanbaozhashengyin;
voidOnTriggerEnter(Colliderother){
//自己的寫法
if(other.tag=="Boundary")//這里的if是不讓邊界盒子跟行星發生碰撞
{
return;//這里的return是返回一個空的值,表示什么也不做,也可以不寫這個return
}
if(other.tag=="Bolt")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
Instantiate(zidanbaozhashengyin,transform.position,Quaternion.identity);
}
if(other.tag=="Player")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(feichuanbaozha,transform.position,Quaternion.identity);
Instantiate(feichuanbaozhashengyin,transform.position,Quaternion.identity);
}
if(other.tag=="xingxing")
{
Destroy(other.gameObject);
Destroy(gameObject);
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
}
//官方教程的寫法
/*if(other.tag=="Boundary")//這里的if是不讓邊界盒子跟行星發生碰撞
{
return;//這里的return是返回一個空的值,表示什么也不做,也可以不寫這個return
}
Instantiate(zidanbaozha,transform.position,Quaternion.identity);
if(other.tag=="Player")
{
Instantiate(feichuanbaozha,transform.position,Quaternion.identity);
}
Destroy(other.gameObject);
Destroy(gameObject);*/
1:這里的代碼是分類子彈的collider碰撞到不同的物體的時候,會有不同的爆炸特效,判斷的一句是gameobject的Tag。我的寫法相比官方的想法可能會更死板,不簡練,但畢竟不是寫代碼的老手,這樣寫代碼方便自己理解,以后代碼量上去了我相信自己還是能夠養成良好的代碼習慣。
2:這里的Quaternion.identity表示物體沒有旋轉。
五 生成波
代碼如下
publicGameObjecthazard;
publicVector3spawnwaves;
publicinthazardcount;
publicfloatwaittime;
publicfloatspawntime;
publicfloatjiangeshijian;
voidStart(){
StartCoroutine(SpawnWaves());
}
IEnumeratorSpawnWaves(){
yieldreturnnewWaitForSeconds(waittime);
while(true)
{
for(inti=0;i
{
floata=Random.Range(-4.76f,4.76f);
hazard.transform.position=newVector3(a,0.0f,14.0f);
hazard.transform.rotation=Quaternion.identity;
Instantiate(hazard,hazard.transform.position,hazard.transform.rotation);
yieldreturnnewWaitForSeconds(spawntime);
}
yieldreturnnewWaitForSeconds(jiangeshijian);
}
}
1:這里的代碼是讓“敵人”也就是行星能夠一波一波地產生,并且在每次游戲開始之前,會過1s后才開始,讓玩家做好游戲準備,并且“敵人”會一波一個產生,每一波之間也會有間隔時間。
2:這里用到了SpawnWaves()方法,其實我覺得有沒有這個方法也是一樣的,因為能夠產生一波一波效果的主要是協同方法中的WaitForSeconds類來控制的,而且SpawnWaves()方法必須要在void Star()方法中手動調用才行。
3:能夠實現一波一波效果的,最關鍵是用到了協同函數,來實現延遲的效果也就是StartCoroutine(method name)方法,查看官方文檔知道,StartCoroutine(方法名)就能啟用Coroutine方法,并且返回值的類型不再是void,而是IEnumerator,并且用關鍵字yield return new WaitForSeconds (float value)來標識WaitForSeconds類,這里的value是標識延遲的時間。(http://docs.unity3d.com/ScriptReference/WaitForSeconds.html)這里插入官方文檔關于如何調用Coroutine,并且使用WaitForSeconds來實現延遲效果有很好的解釋
六 結束游戲
在這一塊中,主要困惑點是在重新加載當前游戲的api,因為官方教程用的是4.2的版本,用Application.loadlevel(Application.loadlevel) 來加載當前場景,當時在5.3版本中完全不能用,在5.3版本中使用SceneManager.LoadScene(scene name or index of the scene),并且在用這個類的時候,還必須引用Scene Management,在官網文檔中其實也沒解釋清楚,在網上搜了好久才知道這個的用法,最終,在5.3中重新加載當前場景,我的代碼是
usingUnityEngine;
usingSystem.Collections;
usingUnityEngine.SceneManagement;
publicclassGameController:MonoBehaviour{
void Update()
{
?if (gameover)
{
? ? ?if(Input.GetKeyDown(Keycode.R))
? ?{
? ? ? SceneManager.LoadScene(0);
? ?}
}
}
}
這樣就當按下R鍵的時候,就會重新加載當前的場景,重新開始游戲