1. 程式人生 > >Unity學習筆記2 簡易2D橫版RPG遊戲製作(二)

Unity學習筆記2 簡易2D橫版RPG遊戲製作(二)

十二、敵人受攻擊時的閃爍和Player的生命值的修正

上一篇中,我們利用Controller2D中的IEnumerator TakenDamage介面,使得我們的Player受到攻擊時會進行閃爍,我們同樣地也希望在我們的敵人身上可以實現相同的效果。所以我們現在需要複製Controller2D腳本里面的兩個內容到我們的Enemy2D腳本里面去:

第一個內容:

//顯示角色當前正受到攻擊
	float takenDamage = 0.2f;

第二個內容:

public IEnumerator TakenDamage(){
		renderer.enabled = false;
		yield return new WaitForSeconds(takenDamage);
		renderer.enabled = true;
		yield return new WaitForSeconds(takenDamage);
		renderer.enabled = false;
		yield return new WaitForSeconds(takenDamage);
		renderer.enabled = true;
		yield return new WaitForSeconds(takenDamage);
		renderer.enabled = false;
		yield return new WaitForSeconds(takenDamage);
		renderer.enabled = true;
		yield return new WaitForSeconds(takenDamage);
	} 

接下來我們需要對Bullet指令碼進行處理,使其在碰撞到我們的敵人時,向敵人傳送一個閃爍的訊號:(修改內容如下)

void OnTriggerEnter(Collider other){
		if (other.gameObject.tag == "Enemy") {
			Destroy(gameObject);
			other.gameObject.SendMessage("EnemyDamaged",damageValue,SendMessageOptions.DontRequireReceiver);
			other.gameObject.SendMessage("TakenDamage",SendMessageOptions.DontRequireReceiver);
		}

		if (other.gameObject.tag == "LevelObjects") {
			Destroy(gameObject);
		}
	}

其實就是增加了一句:
other.gameObject.SendMessage("TakenDamage",SendMessageOptions.DontRequireReceiver);

解決了敵人的閃爍問題之後,我們接下來要處理另外一個問題了。這個問題就是,我們的角色每消滅一些敵人就會增加經驗,而經驗的增加會自動補充HP,這個有點不對頭和不合理,我們升級的時候增加的應該是最大HP,在每個等級下,我們應該設定一個當前的最大HP,不能無限制地增加HP才對,所以呢,我們需要將原來的GameManager腳本里面的playersHealth變數改名為curHealth,並且增加一個maxHealth變數,同時設定為3,將原本的playersHealth++修改為maxHealth++,這樣的話,消滅敵人只會增加你的最大HP,而不會增加你當前的HP,至於升級之後是不是要讓HP全滿呢,這個功能很容易實現,我暫時不想加進去。
接下來我們就需要考慮下一個問題了,那就是角色的戰鬥力必須隨著等級的提升而得到提升才比較合理。不能每次攻擊都只扣敵人一滴血啊。而且敵人只有3到6滴血的設定不太合理,其實反正敵人的血是不顯示出來的,完全可以設定一些大一點的數值,比如100,500之類的,這個就到後面有需要再改吧。現在暫時先不動。

十三、補血藥的設定

在這一講裡面,首先要處理一個問題:我們的Player應該是有戰鬥力的,而不是每次只向敵人傳送1滴的傷害值。利用某某++的方法可以很容易實現升級時增加戰鬥力,這個就不說了。關鍵是怎麼弄成用我們的戰鬥力去減敵人的血值。首先,我們在GameManager腳本里面新增這麼一行:

static public int bulletDamage = 1;
然後把我們的curHealth,也就是當前HP的值也改成static public。這裡順便補充一下,static在c#中的作用。
靜態分配的,有兩種情況:
 1. 用在類裡的屬性、方法前面,這樣的靜態屬性與方法不需要建立例項就能訪問,
  通過類名或物件名都能訪問它,靜態屬性、方法只有“一份”:即如果一個類新建有N個
  物件,這N 個物件只有同一個靜態屬性與方法;
2.  方法內部的靜態變數:
   方法內部的靜態變數,執行完靜態變數值不消失,再次執行此物件的方法時,值仍存在,
   它不是在棧中分配的,是在靜態區分析的, 這是與區域性變數最大的區別;
如果這個說得不具體的話,那麼可以看一下下面這個,紅黑聯盟裡面講的,非常形象,保證一看馬上就明白了:(我也是在看視訊教程的過程中碰到了static不懂,然後看下面這個理解透徹的)

