脚本(Script)
事件功能
MonoBehavior的详细回调时间表
定期的更新活动
Update在渲染帧之前以及在计算动画之前被调用。
void Update() {
}
在每次物理更新之前调用一个名为FixedUpdate的单独事件函数。按固定时间间隔调用。(注意FixedUpdate与Update一样是在主线程进行回调的,Unity 在内部使用了一个委托队列来实现固定时间调用FixedUpdate,Update与FixedUpdate之间的调用有Unity根据算法生成的先后顺序,所以在FixedUpdata中修改变量并不用加锁,如此可见,在Update中使用死循环FixedUpdate会卡住,同理,如果在FixedUpdate中使用死循环Update也会一样卡住)
void FixedUpdate() {
}
有时也可以在对场景中的所有对象调用Update和FixedUpdate函数之后以及在计算所有动画之后的某个时间点进行其他更改。该LateUpdate功能,可用于这类情况。(通常用于摄像机的跟随)
void LateUpdate() {
}
初始化事件
所有的Awake都将在第一个Start被调用之前完成。这意味着Start函数中的代码可以利用之前已经在Awake阶段执行的其他初始化,Awake将在脚本的构造函数调用后立即在主线程被调用。
GUI事件
Unity具有用于在场景中的主要操作上呈现GUI控件并响应这些控件的点击的系统。该代码的处理方式与正常的帧更新有所不同,因此它应该放在OnGUI函数中,这将被定期调用。(注意,这是旧版的UI系统,这很慢,会导致游戏帧数下降,建议使用新的UGUI或者第三方的NGUI)
void OnGUI() {
GUI.Label(labelRect, "Game Over");
}
您还可以检测出现在GameObject中的鼠标事件。这可以用于定位武器或显示当前在鼠标指针下的角色的信息。一组OnMouseXXX事件函数(例如,OnMouseOver,OnMouseDown)可用于允许脚本使用鼠标对用户操作做出反应。例如,如果在指针在特定对象上方按下鼠标按钮,则该对象脚本中的OnMouseDown函数将被调用(如果存在,而且需要碰撞器)。
物理事件
物理引擎将通过调用该对象脚本的事件函数来报告对象的冲突。该OnCollisionEnter,OnCollisionStay和OnCollisionExit如接触时函数被调用,召开和破碎。当对象的对撞机被配置为触发器(即,对象机,只是简单地检测到什么东西进入而不是身体反应)时,将调用相应的OnTriggerEnter,OnTriggerStay和OnTriggerExit函数。如果在物理更新期间检测到多个联系人,则可以连续调用这些功能,因此将参数传递给提供冲突细节的功能(进入对象的位置等)的功能。
privatevoidOnCollisionEnter2D(Collision2Dcollision)
{
}
其他回调我将会在我的[Unity Tips中慢慢更新]
注:在Vs中右键可选择实现Unity事件函数。
以下是Unity官方手册上的一些脚本例子:
创建和销毁GameObject
一些游戏在场景中保持不变的对象数量,但是在游戏过程中创建和删除角色,珍宝和其他对象非常常见。在Unity中,可以使用创建现有对象的新副本的Instantiate函数创建一个GameObject :
public GameObject enemy;
void Start() {
for (int i = 0; i < 5; i++) {
Instantiate(enemy);
}
}
请注意,制作副本的对象不一定存在于场景中。在编辑器中使用从"项目"面板拖动到public变量的预设体更为常见。另外,实例化一个GameObject将会复制原始文件中的所有组件。
还有一个Destroy(),在帧更新完成后或在短时间延迟后可选择地破坏对象:
void OnCollisionEnter(Collision otherObj) {
if (otherObj.gameObject.tag == "Missile") {
Destroy(gameObject,0.5f);
}
}
请注意,Destroy函数可以破坏各个组件而不影响GameObject本身。一个常见的错误是写下如下:
Destroy(this);
这将实际上只是销毁调用它的脚本组件,而不是摧毁脚本所附加的GameObject。
随机选择元素
随机选择项目或值在很多游戏中很重要。这部分展示了如何使用Unity的内置随机函数来实现一些常见的游戏机制。
从数组中随机选择项
随机抽取数组元素可以选择零和数组最大索引值之间的随机整数(等于数组的长度减去一个)。这可以使用内置的Random.Range函数轻松完成:
varelement = myArray[Random.Range(0, myArray.Length)];//范围随机数
注意:Random.Range返回的值在[0,myArray)之间,即包括第一个数但不包括最后一个数。
选择不同概率的项目
例如,NPC在遇到玩家时可能会以几种不同的方式作出反应:
- 50%的几率友好问候
- 25%的几率逃跑
- 20%的几率立即攻击
-
5%的几率提供钱作为礼物
你可以把这些不同的结果想象成一个纸条,把纸条分成不同的部分,每个部分占据了整条带子的一定长度。被占用的部分等于所选结果的概率。做出这样的选择相当于随机选取一条狭长的地带投掷一个飞镖,然后观察它落在哪个部分。
在脚本中,纸条实际上是一组浮点数,用来表示不同项目的概率。
随机点是通过将Random.value(会返回0.0到1.0之间的随机数)乘以数组中所有浮点数的总和获得的,它们不需要加起来为1,因为计算的是单个浮点值相对于浮点值和的相对大小。要查找点在纸条上的哪个区域,依次比较随机点与数组的值,如果随机点小于数组中的值,表明标中这个元素,如果大于,把随机点减去数组上的这个值,继续查找,直到找到正确的元素。下面是代码:
floatChoose(float[]probs)
{
floattotal = 0;
foreach (floateleminprobs)
{
total +=elem;
}
floatrandomPoint =Random.value *total;
for (inti = 0;i < probs.Length;i++)
{
if (randomPoint <probs[i])
{
returni;
}
else
{
randomPoint -=probs[i];
}
}
returnprobs.Length - 1;
}
注意:最后的返回语句是必需的,因为Random.value可以返回1这个的结果。在这种情况下,搜索将不会在任何地方找到随机点,所以这时可以返回一个您设定的特殊值。
if (randomPoint <probs[i])
注意:如果使用<=而不是<,可以不用加额外的返回语句,但偶尔您可能会需要选择一个概率是0的项目(可以把0概率设为最后一个项目)。
加权连续随机值
如果说,你要随机金币宝箱中发现的数量,并且您希望可能会在1到100之间的任何数字中得到最终结果,但是让数字尽可能较低。如果使用浮点方法来实现这一点,需要您设置一个100个浮点数(即纸带上的部分),这是难以处理的;如果您不限于整数,而是希望在范围内使用任何类型的数字,则不可能使用该方法。
一个更好的连续结果的方法是使用AnimationCurve(动画曲线)将"原始"随机值转换为"加权"值; 通过绘制不同的曲线形状,您可以产生不同的权重。代码也更简单:
publicAnimationCurvecurve;
floatCurveWeightedRandom(AnimationCurvecurve)
{
returncurve.Evaluate(Random.value);
}
通过从Random.value读取来选择0到1之间的随机值。然后将其传递给curve.Evaluate(),将其视为水平坐标,并返回该水平位置处的曲线的相应垂直坐标。曲线的较平缓部分具有更大的被选择的机会,而较陡峭的部分的拾取机会较低。
线性曲线,y=x
该曲线在开始时较平缓,然后在最后更陡峭,因此它具有较低的价值机会,降低了高值的几率。
这个曲线在开始和结束都很浅,使得接近两端的值更常见,而在中间陡峭,这将使这些值变得罕见。还要注意,用这个曲线,高度值已经向上移动:曲线底部为1,曲线顶部为10,这意味着曲线产生的值将在1-10范围内,而不是像以前的曲线那样的0-1。
通过在您的一个脚本上定义一个公共AnimationCurve(动画曲线)变量,您将可以通过视觉视图查看和编辑曲线,而不需要计算值。
这种技术产生浮点数。如果要计算整数结果 - 例如,您需要82个金件,而不是82.1214个金件,您可以将计算的值传递给像Mathf.RoundToInt()这样的函数进行转换。
洗牌
voidShuffle(int[]deck)
{
for (inti = 0;i < deck.Length;i++)
{
inttemp =deck[i];
intrandomIndex =Random.Range(0,deck.Length);
deck[i] =deck[randomIndex];
deck[randomIndex] =temp;//随机交换
}
}
选择一组不重复的项目
一个常见的任务是从一个集合中随机选择一些项目,而不是多次挑选相同的项目。例如,您可能希望在随机生成点生成一些NPC,但确保在每个点只生成一个NPC。这可以通过顺序迭代项目来完成,为每个项目随机决定是否将其添加到所选择的那组集合中。
例如,假设十个产生点可用,但只能选择五个。选择第一个项目的概率为5/10或0.5。如果第一个项目被选择,那么第二个项目的概率将是4/9或0.44(即仍然需要四个项目,有九个可供选择)。但是如果没有选择第一个,那么第二个的概率将为5/9或0.56(即,仍然需要五个,剩下九个可供选择)。这一直持续到集合包含所需的五个项目。您可以通过以下代码完成此操作:
Transform[]ChooseSet(Transform[]spawnPoints, intnumRequired)
{
Transform[]result = newTransform[numRequired];
intnumToChoose =numRequired;
for (intnumLeft =spawnPoints.Length;numLeft > 0; numLeft--)
{
floatprob = (float)numToChoose / (float)numLeft;
if (Random.value <=prob)
{
numToChoose--;
result[numToChoose] =spawnPoints[numLeft - 1];
if (numToChoose == 0)
{
break;
}
}
}
returnresult;
}
请注意,虽然这样选择是随机的,但所选集中的项目将与原始数组中的顺序相同,因此可能需要在使用前洗牌。
空间随机点
可以通过将Vector3的每个值设置为Random.value返回的值来选择立方体积中的随机点:
varrandVec =Vector3(Random.value,Random.value,Random.value);
这就给出了一个立方体内一个单位长度为1单位的点。这个立方体可以简单地通过将向量的X、Y和Z分量相乘,得到所需的值。如果其中一个坐标轴被设为零,那么这个点总是在一个平面内。例如,在"地面"上随机选取一个点时,通常是随机设置X和Z分量,并将Y分量设置为零。
当需要的是一个球体内的点(例如,当你想要一个从原点到给定半径内的任意点的时候),你可以随机使用。内单位球乘以所需半径:
Vector3randWithinRadius =Random.insideUnitSphere *radius;//insideUnitSphere返回半径是1的球内的点
注意:如果将结果向量(vector3)的一个值设置为零,并不能再一个圆内得到一个较好的随机点。尽管这一点确实是随机的,并且在正确的半径范围内,但它的概率是严重偏向于圆的边缘的。因此点将会非常不均匀地分布。您应该为此任务使用Random.insideUnitCircle:
varrandWithinCircle =Random.insideUnitCircle *radius;