Unity 的各种小实验

FormerlySerializedAs

如果之前代码中的一个变量名字看他很不爽,想要给他改一个名字,改完之后回到 Unity 就会发现在 Inspector 面板上面那个引用已经为空了,这时只需要使用这一条语句就可以保留原来名字的引用。具体实验如下:
Step1:新建一个脚本,这里使用的是 Private 成员,也可以使用 Public 的,Public 默认会被序列化,就不需要加 [SerializeField] 了。

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField]
    private string MyString;
}

这个时候 Inspector 面板显示如下:


Inspector 面板

Step2:这个时候你想将 MyString 这个变量名改成 MyBatterStringName,那么就改吧~

public class NewBehaviourScript : MonoBehaviour
{
    [SerializeField]
    private string MyBatterStringName;
}

此时 Inspector 面板变成了:

更改后的 Inspector 面板

是的,它的引用不见了,如果这里的 string 是 GameObject 的话就会变成 Null,运行就有可能会报空指针异常。
Step3:我们再修改一下代码:

public class NewBehaviourScript : MonoBehaviour
{
    [FormerlySerializedAs("MyString")]
    [SerializeField]
    private string MyBatterStringName;
}

然后保存一下回到 Unity,会发现依然没有变化,那我们再重新恢复到 Step1 完成之后的样子,直接跳过 Step2,到 Step3,保存一下发现我们的 String 的内容没有被重置。

加上 FormerlySerializedAs 之后

总结:所以对于序列化的成员,我们以后在改名字的时候可以顺手加上 [FormerlySerializedAs("OldName")],这样就不用在 Inspector面板重新赋值了,多次改名保留上一次的名字就好了,也就是只需要保留一条这个语句。

Mathf.PingPang

这是 UGUI 的 Graphic 类中的一个小栗子。效果感觉跟调节 Image 的 Inspector 面板上面的 Color 一样,运行结果如下:

运行结果

代码如下:

using UnityEngine;
using UnityEngine.UI;

public class Test : MonoBehaviour
{
    Graphic m_Graphic;
    Color m_MyColor;

    void Start()
    {
        //Fetch the Graphic from the GameObject
        m_Graphic = GetComponent<Graphic>();
        //Create a new Color that starts as red
        m_MyColor = Color.red;
        //Change the Graphic Color to the new Color
        m_Graphic.color = m_MyColor;
    }

    // Update is called once per frame
    void Update()
    {
        Debug.Log(Mathf.PingPong(Time.time, 1));
        //Debug.Log(Time.time);
        //When the mouse button is clicked, change the Graphic Color
        if (Input.GetKey(KeyCode.Mouse0))
        {
            //Change the Color over time between blue and red while the mouse button is pressed
            m_MyColor = Color.Lerp(Color.red, Color.black, Mathf.PingPong(Time.time, 1));
        }
        //Change the Graphic Color to the new Color
        m_Graphic.color = m_MyColor;
    }
}

Lerp 函数的 t 范围为 0-1 的一个值,0 的时候为结果为 颜色 a,也就是这里的 Color.red,t=1 的时候结果为颜色 b,也就是这里的 Color.black 的颜色(Vector3.Lerp 和 Mathf.Lerp 也只这样子哦~ 如果 t 为 0.5 的话,那么结果就是 a 和 b 的中间颜色/中点/中间值),而 PingPang 的第一个参数需要一个自增量,一般设置为 Time.time,然后第一个参数是 length,会不断返回 0-length 之间的一个值,具体可以看上面运行结果下方的控制台输出值,就是当前的 PingPang 返回值。(所以 PingPang 和 Lerp 真是绝配)
TODO: 在这个例子中不知道如何控制变换速度,应该要从 PingPang 的返回值入手,可能需要自己定义一个自增值。

internal

internal 限制了只能在同一个程序集中进行访问,下面是 Unity 中的脚本编译规则

脚本编辑规则:
最终所有代码都会生成 dll,放在 Project/Library/ScriptAssembiles 下面。