提起static,一般理解為靜態、全域性。
何為static?我理解的static屬於程式的直屬單位,而非static就是非直屬單位。
舉一個非常常見的例子,中國有4個直轄市,北京、上海、天津、重慶,這些相當於static,而廣州、南京、杭州等就是非static,中央可以直接管理北京、上海、天津、重慶,而廣州、南京、杭州應由各省政府管理,Main方法可以直接呼叫static,而呼叫非static需要例項化。

class City() 
{ 
    //4個直轄市static 靜態全域性型別 
    public static void Beijing(){} 
    public static  void ShangHai(){} 
    public static  void Tianjin(){} 
    public static  void Chongqing(){} 
    //其他城市 非靜態 
    public void Guangzhou(){} 
    public void Nanjing(){}  
} 
void Main() 
{ 
    //呼叫static型別的方法 
    City.Beijing();//呼叫北京 
    City.Shanghai();//呼叫上海 
 
    //呼叫非static型別的方法 
    //沒有直接呼叫權利,必須先例項化 
    City chengShi=new City(); 
    chengShi.Guangzhou();//呼叫廣州 
} 
講的形象就達到目的了,為剛開始學習程式設計的同學加把勁兒。

好啦,然後我們繼續。接著我們開啟我們的Bullet指令碼,把裡面的內容修改成這樣:

using UnityEngine;
using System.Collections;

public class Bullet : MonoBehaviour {

	//用於碰撞時摧毀兩個物體
	void OnTriggerEnter(Collider other){
		if (other.gameObject.tag == "Enemy") {
			Destroy(gameObject);
			other.gameObject.SendMessage("EnemyDamaged",GameManager.bulletDamage,SendMessageOptions.DontRequireReceiver);
			other.gameObject.SendMessage("TakenDamage",SendMessageOptions.DontRequireReceiver);
		}

		if (other.gameObject.tag == "LevelObjects") {
			Destroy(gameObject);
		}
	}

	void FixedUpdate(){
		Destroy (gameObject, 1.25f);
	}
}
其實上面的內容只是將
other.gameObject.SendMessage("EnemyDamaged",GameManager.bulletDamage,SendMessageOptions.DontRequireReceiver);
這一行進行了處理,我們原本的那個damageValue變數已經被刪掉,然後替換成我們GameManager裡面的bulletDamage,因為我們的這個bulletDamage已經修改為static,所以現在可以這樣呼叫了。以後如果想要升級增加角色的殺傷力的話,就容易多了。

接下來我們想要在運行遊戲中按C的時候順便顯示我們的戰鬥力,這個太簡單了,只需要對GameManager裡面做一點點修改:

if (playerStats) {
			statsDisplay.text = "等級:" + level + "\n經驗:" + curEXP + "/" + maxEXP + "\n攻擊力:" + bulletDamage;
		} 
這一部分大家可以隨便修改,你想顯示什麼就修改什麼。至於在畫面中的位置,可以調整GUIStats的transform,這個就不再贅述。

我稍微做了點修改。

接下來我們需要在場景中新增一個quad,去掉Mesh Collider,加上Box Collider,然後Box Collider的size全部改成1,transform裡面的position的z值別忘了設成0,名字隨便起,是用來做成補血藥的,我命名為HealthPotion,然後為它增加一個同名的tag。為了區分,我順便弄了一個同名的material扔上去,將顏色調成粉紅色。(呵呵……感覺是毒藥而不是血藥啊……)

接著我們開啟上次弄的那個StickToPlatform指令碼,把裡面的東西複製到我們的Controller2D上面,其實因為兩個指令碼都是繫結在Player身上,就沒必要弄兩個指令碼了,直接合併成一個就成了。

然後我們再增加一段小程式碼來使得我們的Player碰到帶有HealthPotion的物體時,將這個物體摧毀並且curHealth值加1。

void OnTriggerStay(Collider other){
		if (other.tag == "Platform") {
			this.transform.parent = other.transform;
		}
	}
	
	void OnTriggerExit(Collider other){
		if (other.tag == "Platform") {
			this.transform.parent = null;
		}
	}

	void OnTriggerEnter(Collider other){
		if (other.tag == "HealthPotion") {
			GameManager.curHealth++;
			Destroy(other.gameObject);
		}
	}

很簡單吧,現在補血藥就已經做好了。想要做出補多少血的補血藥都已經不是什麼問題了。想要做出補藍、增加戰鬥力什麼的補血藥,也不是什麼問題了哈哈。

