资讯动态

Unity跑酷小游戏源工程拆解:核心玩法、对象池与真机适配

发布时间:2026/10/27 12:24:45 来源:尧图企业网站定制
简介这份Unity跑酷小游戏源工程面向已有一定Unity基础、希望从完整项目切入学习游戏开发的学习者也可作为课程实训与个人练习的参考案例。zip压缩包共2000个文件、约22.39MB包含215个C#脚本、29个预制体、40个资源文件、30张PNG与29张PSD美术素材、25个FBX模型以及场景、材质、动画控制器和项目配置等覆盖角色控制、碰撞检测、关卡搭建、动画与物理逻辑等核心环节。已有6437人学习下载说明其在Unity跑酷类项目学习中具有一定参考价值。读者可通过梳理Assets目录下的资源组织与脚本分工理解跑酷游戏的实现流程结合ProjectSettings与Packages配置还能观察输入、图形质量、第三方包管理等项目设置并借助日志与临时文件排查运行问题适合用来拆解结构、复盘实现并积累二次开发经验。1. 一套自动往前跑的骨架Unity 跑酷小游戏源工程能省掉哪些从零搭的活半夜十一点朋友说想做个 Unity 跑酷小游戏三天后要拿着 demo 去聊。我劝他别从零写角色控制器先找一份能直接跑的源工程改。Unity 跑酷小游戏源工程的价值不在代码多优雅而在于它把「恒定前进 三跑道切换 障碍对象池 难度递增」这条主线跑通了剩下的换皮、调数值、改关卡节奏都是体力活。适合刚学完 Unity 基础、想做出第一个能装到手机上的完整 Unity 游戏的人也适合临时接了小活儿、需要在两周内出包的人。真正卡住新手的从来不是「不会写代码」而是不知道一个能玩的循环该由哪几个脚本拼起来。2. 工程结构拆解Assets 目录里每个文件夹在管哪一段逻辑拿到一份别人写好的工程先别急着点 Play。花十分钟把目录看一遍比盲目改三小时代码更值。跑酷这类玩法的结构其实高度同质化角色只管自己的位移和状态关卡只管往玩家前面铺东西分数和流程由一个全局管理器收口。目录如果分得清楚后面加道具、换角色、接广告 SDK 都是往固定格子里塞东西。2.1 典型的目录骨架与各层职责一份结构完整的跑酷工程Assets 下大概率长这样Assets/ ├── Scenes/ │ ├── Boot.unity # 空场景只挂初始化脚本负责加载正式场景 │ └── Main.unity # 主玩法场景玩家、地块、生成器、UI 都在这里 ├── Scripts/ │ ├── Player/ # PlayerMotor、SwipeInput、PlayerAnimator │ ├── Level/ # TileSpawner、ObstacleSpawner、CoinSpawner、Tile │ ├── Game/ # GameManager、ScoreSystem、DifficultyCurve │ ├── UI/ # HUDController、GameOverPanel │ └── Utils/ # ObjectPool、PoolManager、Singleton ├── Prefabs/ │ ├── Player.prefab │ ├── Tiles/ # 直道、弯道、带金币的直道长度都是 30 米一格 │ ├── Obstacles/ # 低栏、高栏、整排墙 │ └── Coins/ ├── Materials/ Textures/ Audio/ └── Settings/ └── GameConfig.asset # ScriptableObject速度和难度的数值都在这Scenes拆成 Boot 和 Main 是常见做法Boot 只做一件事把需要常驻的管理器初始化好再异步加载 Main。这样做的好处是以后加新手引导、加登录、加资源热更都有地方放不用把逻辑全塞进主场景。Scripts按职责分而不是按类型分不是把 MonoBehaviour 全丢一个文件夹改玩法的时候你只会打开 Level 目录不会在两百个脚本里翻。Prefabs 里地块按「长度一致」组织很关键。地块长度一致回收复用才有稳定的数学关系position.z length * count就能把最后一块搬到最前面。一旦有 30 米和 45 米混着的情况就得记每条地块的实际长度血泪经验是别给自己找这个麻烦。2.2 技术选型CharacterController、Rigidbody 还是纯 Transform这是这份工程里最值得先确认的一件事因为它决定了后面碰撞、跳跃、下滑全部怎么写。方案适合的场景代价CharacterController三跑道跑酷、需要稳定的贴地判定、不想处理物理弹飞要自己写重力isGrounded有玄学时刻Rigidbody 约束需要被撞飞、被击退、有物理反馈的玩法容易出现角色贴墙卡住、速度被物理引擎吃掉纯 Transform 位移完全不碰撞、只靠触发器判定高速时会穿模需要自己写扫掠检测跑酷这个品类我一般选 CharacterController。原因很直接玩家需要的是「按预期跳起来、按预期落下去」而不是真实的物理反馈。Rigidbody 在高速前进时一旦撞到障碍物边角角色会沿切线方向滑走甚至原地打转测试同学会给你提一堆「人物自己往左飘」的 bug。CharacterController 的Move是主动推位置撞墙就停行为可预测。输入方案上新项目可以用 Input System 的InputAction做一套 Swipe 手势老工程用Input.touchCount或鼠标模拟也完全够。跑酷只需要四个方向的手势识别为一个手势引入整套输入框架收益不明显除非你后面还要接手柄。2.3 脚本职责表与启动顺序把职责列清楚改动时才知道该动谁脚本挂在谁身上只负责什么GameManager空物体_Game游戏状态、距离、金币总数别的都不管PlayerMotorPlayer前进、换道、跳跃、下滑不碰 UISwipeInputPlayer读手势调PlayerMotor的公开方法TileSpawner场景里的 Spawner 节点地块的生成与回收ObstacleSpawner同上按距离铺障碍保证可通行ObjectPool每个预制体一个池取、还、重置状态启动流程通常是GameManager.Awake把状态置为Ready玩家和生成器全部冻结玩家点击屏幕后调StartRun()状态切到RunningUpdate里的位移和生成逻辑才真正开始跑。这套状态机看着简单但它同时解决了三个问题开局前的动画播放、失败后的冻结、以及「重开一局」时不用重新加载场景。public enum GameState { Ready, Running, Dead } public class GameManager : MonoBehaviour { public static GameManager Instance; public GameState State { get; private set; } GameState.Ready; public float Distance { get; private set; } public int Coins { get; private set; } void Awake() { Instance this; } void Update() { // 只有 Running 状态才累加距离失败面板弹出后数字必须冻住 if (State ! GameState.Running) return; Distance PlayerMotor.CurrentSpeed * Time.deltaTime; } public void StartRun() State GameState.Running; public void GameOver() { State GameState.Dead; int best PlayerPrefs.GetInt(Best, 0); if ((int)Distance best) PlayerPrefs.SetInt(Best, (int)Distance); } public void AddCoin(int n) Coins n; }这段代码里有两个刻意的设计。Distance用静态的CurrentSpeed而不是引用玩家实例是为了避免生成器、UI、难度曲线三方各自持有 Player 引用形成一堆 Inspector 拖拽。GameOver里直接写PlayerPrefs是因为最高分这种单值数据不值得为它引入一套存档框架等你要存道具、存关卡进度再换 JSON 也不迟。Instance单例在跑酷这种单场景小游戏里够用代价是场景里不能出现第二个 GameManager否则Awake顺序一变就是玄学空引用。3. 核心玩法链路从一次上滑到障碍物回池的完整执行路径玩起来只有四个动作跑、换道、跳、滑。但要把这四个动作做「跟手」代码里要处理的细节比想象中多。这一章按执行顺序拆开看速度怎么涨、跑道怎么切、跳跃怎么算、障碍怎么铺和怎么收。3.1 恒定前进与速度爬升跑酷的节奏感全压在速度曲线上。起步太慢玩家会觉得闷涨太快前三秒就死。常见做法是baseSpeed给 8 米/秒左右之后每秒加 0.2 到 0.3逼近maxSpeed后停住剩下的难度交给障碍密度去调。速度封顶这个设计很重要因为再快下去横向换道就跟不上了玩家会觉得「不是我菜是游戏不讲理」。void Update() { // 速度线性爬升到上限给玩家越来越快的压迫感但不会无限失控 speed Mathf.Min(maxSpeed, speed accel * Time.deltaTime); // 横向用 MoveTowards 逼近目标跑道Lerp 会永远差一点点导致手感发黏 float targetX (lane - 1) * laneWidth; float dx Mathf.MoveTowards(transform.position.x, targetX, laneLerp * Time.deltaTime) - transform.position.x; // 贴地时给一个很小的向下速度否则 isGrounded 会周期性抖动 if (controller.isGrounded verticalVel 0f) verticalVel -2f; verticalVel gravity * Time.deltaTime; Vector3 motion new Vector3(dx, verticalVel * Time.deltaTime, speed * Time.deltaTime); controller.Move(motion); }三个参数决定手感。laneWidth必须和地块预制体上画的三条跑道中心距完全一致差 0.1 米玩久了角色就会逐渐偏到跑道边缘。laneLerp是横向跟随速度8 偏肉、16 偏贼12 是多数人觉得舒服的中间值。verticalVel -2f这行看着多余实际是给 CharacterController 一个持续向下的推力让isGrounded稳定返回 true否则在斜坡或者地块接缝处角色会出现「明明站在地上却跳不起来」的情况。注意motion里的 z 分量必须乘Time.deltaTime。这一点在新手工程里翻车率极高把速度直接写进Move在 60 帧的编辑器里正常到 120Hz 的手机上速度直接翻倍。3.2 三跑道切换与手势判定跑道用整数索引比用世界坐标好维护。索引 0/1/2 对应左中右目标 x 由(lane - 1) * laneWidth算出来加一条跑道只需要改laneWidth和 UI 布局逻辑不动。手势判定要处理的第一个坑是「点击和滑动混在一起」。滑动阈值设小了玩家点一下屏幕想开始游戏角色就跳了设大了快速小幅度滑动又不响应。60 像素左右是手机上比较稳的经验值。void Update() { if (Input.GetMouseButtonDown(0)) { startPos Input.mousePosition; pressing true; } if (Input.GetMouseButtonUp(0) pressing) { Vector2 delta (Vector2)Input.mousePosition - startPos; pressing false; // 位移太小当成点击不触发任何动作避免误操作 if (delta.magnitude minSwipe) return; if (Mathf.Abs(delta.x) Mathf.Abs(delta.y)) motor.SwitchLane(delta.x 0 ? 1 : -1); // 横滑换道 else if (delta.y 0) motor.Jump(); // 上滑跳 else motor.Slide(); // 下滑铲 } }这段是编辑器里用鼠标模拟的写法真机上把GetMouseButtonDown/Up换成Input.touches里对TouchPhase.Began/Ended的判断即可手势逻辑本身不用动。SwitchLane(int dir)内部要做边界钳制lane Mathf.Clamp(lane dir, 0, 2)否则在最左道再往左滑角色会飞出屏幕而且再也回不来。3.3 跳跃与下滑的状态处理跳跃的初速度不要硬编码。用Mathf.Sqrt(-2 * gravity * jumpHeight)反推改跳跃高度时只需调一个参数落点时间自动跟着变。下滑麻烦一点因为 CharacterController 的胶囊体要临时压低。压低之后碰撞体中心也得往下移一半高度否则角色会悬在空中钻不过障碍。恢复时间用计时器控制到点把height和center还原。public void Jump() { if (!controller.isGrounded) return; // 想开二段跳就把这行删掉 verticalVel Mathf.Sqrt(-2f * gravity * jumpHeight); } public void Slide() { if (slideTimer 0f) return; // 连点不叠加 slideTimer slideTime; controller.height 0.9f; // 站立 1.8 米压到 0.9 米 controller.center new Vector3(0f, 0.45f, 0f); } void Update() { if (slideTimer 0f) { slideTimer - Time.deltaTime; if (slideTimer 0f) { controller.height 1.8f; controller.center new Vector3(0f, 0.9f, 0f); } } }这里有一个必须提前想好的判定如果障碍物正好压在头顶角色恢复站立会因为空间不足卡住。简单做法是下滑期间不恢复一直保持到离开障碍稳妥做法是在恢复前做一次Physics.CheckCapsule空间不够就延后一帧。跑酷的障碍通常是一闪而过前者基本够用。3.4 障碍生成器与对象池跑酷不能靠 Instantiate 和 Destroy 硬撑。一局三分钟每秒铺一个障碍不池化就是几百次 GC手机上能明显看到掉帧。对象池的写法很多核心只有两条取出时重置状态归还时停止物理和动画。生成逻辑的重中之重是「永远留一条活路」。随机铺障碍最容易犯的错是三条道同时被挡玩家技术再好也只能死。常见做法是每排先抽一个保底空道再在剩下的道里随机填。void Update() { // 玩家前方 45 米内始终铺满东西再多是浪费 while (nextSpawnZ player.position.z spawnAhead) { SpawnRow(nextSpawnZ); nextSpawnZ Random.Range(gapMin, gapMax); } } void SpawnRow(float z) { int freeLane Random.Range(0, 3); // 保底空出来的一条道 int blockCount Random.Range(1, 3); // 这一排最多挡两条 for (int i 0; i 3 blockCount 0; i) { if (i freeLane) continue; var prefab obstaclePrefabs[Random.Range(0, obstaclePrefabs.Length)]; var go PoolManager.Instance.Get(prefab); go.transform.position new Vector3((i - 1) * laneWidth, 0f, z); blockCount--; } }spawnAhead取 45 米是因为玩家在最快速度 18 米/秒下前方 2.5 秒的东西才看得清再远只是白白占用对象池。gapMin和gapMax之间的随机间隔决定了「呼吸感」全部固定值会让节奏变得像节拍器玩家很快就能背下来。生成之后一定要给障碍物挂一个自回收逻辑if (transform.position.z player.position.z - 10f) PoolManager.Instance.Release(gameObject);否则池子会越跑越大跑十分钟后内存直接吃掉几百兆。4. 关卡与表现层地面复用、金币判定、相机跟随的落地写法玩法逻辑跑通之后剩下的就是这个东西「看起来像不像一个游戏」。地面无限延伸、金币吃到有反馈、镜头不抖、UI 不挡视线这几件事做到位玩家才会觉得完成度在线。4.1 地面分段滚动与回收不做真实地形用固定长度的地砖循环。三块地砖就够脚下这块、前面一块、后面一块。当最后一块落到玩家身后一定距离把它往前搬三个身位。public class Tile : MonoBehaviour { public float length 30f; // 必须和预制体实际长度一致 public Transform player; public float recycleBehind 15f; void Update() { // 落后玩家超过设定距离就搬到队首形成视觉上的无限地面 if (transform.position.z length player.position.z - recycleBehind) transform.position Vector3.forward * length * 3f; } }length * 3f里的 3 就是地砖块数改块数时这里要同步改所以更好的写法是把它提成一个静态常量或者由 Spawner 统一管理。recycleBehind不能太小否则玩家回头如果相机允许能看到地面断掉15 米配合默认的相机视野足够了。地砖上的装饰物、路边建筑要挂在地砖节点下跟着一起搬。如果装饰物是独立的对象自己管自己搬地砖的时候就会看到树留在原地然后突然出现在你前面这个 bug 每次演示都会被指出来。4.2 金币与触发判定金币用触发器判定最省事但有两个前提玩家身上必须有 Collider 和 RigidbodyRigidbody 可以勾isKinematic金币的 Collider 勾上Is Trigger并且两者的 Layer 在碰撞矩阵里是开启的。void OnTriggerEnter(Collider other) { if (!other.CompareTag(Player)) return; GameManager.Instance.AddCoin(1); AudioManager.Instance.Play(coin); // 回池而不是销毁下次取出时在 OnSpawn 里把可见性和动画重置 gameObject.SetActive(false); }CompareTag比other.tag Player快而且 tag 没定义时会直接报错比无声失败好排查。金币回池后一定要在OnSpawn里重置可见性打开、旋转角度归零、粒子重新播放。漏了哪一步第二次经过同一个位置就会出现「金币是透明的但还能吃」或者「金币不转」这类诡异现象。高速情况下触发器可能漏判。玩家 18 米/秒一帧跑 0.3 米如果金币的触发器只有 0.2 米厚理论上会穿过去。常见做法是把金币的 Trigger 沿 z 轴拉长到 0.8 到 1 米比在代码里做扫掠检测省事得多。4.3 相机跟随与 HUD 布局相机只跟 x 和 zy 固定。跟 y 的下场是角色一跳镜头就上下晃玩两分钟就晕。void LateUpdate() { // 只跟随平面位置y 锁死跳跃时镜头保持平稳 Vector3 want new Vector3(target.position.x * 0.5f, offset.y, target.position.z offset.z); transform.position Vector3.Lerp(transform.position, want, followLerp * Time.deltaTime); }这里 x 乘了 0.5是让镜头横移幅度小于角色换道时有一点轻微的镜头位移但不夸张。用手感调参的话followLerp取 8 到 10 之间最稳太大了相机会跟着角色一起抖太小了换道时角色会跑出画面中心。LateUpdate是必须的放在Update里会读到角色移动前的旧位置产生一帧延迟的高频抖动。HUD 上真正要显示的只有三样当前距离、金币数、最高分。移动端还要处理刘海和安全区给 Canvas 下挂一个SafeArea脚本在Screen.safeArea变化时调整RectTransform.anchorMin/anchorMax。不做这一步横屏手机上分数就被摄像头挖孔挡掉一半。4.4 从打开工程到真机跑通的操作顺序拿到工程之后按这个顺序走能少走很多弯路用 Unity Hub 打开项目先看ProjectSettings/ProjectVersion.txt里的版本号用相同或更高的大版本打开不要用低版本强行打开高版本工程。打开Boot.unity或Main.unity直接点 Play先确认能跑起来再改东西。看 Console 有没有红色报错尤其是NullReferenceException多半是 Inspector 上的引用丢了。检查Edit Project Settings Input Manager里有没有被裁掉的轴老工程用旧输入系统时经常因为项目模板不同丢配置。切到 Android/iOS 平台Player Settings里确认包名、横竖屏、最低 API 等级然后 Build 一次空包验证工具链。真机上先测三件事滑动手势的阈值是否合适、帧率是否稳在 60、UI 有没有被刘海遮挡。打包环节最容易翻车的是纹理压缩和渲染管线不匹配。如果工程用的是内置管线导入时却选了 URP 模板材质会变成紫色。这时候不要挨个改材质直接在Project Settings Graphics里确认渲染管线资产或者把材质批量升级。5. 排查清单这套跑酷工程最容易翻车的五处细节下面这五条都是我在改跑酷工程时真实撞过的。每一条都按「看到什么现象 → 为什么 → 怎么解」写对着查基本能覆盖八成问题。5.1 换道时角色抖动或者轻微穿模现象是横滑切道时角色在目标跑道附近来回抖两下才停稳高速时还会短暂陷进地砖里。原因是横向位移用了Vector3.Lerp并且每帧把当前位置当起点。Lerp 是渐近的永远到不了目标点配合浮点误差就会在目标附近来回。另一个原因是横向和竖直位移合并在一次controller.Move里横向速度过大时斜向移动会被地面碰撞体挤一下。解决是把横向改成Mathf.MoveTowards一定帧数内精确抵达目标值同时给横向速度设一个上限不超过laneWidth / 0.15f保证 0.15 秒内能完成一次换道。如果还是穿模检查地砖的 Collider 是不是MeshCollider换成BoxCollider会稳定很多。5.2 跑着跑着出现无解障碍组合现象是玩家在某一排障碍前必死三条道全被挡或者跳跃高度刚好够不着、下滑时间刚好不够。原因是生成器按「每排随机挡 1 到 2 条」铺但没考虑相邻两排的间距。间距只有 6 米时玩家刚落地就得立刻换道操作窗口不足。另外低栏和高栏混在同一排视觉上分不清哪个要跳哪个要滑。解决方法有两层生成时给每排记录保底空道并保证相邻两排的空道不跨越两条以上也就是不能从左道直接要求跳到右道间距按下限做一次校验gap 8f时强制重roll。进阶做法是把障碍组合做成预制体 pattern人工验证过再放进池子里随机抽比纯随机靠谱得多。5.3 对象池复用后金币不显示、障碍物不触发现象是第二局开始某些位置的金币看不见但能吃或者障碍物明明在眼前却没有碰撞。原因是回池时只调了SetActive(false)没有重置内部状态。金币的收集标记还是 true、动画曲线停在上次的位置、粒子的Play On Awake已经消耗掉了。障碍物则可能是 Collider 在碰撞回调里被临时关掉过回池时没打开。解决是给每个池化对象写一个OnSpawn()和OnDespawn()取出时统一重置所有可变状态归还时统一停下动画和音效。这件事不要指望在生成器里做散落在各处的重置代码一定会漏。5.4 编辑器里跑得好好的打包到手机速度翻倍现象是手机上角色跑得飞快两秒就撞死编辑器里完全正常。原因是位移没有乘Time.deltaTime或者用了Time.deltaTime但同时在别处开了一个固定值的协程位移。编辑器 60 帧、手机 120Hz 高刷屏速度直接翻倍。还有一种情况是Application.targetFrameRate没设某些机型跑在 30 帧看起来像卡顿。解决是把所有与时间相关的位移统一乘Time.deltaTime并在启动时显式设置Application.targetFrameRate 60且关闭QualitySettings.vSyncCount。真机测试不要只在模拟器上看模拟器的帧率参考价值很低。5.5 真机上滑动手势不响应或误触现象是打包到手机后滑动偶尔没反应或者轻点一下屏幕角色就跳起来。原因是滑动阈值是拿编辑器的鼠标位移调出来的鼠标移动快、位移大手机的触摸采样频率和像素密度完全不同。另外 UI 层铺了全屏的透明 Image 拦截射线触摸事件到不了游戏层。解决是把阈值改成按屏幕高度比例算比如minSwipe Screen.height * 0.05f这样在不同分辨率上手感一致。UI 那一层全屏遮罩要检查Raycast Target是否被误勾不需要交互的背景图全部取消勾选。开始游戏的点击和游戏中的滑动分成两个状态处理Ready状态下任何点击都算开始Running状态才进手势判定能干掉一大半误触。5.6 一套通用的排查顺序遇到说不清的现象按这个顺序走比乱猜快先看 Console 有没有报错再看 Inspector 上的引用是不是空的然后看 Layer 和碰撞矩阵最后才怀疑逻辑。跑酷工程九成的问题在第一步和第二步就能定位真正需要读代码的很少。如果怀疑是性能问题用 Profiler 看GC Alloc每帧几百字节的分配基本都是字符串拼接或GetComponent造成的挪到Awake里就好。6. 二次开发换皮、加道具与难度曲线的三条改造路线工程能跑之后多数人要做的不是重写而是改。按改动成本从低到高我一般分三条路线走。第一条是换皮成本最低。把 Player 预制体换成自己的模型注意两件事模型根节点的朝向要和代码里的 Z 一致不一致就在父节点上加一个 180 度旋转的空物体别去改代码以及重新套一次CharacterController的高度和半径。美术资源换完之后把所有材质换成同一个 Shader 变体能显著减少 Draw Call跑酷这种一直有物体在动的场景合批收益很直接。第二条是加道具护盾、磁铁、双倍金币这三样是跑酷的标配。实现方式我倾向于「状态挂在 GameManager 上效果挂在玩家身上」GameManager记hasShield和剩余时间PlayerMotor撞到障碍时先问一句if (GameManager.Instance.ConsumeShield()) return;有盾就吃掉盾不死没盾才走死亡流程。磁铁用Physics.OverlapSphere把附近金币往玩家身上吸注意在FixedUpdate里做Update里改刚体会抖。第三个是难度曲线这也是最值得花时间的地方。把速度和密度从代码里挪到 ScriptableObject改数值不用重新编译策划能自己在 Inspector 里调[CreateAssetMenu(menuName Runner/GameConfig)] public class GameConfig : ScriptableObject { public float baseSpeed 8f; public float maxSpeed 18f; [Tooltip(横轴 0~1 表示本局进度纵轴是速度倍率)] public AnimationCurve speedCurve AnimationCurve.EaseInOut(0f, 1f, 1f, 2.2f); [Tooltip(每 100 米增加的障碍密度0.02 表示 1%)] public float densityStep 0.02f; public float SpeedAt(float progress01) Mathf.Min(maxSpeed, baseSpeed * speedCurve.Evaluate(progress01)); }progress01的算法是Mathf.Clamp01(Distance / targetDistance)targetDistance取 2000 米左右这样一局大概三分钟到顶。曲线用EaseInOut而不是线性是因为线性在开局阶段涨得太快玩家还没熟悉操作就开始加速。densityStep决定障碍生成的间距下限可以直接喂给ObstacleSpawner的gapMin。验证这套改动有个笨办法但很有效在编辑器里挂一个调试脚本把Time.timeScale设成 3让游戏以三倍速跑完 3000 米全程用录制或者日志记下每次死亡的位置。如果发现死亡集中在某个距离段那就是那一段的难度曲线有断崖去调对应的AnimationCurve关键帧。正常的一局死亡位置应该是分散的全挤在 800 米附近说明那里难度跳变太大。最后提醒一句关于重开的设计。很多跑酷工程重开是重新加载场景玩家能明显感觉到一次黑屏。改成「状态复位 场景物体归位」会顺滑很多把玩家的 z 归零、跑道索引归 1、生成器游标重置、所有池化对象回收、相机瞬移回初始位置整套做完不到一帧。我第一版图省事用了SceneManager.LoadScene结果每次重开都要卡 300 毫秒测试同学提了三次。从那以后我每次做重开逻辑都强制走一遍「复位而不是重载」的检查。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