unity随机生成未知符号教程 - 教程
前言
在某些游戏中,有一些让人感到意味不明的未知符号,例如在游戏《巴别塔圣歌》中,就有这样一些能让人在初次就看不懂的未知符号。

或者在其他时候,这些未知符号如果跟粒子系统结合在一起的话,也许可当作是一种特效,可以增加游戏的神秘感。
而现在,如果你想学的话,那么现在就行开始学了。
方法1
首先讲最容易的方法,最便捷的就先随机地显示Wingdings字体的字符,然后在随机显示字符后等待一小会即可,这个对于很多人来说都容易去理解,但,这个方法主要的难点是如何将TextMeshPro中的字体设为Wingdings字体,因此,接下来就要详细的讲一下如何更改TextMeshPro组件的字体。
打开文件资源管理器;
转到C:\Windows\Fonts,选取里面的Wingdings 常规;

- 将Wingdings 常规拖到unity的工程栏里,并右键选中拖过来的字体,创建
TextMeshPro的Wingdings字体资产。

- 将
TextMeshPro组件的字体属性(Font Asset)设为刚才得到的Wingdings字体资产,TextMeshPro组件的字体也更改好了。

其他只要你学过unity,实现起来就方便,这里就直接上方法了(想用这个方法得需要TextMeshPro组件)。
using System.Collections ; using System.Collections.Generic ; using UnityEngine ; using Unity.Collections ; using Unity.VisualScripting ; using System.Runtime.CompilerServices ; using TMPro ; public class randomSymbolSummon : MonoBehaviour { public floatwaitTime= 0.1f ; private bool isEnd = true ; private char[] chars; IEnumerator write( ) { isEnd = false ; GetComponent<TextMeshPro>( ).text = chars[Random.Range(0 , chars.Length)].ToString( ) ; yield return new WaitForSeconds(waitTime) ; isEnd = true ; } void Start( ) { chars = new char[127 - 33] ; char ch = ( char )33 ; for ( ; ch < 127 ; ch++ ) { chars[ch - 33] = ch; } write( ) ; } void Update( ) { if (isEnd) { StartCoroutine(write( ) ) ; } } } 下面就是最便捷的方法的效果。