有個地方需要大家注意一下,那就是curHealth必須加上static,否則會出現An object reference is required to access non-static member這樣的報錯顯示。在全域性靜態函式裡面是不可以使用非全域性靜態變數的。

吃藥前(注意左上角兩個紅心)

吃藥後,哈哈,療程短見效快,一粒補一滴血~

關於Box Collider的範圍的問題,可以參考上一篇講到的Player的Character Controller元件的問題,所以我們可以順便把藥物的Box Collider的size的X值改為1.3。

十四、遊戲暫停和遊戲存檔

遊戲存檔看似簡單,不過也是個比較蛋疼的問題。我們開啟GameManager指令碼:
首先,我們增加一個布林值:

//用於暫停的布林值
	bool pauseMenu;

然後我們增添以下程式碼:

if (pauseMenu) {
		if(GUI.Button(new Rect(Screen.width*.25f,Screen.height*.4f,Screen.width*.5f,Screen.height*.1f),"儲存遊戲")){
			print ("已儲存");
			PlayerPrefs.SetInt("Player Level",level);
			PlayerPrefs.SetInt("Player EXP",curEXP);
		}
		if(GUI.Button(new Rect(Screen.width*.25f,Screen.height*.6f,Screen.width*.5f,Screen.height*.1f),"顯示儲存的資料")){
			print ("顯示儲存的資料");
			print("當前等級:"+ PlayerPrefs.GetInt("Player Level"));
			print("當前經驗:"+ PlayerPrefs.GetInt("Player EXP"));
		}
	}
然後儲存指令碼。

現在我們在運行遊戲的過程中按P鍵,就可以調出一個這樣的畫面:


當我們點選儲存遊戲時,就會儲存相應的資料。我們可以先殺掉一隻小怪,然後儲存,然後重新執行,然後點選“顯示儲存的資料”,這個時候就會看到:


但是這個時候有兩個問題,第一個問題是,雖然在這裡我們可以看到儲存的資料,但是實際上在遊戲裡面,如果我們按C鍵檢視角色的屬性時,發現經驗值並沒有儲存下來。第二個問題是,我們的存檔還不知道怎麼刪除……

接下來馬上解決這個問題:

十五、載入遊戲和刪除存檔

好了,我們繼續修改我們的GameManager指令碼,首先我們要增加一個void Awake()函式,這個東西和void Start()有什麼不同呢?我順便摘錄了一段網上搬過來的筆記:

Unity3D初學者經常把Awake和Start混淆。

簡單說明一下,Awake在MonoBehavior建立後就立刻呼叫,Start將在MonoBehavior建立後在該幀Update之前,在該Monobehavior.enabled == true的情況下執行。

[javascript] view plaincopy
  1. void Awake (){  
  2. }       
  3. //初始化函式,在遊戲開始時系統自動呼叫。一般用來建立變數之類的東西。
  4. void Start(){  
  5. }  
  6. //初始化函式,在所有Awake函式執行完之後(一般是這樣,但不一定),在所有Update函式前系統自動條用。一般用來給變數賦值。

我們通常書寫的指令碼,並不會定義[ExecuteInEditMode]這個Attribute,所以Awake和Start都只有在Runtime中才會執行。

例1:

[javascript] view plaincopy
  1. publicclass Test : MonoBehaviour {  
  2.     void Awake () {  
  3.         Debug.Log("Awake");  
  4.         enabled = false;  
  5.     }  
  6.     void Start () {  
  7.         Debug.Log("Start");  
  8.     }  
  9. }  

以上程式碼,在Awake中我們呼叫了enabled = false; 禁止了這個MonoBehavior的update。由於Start, Update, PostUpdate等屬於runtime行為的一部分,這段程式碼將使Start不會被呼叫到。

在遊戲過程中,若有另外一組程式碼有如下呼叫:

[javascript] view plaincopy
  1. Test test = go.GetComponent<Test>();  
  2. test.enabled = true;  

這個時候,若該MonoBehavior之前並沒有觸發過Start函式,將會在這段程式碼執行後觸發。

例2:

player.cs

[javascript] view plaincopy
  1. private Transform handAnchor = null;  
  2. void Awake () { handAnchor = transform.Find("hand_anchor"); }  
  3. // void Start () { handAnchor = transform.Find("hand_anchor"); }
  4. void GetWeapon ( GameObject go ) {  
  5.     if ( handAnchor == null ) {  
  6.         Debug.LogError("handAnchor is null");  
  7.         return;  
  8.     }  
  9.     go.transform.parent = handAnchor;  
  10. }  

