Unity学习笔记3:随机数和动画脚本
2013-03-17 09:57 Mr.Raindrop 阅读(490) 评论(0) 编辑 收藏 举报Random的几个典型应用介绍如下。
1.随机数的获取:Random.Range(0,array.Length);在0到array.Length-1之间随机取一个数。在数组(比如说,物品数组)myArray中随机取一个数(随机取一件物品):
var index = Random.Range(0, myArray.Length); myArray[index];
2.不同概率随机数的获取:
function Choose(probs: float[]) { var total = 0; for (elem in probs) { total += elem; } var randomPoint = Random.value * total; for (i = 0; i < probs.Length; i++) { if (randomPoint < probs[i]) return i; else randomPoint -= probs[i]; } return probs.Length - 1; }
其中probs是各elem取得的概率,total是它们相加的总和(可以不为1),Random.value随机获取一个概率数,最后一个return是防止randomPoint=total(等于1)的情况,第二个for循环无法为此时的randomPoint找到一个匹配值。
Shffling a List——随机出一个列表,比如洗牌。方法是把牌堆里的每张牌按序和一张随机位置的牌交换顺序:
function Shuffle(deck: int[]) { for (i = 0; i < deck.Length; i++) { var temp = deck[i]; var randomIndex = Random.Range(0, deck.Length); deck[i] = deck[randomIndex]; deck[randomIndex] = temp; } }
3.无重复的选择一组随机物品集合:有很多应用场合,比如无重复选择一组NPC的出生地点,假设有10个出生点,5个NPC,于是要求随机从10个出生点筛选出5个无重复的出生点,第一个被选出的地点概率为5/10,第二个为4/9,依次类推。
var spawnPoints: Transform[]; function ChooseSet(numRequired: int) { var result = new Transform[numRequired]; var numToChoose = numRequired; for (numLeft = spawnPoints.Length; numLeft > 0; numLeft--) { // Adding 0.0 is simply to cast the integers to float for the division. var prob = numToChoose + 0.0 / numLeft + 0.0; if (Random.value <= prob) { numToChoose--; result[numToChoose] = spawnPoints[numLeft - 1]; if (numToChoose == 0) break; } } return result; }
在上述NPC出生点例子中,spawnPoints[]是出生地点的集合。numRequired为5,每当随机值Random.Value不大于地点选择概率(第一个为5/9)则numToChoose自减1且把该地点(即spawnPointsp[numLeft-1])添加到result[numToChoose]中,从result[4]筛选到result[0]结束。另外,如果出生顺序需要打乱,把spawnPoints[]的顺序洗牌即可。
4.空间中取随机点:
cube空间中取随机点:var randVec = Vector3(Random.value, Random.value, Random.value);因为Random.value返回的值介于0和1之间,如果cube的x,y,z不为1,则将Random.value分别乘以各个轴上的值即可。
半径为radius的sphere空间中取随机点:var randWithinRadius = Random.insideUnitSphere * radius;
注:Note that if you set one of the resulting vector's components to zero, you will *not* get a correct random point within a circle. Although the point is indeed random and lies within the right radius, the probability is heavily biased toward the edge of the
circle and so points will be spread very unevenly. You should use Random.insideUnitCircle for this task instead:
var randWithinCircle = Random.insideUnitCircle * radius;
------------
Animation Scripting:
Animation Blending:动画Blending就是在两个角色动画片段之间的平滑过渡,比如从走路动画过渡到站立动画,首先设置Wrap Mode为Loop,关闭play automatically,这样动画控制完全由Script掌控。以下是实现两种状态自然过渡的javascript代码:
function Update () { if (Input.GetAxis("Vertical") > 0.2) animation.CrossFade ("walk"); else animation.CrossFade ("idle"); }
动画层(Layer):在两个动画blending的时候有个权重的概念,而且权重和是要标准化为1的,比如动画run和walk的权重各为1,blending以后各为0.5(加起来为1),但是当我需要做”射击“动作的时候,希望”射击“动画的权重是100%,即立即切换为射击动画。简单的实现方法是让射击动画的层放在比run和walk更高的层,这样需要”射击“动画Fade in的时候直接在极短的时间内从0到100%。
function Start () { // Set all animations to loop animation.wrapMode = WrapMode.Loop; // except shooting animation["shoot"].wrapMode = WrapMode.Once; // Put idle and walk into lower layers (The default layer is always 0) // This will do two things // - Since shoot and idle/walk are in different layers they will not affect // each other's playback when calling CrossFade. // - Since shoot is in a higher layer, the animation will replace idle/walk // animations when faded in. animation["shoot"].layer = 1; // Stop animations that are already playing //(In case user forgot to disable play automatically) animation.Stop(); } function Update () { // Based on the key that is pressed, // play the walk animation or the idle animation if (Mathf.Abs(Input.GetAxis("Vertical")) > 0.1) animation.CrossFade("walk"); else animation.CrossFade("idle"); // Shoot if (Input.GetButtonDown ("Fire1")) animation.CrossFade("shoot"); }
Animation Mixing:和Blending不同,Blending是两阶段动画的自然过渡,Mixing则是两种动画的混合,比如挥手动画,如果在站定和走路两种状态都有挥手动画,那么如果不使用Mixing就需要做两个动画,一个是站立挥手,一个是边走路边挥手。现在可以把挥手动画和肩膀的移动混合起来,这样挥手就只要做一个动画,然后和肩膀动画混合起来即可。
/// Adds a mixing transform using a Transform variable /// 此处shoulder是一个transform变量。 var shoulder : Transform; animation["wave_hand"].AddMixingTransform(shoulder);
shoulder动画也可以从某个路径导入,如下:
function Start () { // Adds a mixing transform using a path instead var mixTransform : Transform = transform.Find("root/upper_body/left_shoulder"); animation["wave_hand"].AddMixingTransform(mixTransform); }
拿身体倾斜和行走/跑步来说,做好行走/跑步循环,身体倾斜(左倾、右倾)的动画,然后用下面的脚本把倾斜叠加到行走/跑步上。Additive Animation:叠加动画。比如行走,跑步,身体左倾,身体右倾,这几个动作可以组成4个动画,行走+身体左倾,行走+身体右倾,跑步+身体左倾,跑步+身体右倾。如果再加上转头(左转和右转)动画,就要做2x2x2=8个动画。如果继续增加,动画制作的工作量会急剧增大。叠加动画的作用是把这些动画分解,只需要做2+2+2=6个动画,然后叠加起来。
private var leanLeft : AnimationState; private var leanRight : AnimationState; function Start () { leanLeft = animation["leanLeft"]; leanRight = animation["leanRight"]; // Put the leaning animation in a separate layer // So that other calls to CrossFade won't affect it. leanLeft.layer = 10; leanRight.layer = 10; // Set the lean animation to be additive leanLeft.blendMode = AnimationBlendMode.Additive; leanRight.blendMode = AnimationBlendMode.Additive; // Set the lean animation ClampForever // With ClampForever animations will not stop // automatically when reaching the end of the clip leanLeft.wrapMode = WrapMode.ClampForever; leanRight.wrapMode = WrapMode.ClampForever; // Enable the animation and fade it in completely // We don't use animation.Play here because we manually adjust the time // in the Update function. // Instead we just enable the animation and set it to full weight leanRight.enabled = true; leanLeft.enabled = true; leanRight.weight = 1.0; leanLeft.weight = 1.0; // For testing just play "walk" animation and loop it animation["walk"].wrapMode = WrapMode.Loop; animation.Play("walk"); } // Every frame just set the normalized time // based on how much lean we want to apply function Update () { var lean = Input.GetAxis("Horizontal"); // normalizedTime is 0 at the first frame and 1 at the last frame in the clip leanLeft.normalizedTime = -lean; leanRight.normalizedTime = lean; }
Tip: When using Additive animations, it is critical that you also play some other non-additive animation on every transform that is also used in the additive animation, otherwise the animations will add on top of the last frame's result. This is most certainly not what you want.
重采样和帧率:在建模软件中制作的动画一般设置好了一个固定的帧率,比如是60fps,但是在游戏中动画的帧要经过重新采样,这是因为游戏中的帧率是一个变动的值,可能上一秒是52.4fps下一秒就是74.9fps,因此不能保证你在动画中制作的每一帧在游戏中都能采样出来。这就导致两个微妙的问题:
1.比如你在一个180度转向的动画中,要设置一个if条件语句,条件是转到一半的时候,也就是90度。但是这个条件可能在整个转向过程中都不成立,因为有可能采样正好是采样到90度的前一个帧和后一个帧而跳过90度的那个帧。
解决方法是在animation面板中插入animation event:
首先在游戏对象脚本中设置好事件响应函数。
// This JavaScript function can be called by an Animation Event function PrintFloat (theValue : float) { Debug.Log ("PrintFloat is called with a value of " + theValue); }
接下来如图所示:
2.另一个问题是,设置动画循环模式WrapMode为once,由于帧率变化和重采样的关系,游戏可能采样不到最后一帧。如果你需要确保采样到最后一帧,但又不需要循环播放,解决方法是设置WrapMode为ClampForever,最后一帧会被采样且一直停留在最后一帧直到动画停止播放。