有陷阱!小心! - 設計我們的觸發器

陷阱

陷阱,最常見到的情節就是在探險類的電影及遊戲,探險家踩到地板的凸起的石頭,接著後面就滾下來了一個更大的石頭,要壓爆探險家!

設計

陷阱我認為它是一個觸發器 + 目標物件(如滾下來的石頭)所組合而成的東西。

目標物件可能是一個落石、夾子、機關…等很多奇怪的東西,目標物件將提供函式給觸發器呼叫控制。

觸發器將是本篇的重點,我們會設計一個通用的可編輯的觸發器,來控制一個或多個目標物件。在觸發器裡挑選要呼叫目標物件的那一支函式,類似 uGUI 的 UI Button ,如圖:

螢幕快照 2015-05-16 下午6.12.32

UI Button 它是用按 Button 來觸發事件,然後再執行所選擇的函式。

我們就做一個類似的功能,用 Trigger 來觸發事件,然後再對目標物件使用 SendMessage() 來呼叫所選擇的函式。

接下來我們將會需要用到以下的東西:

  • 取出 class 裡函式
    • System.Reflection
    • GetType
    • GetMethods
    • GetParameters
    • ParameterInfo
  • 用來畫我們自訂腳本編輯介面
    • UnityEditor
    • GUILayout
    • EditorGUILayout
    • GenericMenu
    • GUIContent
想知道他們更多、更詳細的內容請去參考文件或 Google 哦。

取出目標物件可呼叫的函式

因為我們是使用 SendMessage ,它只能送訊息給 MonoBehaviour 的 class,所以一開始我們先取出 GameObject 身上所有的 MonoBehaviour 的 class。

//GameObject go
MonoBehaviour[] cpns = go.GetComponents<MonoBehaviour>();

然後用 GetMethods() 設定 Flag 取出特定種類的函式 。MSDN 說明


		for (int i = 0; i < cpns.Length; i++)
		{
			Component cpn = cpns[i];

			System.Type tp = cpn.GetType();
			MethodInfo[] methodInfos = tp.GetMethods(System.Reflection.BindingFlags.Instance 
			                                         | System.Reflection.BindingFlags.Public 
			                                         | System.Reflection.BindingFlags.DeclaredOnly

			                                         );
		}

透過 GetParameters() 取出函式內的參數。


			for (int j = 0; j < methodInfos.Length; j++)
			{
				MethodInfo mf = methodInfos[j];
				ParameterInfo[] pms = mf.GetParameters();

					for (int k = 0; k < pms.Length; k++)
					{
						ParameterInfo pm = pms[k];
					}
			}

以上的做法可以取出我們想要的特定種類函式,但可呼叫的函式太多了且有的函式參數太多了,我們在做編輯介面會寫很多 code,所以為了簡化我們還是給一些限制好了。

我們限制如下:

  • 函式最多只能有一個參數
    • pms.Length <= 1
  • 且參數的種類只能是
    • float
    • int
    • string
    • bool

這樣子我們的程式好寫多了,編輯時也不用在一堆函式裡大海撈針。


	bool IsSupParmType(ParameterInfo pm)
	{
		if (pm.ParameterType == typeof(float))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(int))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(string))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(bool))
		{
			return true;
		}

		return false;
	}