other.cs

[javascript] view plaincopy
  1. ...  
  2. GameObject go = new GameObject("player");  
  3. player pl = go.AddComponent<player>(); // Awake invoke right after this!
  4. pl.GetWeapon(weaponGO);  
  5. ...  

以上程式碼中,我們在player Awake的時候去為handAnchor賦值。如果我們將這步操作放在Start裡,那麼在other.cs中,當執行GetWeapon的時候就會出現handAnchor是null reference.

總結:我們儘量將其他Object的reference設定等事情放在Awake處理。然後將這些reference的Object的賦值設定放在Start()中來完成。
當MonoBehavior有定義[ExecuteInEditMode]時

當我們為MonoBehavior定義了[ExecuteInEditMode]後,我們還需要關心Awake和Start在編輯器中的執行狀況。

    當該MonoBehavior在編輯器中被賦於給GameObject的時候,Awake, Start 將被執行。
    當Play按鈕被按下游戲開始以後,Awake, Start 將被執行。
    當Play按鈕停止後,Awake, Start將再次被執行。
    當在編輯器中開啟包含有該MonoBehavior的場景的時候,Awake, Start將被執行。

值得注意的是,不要用這種方式來設定一些臨時變數的儲存(private, protected)。因為一旦我們觸發Unity3D的程式碼編譯,這些變數所儲存的內容將被清為預設值。

下面再來看看Unity聖典中的解釋。

 Awake()

當一個指令碼例項被載入時Awake被呼叫。

Awake用於在遊戲開始之前初始化變數或遊戲狀態。在指令碼整個生命週期內它僅被呼叫一次.Awake在所有物件被初始化之後呼叫,所以你可以安全的與其他物件對話或用諸如 GameObject.FindWithTag 這樣的函式搜尋它們。每個遊戲物體上的Awke以隨機的順序被呼叫。因此,你應該用Awake來設定指令碼間的引用,並用Start來傳遞資訊。Awake總是在Start之前被呼叫。它不能用來執行協同程式。

Start()

Start僅在Update函式第一次被呼叫前呼叫。Start在behaviour的生命週期中只被呼叫一次。它和Awake的不同是Start只在指令碼例項被啟用時呼叫。
你可以按需調整延遲初始化程式碼。Awake總是在Start之前執行。這允許你協調初始化順序。

好了,這樣就清楚為什麼要用Awake()函數了吧。

接下來我們就這樣弄,首先我們設定一個int saved,讓它等於零。(如果不賦值的話,它也會預設等於零)

//用於判斷是否是否儲存
	int saved = 0;

接下來我們就寫下這麼一個Awake函式:

void Awake(){
	saved = PlayerPrefs.GetInt ("Game Saved");
 	    if (saved == 1) {
			curEXP = PlayerPrefs.GetInt ("Player EXP");
			level = PlayerPrefs.GetInt ("Player Level");
			maxEXP = level * 50;
			maxHealth = level + 2;
			curHealth = maxHealth;
	   } 
	}
在執行這個Awake函式的時候,就會讓saved先獲取我們儲存的值。(等一下在下面存檔的那部分腳本里面,我們會儲存先把saved賦值為1,再進行儲存),因為PlayerPrefs不能儲存布林值,所以我們用一個int的0和1來代替就行了,一樣的。如果我們的遊戲沒有存檔的話,saved讀取不到任何資料,就會預設為零,那麼就相當於不會接下去讀取我們儲存的資料了,反之,如果讀取到了1,就相當於讀取到了“已經有儲存的資料”的情況,就需要繼續執行。

在儲存資料部分,我是這樣弄的:

if (pauseMenu) {
		//“儲存遊戲”按鈕
		if(GUI.Button(new Rect(Screen.width*.25f,Screen.height*.4f,Screen.width*.5f,Screen.height*.1f),"儲存遊戲")){
			print ("已儲存");
			saved = 1;
			PlayerPrefs.SetInt("Player Level",level);
			PlayerPrefs.SetInt("Player EXP",curEXP);
			PlayerPrefs.SetInt("Game Saved",saved);
		}
		//“顯示儲存的資料”按鈕
		if(GUI.Button(new Rect(Screen.width*.25f,Screen.height*.6f,Screen.width*.5f,Screen.height*.1f),"顯示儲存的資料")){
			print ("顯示儲存的資料");
			print("當前等級:"+ PlayerPrefs.GetInt("Player Level"));
			print("當前經驗:"+ PlayerPrefs.GetInt("Player EXP"));
			print("是否儲存:"+ PlayerPrefs.GetInt("Game Saved"));
		}
	}
