1.使用触发检测完成道具触发的逻辑
2.学习SendMessage方法及相关机制
3.搭好游戏结束结算界面的UI框架
4.实现动态随机生成道具
个人理解,SendMessage提供了一种跨脚本的信息(参数)传递的途径,相当于是给一个物体发送消息,让它完成某个特定的功能(执行一个方法),在以后的开发中应该会非常有用。我们要尽量减少全局变量的定义,仅在非常重要,所有场景都有可能用到的值才将其加入全局变量,其他时候用SendMessage就行了。来看一看官网的API:
SendMessage ("函数名",参数,SendMessageOptions)
这是最基本的方法,仅调用自身的脚本
BroadcastMessage ("函数名",参数,SendMessageOptions)
向下传播,调用自身的和子Object的Script
SendMessageUpwards ("函数名",参数,SendMessageOptions)
会先遍历父对象然后调用自身和父对象的Script
我们先新建一个测试场景,用于测试SendMessage方法。
建一个Object脚本作为测试的函数,三种写法如下。
不带参数的:
public void DisplayNumber()
{
Debug.Log("我是被调用的方法");
}
带一个参数的:
public void DisplayNumber(string str1)
{
Debug.Log("我是被调用的方法"+str1);
}
传一个数组作为参数:
public void DisplayNumber2(System.Object[] ObjArray)
{
Debug.Log("number:"+ObjArray[0]);
Debug.Log("number:"+ObjArray[1]);
Debug.Log("number:"+ObjArray[2]);
}
然后再建一个脚本,来写sendMessage方法(此处仅测试最后一个):
public class Test_SendMessage : MonoBehaviour {
public GameObject invokeobj;
// Use this for initialization
void Start () {
System.Object[] testobj = new System.Object[3];
testobj[0] = 1;
testobj[1] = "im a string";
testobj[2] = 1.23f;
invokeobj.SendMessage("DisplayNumber2",testobj);
}
}
给这颗红宝石来加一个碰撞体Collider(记得勾选IsTrigger,使之能进行触发检测),新建一个Diamond脚本来写碰撞检测。触发检测内容包括:播放一个“ding”的声音,然后让计数器递增(在GlobalManager中定义一个DiamondsCount整形变量)
创建AudioSource用于播放碰到道具时的声音
很简单,附上Diamond脚本的内容
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Diamonds : MonoBehaviour {
public float DiamondRotateSpeed = 1f;
private const string HeroName = "Hero";
private AudioSource RedDiamondCollision;
// Use this for initialization
void Start () {
//rotate(invoke to save more memory)
InvokeRepeating("RedDiamondRotate", 1f, 0.1f);
RedDiamondCollision = GameObject.Find("_AudioManager/_AudioEffect").GetComponent<AudioSource>();
switch(GlobalManager.GlobalVol)
{
case ProjectVolume.none:
RedDiamondCollision.volume = 0f;
break;
case ProjectVolume.NoneVolume:
RedDiamondCollision.volume = 0f;
break;
case ProjectVolume.HalfVolume:
RedDiamondCollision.volume = 0.5f;
break;
case ProjectVolume.FullVolume:
RedDiamondCollision.volume = 1f;
break;
}//应用上这个全局声音设置
RedDiamondCollision.loop = false;//
//count ++
++GlobalManager.RedDiamondCount;
//destroy diamond triggered
Destroy(this.gameObject);
Debug.Log("eat diamond!");
}
}
void RedDiamondRotate()
{
//RedDiamond Rotation
this.transform.Rotate(Vector3.down * DiamondRotateSpeed, Space.World);
}
// Update is called once per frame
void Update () {}
}
预计在结算界面写奔跑的距离,道具数目,退出按钮,再来一次按钮等
首先是定义部分,挂上所有贴图,并且应用之前定义过的皮肤
//public
public GUISkin ProjectGUISkin;
public Texture2D TxtBackground;
public Texture2D TxtRedDiamond;
public Texture2D TxtBlueDiamond;
public Texture2D TxtTryAgain;
public Texture2D TxtExit;
//为了测试,把路程设为100
void Start () {
GlobalManager.RunningDistance = 100;//Used for test!!
StartCoroutine("DisplayRunningDistance");
StartCoroutine("DisplayRedDiamond");
}
创建一个协程来实现数据从0递增到要显示的数据的效果(同样,在GlobalManager中定义好跑步的里程,此处进行调用)。
//Display distance
IEnumerator DisplayRunningDistance()
{
yield return new WaitForEndOfFrame();
while(true)
{
yield return new WaitForSeconds(0.03f);
if(RunLength>=GlobalManager.RunningDistance-1)
{
StopCoroutine("DisplayRunningDistance");
}
RunLength++;
}
}
然后是道具的结算,代码大同小异,复制过来改一些地方就行了
IEnumerator DisplayRedDiamond()
{
yield return new WaitForEndOfFrame();
while (true)
{
yield return new WaitForSeconds(0.03f);
if (RedDiamondsAmount >= GlobalManager.RedDiamondCount - 1)
{
StopCoroutine("DisplayRedDiamond");
}
RedDiamondsAmount++;
}
}
然后是OnGUI方法中的内容,创建按钮来实现场景转换,都是复习过的内容,就不再赘述了,直接上代码
private void OnGUI()
{
GUI.skin = ProjectGUISkin;
//Draw BackGround
GUI.DrawTexture(new Rect(0, 0, Screen.width, Screen.height), TxtBackground);
//Display Distance
GUI.Label(new Rect(250,150,250,100), "Distance:" + RunLength);
//Display Diamonds
//red
GUI.DrawTexture(new Rect(250, 260, 50, 50), TxtRedDiamond);
GUI.Label(new Rect(310, 260, 250, 100), ":" + RedDiamondsAmount.ToString());
//Try Again
if (GUI.Button(new Rect(500, 150, 100, 50),"", ProjectGUISkin.GetStyle("Btn_TryAgain")))
{
SceneManager.LoadScene(1);
}
//Exit
if (GUI.Button(new Rect(500, 230, 100, 50), "", ProjectGUISkin.GetStyle("Btn_Exit")))
{
SceneManager.LoadScene(0);
}
}
一个比较简陋的结算界面就完成了,在关卡界面也加一个GUI,在右上角显示里程和道具数量,打开LevelOneGUI,添加代码。
//Distance Display
GUI.Label(new Rect(0, 0, 70, 35), "Distance:" + GlobalManager.RunningDistance.ToString());
//Diamonds UI Display
GUI.DrawTexture(new Rect(0, 40, 40, 40), txtRedDiamond);
GUI.Label(new Rect(45, 40, 50, 30), ":" + GlobalManager.RedDiamondCount.ToString());
个人觉得动态随机生成应该是整个项目最麻烦的部分了,需要用到随机数的生成还有之前讲到的SendMessage方法,以及回调函数,要传入一个Object数组并且将其解析。新创建一个DynamicCreating脚本,用于实现动态生成。我们首先要分析传入的参数应该有哪些,即生成一个物体需要的数据:物体源对象(一个prefab),生成数量,销毁时间,生成位置(需要一个Vector3,为了实现随机在一个范围中生成,还需要考虑最小值和最大值),把这些思考反映在注释上,之后来一个一个实现。
//This Script is used for Prefab Clone
//use instantiate() method
//parameters:x:xmin->xmax
//y:const
//z:zmin->zmax
//->
//vec3(x,y,z)
//
//object
//object amount
//destroy time
//
写一个DynamicCreate函数,传入一个System.Object[]
void DynamicCreate(System.Object[] objArray)
这个函数将来要作为SendMessage的目标对象,包含了创建的所有内容。(用Instantiate方法来创建物体,为了避免解析时发生类型错误,做好类型转换)
void DynamicCreate(System.Object[] objArray)
{
GameObject ClonePrefab = null;
int CloneAmount = 0;
int DestroyTime = 0;
int x_min = 0;
int x_max = 0;
int y = 0;
int z_min = 0;
int z_max = 0;
//parse parameters
if(objArray!=null)
{
if(objArray.Length==8)
{
ClonePrefab = (GameObject)objArray[0];
CloneAmount = System.Convert.ToInt32(objArray[1]);
DestroyTime= System.Convert.ToInt32(objArray[2]);
x_min= System.Convert.ToInt32(objArray[3]);
x_max= System.Convert.ToInt32(objArray[4]);
y= System.Convert.ToInt32(objArray[5]);
z_min= System.Convert.ToInt32(objArray[6]);
z_max= System.Convert.ToInt32(objArray[7]);
}
}
//set value
GameObject GoClone = (GameObject)Instantiate(ClonePrefab);
GoClone.transform.position = new Vector3(GetRandomNumber(x_min, x_max),
y, GetRandomNumber(z_min, z_max));
//confirm destroy time
Destroy(GoClone, (float)DestroyTime);
}
这里用到了随机数,还得写一个返回随机数的函数
private int GetRandomNumber(int min,int max)
{
return Random.Range(min, max);
}
然后打开LevelOneManager来添加回调函数,sendMessage方法以及相应的参数传递。先定义对应的GameObject变量。(写好注释,不然会晕)
//used for dynamicCreating
public GameObject GoClonePrefabOriginal;//prefab PROTOTYPE
public GameObject GoDynamicCloneObject; //Script Object(Load DynamicCreateScript)
public GameObject REF_Position_left; //spwaning start position
public GameObject REF_Position_right;
public Transform HeroTransform;
可能会用到人物的位置,所以我们用一个transform来获取角色的transform组件
//Get Hero's Transform Component
HeroTransform = GameObject.Find("Hero").GetComponent<Transform>();
然后来写SendMessage的函数,我们只需要三个参数:物体原型,创建数量,销毁时间。其他的位置信息我们在场景创建两个cube,摆到合适的位置,相当于两个锚点,确定在两个position之间随机生成道具。
注意要在场景中把对应的物体GameObject绑到脚本组件上去
上代码
private void DynamicCreateProp(GameObject CloneOriginal,int Amount,int DestroyTime)
{
//collect parameters
System.Object[] ObjArray = new System.Object[8];
ObjArray[0] = CloneOriginal;
ObjArray[1] = Amount;
ObjArray[2] = DestroyTime;
ObjArray[3] = REF_Position_left.transform.position.x;
ObjArray[4] = REF_Position_right.transform.position.x;
ObjArray[5] = REF_Position_left.transform.position.y;
ObjArray[6] = HeroTransform.position.z + 100;
ObjArray[7] = HeroTransform.position.z + 300;
//send value
GoDynamicCloneObject.SendMessage("DynamicCreate", ObjArray,
SendMessageOptions.DontRequireReceiver);
}
然后来写生成函数以及回调函数,预计五秒来进行一次生成。
private void DynamicCreateAll()
{
if(GlobalManager.RunningDistance%10==0)
{
DynamicCreateProp(GoClonePrefabOriginal, 5, 20);
}
//7.27update Dynamic Creating Items
InvokeRepeating("DynamicCreateAll", 1f, 1f);
差不多了,这样测试一下就有效果了,由于网突然断了导致重新写了一遍,心态有点崩,总结得很不清楚,见谅见谅(反正估计也没几个人看= =