所以我們的程式就變成


		for (int j = 0; j < methodInfos.Length; j++)
		{
			MethodInfo mf = methodInfos[j];
			ParameterInfo[] pms = mf.GetParameters();

			//sup 0 ~ 1 parm
			if (pms.Length == 0)
			{
			}
			else if (pms.Length == 1)
			{
				ParameterInfo pm = pms[0];
				bool issup = IsSupParmType(pm);

				if (issup)
				{
				}
			}

編輯可呼叫函式的資料內容

能列出可呼叫的函式後,我們就要考慮如何去編輯,這些可編輯的設定都應被記錄下來供遊戲執行時使用。

這裡列出編輯時我們要記住的資料有:

  • 陷阱被觸發時的目標物件 GameObject
  • 呼叫 GameObject 裡的什麼函式
  • 函式參數的種類
  • 參數的值
  • 進入時或離開時觸發呼叫函式

我們寫一個名為 FuncCallData 的 class 程式如下:


[Serializable]
public class FuncCallData
{
	public enum TriggleType
	{
		ON_ENTER,
		ON_EXIT
	}

	public GameObject targetObject;
	public TriggleType triggerType = TriggleType.ON_ENTER;
	public string funcName;
	public string parmType;
	public int parmInt;
	public float parmFloat;
	public string parmString;
	public bool parmBool;

}
[Serializable] 的屬性可在編輯模式切換至 Play 模式時,讓它的資料不被清空。

觸發器腳本

再來就是本篇的主角登場了,我們寫個叫做 EventTriggerCaller 腳本,這個腳本只要記住二個東西就好:

  • 用來存放函式呼叫資料的清單: funcdatas
  • 那些 Tag 名稱的物件會觸發這個陷阱:tagNames

public class EventTriggerCaller : MonoBehaviour {

	public List<FuncCallData> funcdatas = new List<FuncCallData>();

	public string[] tagNames;
}

然後加入 HasTagName() 函式 判斷 Tag 名稱( 我相信用 for 會比用 System.Array.IndexOf 來得快,不信大家去測看看)與 Trigger 相關函式。


	bool HasTagName(string tagname)
	{
		for (int i = 0; i < tagNames.Length; i++)
		{
			if (tagNames[i] == tagname)
			{
				return true;
			}
		}
		return false;
	}

	void OnTriggerEnter(Collider other)
	{
		if (HasTagName(other.tag))
		{
			for (int i = 0; i < funcdatas.Count; i++)
			{
				FuncCallData fd = funcdatas[i];
				if (fd.triggerType == FuncCallData.TriggleType.ON_ENTER)
				{
					if (fd.targetObject != null && fd.funcName != "")
					{
						if (fd.parmType == "")
						{
							fd.targetObject.SendMessage(fd.funcName);
						}
						else
						{
							fd.targetObject.SendMessage(fd.funcName, fd.GetParm());
						}

					}				
				}
			}

		}
	}

	void OnTriggerExit(Collider other)
	{
		if (HasTagName(other.tag))
		{
			for (int i = 0; i < funcdatas.Count; i++)
			{
				FuncCallData fd = funcdatas[i];
				if (fd.triggerType == FuncCallData.TriggleType.ON_EXIT)
				{
					if (fd.targetObject != null && fd.funcName != "")
					{
						if (fd.parmType == "")
						{
							fd.targetObject.SendMessage(fd.funcName);
						}
						else
						{
							fd.targetObject.SendMessage(fd.funcName, fd.GetParm());
						}
					}
				}
			}

		}
	}

如上面程式所示,當OnTriggerEnter、OnTriggerExit 時,我們就判斷觸發的物件 Tag 是不是與我們設定可觸發陷阱的 Tag 名稱一樣。

然後再判斷要呼叫的函式是要在 Enter 或 Exit 時呼叫。

最後就是用 SendMessage 去呼叫的物件函式。

使用這個腳本的 GameObject 身上要有 Collider 並設定為 Trigger 哦!

陷阱觸發器腳本的編輯介面

有了一堆資料後,還要有一個合適的介面來編輯它,這裡建立一個名為 EventTriggerCallerEditor 的腳本使用 [CustomEditor] 的屬性。

要建立一個名為 Editor 的資料夾,並把這個腳本放進去才會有作用哦。

覆寫 OnInspectorGUI() 的函式,接下來我們將在這個函式裡把編輯介面寫出來。

using UnityEditor;
[CustomEditor(typeof(EventTriggerCaller))]
public class EventTriggerCallerEditor : Editor {

	override public void OnInspectorGUI()
	{
	}
}

我們先畫出成員變數 tagNames

利用 serializedObject.FindProperty 取出該成員變數,並用 Unity 原本提供的功能 EditorGUILayout.PropertyField() 來畫它。其它程式碼是用來檢查這個成員變數有沒有被修改過,有則把修改過的值設定進去。


		SerializedProperty tps = serializedObject.FindProperty ("tagNames");
		EditorGUI.BeginChangeCheck();
		EditorGUILayout.PropertyField(tps, true);
		if(EditorGUI.EndChangeCheck())
		{
			serializedObject.ApplyModifiedProperties();
		}

再來加兩個按鈕來增減 List 清單的內容。

  • “+" 鈕:增加一筆資料在最後。
  • “-" 鈕:刪除最後一筆資料。

		EventTriggerCaller etc = (EventTriggerCaller)target;
		GUILayout.BeginHorizontal();
		if (GUILayout.Button("+"))
		{
			FuncCallData dd = new FuncCallData();
			etc.funcdatas.Add(dd);
		}

		if (GUILayout.Button("-"))
		{
			if (etc.funcdatas.Count > 0)
			{
				etc.funcdatas.RemoveAt(etc.funcdatas.Count - 1);
			}
		}
		GUILayout.EndHorizontal();

再來就是畫出 List 的內容。

歷遍 List 裡所有的資料。


		EventTriggerCaller etc = (EventTriggerCaller)target;

		for (int i = 0; i < etc.funcdatas.Count; i++)
		{
			GUILayout.BeginHorizontal();
			FuncCallData fd = etc.funcdatas[i];
		}

我們規畫一下 List 裡資料要顯示那些欄位:

  • 可設定 GameObject 的欄位。
  • 可設定是在 Enter 或 Exit 時呼叫觸發的欄位。
  • 顯示呼叫那支函式的欄位。
  • 顯示設定參數的欄位。
  • 選擇函式的按鈕。
  • 顯示可呼叫的函式選單。

設定 GameObject 的欄位:

			fd.targetObject = (GameObject)EditorGUILayout.ObjectField(fd.targetObject, typeof(GameObject), true);

可設定是在 Enter 或 Exit 時呼叫觸發的欄位:

				fd.triggerType = (FuncCallData.TriggleType)EditorGUILayout.EnumPopup(fd.triggerType);

顯示被呼叫函式名稱的欄位:

GUILayout.Label(fd.funcName);

顯示設定參數的欄位,依參數的種類來決定使用那種 Field:

	void ShowInputParmField(FuncCallData fd)
	{

		if (fd.parmType == typeof(float).ToString())
		{
			fd.parmFloat = EditorGUILayout.FloatField(fd.parmFloat);
		}
		else if (fd.parmType == typeof(int).ToString())
		{
			fd.parmInt = EditorGUILayout.IntField(fd.parmInt);
		}
		else if (fd.parmType == typeof(string).ToString())
		{
			fd.parmString = EditorGUILayout.TextField(fd.parmString);
		}
		else if (fd.parmType == typeof(bool).ToString())
		{
			fd.parmBool = EditorGUILayout.Toggle(fd.parmBool);
		}
	}

選擇函式的選單按鈕:

				if (GUILayout.Button("func"))
				{
					ShowFuncMenu(etc.funcdatas[i].targetObject, fd);
				}

顯示可呼叫的函式選單:


	void ShowFuncMenu(GameObject go, FuncCallData fd)
	{

		MonoBehaviour[] cpns = go.GetComponents<MonoBehaviour>();

		GenericMenu gmenu = new GenericMenu();

		for (int i = 0; i < cpns.Length; i++)
		{
			Component cpn = cpns[i];
			CreateGUIContents(fd, gmenu, cpn);
		}

		gmenu.ShowAsContext();

	}

	void CreateGUIContents(FuncCallData fd, GenericMenu gmenu, object classobj)
	{
		System.Type tp = classobj.GetType();
		MethodInfo[] methodInfos = tp.GetMethods(System.Reflection.BindingFlags.Instance 
		                                         | System.Reflection.BindingFlags.Public 
		                                         | System.Reflection.BindingFlags.DeclaredOnly
		                                         );

		for (int j = 0; j < methodInfos.Length; j++)
		{
			MethodInfo mf = methodInfos[j];
			ParameterInfo[] pms = mf.GetParameters();

			//sup 0 ~ 1 parm
			if (pms.Length == 0)
			{
				GUIContent gcnt = new GUIContent(classobj.GetType().Name + "/" + mf.Name + "()");
				MenuData md = new MenuData();
				md.funcData = fd;
				md.functionName = mf.Name;
				gmenu.AddItem(gcnt, false, MenuFunc, md);

			}
			else if (pms.Length == 1)
			{
				ParameterInfo pm = pms[0];
				bool issup = IsSupParmType(pm);

				if (issup)
				{
					GUIContent gcnt = new GUIContent(classobj.GetType().Name + "/" + mf.Name + "(" + pm.ParameterType + ")");
					MenuData md = new MenuData();
					md.funcData = fd;
					md.functionName = mf.Name;
					md.parmType = pm.ParameterType.ToString();
					gmenu.AddItem(gcnt, false, MenuFunc, md);
				}

			}
		}
	}

在上面的 Code 裡,我們使用 GenericMenu 建立選單,把可呼叫的函式取出,用 GUIContent 建立選項。

我們在加入選項(GUIContent)時,可設定選項被點擊中的回呼函式及設定回呼函式參數。

我們要傳入的參數應有:

  • 是那一個 FuncCallData 在做選擇選函式
  • 該選項的函式名稱
  • 該選項的參數種類

但回呼函式參數只能傳入一個,所以我們建立一個叫 MenuData 的 class 來把該記錄參數包起來。

	class MenuData
	{
		public FuncCallData funcData;
		public string functionName;
		public string parmType;
	}

再建立一支回呼函式 MenuFunc,它要做的事就是把函式參數 object 轉型回 MenuData,然後把名稱及參數種類設定至 funcData 裡。

	void MenuFunc(object data)
	{
		MenuData md = (MenuData)data;
		md.funcData.funcName = md.functionName;
		md.funcData.parmType = md.parmType;
	}

最後,加入選項前先設定 MenuData 的內容,然後加入選項時設定我們的回呼函式及回呼函式參數。

							GUIContent gcnt = new GUIContent(cpn.GetType().Name + "/" + mf.Name + "(" + pm.ParameterType + ")");
							MenuData md = new MenuData();
							md.funcData = fd;
							md.functionName = mf.Name;
							md.parmType = pm.ParameterType.ToString();
							gmenu.AddItem(gcnt, false, MenuFunc, md);
 
設定 GUIContent 內容時,可利用 "/" 做選項的分層,把同元件的函式都整理在一起。

呼!寫了一堆 Code 終於把它完成了!相信這個辛苦會是值得的!

目標物件

目標物件可是很多很天馬行空的東西,它的設計是各式各樣的,但是有一個共通的關鍵就是要提供一些能給別人呼叫的函式,利用這些函式來控制它。接下來會用實例來說明。

實例

我們建立三個 GameObject

  • trigger:陷阱,加入我們剛才寫的 EventTriggerCaller 腳本。
  • player:玩家,身上的 Tag 設定為 “Player" 。
  • rock:落石,目標物件。

我們先在落石身上隨便寫一個腳本,裡面有一些函式可以供 trigger 呼叫:


	public void nofun()
	{
		Debug.Log("nofun");
	}
	public void Strfun(string str)
	{
		Debug.Log("Strfun:" + str);
	}
	public void intfun(int va)
	{
		Debug.Log("intfun:" + va);
	}
	public void boolfun(bool va)
	{
		Debug.Log("boolfun:" + va);
	}
當然,在遊戲中落石應會有播動作、放音效的函式供我們呼叫。

再來設定 trigger,設定成只有 Tag 名稱是 Player 時才會觸發。
螢幕快照 2015-05-17 上午1.13.01

按 “+" 鈕,新增一筆資料。
螢幕快照 2015-05-17 上午1.13.27

把 rock GameObject 拉進來。
螢幕快照 2015-05-17 上午1.13.41

選擇 Enter 時,以及要呼叫的函式。
螢幕快照 2015-05-17 下午9.27.54
螢幕快照 2015-05-17 下午4.45.04

設定函式參數。
螢幕快照 2015-05-17 上午1.14.48

再建立一筆資料,設定 Exit 時要呼叫的函式及參數。
螢幕快照 2015-05-17 上午1.15.21

最後當 player 進入及離開 trigger 時,rock 就會分別執行 StrFun() 及 intfun() 二支函式。
螢幕快照 2015-05-17 上午1.15.42

如何?有沒有很有趣、方便好用?

有了這個觸發器腳本,我們就能控制很多東西,比如動作、音樂音效、角色資訊…等,只要把想被控制的東西寫成函式,再透過 EventTriggerCaller 去呼叫它們,就能做出很多效果組合。

雖然它不可能能應用在各種情況,但是這種設計己經算是通用了,當然大家也可以依自己的需求去改造它哦!

完整程式碼

//https://arclee0117.wordpress.com/
//arclee0117@gmail.com
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;

[Serializable]
public class FuncCallData
{
	public enum TriggleType
	{
		ON_ENTER,
		ON_EXIT
	}

	public GameObject targetObject;
	public TriggleType triggerType = TriggleType.ON_ENTER;
	public string funcName;
	public string parmType;
	public int parmInt;
	public float parmFloat;
	public string parmString;
	public bool parmBool;

	public void RestFuncData()
	{
		funcName = "";
		parmType = "";
//		triggerType = TriggleType.ON_ENTER;
//	  	parmInt = 0;
//		parmFloat = 0;
//		parmString = "";
//		parmBool = false;
	}

	public object GetParm()
	{
		if (parmType == typeof(int).ToString())
		{
			return parmInt;
		}
		else if (parmType == typeof(float).ToString())
		{
			return parmFloat;
		}
		else if (parmType == typeof(string).ToString())
		{
			return parmString;
		}
		else if (parmType == typeof(bool).ToString())
		{
			return parmBool;
		}

		return null;
	}

}

public class EventTriggerCaller : MonoBehaviour {

	public List<FuncCallData> funcdatas = new List<FuncCallData>();

	public string[] tagNames;
	// Use this for initialization
	void Start () {

	}

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

	}

	bool HasTagName(string tagname)
	{
		for (int i = 0; i < tagNames.Length; i++)
		{
			if (tagNames[i] == tagname)
			{
				return true;
			}
		}
		return false;
	}

	void OnTriggerEnter(Collider other)
	{
		if (HasTagName(other.tag))
		{
			for (int i = 0; i < funcdatas.Count; i++)
			{
				FuncCallData fd = funcdatas[i];
				if (fd.triggerType == FuncCallData.TriggleType.ON_ENTER)
				{
					if (fd.targetObject != null && fd.funcName != "")
					{
						if (fd.parmType == "")
						{
							fd.targetObject.SendMessage(fd.funcName);
						}
						else
						{
							fd.targetObject.SendMessage(fd.funcName, fd.GetParm());
						}

					}				
				}
			}

		}
	}

	void OnTriggerExit(Collider other)
	{
		if (HasTagName(other.tag))
		{
			for (int i = 0; i < funcdatas.Count; i++)
			{
				FuncCallData fd = funcdatas[i];
				if (fd.triggerType == FuncCallData.TriggleType.ON_EXIT)
				{
					if (fd.targetObject != null && fd.funcName != "")
					{
						if (fd.parmType == "")
						{
							fd.targetObject.SendMessage(fd.funcName);
						}
						else
						{
							fd.targetObject.SendMessage(fd.funcName, fd.GetParm());
						}
					}
				}
			}

		}
	}
}