脚本分为运行时和编辑时两类,运行时脚本最终会编译进游戏包中,而编辑时脚本仅用于编辑器模式下,不会被打包进游戏包。
脚本编译顺序:
最先编译 Plugins 目录下的,然后是 Plugins 下的所有 Editor 子目录,然后编译其他目录,最后编译其他 Editor 目录。
先编译的不可以访问后面的数据,所以 Plugins 下的代码不能访问其他代码,后编译的可以访问先编译的脚本。
各目录脚本最终所在的 dll:
Plugins 下非 Editor 目录脚本编译进 Assembly-CSharp-firstpass.dll
Plugins 下的 Editor 目录脚本编译进 Assembly-CSharp-Editor-firstpass.dll
其他非 Editor 目录脚本编译进 Assembly-CSharp.dll
其他 Editor 目录脚本编译进 Assembly-CSharp-Editor.dll

所以可以看到在 Editor 下面的代码和非 Editor 下面的代码是在不同的程序集的,测试一下在非 Editor 目录下写一个类:

namespace ZhangqrTools
{
     internal class ObjectPool<T> where T : new()
    {
    }
}

然后在 Editor 目录下写一个测试类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ZhangqrTools;

public class TestEditor : MonoBehaviour
{
    void Start()
    {
        ObjectPool<List<int>> obp = new ZhangqrTools.ObjectPool<List<int>>();  
    }
}

会发现报错了,说 不可访问,因为有一定的保护级别。将 internal 改为 Public 之后就可以访问了。

Texture Sheet Animation

官方 Texture Sheet Animation
制作了一张这样的图(接缝处可能会有几像素的偏差),然后导到 Unity 之后图片设置就默认,2D就好了。

测试用色阶图

做一个标准 Shader 的材质球,渲染模式因为这张图没有透明的部分,所以选啥都可以。


材质球设置

因为这个图有 X 轴上被分为 4 块,Y 轴被分为 5 块,所以按照下面的设置


Texture Sheet Animation 设置

效果为:
运行结果

可以看到在整个例子的生命周期,是按照这样的顺序逐帧播放的动画:
动画顺序

现在将设置改为:

单行动画的设置

运行结果为:
单行的运行效果

可以看到它只会播放第一行的动画了。随机行数之后,每个例子随机选一行在整个生命周期完成动画。(换了个背景,不然看不清楚)
随机行数之后的运行效果

上面都是随着例子生命的流逝而做的帧动画,下面是 FPS,FPS 越大,变换越快。
60FPS

60FPS运行效果

10FPS运行效果.gif

