尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity基础:Start与Update方法——Unity脚本生命周期初探

Unity基础:Start与Update方法——Unity脚本生命周期初探 Unity基础Start与Update方法——Unity脚本生命周期初探大家好欢迎回到Unity教程系列。上一篇文章我们学会了创建和挂载C#脚本还写了几个简单的脚本来让物体旋转和移动。在那些脚本中我们用到了Start和Update两个方法但只是简单带过。今天这篇文章我们要深入理解这两个最核心的Unity生命周期方法。Start和Update是Unity中使用频率最高的两个方法几乎每个脚本都会用到它们。理解它们的调用时机、执行频率和正确用法是写出高质量Unity代码的基础。一、什么是生命周期1.1 生命周期的概念 Unity中的每个MonoBehaviour脚本都有一个完整的生命周期——从创建到销毁的过程中Unity引擎会在特定时机自动调用脚本中预定义的方法。这些方法按照固定的顺序被调用构成了脚本的生命周期。理解生命周期很重要因为在不同的生命周期阶段你应该做不同的事情有些代码只需要执行一次初始化有些代码需要每帧都执行更新逻辑有些代码需要在每帧的特定阶段执行物理更新、渲染后更新有些代码需要在销毁前执行清理工作1.2 生命周期方法概览Unity脚本的完整生命周期包含以下关键方法按执行顺序Awake() ──→ 脚本实例创建时调用一次 OnEnable() ──→ 脚本启用时调用 Start() ──→ 首次Update前调用一次 FixedUpdate() ──→ 固定时间间隔调用物理更新 Update() ──→ 每帧调用 LateUpdate() ──→ Update之后调用 OnDisable() ──→ 脚本禁用时调用 OnDestroy() ──→ 脚本销毁时调用这篇文章我们重点讲解Start和Update其他方法在后续文章中会逐一深入。二、Start方法——初始化入口2.1 Start的调用时机Start在脚本实例被启用后第一次Update之前被调用且只被调用一次。具体来说Start的调用条件GameObjet处于激活状态activeInHierarchy为true脚本组件处于启用状态enabled为trueStart在这个脚本实例的整个生命周期中只执行一次2.2 Start的典型用途Start是初始化的标准位置适合做以下事情获取组件引用privateRigidbodyrb;privateAnimatoranimator;privateAudioSourceaudioSource;voidStart(){rbGetComponentRigidbody();animatorGetComponentAnimator();audioSourceGetComponentAudioSource();}设置初始值voidStart(){// 设置初始位置transform.positionspawnPoint.position;// 初始化生命值currentHealthmaxHealth;// 设置初始颜色GetComponentRenderer().material.colorColor.white;}建立初始状态voidStart(){// 暂停游戏显示菜单Time.timeScale0;// 生成初始波次的敌人SpawnEnemyWave();// 播放背景音乐bgmAudioSource.Play();}2.3 Start vs AwakeStart和Awake都在初始化阶段被调用且都只调用一次。那么有什么区别呢特性AwakeStart调用时机脚本实例加载时立即调用第一次Update之前调用GameObject激活要求不要求即使物体未激活也会调用要求物体和脚本都激活获取自身组件✅ 适合✅ 适合获取其他物体的组件⚠️ 可能未就绪✅ 其他物体已完成Awake使用Instantiate创建物体⚠️ 谨慎✅ 更合适 简单来说Awake负责自身初始化Start负责关联初始化。// Awake初始化自身相关voidAwake(){rbGetComponentRigidbody();colliderGetComponentCollider();}// Start初始化与其他物体的关联voidStart(){GameObjectplayerGameObject.FindWithTag(Player);if(player!null){targetplayer.transform;}}2.4 Start是协程Start方法有一个特殊用法——它可以作为协程的入口// 将Start声明为返回IEnumerator的方法Unity会自动以协程方式执行IEnumeratorStart(){Debug.Log(开始初始化...);// 等待1秒yieldreturnnewWaitForSeconds(1f);Debug.Log(1秒后继续初始化...);// 等待网络连接yieldreturnStartCoroutine(ConnectToServer());Debug.Log(初始化完成);}⚠️ 注意如果Start返回IEnumeratorUnity会将其作为协程执行每次yield return后等待条件满足再继续。但这种方式下Start仍然只执行一次整个协程执行一次。三、Update方法——游戏逻辑的心脏3.1 Update的调用频率Update在游戏的每一帧被调用一次。这是Unity中最重要的方法——绝大多数游戏逻辑都在Update中执行。Update的调用频率取决于游戏的帧率FPS60 FPS时Update每秒调用60次30 FPS时Update每秒调用30次144 FPS时Update每秒调用144次3.2 Update的典型用途输入检测高频检查voidUpdate(){// 检测按键输入if(Input.GetKeyDown(KeyCode.Space)){Jump();}if(Input.GetMouseButtonDown(0)){Shoot();}}持续移动voidUpdate(){floathorizontalInput.GetAxis(Horizontal);floatverticalInput.GetAxis(Vertical);Vector3movementnewVector3(horizontal,0,vertical);transform.Translate(movement*speed*Time.deltaTime);}状态检查和更新voidUpdate(){// 检查距离floatdistanceVector3.Distance(transform.position,target.position);if(distanceattackRange){Attack();}// 更新计时器cooldownTimer-Time.deltaTime;if(cooldownTimer0){canAttacktrue;}}条件触发的逻辑voidUpdate(){// 玩家血量低于阈值时触发效果if(currentHealthmaxHealth*0.2f){ShowLowHealthWarning();}// 掉出地图时重置位置if(transform.position.y-50f){Respawn();}}3.3 Update的性能注意事项⚠️ Update每帧都执行因此性能优化至关重要不要在Update中做不必要的操作// ❌ 不好每帧都调用GetComponentvoidUpdate(){RigidbodyrbGetComponentRigidbody();rb.AddForce(Vector3.up);}// ✅ 好在Start中缓存引用privateRigidbodyrb;voidStart(){rbGetComponentRigidbody();}voidUpdate(){rb.AddForce(Vector3.up);}避免在Update中做昂贵的查找// ❌ 不好每帧都查找GameObjectvoidUpdate(){GameObjectplayerGameObject.Find(Player);// ...}// ✅ 好在Start中查找并缓存privateGameObjectplayer;voidStart(){playerGameObject.Find(Player);}避免在Update中实例化物体// ❌ 不好每帧都创建新对象造成大量GCvoidUpdate(){GameObjectbulletInstantiate(bulletPrefab,transform.position,Quaternion.identity);}// ✅ 好按条件创建voidUpdate(){shootTimer-Time.deltaTime;if(shootTimer0Input.GetButton(Fire1)){Instantiate(bulletPrefab,transform.position,Quaternion.identity);shootTimershootInterval;}}3.4 Time.deltaTime——让运动与帧率无关由于Update的调用频率随帧率变化如果直接用固定值做运动计算高帧率设备上物体会移动得更快。Time.deltaTime就是为了解决这个问题。// ❌ 帧率相关60FPS时每秒移动60单位30FPS时只移动30单位voidUpdate(){transform.Translate(Vector3.forward*1f);}// ✅ 帧率无关无论多少FPS每秒都移动5单位voidUpdate(){transform.Translate(Vector3.forward*5f*Time.deltaTime);}Time.deltaTime的值为上一帧到当前帧的间隔时间秒60 FPS → deltaTime ≈ 0.0167秒30 FPS → deltaTime ≈ 0.0333秒144 FPS → deltaTime ≈ 0.0069秒四、实战示例——时钟动画4.1 制作一个模拟时钟这个综合示例演示了Start和Update的分工usingUnityEngine;publicclassClockAnimation:MonoBehaviour{publicTransformhourHand;// 时针publicTransformminuteHand;// 分针publicTransformsecondHand;// 秒针[SerializeField]privatefloattimeSpeedMultiplier1f;privatefloathours,minutes,seconds;voidStart(){// Start中设置初始时间使用系统时间System.DateTimenowSystem.DateTime.Now;hoursnow.Hour%12;minutesnow.Minute;secondsnow.Second;Debug.Log($时钟初始化{hours}时{minutes}分{seconds}秒);}voidUpdate(){// Update中更新时间和指针旋转secondsTime.deltaTime*timeSpeedMultiplier;if(seconds60){seconds-60;minutes;}if(minutes60){minutes-60;hours;}if(hours12){hours-12;}// 旋转指针floatsecondAngle(seconds/60f)*360f;floatminuteAngle(minutes/60fseconds/3600f)*360f;floathourAngle(hours/12fminutes/720f)*360f;secondHand.localRotationQuaternion.Euler(0,0,-secondAngle);minuteHand.localRotationQuaternion.Euler(0,0,-minuteAngle);hourHand.localRotationQuaternion.Euler(0,0,-hourAngle);}}五、常见问题5.1 Start不执行怎么办检查以下几点GameObject是否激活activeInHierarchy是否为true脚本组件是否启用enabled是否为true方法名是否拼写正确C#区分大小写必须是Start不是start返回值类型是否是void或IEnumerator用于协程5.2 Update频繁调用导致性能问题检查Update中是否有不必要的重复计算考虑降低检测频率比如不需要每帧检测的条件改用协程WaitForSeconds使用对象池替代频繁的Instantiate/Destroy5.3 Start中获取的组件为null确认目标组件确实挂载在同一个GameObject上确认GetComponent的泛型类型参数正确考虑在Awake而非Start中获取自身组件六、本篇总结✅ 本文核心知识点回顾Start在脚本启用后调用一次适合初始化获取组件、设置初始值Update每帧调用一次是游戏逻辑的核心驱动力Time.deltaTime让运动计算与帧率无关Awake适合自身初始化Start适合关联初始化Update中避免GetComponent、GameObject.Find等昂贵操作应该缓存引用Start可以声明为IEnumerator以协程方式执行 下一篇文章将学习Debug.Log与Console窗口——Unity中的调试输出。动手任务① 写一个脚本在Start中输出物体的初始位置在Update中输出实时位置② 对比有Time.deltaTime和无Time.deltaTime时物体的移动差异③ 尝试将Start改为协程IEnumerator在其中使用yield return关于作者拥有多年Unity开发经验的游戏开发者专注Unity系统化教学与最佳实践分享。
返回列表