//https://arclee0117.wordpress.com/
//arclee0117@gmail.com
using UnityEngine;
using System.Collections;
using UnityEditor;
using System;
using System.Reflection;

[CustomEditor(typeof(EventTriggerCaller))]
public class EventTriggerCallerEditor : Editor {

	class MenuData
	{
		public FuncCallData funcData;
		public string functionName;
		public string parmType;
	}

	UnityEngine.Object go;
	// Use this for initialization
	void Start () {

	}

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

	}

	bool IsSupParmType(ParameterInfo pm)
	{
		if (pm.ParameterType == typeof(float))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(int))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(string))
		{
			return true;
		}
		else if (pm.ParameterType == typeof(bool))
		{
			return true;
		}

		return false;
	}

	void MenuFunc(object data)
	{
		MenuData md = (MenuData)data;
		md.funcData.funcName = md.functionName;
		md.funcData.parmType = md.parmType;
	}

	void CreateGUIContents(FuncCallData fd, GenericMenu gmenu, object classobj)
	{
		System.Type tp = classobj.GetType();
		MethodInfo[] methodInfos = tp.GetMethods(System.Reflection.BindingFlags.Instance 
		                                         | System.Reflection.BindingFlags.Public 
		                                         | System.Reflection.BindingFlags.DeclaredOnly
		                                         );

		for (int j = 0; j < methodInfos.Length; j++)
		{
			MethodInfo mf = methodInfos[j];
			ParameterInfo[] pms = mf.GetParameters();

			//sup 0 ~ 1 parm
			if (pms.Length == 0)
			{
				GUIContent gcnt = new GUIContent(classobj.GetType().Name + "/" + mf.Name + "()");
				MenuData md = new MenuData();
				md.funcData = fd;
				md.functionName = mf.Name;
				gmenu.AddItem(gcnt, false, MenuFunc, md);

			}
			else if (pms.Length == 1)
			{
				ParameterInfo pm = pms[0];
				bool issup = IsSupParmType(pm);

				if (issup)
				{
					GUIContent gcnt = new GUIContent(classobj.GetType().Name + "/" + mf.Name + "(" + pm.ParameterType + ")");
					MenuData md = new MenuData();
					md.funcData = fd;
					md.functionName = mf.Name;
					md.parmType = pm.ParameterType.ToString();
					gmenu.AddItem(gcnt, false, MenuFunc, md);
				}

			}
		}
	}

	void ShowFuncMenu(GameObject go, FuncCallData fd)
	{

		MonoBehaviour[] cpns = go.GetComponents<MonoBehaviour>();

		GenericMenu gmenu = new GenericMenu();

		for (int i = 0; i < cpns.Length; i++)
		{
			Component cpn = cpns[i];
			CreateGUIContents(fd, gmenu, cpn);
		}

		gmenu.ShowAsContext();

	}

	void ShowInputParmField(FuncCallData fd)
	{

		if (fd.parmType == typeof(float).ToString())
		{
			fd.parmFloat = EditorGUILayout.FloatField(fd.parmFloat);
		}
		else if (fd.parmType == typeof(int).ToString())
		{
			fd.parmInt = EditorGUILayout.IntField(fd.parmInt);
		}
		else if (fd.parmType == typeof(string).ToString())
		{
			fd.parmString = EditorGUILayout.TextField(fd.parmString);
		}
		else if (fd.parmType == typeof(bool).ToString())
		{
			fd.parmBool = EditorGUILayout.Toggle(fd.parmBool);
		}
	}

	void DrawFuctionCallData()
	{
		EventTriggerCaller etc = (EventTriggerCaller)target;

		for (int i = 0; i < etc.funcdatas.Count; i++)
		{
			GUILayout.BeginHorizontal();
			FuncCallData fd = etc.funcdatas[i];
			GameObject lasttarget = fd.targetObject;
			fd.targetObject = (GameObject)EditorGUILayout.ObjectField(fd.targetObject, typeof(GameObject), true);

			if (fd.targetObject != null)
			{
				if (lasttarget != fd.targetObject)
				{
					fd.RestFuncData();
				}
				fd.triggerType = (FuncCallData.TriggleType)EditorGUILayout.EnumPopup(fd.triggerType);
				GUILayout.Label(fd.funcName);
				ShowInputParmField(fd);
				if (GUILayout.Button("func"))
				{
					ShowFuncMenu(etc.funcdatas[i].targetObject, fd);
				}
			}

			GUILayout.EndHorizontal();
		}

	}

	override public void OnInspectorGUI()
	{
		EventTriggerCaller etc = (EventTriggerCaller)target;

		SerializedProperty tps = serializedObject.FindProperty ("tagNames");
		EditorGUI.BeginChangeCheck();
		EditorGUILayout.PropertyField(tps, true);
		if(EditorGUI.EndChangeCheck())
		{
			serializedObject.ApplyModifiedProperties();
		}

		DrawFuctionCallData();

		GUILayout.BeginHorizontal();
		if (GUILayout.Button("+"))
		{
			FuncCallData dd = new FuncCallData();
			etc.funcdatas.Add(dd);
		}

		if (GUILayout.Button("-"))
		{
			if (etc.funcdatas.Count > 0)
			{
				etc.funcdatas.RemoveAt(etc.funcdatas.Count - 1);
			}
		}
		GUILayout.EndHorizontal();
	}
}


//https://arclee0117.wordpress.com/
//arclee0117@gmail.com
using UnityEngine;
using System.Collections;

public class eventTest : MonoBehaviour {

	// Use this for initialization
	void Start () {

	}

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

	}

	public void nofun()
	{
		Debug.Log("nofun");
	}
	public void Strfun(string str)
	{
		Debug.Log("Strfun:" + str);
	}
	public void intfun(int va)
	{
		Debug.Log("intfun:" + va);
	}
	public void boolfun(bool va)
	{
		Debug.Log("boolfun:" + va);
	}
}

參考

發表留言