方法2
继而来讲难一点的方法,难一点的方法呢,它的核心就是一个可以当作是用于写符号的笔的指针在点上随机移动。
开始,我们要定义一些公有的成员变量。就如点离点的距离,“画板”的长与宽,是否只显示一次符号,线的渲染,线的宽度及等待时间。然后,新建一个summon我们生成方法2中的符号的核心方法。就是手段,这将
public float space = 0.809f ; public uint a = 2 ; public uint b = 3 ; public MateriallineMaterial; public floatlineWidth= 0.1f ; public floatwaitTime= 0.1f ; private bool isEnd = true ; private void summon( ) { } 然后,大家在Start方法里调用一下summon方法,这将使对象一开始就能生成未知符号,并在Update函数里面设置执行summon方式的条件,只有能呈现多次符号,并且等会实现的write协程已经执行完毕,那么就才能执行write协程。
write协程,大家要让它能等待,就需要yield return和新定义的isEnd变量,因此,刚才就需要什么手段就很明了了。顺带一提醒,就是Start方法及Update等办法都不能是协程,这点unity萌新一定要注意!
void Start( ) { summon( ) ; } void Update( ) { if (!summonOneShot&& isEnd) { StartCoroutine(write( ) ) ; } } 接着,就要实现主要的summon方法了,第一根据“画板”的长与宽和点离点的距离,我们定义一个数组,数组大小为( a + 1 ) ( b + 1 ) (a+1)(b+1)(a+1)(b+1)通过。为了省事,能够在Start方法内让a和b各自增1。并利用指针来一个个的设置点的位置。
private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } } void Start( ) { a++ ; b++ ; summon( ) ; } 之后定义一个Vector3的paths可变数组,一个有8个bool的canMove数组,和一个有8个int的modeMove数组,初始化modeMove数组时,将modeMove数组第3项和第4项分别设为-1和1。
随后,我们知道,在二维数组中,指针移动二维数组的长度位,就是将指针以垂直方向移动。因此,就可以用变量a来设置modeMove数组。
private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } } 大家需要设定一个绘制符号的结束条件,结束条件呢,就是这个“笔”移到了“画板”每一行及每一列的点,因此,我们就定义一个bool类型的大小为a + b a+ba+b的dotMove数组,如果dotMove数组全是真,那么等于“笔”移到了第1 ∼ a 1\sim a1∼a列的所有点和第1 ∼ b 1\sim b1∼b行的所有点了。就要求一个check方法来检测dotMove是否全是真。
且结束条件为真时,由于“笔”可能还有一次画的操作,不过“笔”不能回头,因而就也要定义lastDraw的变量和mode变量了。
private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; int mode = 0 ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; while (check(dotMove) ||LastDraw) { if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; } } } 而在summon方法中,我们要对“笔”移动到的点检测一下,假如dots是一个长为a + 1 a+1a+1,宽为b + 1 b+1b+1的二维数组,就要让pos指针分别去通过运算符%和/与a + 1 a+1a+1进行运算,从而得到pos在dots二维数组中的坐标,就可以设置dots数组项的值。
private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; while (check(dotMove) ||LastDraw) { if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; }dotMove[pos % a] = true ;dotMove[a + pos / a] = true ; } } 之后,我们定义一个pos变量,将他的值设为0到a b − 1 ab-1ab−1内的随机值,这代表了“笔”的位置,并将这个位置传到paths数组里,作为办法2中符号的起点。再接着,我们就设置“笔”要移动的方向。
“笔”方向有8种,“↖↑↗←→↙↓↘”这些全都是(分别代表了0~7的数字,这以后会用到)。因此,刚才定义的canMove和modeMove数组大小都为8个,并且,假设有一个方向x xx,因为x + x 反 = 7 x+x_反=7x+x反=7,那么它的反方向就是7 − x 7-x7−x,因此,刚才就将canMove[7 - mode]设为假,就不让他回头了。
转到正题,要设置“笔”要移动的方向,就要检测这8个方向中可以移动的方向,就要用到canMove数组中的每一项作标记。
假如某个方向不能移动,那么就说明到了dots数组的边界旁,假如让一个指针分别去利用运算符%和/与二维数组的长进行运算,那么可以获得二维数组的坐标,我们就可以设置canMove数组。
private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; while (check(dotMove) ||LastDraw) {canMove[1] = (0 != pos / a) ;canMove[3] = (0 != pos % a) ;canMove[4] = ((a - 1 ) != pos % a) ;canMove[6] = ((b - 1 ) != pos / a) ;canMove[0] = (canMove[1] &&canMove[3] ) ;canMove[2] = (canMove[1] &&canMove[4] ) ;canMove[5] = (canMove[6] &&canMove[3] ) ;canMove[7] = (canMove[6] &&canMove[4] ) ; if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; }dotMove[pos % a] = true ;dotMove[a + pos / a] = true ; } } 那么,我们就因此定义一个mode通过变量来从这些能够移动的方向中选取其中的一个让pos移动了,在pos移动之后,我们就将pos移动到的点添加进paths数组里,一次pos移动的操作就完毕了。以此往复下去,一个完全随机的paths数组生成了。
private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; int mode = 0 ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; while (check(dotMove) ||LastDraw) {canMove[1] = (0 != pos / a) ;canMove[3] = (0 != pos % a) ;canMove[4] = ((a - 1 ) != pos % a) ;canMove[6] = ((b - 1 ) != pos / a) ;canMove[0] = (canMove[1] &&canMove[3] ) ;canMove[2] = (canMove[1] &&canMove[4] ) ;canMove[5] = (canMove[6] &&canMove[3] ) ;canMove[7] = (canMove[6] &&canMove[4] ) ; if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; }dotMove[pos % a] = true ;dotMove[a + pos / a] = true ; mode =Random.Range(0 , 8 ) ; while (!canMove[mode] ) { mode =Random.Range(0 , 8 ) ; } pos +=modeMove[mode] ; paths.Add(dots[pos] ) ; } } 此时,就可以用这个paths数组来设置Unity组件LineRenderer的线条了,不过要设置线条,得先设置线条端点的数量,后设置线条,由于pos线条,用就是移动的路径就paths数组设置线条就很合适。
private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; int mode = 0 ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; while (check(dotMove) ||LastDraw) {canMove[1] = (0 != pos / a) ;canMove[3] = (0 != pos % a) ;canMove[4] = ((a - 1 ) != pos % a) ;canMove[6] = ((b - 1 ) != pos / a) ;canMove[0] = (canMove[1] &&canMove[3] ) ;canMove[2] = (canMove[1] &&canMove[4] ) ;canMove[5] = (canMove[6] &&canMove[3] ) ;canMove[7] = (canMove[6] &&canMove[4] ) ; if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; }dotMove[pos % a] = true ;dotMove[a + pos / a] = true ; mode =Random.Range(0 , 8 ) ; while (!canMove[mode] ) { mode =Random.Range(0 , 8 ) ; } pos +=modeMove[mode] ; paths.Add(dots[pos] ) ; } GetComponent<LineRenderer>( ).positionCount= paths.Count; GetComponent<LineRenderer>( ).SetPositions(paths.ToArray( ) ) ; } 做到这里,还有几个问题没有解决。
- 变量
a或b不能为0,space不能为0。
解决方法:只需要在Start办法一开始判断a,b或space条件达成,就将它们设为默认值。就是为0即可,要
void Start( ) { a = (0 == a ? 2 : a + 1 ) ; b = (0 == a ? 3 : b + 1 ) ; space = (0 == space ? 0.809f : space) ; summon( ) ; } - 有时对象并没有
LineRenderer组件,却仍要获取对象的LineRenderer组件,并且必须将LineRenderer使用世界空间的开关关掉。
克服方法:假如没有LineRenderer组件,就为它进行初始化,并强制将组件的useWorldSpace给设为假,让符号一直在物体上。
void Start( ) { a = (0 == a ? 2 : a + 1 ) ; b = (0 == a ? 3 : b + 1 ) ; space = (0 == space ? 0.809f : space) ; if ( null == GetComponent<LineRenderer>( ) ) {transform.AddComponent<LineRenderer>( ) ; GetComponent<LineRenderer>( ).material=lineMaterial; GetComponent<LineRenderer>( ).startWidth= GetComponent<LineRenderer>( ).endWidth=lineWidth; } GetComponent<LineRenderer>( ).useWorldSpace= false ; summon( ) ; } 通过这些解决方法弄完之后,也就能够开始看技巧2中生成的符号是什么样了。下面就是方法2的效果。