我們可以看到,在儲存之前,我們先將saved賦值為1,然後再儲存,這樣的話,當我們重新載入遊戲時,就會進行一個是否儲存了遊戲的判斷。

至於下面那個“顯示儲存的資料”的按鈕功能,只是我用來debug.log的,沒什麼用,純屬除錯,可以無視。

可能有人會說,儲存遊戲就直接儲存,然後直接讀取資料不就行了,為什麼還要弄一個saved來判斷呢?我一開始也是沒有弄這個玩意的,後來發現了一個問題,在這裡解釋一下:假設我們現在刪除了遊戲存檔,而沒有這個用來判斷是否有存檔的saved值的話,那麼我們的指令碼自然就不管三七二十一,你沒有存檔它也會當成你是有存檔的,這樣會出現什麼問題呢?這樣的話,我們的curEXP = PlayerPrefs.GetInt ("Player EXP");和level = PlayerPrefs.GetInt ("Player Level");這兩句話就會得不到任何資料,那麼就會預設為零,那麼,下面的maxEXP = level * 50;還有maxHealth = level + 2;這兩句的計算就肯定會出問題了。maxEXP會變成0,而maxHealth會變成2,最大經驗值變成零也就算了,我們的血值還從3變成了2,這不是坑爹麼……哈哈,所以,這下子明白為什麼要做一個是否saved的判斷了吧。對於我這樣的小遊戲來說,就已經需要製作一個是否saved的判斷了,對於需要儲存大量資料的大遊戲來說那就更是如此。希望大家如果是製作RPG型別的遊戲的話,也可以養成類似的習慣。

接著要解決刪檔的問題。這個也是RPG遊戲的一個重點內容。我們開啟MainMenu指令碼,然後在if (showGUIOutline)裡面加入以下內容:

//“刪除存檔”按鈕
if (GUI.Button (new Rect (Screen.width * guiPlacementX3, Screen.height * guiPlacementY3, Screen.width * .5f, Screen.height * .1f), 
"刪除存檔")) {
PlayerPrefs.DeleteAll();
print ("已刪除存檔");
}
其實主要就是新增一個PlayerPrefs.DeleteAll();而已,沒什麼複雜的。

當然,為了方便我們在視窗中調整GUI的位置,我們也增加了guiPlacementX3還有guiPlacementY3這兩個public的float值。這裡就沒必要貼出來了。
接著在Mainmenu場景裡面的Maincamera上面調整好三個GUI按鈕的位置:


接著我們來測試執行一下,我們進入Mainmenu場景,然後執行,點選“刪除存檔”按鈕:


我們會看到print出了一句已刪除存檔的提示。
接著我們載入遊戲,來到我們那個醜醜的遊戲場景,然後按C,檢視一下我們當前的各項狀態。


現在我們還是初始狀態,我們去隨便刷掉兩隻怪,然後順便去作死一下,扣掉一滴血。然後按P鍵調出儲存選單。


現在我已經按了儲存,然後我按了“顯示儲存的資料”按鈕,現在我們可以從右邊的Console列表裡面看到我們儲存的資料。

好了,我們退出,重新進來~,再檢視一下:


細心一點的朋友應該注意到,這次稍微有了點不同。那就是我又恢復成3滴血了。這是因為我們重新載入的時候,curHealth會變成當前等級的maxHealth,所以我們重新載入之後不是2滴血,而是3滴血。

現在我們重新進入Mainmenu場景,然後刪除存檔,再重新進來一遍:


可以看到,我們的角色的全部資料都清零了。(右手邊的Console顯示了我剛剛有進行“刪除存檔”按鈕的點選操作,沒有造假,哈哈)

好了,現在我們已經解決了角色存檔的問題。這部分可能自己實際操作的時候會碰到一些問題,需要大家多做幾次,特別是不同的遊戲,情況肯定不一樣,這個沒辦法有統一的標準,這裡只是提供一個思路。

十六、自動存檔

前面我們提到了存檔的方法了。有些遊戲是即時存檔的,就是在Update每一幀都進行一次存檔的操作,對於小遊戲來說,這種做法無可厚非,但是對於類似寵物小精靈這種有龐大資料的遊戲來講,即時存檔是不太可行的。也許有其他優化的方法,比如在獨立遊戲Terraria中,它就是支援大資料即時存檔的,目前我尚不知道這種方法要如何實現。

