老蒋的知识库

  • 首页
  • 文章归档
  • 关于页面

  • 搜索

Unity (避坑指南)

发表于 2023-10-01 | 分类于 Unity | 0 | 阅读次数 64

Unity是单线程

Unity是单线程轮询,每一次轮询计算一帧动画运算结果。修改特定属性的时候要思考该属性在其他的地方是否有变更。

一、FixedUpdate()或Update()不判断状态就修改属性,会导致其他地方修改无效

原因:FixedUpdate或Update 每一帧都会修改一次数据,其他地方的修改属性每一帧都会被重置。
例如:

  • 定义野怪移动方式时,使用FixedUpdate()不断的修改Rigidbody2D#velocity来进行移动。
  • 同时定义野怪被攻击时存在被击退效果,也使用Rigidbody2D#AddForce来实现击退效果。
  • 此时都是通过修改Rigidbody2D的移动属性来控制野怪的运动方式,移动是每一帧都会运行一次,攻击击退只会运行一次,所以运行时被攻击击退效果就会失效。

DontDestroyOnLoad 实现是基于多场景

DontDestroyOnLoad(this) 会将一个对象设定为场景销毁时保留此对象,本质是在游戏运行时创建一个DontDestroyOnLoad场景,然后将不需要销毁的对象迁移至此场景。
image

官方目前建议直接使用多场景,创建一个主场景Main将所有需要夸场景的对象放到这里面,切换场景时同时加载地图场景Map,也就是同时存在Main和Map,多场景之间的对象会互相影响,如同一个场景一样。

一、场景直接挂载的对象如果使用DontDestroyOnLoad(this)会导致场景来回切换时存在多个未被销毁的对象。

原因:假设Player GameObject 需要切换场景时保留设定DontDestroyOnLoad(this),设定场景时直接挂载到场景中,此时场景每次重新加载就等于所包含的对象都会重新创建一份,也就重新创建了一份Player GameObject ,此时之前的对象因为DontDestroyOnLoad(this)未被销毁,同时又创建了新的对象导致每次来回切换场景都会保留一份之前未被销毁的Player GameObject。
解决方法:

  1. 使用多场景,Player GameObject放置Main场景中,切换场景同时加载Map场景进行切换。(官方推荐)
  2. Player GameObject从场景中删除,使用脚本进行动态加载,并且设置Player GameObject为单例模式保证每次加载都是唯一对象。(太麻烦不建议用)
  3. 场景切换时删除新增的Player GameObject。(太蠢不建议使用)
  4. 游戏开始创建一个Init场景,将需要持久化存在的对象放置其中,然后再切换到第一个场景中,后续不再切换回Init场景就不会出现多次创建的情况。(那为什么不直接用多场景?)

Animation 篇

一、Animation视图存在AnimationClip修改GameObject属性,会导致脚本无法修改该属性

如果添加的 AnimationClip 中存在修改 Transformm.Rotation 的操作,此时外部脚本修改Transform.Rotation是无效的。就算 AnimationClip 不处于运行状态也是无法修改,Transformm.Rotation 会自动变成默认值,如果想要修改就在视图中移除AnimationClip

二、通过AnimatorState.motion = AnimationClip,修改动画会重置 Animator 参数

错误如图
48f2a1f61f5ce0d707f1afe2a512d688
d144eefbdd237cfea3c144e7cebbc661

正确做法,通过:AnimatorOverrideController覆盖AnimationClip,从而达到修改动画的效果。

AnimatorOverrideController是官方提供的专门修改动画效果的API,可以保证原有结构、参数、逻辑不变的情况下修改动画。
参考资料:
https://docs.unity3d.com/cn/2021.3/Manual/AnimatorOverrideController.html
https://docs.unity3d.com/cn/2021.3/ScriptReference/AnimatorOverrideController.html


                AnimatorOverrideController  animatorOverrideController = new AnimatorOverrideController(gameObject.GetComponent<Animator>().runtimeAnimatorController);

                gameObject.GetComponent<Animator>().runtimeAnimatorController = animatorOverrideController;

                _handle = Addressables.LoadAssetAsync<AnimationClip>("Assets/Game Assets/Animation/Skill/Hit.anim");
                _handle.Completed += (handle) =>
                {
                    if (handle.Status == AsyncOperationStatus.Succeeded)
                    {
                    	// 这里的 Hit 就是要被覆盖的AnimationClip.name
                        animatorOverrideController["Hit"] = Instantiate(handle.Result);
                    }
                    else
                    {
                        Debug.LogError("Asset load failed");
                    }
                };

Unity生命周期

Unity 中使用 Instantiate 实例化物体时生命周期执行顺序:

  1. 如果在 Awake 函数中实例化物体,本帧会执行 Awake 层(包括 OnEnable),下一帧会执行后续的 Start 和 Update。
  2. 如果在 Start 函数中实例化物体,所有内容会在同一帧执行。
  3. 如果在 Update 函数中实例化物体,本帧会执行 Awake 层(包括 OnEnable)和 Start 层,下一帧会执行后续的 Update。

Gameobject.SetActive(false) 影响

  1. GameObject 收到 GameObject.SetActive(false) 方法时,将调用 OnDisable() 方法
  2. GameObject.SetActive(false) 会停用 GameObject 及其所有组件,包括 Rigidbody2D1。这意味着您不能直接访问或修改 Rigidbody2D 的属性,例如 velocity无法修改。
  • 本文作者: jagger
  • 本文链接: /archives/unity核心思想闭坑指南
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
Visual Studio 2022 的坑
Unity 对象池优化性能
jagger

jagger

66 日志
31 分类
0 标签
Creative Commons
0%
© 2026 jagger
由 Halo 强力驱动