脚本
using System.Collections ; using System.Collections.Generic ; using UnityEngine ; using Unity.Collections ; using Unity.VisualScripting ; using System.Runtime.CompilerServices ; public class randomSymbolSummon : MonoBehaviour { public float space = 0.809f ; public uint a = 2 ; public uint b = 3 ; public MateriallineMaterial; public floatlineWidth= 0.1f ; public floatwaitTime= 0.1f ; private bool isEnd = true ; public boolsummonOneShot= false ; private bool check(bool[]dotMove) { foreach (bool b indotMove) { if (!b) { return false ; } } return true ; } private void summon( ) { Vector3[] dots = new Vector3[a * b] ; for (int doti = 0 ; doti < dots.Length; ++doti) { dots[doti] = new Vector3(doti % a * space, doti / a * -space, 0 ) ; } List<Vector3> paths = new List<Vector3>( ) ; bool[]canMove= new bool[8] ; int[]modeMove= { 0 , 0 , 0 , -1 , 1 , 0 , 0 , 0 } ; for (int modei = 0 , step = ( int )a + 1 ; modei < 3 ; ++modei) {modeMove[modei] = -step;modeMove[modeMove.Length- 1 - modei] = step-- ; } bool[]dotMove= new bool[a + b] ; int pos =Random.Range(0 , ( int )(a * b) ) ; int mode = 0 ; boolLastDraw= 1 ==Random.Range(0 , 2 ) ; paths.Add(dots[pos] ) ; while (!check(dotMove) ||LastDraw) {canMove[1] = (0 != pos / a) ;canMove[3] = (0 != pos % a) ;canMove[4] = ((a - 1 ) != pos % a) ;canMove[6] = ((b - 1 ) != pos / a) ;canMove[0] = (canMove[1] &&canMove[3] ) ;canMove[2] = (canMove[1] &&canMove[4] ) ;canMove[5] = (canMove[6] &&canMove[3] ) ;canMove[7] = (canMove[6] &&canMove[4] ) ; if (check(dotMove) ) {canMove[7 - mode] = false ;LastDraw= false ; }dotMove[pos % a] = true ;dotMove[a + pos / a] = true ; mode =Random.Range(0 , 8 ) ; while (!canMove[mode] ) { mode =Random.Range(0 , 8 ) ; } pos +=modeMove[mode] ; paths.Add(dots[pos] ) ; } GetComponent<LineRenderer>( ).positionCount= paths.Count; GetComponent<LineRenderer>( ).SetPositions(paths.ToArray( ) ) ; } IEnumerator write( ) { isEnd = false ; yield return new WaitForSeconds(waitTime) ; summon( ) ; isEnd = true ; } // Start is called before the first frame update void Start( ) { a = (0 == a ? 2 : a + 1 ) ; b = (0 == a ? 3 : b + 1 ) ; space = (0 == space ? 0.809f : space) ; if ( null == GetComponent<LineRenderer>( ) ) {transform.AddComponent<LineRenderer>( ) ; GetComponent<LineRenderer>( ).material=lineMaterial; GetComponent<LineRenderer>( ).startWidth= GetComponent<LineRenderer>( ).endWidth=lineWidth; } GetComponent<LineRenderer>( ).useWorldSpace= false ; summon( ) ; } void Update( ) { if (!summonOneShot&& isEnd) { StartCoroutine(write( ) ) ; } } } 后言
刚才看到的这些符号,如果用粒子系统搭配的话,使你的游戏就能更好。但是,实际上,你却很难在unity原生的粒子系统上实现这个事情。因此,就得要一个自创的粒子系统,下篇博文教你如何自创粒子系统。
浙公网安备 33010602011771号