下面,我們可以利用類似技能冷卻的原理,設定一個定時自動存檔器,比如說每隔五秒鐘或者十秒鐘自行存檔一次,為了讓玩家知道有自動存檔的情況,我們可以呼叫一些GUI來顯示(我這裡就直接print了)。同理,我們也可以在場景中佈置一些特殊物體,進行自動的存檔,比如說某一個關卡末尾的大門,我們的Player碰到大門之後就會跳到下一個關卡,同時自動儲存我們Player身上的全部資料。(至於這個場景跳轉門或者說是位置跳轉門能不能雙向跳動,這個就要看你的遊戲是怎麼佈置的了。)

廢話不多說,馬上開始:

首先我們需要在GameManager腳本里面建立一個叫做SaveGame()的函式,然後改為public型別(為什麼要這樣做後面會解釋),然後把我們前面

在if(pauseMenu)裡面的部分內容移到這個函式裡面去:

public void SaveGame(){
		saved = 1;
		PlayerPrefs.SetInt("Player Level",level);
		PlayerPrefs.SetInt("Player EXP",curEXP);
		PlayerPrefs.SetInt("Game Saved",saved);
		print ("已儲存");
	}

只有加上public,才能在外部進行呼叫嘛。好了,現在我們增加下面的內容:

if (other.tag == "Door") {
		string thisLevel = Application.loadedLevelName;
		int intThisLevel = int.Parse(thisLevel);
		int intNextLevel = intThisLevel+1;
		string nextLevel = intNextLevel.ToString();
		Application.LoadLevel(nextLevel);
	}
想要跳到下一個關卡有個很簡單的函式可以使用,就是Application.LoadLevel(),可是有個問題,就是如果我們直接選擇載入某一個關卡的話,我們這個指令碼就不能重複利用了。也就是說,我們想要做成一個只要一碰到就會自動跳轉到下一關的功能的指令碼,這樣就不必在每一個關卡里面都來寫一個特定的指令碼。首先,我們用string thisLevel = Application.loadedLevelName;這句話獲取當前關卡的名字,我們之前將關卡命名為Scene1,現在直接改成1就行了。這樣的話,我們就相當於得到了關卡的序列號。理論上來講,我們只需要Application.LoadLevel(thisLevel+1);就應該是可以跳轉到下一個關卡的了。如果這樣的話那就很方便了。可是實際上有個很大的問題,那就是關卡的名字是字串,字串可不能直接做加減法的,所以我們需要將字串強制轉換為int型別。C#裡面有Convert.ToInt32的轉換方法,但是我在unity裡面沒辦法使用(難道是我的開啟方式不對?)所以我採用了另一種方法,也就是上面指令碼的int intThisLevel = int.Parse(thisLevel);這樣,我們獲取當前的字串名稱1就會變成整數1(為了實現這個功能,我們必須將除了Mainmenu關卡之外的其他關卡全部命名為阿拉伯數字名稱,即1、2、3……)然後我們對這個整數1進行加1的處理,即:int intNextLevel = intThisLevel+1;接著,我們將得到的這個新的整數重新轉換為字串string nextLevel = intNextLevel.ToString();到這裡,載入下一個關卡的任務就完成了,最後我們加上這麼一句:Application.LoadLevel(nextLevel);即可跳轉到下一關卡。

C#,int轉成string,string轉成int

1,int轉成string
用toString 
或者Convert.toString()如下 

例如:
int varInt = 1; 
string varString = Convert.ToString(varInt); 
string varString2 = varInt.ToString();

2,string轉成int
如果確定字串中是可以轉成數字的字元,可以用int.Parse(string s),該語句返回的是轉換得到的int值;
如果不能確定字串是否可以轉成數字,可以用int.TryParse(string s, out int result),該語句返回的是bool值,指示轉換操作是否成功,引數result是存放轉換結果的變數。

例如:
string str = string.Empty;
str = "123";
int result=int.Parse(str);

string str = string.Empty;
str = "xyz";
int result;
int.TryParse(str, out result);


接下來處理下一個問題,我們在剛剛的if (other.tag == "Door") {}裡面插入一句gameManager.SaveGame();當然,在此之前,我們需要在這個腳本里面加上這麼一行:

//引用GameManager
	public GameManager gameManager;

