三、编写脚本
创建一个名为Dice的脚本,挂载到游戏对象Dice上。
(一)抛起骰子
前面已经分析了抛骰子需要使用的函数方法,实现起来就很简单了。脚本如下:
    
    
using System . Collections ; using System . Collections . Generic ; using UnityEngine ; public class Dice : MonoBehaviour { Rigidbody rb ; Vector3 force ; Vector3 torque ; // Start is called before the first frame update void Start ( ) { rb = GetComponent < Rigidbody > ( ) ; } // Update is called once per frame void Update ( ) { if ( Input . GetKeyDown ( "space" ) ) { force = new Vector3 ( 0 , Random . Range ( 20f , 30f ) , 0 ) ; torque = new Vector3 ( Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) ) ; rb . AddForce ( force , ForceMode . Impulse ) ; rb . AddTorque ( torque , ForceMode . Impulse ) ; } }
现在回到游戏中,按空格键就可以抛起骰子了。请注意,这里对力和扭矩都赋予了一定随机性,大家可以根据自己的需求调整具体数值。
(二)获得初始时骰子的局部坐标
Unity提供了获取碰撞体大小以及中点局部坐标的方法,即Bound.size和Bounds.center,结合之前画的示意图,我们可以轻松得到初始状态下骰子八个顶点的局部坐标。
    
    
public Collider diceCollider ; Vector3 [ ] vertices = new Vector3 [ 8 ] ; void Start ( ) { rb = GetComponent < Rigidbody > ( ) ; GetLocalVertices ( ) ; } void GetLocalVertices ( ) { Vector3 diceCenter = diceCollider . bounds . center ; float dicex = diceCollider . bounds . size . x ; float dicey = diceCollider . bounds . size . y ; float dicez = diceCollider . bounds . size . z ; //下面四个点 vertices [ 0 ] = diceCenter + new Vector3 ( dicex , - dicey , dicez ) * 0.5f ; vertices [ 1 ] = diceCenter + new Vector3 ( - dicex , - dicey , dicez ) * 0.5f ; vertices [ 2 ] = diceCenter + new Vector3 ( - dicex , - dicey , - dicez ) * 0.5f ; vertices [ 3 ] = diceCenter + new Vector3 ( dicex , - dicey , - dicez ) * 0.5f ; //上面四个点 vertices [ 4 ] = diceCenter + new Vector3 ( dicex , dicey , dicez ) * 0.5f ; vertices [ 5 ] = diceCenter + new Vector3 ( - dicex , dicey , dicez ) * 0.5f ; vertices [ 6 ] = diceCenter + new Vector3 ( - dicex , dicey , - dicez ) * 0.5f ; vertices [ 7 ] = diceCenter + new Vector3 ( dicex , dicey , - dicez ) * 0.5f ; }
(三)获得骰子落下后的顶点坐标
这一步稍微麻烦一些,因为骰子落回桌面后,不会马上停下来,有时还会在桌面上翻滚。因此,不能用碰撞检测作为取得骰子坐标的判断条件。当骰子彻底停下来时,它的速度和角速度都会变为零,我们就用它来进行判断。此外,当游戏刚开始时,骰子的速度和角速度也为零,所以我们要通过一个布尔值来排除该情况。
确定骰子停止后,我们要将骰子的所有顶点转换成世界坐标,用到的方法是Transform.TransformPoint(详见 https://docs.unity3d.com/cn/2021.1/ScriptReference/Transform.TransformPoint.html )
这一步的脚本如下:
    
    
Vector3 [ ] newVertices = new Vector3 [ 8 ] ; bool isStopped = true ; void Update ( ) { if ( rb . velocity != Vector3 . zero && rb . angularVelocity != Vector3 . zero ) { isStopped = false ; } if ( rb . velocity == Vector3 . zero && rb . angularVelocity == Vector3 . zero && isStopped == false ) { for ( int i = 0 ; i < newVertices . Length ; i ++ ) { newVertices [ i ] = diceCollider . transform . TransformPoint ( vertices [ i ] ) ; } isStopped = true ; } }
(四)获得骰子点数
现在,我们已经知道骰子每个面对应的顶点以及结束时的骰子顶点世界坐标。那么,只要找出此时骰子顶面的顶点有哪些,就可以获得骰子点数了。第二步介绍了一种判断顶面顶点的方法,这里还可以使用另一种方法:只要某个顶点的y值大于它对面顶点的y值,那么就可以判断出该顶点位于顶面。
再次来看骰子的图形。例如,2点对面是5点,所以顶点6对应顶点2,顶点5对应顶点1,顶点4对应顶点0,顶点7对应顶点3。届时只要逐对比较这些顶点的y值就可以了。
但是,这里还隐藏着一个坑。虽然我们的桌面应该是水平的,理论上骰子与桌面接触面的顶点y值应该相同,但是不排除这些顶点之间有细微高度差。即使在小数点后很多位有差异,在判断时也会认为这些值不等,从而影响我们的判断结果。仍以上图中的骰子为例。如果顶点0和顶点1的y值略高于顶点2和顶点3的y值,顶点4和顶点5的y值略高于顶点6和顶点7的y值,那么最后可能得出顶面上的骰子点数为1点或2点。这显然不是我们想要的结果。为了得到唯一值,我们可以将y值四舍五入,这样就能避免上面提到的情况。
最后,将得到的点数传给UI,就可以在画面上显示出读数了。写好脚本后,记得要把Inspector中的Number Text拖入脚本相应栏位。
    
    
public TMP_Text number ; void Update ( ) { if ( Input . GetKeyDown ( "space" ) && isStopped == true ) { force = new Vector3 ( 0 , Random . Range ( 20f , 30f ) , 0 ) ; torque = new Vector3 ( Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) ) ; rb . AddForce ( force , ForceMode . Impulse ) ; rb . AddTorque ( torque , ForceMode . Impulse ) ; } if ( rb . velocity != Vector3 . zero && rb . angularVelocity != Vector3 . zero ) { isStopped = false ; } if ( rb . velocity == Vector3 . zero && rb . angularVelocity == Vector3 . zero && isStopped == false ) { for ( int i = 0 ; i < newVertices . Length ; i ++ ) { newVertices [ i ] = diceCollider . transform . TransformPoint ( vertices [ i ] ) ; } GetDiceNumber ( ) ; isStopped = true ; } } void GetDiceNumber ( ) { float newVertice_0_y = Mathf . Round ( newVertices [ 0 ] . y ) ; float newVertice_1_y = Mathf . Round ( newVertices [ 1 ] . y ) ; float newVertice_2_y = Mathf . Round ( newVertices [ 2 ] . y ) ; float newVertice_3_y = Mathf . Round ( newVertices [ 3 ] . y ) ; float newVertice_4_y = Mathf . Round ( newVertices [ 4 ] . y ) ; float newVertice_5_y = Mathf . Round ( newVertices [ 5 ] . y ) ; float newVertice_6_y = Mathf . Round ( newVertices [ 6 ] . y ) ; float newVertice_7_y = Mathf . Round ( newVertices [ 7 ] . y ) ; if ( newVertice_0_y > newVertice_3_y && newVertice_1_y > newVertice_2_y && newVertice_4_y > newVertice_7_y && newVertice_5_y > newVertice_6_y ) { number . SetText ( "1" ) ; } if ( newVertice_4_y > newVertice_0_y && newVertice_5_y > newVertice_1_y && newVertice_6_y > newVertice_2_y && newVertice_7_y > newVertice_3_y ) { number . SetText ( "2" ) ; } if ( newVertice_1_y > newVertice_0_y && newVertice_2_y > newVertice_3_y && newVertice_5_y > newVertice_4_y && newVertice_6_y > newVertice_7_y ) { number . SetText ( "3" ) ; } if ( newVertice_0_y > newVertice_1_y && newVertice_3_y > newVertice_2_y && newVertice_4_y > newVertice_5_y && newVertice_7_y > newVertice_6_y ) { number . SetText ( "4" ) ; } if ( newVertice_0_y > newVertice_4_y && newVertice_1_y > newVertice_5_y && newVertice_2_y > newVertice_6_y && newVertice_3_y > newVertice_7_y ) { number . SetText ( "5" ) ; } if ( newVertice_2_y > newVertice_1_y && newVertice_3_y > newVertice_0_y && newVertice_6_y > newVertice_5_y && newVertice_7_y > newVertice_4_y ) { number . SetText ( "6" ) ; } }
(五)添加音效
最后,我们还可以添加骰子撞击桌面的音效。首先准备好音效素材,然后来考虑如何设置播放音效的条件。撞击声必然发生在骰子与桌面碰撞时,所以可以用OnCollisionEnter来判断,不过游戏刚开始时,骰子与桌面也有接触,我们可以用一个定时器来解决这个问题。抛起骰子时开始计时,只有计时数大于0时,骰子碰撞桌面才会播放音效。
    
    
public AudioSource dice ; float timer = 0 ; void Update ( ) { if ( Input . GetKeyDown ( "space" ) && isStopped == true ) { force = new Vector3 ( 0 , Random . Range ( 20f , 30f ) , 0 ) ; torque = new Vector3 ( Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) ) ; rb . AddForce ( force , ForceMode . Impulse ) ; rb . AddTorque ( torque , ForceMode . Impulse ) ; timer += Time . deltaTime ; } if ( rb . velocity != Vector3 . zero && rb . angularVelocity != Vector3 . zero ) { isStopped = false ; } if ( rb . velocity == Vector3 . zero && rb . angularVelocity == Vector3 . zero && isStopped == false ) { for ( int i = 0 ; i < newVertices . Length ; i ++ ) { newVertices [ i ] = diceCollider . transform . TransformPoint ( vertices [ i ] ) ; Debug . Log ( "点" + i + "的坐标为" + newVertices [ i ] ) ; } GetDiceNumber ( ) ; isStopped = true ; timer = 0 ; } } void OnCollisionEnter ( Collision other ) { if ( timer > 0 ) dice . PlayDelayed ( 0 ) ; }
写好脚本后,在Dice上添加组件Audio Source,将音效文件拖入AudioClip栏位,再把Audio Source组件拖入脚本的相应栏位,这个小项目就完成了。快点击播放试一试吧!你可以很方便地将这个掷骰子功能植入你的项目!
完整脚本:
    
    
using System . Collections ; using System . Collections . Generic ; using UnityEngine ; using UnityEngine . UI ; using TMPro ; public class Dice : MonoBehaviour { Rigidbody rb ; Vector3 force ; Vector3 torque ; public Collider diceCollider ; Vector3 [ ] vertices = new Vector3 [ 8 ] ; Vector3 [ ] newVertices = new Vector3 [ 8 ] ; bool isStopped = true ; public TMP_Text number ; public AudioSource dice ; float timer = 0 ; // Start is called before the first frame update void Start ( ) { rb = GetComponent < Rigidbody > ( ) ; GetLocalVertices ( ) ; } // Update is called once per frame void Update ( ) { if ( Input . GetKeyDown ( "space" ) && isStopped == true ) { force = new Vector3 ( 0 , Random . Range ( 20f , 30f ) , 0 ) ; torque = new Vector3 ( Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) , Random . Range ( - 3f , 3f ) ) ; rb . AddForce ( force , ForceMode . Impulse ) ; rb . AddTorque ( torque , ForceMode . Impulse ) ; timer += Time . deltaTime ; } if ( rb . velocity != Vector3 . zero && rb . angularVelocity != Vector3 . zero ) { isStopped = false ; } if ( rb . velocity == Vector3 . zero && rb . angularVelocity == Vector3 . zero && isStopped == false ) { for ( int i = 0 ; i < newVertices . Length ; i ++ ) { newVertices [ i ] = diceCollider . transform . TransformPoint ( vertices [ i ] ) ; } GetDiceNumber ( ) ; isStopped = true ; timer = 0 ; } } void GetLocalVertices ( ) { Vector3 diceCenter = diceCollider . bounds . center ; float dicex = diceCollider . bounds . size . x ; float dicey = diceCollider . bounds . size . y ; float dicez = diceCollider . bounds . size . z ; //下面四个点 vertices [ 0 ] = diceCenter + new Vector3 ( dicex , - dicey , dicez ) * 0.5f ; vertices [ 1 ] = diceCenter + new Vector3 ( - dicex , - dicey , dicez ) * 0.5f ; vertices [ 2 ] = diceCenter + new Vector3 ( - dicex , - dicey , - dicez ) * 0.5f ; vertices [ 3 ] = diceCenter + new Vector3 ( dicex , - dicey , - dicez ) * 0.5f ; //上面四个点 vertices [ 4 ] = diceCenter + new Vector3 ( dicex , dicey , dicez ) * 0.5f ; vertices [ 5 ] = diceCenter + new Vector3 ( - dicex , dicey , dicez ) * 0.5f ; vertices [ 6 ] = diceCenter + new Vector3 ( - dicex , dicey , - dicez ) * 0.5f ; vertices [ 7 ] = diceCenter + new Vector3 ( dicex , dicey , - dicez ) * 0.5f ; } void GetDiceNumber ( ) { float newVertice_0_y = Mathf . Round ( newVertices [ 0 ] . y ) ; float newVertice_1_y = Mathf . Round ( newVertices [ 1 ] . y ) ; float newVertice_2_y = Mathf . Round ( newVertices [ 2 ] . y ) ; float newVertice_3_y = Mathf . Round ( newVertices [ 3 ] . y ) ; float newVertice_4_y = Mathf . Round ( newVertices [ 4 ] . y ) ; float newVertice_5_y = Mathf . Round ( newVertices [ 5 ] . y ) ; float newVertice_6_y = Mathf . Round ( newVertices [ 6 ] . y ) ; float newVertice_7_y = Mathf . Round ( newVertices [ 7 ] . y ) ; if ( newVertice_0_y > newVertice_3_y && newVertice_1_y > newVertice_2_y && newVertice_4_y > newVertice_7_y && newVertice_5_y > newVertice_6_y ) { number . SetText ( "1" ) ; } if ( newVertice_4_y > newVertice_0_y && newVertice_5_y > newVertice_1_y && newVertice_6_y > newVertice_2_y && newVertice_7_y > newVertice_3_y ) { number . SetText ( "2" ) ; } if ( newVertice_1_y > newVertice_0_y && newVertice_2_y > newVertice_3_y && newVertice_5_y > newVertice_4_y && newVertice_6_y > newVertice_7_y ) { number . SetText ( "3" ) ; } if ( newVertice_0_y > newVertice_1_y && newVertice_3_y > newVertice_2_y && newVertice_4_y > newVertice_5_y && newVertice_7_y > newVertice_6_y ) { number . SetText ( "4" ) ; } if ( newVertice_0_y > newVertice_4_y && newVertice_1_y > newVertice_5_y && newVertice_2_y > newVertice_6_y && newVertice_3_y > newVertice_7_y ) { number . SetText ( "5" ) ; } if ( newVertice_2_y > newVertice_1_y && newVertice_3_y > newVertice_0_y && newVertice_6_y > newVertice_5_y && newVertice_7_y > newVertice_4_y ) { number . SetText ( "6" ) ; } } void OnCollisionEnter ( Collision other ) { if ( timer > 0 ) dice . PlayDelayed ( 0 ) ; } }
演示视频:
(本教程部分思路参考了雨松老师的文章:https://www.xuanyusong.com/archives/4222)
Logo

分享前沿Unity技术干货和开发经验,精彩的Unity活动和社区相关信息

更多推荐