Speed 感觉要复杂一点:
先给例子加一个 X 轴的加速度:(这个线性速度我觉得应该是相加,因为现在我的粒子初始速度是设置为 5 的,后面经过测试,我觉得它整个生命周期的速度应该在 [5,20] 的样子,也就是 5 加上这个曲线。
X轴速度设置

速度范围为 [5,19]

速度为[5,19]之间

可以看到4个颜色分布还是较均匀的,然后我们把速度范围设置为 [5,65],也就是让 [5,20] 为前 1/4。结果如下:
速度范围[5,65]

可以看到基本只有一种颜色,也就是前 1/4 的颜色。再把范围速度范围设置为 [5,35],也就是让 [5,20] 为前 1/2。结果如下:
速度范围[5,35]

可以看到基本是有两种颜色,也就是前 1/2 的颜色,并且分布较均匀。再把速度范围设置为 [5,25],也就是让 [5,20] 为前 3/4。结果如下:
速度范围[5,25]

现在就有三种颜色了。所以最后总结一下。假设我们的例子整个生命周期的速度是从 [5,20] 线性变换,我们的速度范围为 [5,35],那么整个色带(也就是动画的逐帧图)会均匀的分布在 [5,35] 的范围,真实的速度 [5,20] 会截取前 1/2。前后会用第一张图和最后一张图补齐。
图示.png

最后提一下,粒子效果中怎么保留图片的不透明度,起因是想要做一个麦粒倾泻的效果。参考火焰的效果,可以做一张背景是黑色的图片,然后按下面进行设置:


WheetGrain.png

图片设置

材质球设置

运行效果.gif

可以看到效果比较梦幻,如果素材是火焰的话就会比较好看,但我需要的是现实中的麦粒,不是天堂的麦粒。所以做了一张背景是透明的图片,改用标准的 Shader,渲染模式设置为 Cutout


透明麦粒

图片设置

材质球设置

运行效果

最终效果
麦粒倾泻

获取模型最低点

比如要做一个倒水的效果,水面是一个贴图,需要计算出水点,就可以用这种方法。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour
{
    private Vector3[] vertices;
    Vector3 v_min = Vector3.zero;

    private void Start()
    {
        Vector3 size = this.GetComponent<MeshFilter>().mesh.bounds.size;
        Debug.Log(size.x * transform.localScale.x); // 1 因为 Plane 模型大小是 10,Scale 是 0.1。bounds.size 通常等于两倍的 bounds.extents
    }

    private void Update()
    {
        vertices = this.GetComponent<MeshFilter>().mesh.vertices; // 与模型的相对坐标
        for (int i = 0; i < vertices.Length; ++i)
        {
            Vector3 world_position = transform.TransformPoint(vertices[i]);  // 转换成世界坐标
            if (i == 0)
            {
                v_min = world_position;
            }
            else
            {
                if (world_position.y < v_min.y) 
                {
                    v_min = world_position;
                }
            }
        }
        Debug.Log("y 的最小值为:" + v_min.y);
    }
}
Transform 配置
运行结果

球形差值

public static Vector3 Slerp(Vector3 a, Vector3 b, float t); 这个函数,可以理解为 a 为第一个方向,b 为第二个方向,然后 t 为 0.5 的话,就会在 ∠AB 的角平分画上有一个点,长度是根据 a b 的长度来的,也就是说 t 从 0-1 的过程,所有点组成的线会平滑的穿过 a b 的端点。

运行效果

烟花

下面是使用 visual effect graph 制作的烟花。

烟花

教程:https://www.bilibili.com/video/BV14E411c76W

FixedUpdate 和 Input

在一个移动的物体的 FixedUpdate 中这么写:

        if (Input.GetKey(KeyCode.H))
        {
            //////////////////////////////
            GameObject ob = GameObject.CreatePrimitive(PrimitiveType.Sphere);
            ob.transform.position = transform.position;
            ob.transform.localScale = Vector3.one * 0.02f;
        }

和去掉 Input 的判断,最终生成的小球数量是不一样的,不加 Input 判断生成的小球明显要更多一点。 所以即使 FixedUpdate 的执行次数要更多,但在 Input 里面的内容还是根据 Update 的帧数来(可能是这样的吧,因为 Input 一般都写在 Update 里面)。

父子

foreach(Transform t in transform) // 只能找一层子物体,不管子物体是否处于激活状态
foreach(Transform t in transform.GetComponentsInChildren<Transform>()) // 只会找激活的,包括子子孙孙,也包括父物体
foreach(Transform t in transform.GetComponentsInChildren<Transform>(true)) // 激活的不激活的都可以,包括子子孙孙,也包括父物体
GameObject.Find("Test") // 只寻找场景中激活的物体,不管在第多少层
public void SetParent(Transform parent, bool worldPositionStays);
第二个参数为 True:物体世界位置,旋转,缩放不会发生改变
第二个参数为 False:物体的局部位置,旋转,缩放不会发生改变

Text 首行缩进 And 换行

在前面加上这个 <color=#FFFFFF00>------</color> 可以实现首行缩进,不论是在编辑器面板中的字符还是代码中的。
\n 一个比较坑的点是,在编辑其中的 Text 中写 \n 没用,因为 Unity 会自动把 \n 换成 \\n,所以需要我们在代码中给他变回来 text.Replace("\\n", "\n");在代码中用 \n 没有问题。

Resources.Load

  • 比如一个预制体,在编辑器中直接 Copy Path 的话,他会把后缀名也带上,但是带上的话就找不到了,一定不能带后缀名
  • Resources.Load 只是加载进内存,需要实例化才能在 Scene 中使用,如果没有实例化直接使用,那么改变的是预制体,而不是 Scene 中的实例。
        if(HandId == 0)
        {
            hand = Resources.Load<GameObject>(ConstName.resources_hand_left);
        }
        else
        {
            hand = Resources.Load<GameObject>(ConstName.resources_hand_right);
        }
        hand = Instantiate(hand);
        h = hand.GetComponent<Hand>();

协程

  • 协程执行结束之后是不会变成 null 的,所以不能用 null 来判断协程是否结束。
  • StopCoroutine();内不能传 null,所以在 Stop 的时候要小心此时协程是否被赋值了。而且 Stop 之后,这个协程的值依然不是 null。
  • 那么怎么判断一个协程是否结束呢?目前采用的方式是使用一个成员变量。

OnTriggerEnter

如果把当前的脚本禁用了(也就是 enable 设为了 false),OnTriggerEnter 依然会被执行。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342