這個應該很好理解,就不再贅述了。使用public的原因是為了在外部進行拖拽操作,如果不將其設定為public的話,就會出現NullReferenceException: Object reference not set to an instance of an object字樣的報錯,這個我們在上一篇學習筆記裡面已經有提到過了。出現這個的原因是因為我們此處引用了GameManager裡面的一個SaveGame()函式,但是unity並不知道你到底在用哪個GameManager,所以會報Null,最簡單的解決方法就是拖拽大法,也可以用GetComponent<>的方法。

看下面,這個是我的Player身上的Controller2D指令碼的設定,我已經把GameManager物體拖放到相應位置了。


剛開始的時候,原作者並沒有使用這個方法,而是直接複製了Bullet腳本里面的other.gameObject.SendMessage("TakenDamage",SendMessageOptions.DontRequireReceiver);這句進行修改。現在我們順便來想想為什麼不可以採用這種方法。因為這種方法是對著other.gameObject傳送訊號,而我們是對著自己傳送訊號啊。

可能有人會說,那還不簡單,我們直接把other.gameObject.刪掉不就可以了嗎?原視訊的作者也試了一次,不行。但是他沒解釋。我想了一段時間,終於明白了。因為SendMessage不可以把內容傳送給自己這個指令碼。傳送給同個物體身上的不同指令碼還是行得通的,但是自己傳送給自己就不行了。所以,這種方法是行不通的,就只能用上面提到的那種方法了。

現在我們已經實現了場景跳轉和按鍵存檔功能,接下來就是自動存檔功能了。我們在GameManager指令碼的Update函式裡面進行如下增添:

//自動存檔功能
	autoSaveTimer += Time.deltaTime;
	if (autoSaveTimer >= 1000f) {
	SaveGame();	
	print ("儲存啦~~");
	autoSaveTimer =0;
	}
上面的autoSaveTimer是自定義的一個int變數,我順便設定成public,方便在外面修改。我將它改為10,也就是每隔10秒就會自動儲存一次,前面我們將儲存的功能整合成一個SaveGame()函式就是為了這個目的。那句print(“儲存啦~~”)只是用來賣萌和提示自己的,嘿嘿~

每次儲存之後autoSaveTimer這個自動儲存的計時器就會清零,然後重新進入下一輪的計時,這樣就可以實現迴圈儲存的功效了。因為我們的遊戲資料比較小,所以可以每隔一段時間就自動儲存一次,大資料的遊戲就不建議這樣弄了。

這一講很長,也涉及到了很多問題。所以我花了很長時間整理。如果大家有什麼不太清楚的話,最好多看幾遍,然後自己再試著操作一下試試。當然,這個是針對新手說的,各路大神可以無視哈。

十七、關卡選擇

相關推薦

Unity學習筆記2 簡易2DRPG遊戲製作

十二、敵人受攻擊時的閃爍和Player的生命值的修正 上一篇中,我們利用Controller2D中的IEnumerator TakenDamage介面,使得我們的Player受到攻擊時會進行閃爍,我們同樣地也希望在我們的敵人身上可以實現相同的效果。所以我們現在需要複製Con

Unity學習筆記1 簡易2DRPG遊戲製作

這個教程是參考一個YouTube上面的教程做的,原作者的教程做得比較簡單,我先參考著做一遍,畢竟我也只是個初學者,還沒辦法完全自制哈哈。不過我之前也看過一個2D平臺遊戲的系列教程了,以後會整合起來,做出一個類似冒險島那樣的遊戲。 原視訊連結:點選開啟連結   這是個YouT

簡易2DRPG遊戲制作

學習 class log clas unit sdn 筆記 制作 details Unity學習筆記1 簡易2D橫版RPG遊戲制作 http://m.blog.csdn.net/article/details?id=24601905簡易2D橫版RPG遊戲制作

學習筆記之使用LNMP安裝DISCUZ論壇系統簡述

① 在正式安裝 Nginx 服務程式之前,先安裝pcre # tar xzvf pcre-8.35.tar.gz # cd pcre-8.35 # ./configure --prefix=/usr/local/pcre # make & make in

【day 11】python程式設計:從入門到實踐學習筆記-基於Django框架的Web開發-Django入門

學習筆記目錄 第十八章 Django入門(二) 建立應用程式 django專案由一系列應用程式組成,他們協同工作,讓專案稱謂一個整體。首先我們執行命令python manage.py startapp learning_logs。 定義模型

【day 15】python程式設計:從入門到實踐學習筆記-基於Django框架的Web開發-使用者賬戶

學習筆記目錄 第十九章 使用者賬戶(二) 建立使用者賬戶 這一部分我們來建立使用者註冊和身份驗證系統。 應用程式users 首先使用命令python manage.py startapp users建立名為users的應用程式,現在你的目錄

Unity 4 3 製作一個2D射擊遊戲

using UnityEngine;/// <summary>/// Launch projectile/// </summary>public class WeaponScript : MonoBehaviour{  //-------------------------------

Unity 4.3 製作一個2D射擊遊戲

using UnityEngine;/// <summary> /// Launch projectile /// </summary>public class WeaponScript : MonoBehaviour {   //---------------------------

OpenGL超級寶典第七學習筆記-緩衝區-在緩衝區中填充及複製資料buffers

2017-3-28緩衝區-在緩衝區中填充及複製資料(buffers) 1、  如果你要放入緩衝的資料是不變的值,那麼使用glClearBufferSubData()或者glClearNamedBufferSubData()會更有效率。原型如下: voidglClearBuf

淺析2D過關遊戲關卡製作要點

一、地圖構成 一張地圖一般由這五部分組成:獎勵物品、人物、基本地形、特殊地形、陷阱。 獎勵物品:金幣、寶石、HP藥劑、MP藥劑等遊戲貨幣或者消耗品。 人物:NPC、敵人。 基本地形:我認為的基本地形是由只有物理碰撞的土塊組成的地形。 特殊地形:冰川、熔岩、流沙

2D跳躍遊戲第二節

跳躍系統 按空格讓玩家跳躍起來 **原理:將剛體y的速度等於跳躍的速度 ** 在C#Move腳本里新增程式碼 (1)先設定跳躍的速度 public float jumpVelocity = 10;//跳躍的速度 (2)讓玩家跳起來 將剛體y的速度設定成

李宏毅機器學習筆記2:Gradient Descent(附帶詳細的原理推導過程

s函數 太陽 猴子 網易 http 得到 結合 col ear 李宏毅老師的機器學習課程和吳恩達老師的機器學習課程都是都是ML和DL非常好的入門資料,在YouTube、網易雲課堂、B站都能觀看到相應的課程視頻,接下來這一系列的博客我都將記錄老師上課的筆記以及自己對這些知識內

HTML學習筆記 w3sCss盒子模型應用 第十一節 原創

.com foo margin images href ack har htm com <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> &l

Python學習筆記八 面向對象高級編程

tin 學習筆記 不可 __str__ 有一個 類的屬性 -- pes 實例名 參考教程:廖雪峰官網https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000 一、使

Python學習筆記7 頭文件的添加規則轉載

www. nbsp .html 一個 情況 str tno sys AS 轉載自:https://www.cnblogs.com/taurusfy/p/7605787.html **********************************************

Linux學習筆記之Python3的安裝以及建立虛擬環境CentOS

body { background: #f4f4f4 } .title { width: 100%; background: #5cb85c; padding: 5px; font-size: 20px; margin: 5px } .sub_title { width: 99%; background: #

學習筆記】深入理解js原型和閉包3——prototype原型

既typeof之後的另一位老朋友! prototype也是我們的老朋友,即使不瞭解的人,也應該都聽過它的大名。如果它還是您的新朋友,我估計您也是javascript的新朋友。   在咱們的第一節(深入理解js原型和閉包(1)——一切皆是物件)中說道,函式也是一種物件。他也是屬性的集合,你也可以

學習筆記】深入理解js原型和閉包8——簡述【執行上下文】上

什麼是“執行上下文”(也叫做“執行上下文環境”)?暫且不下定義,先看一段程式碼: 第一句報錯,a未定義,很正常。第二句、第三句輸出都是undefined,說明瀏覽器在執行console.log(a)時,已經知道了a是undefined,但卻不知道a是10(第三句中)。 在一段js程式碼拿過來真正一句一

學習筆記】深入理解js原型和閉包9—— 簡述【執行上下文】下

繼續上一篇文章(https://www.cnblogs.com/lauzhishuai/p/10078231.html)的內容。 上一篇我們講到在全域性環境下的程式碼段中,執行上下文環境中有如何資料: 變數、函式表示式——變數宣告,預設賦值為undefined; this——賦值; 函式宣告

學習筆記】深入理解js原型和閉包11——執行上下文棧

繼續上文的內容。 執行全域性程式碼時,會產生一個執行上下文環境,每次呼叫函式都又會產生執行上下文環境。當函式呼叫完成時,這個上下文環境以及其中的資料都會被消除,再重新回到全域性上下文環境。處於活動狀態的執行上下文環境只有一個。 其實這是一個壓棧出棧的過程——執行上下文棧。如下圖:   可