ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Unity植物大战僵尸源码实战:工程搭建、玩法拆解与避坑指南

Unity植物大战僵尸源码实战:工程搭建、玩法拆解与避坑指南 简介一份基于Unity引擎的《植物大战僵尸》完整源码项目面向想深入Unity游戏开发的中初级开发者尤其适合对塔防玩法实现感兴趣的玩家型程序员。项目基于C#脚本驱动完整复刻了植物种植、僵尸进攻、子弹发射以及阳光资源管理等经典机制并配置了多关卡结构从零搭建出可运行的塔防游戏框架。资源包以rar格式压缩大小约127.12MB文件清单暂未单独提供但目录按功能模块划分便于对照场景、脚本和资源文件学习。目前已有943人学习/下载经社区验证后认可度较高。通过分析源码可以掌握Unity中游戏对象的管理与更新、子弹与僵尸的碰撞检测、动画状态机切换以及关卡进度存取等核心实现同时还能从多个关卡的设计中领悟难度递进、敌人AI差异化和策略资源投放等设计思路借助物理引擎与动画系统的配合理解游戏表现层的搭建方法帮助自己从零编写完整的Unity塔防项目。1. 植物大战僵尸源码 Unity这套克隆工程能跑起来之前先想清楚这三件事“植物大战僵尸源码 Unity”这种包下载回来的人十有八九不是冲着通关去的而是想拆出一套 2D 塔防的骨架卡片冷却、阳光经济、僵尸波次、格子种植。我拿到这类克隆工程后的第一反应不是双击开跑而是先确认版本号、场景相机和 GameManager 挂载情况——这三样只要有一个不对劲后面全是玄学。这套资源更适合已经有 C# 基础的初中级 Unity 从业者照着预制体和代码把玩法主线走一遍比从零写快得多如果你连脚本都不会挂建议先补一个月基础再碰。2. 项目结构先行从版本选型到第一帧运行的完整顺序很多人在下载植物大战僵尸源码后习惯性双击工程文件夹看到 Unity Hub 问要不要升级就一路“Yes”。这个习惯放在 2D 克隆工程里很容易埋雷升级管线、改光源、动画重新绑定全在你不会注意到的配置里发生。正确顺序是先把工程结构扫一遍再决定用哪个编辑器版本打开。2.1 工程目录先分清六个目录再说话一套完整的 Unity 植物大战僵尸克隆工程Assets 下通常按这样组织目录职责关键内容示例Assets/Scenes主场景与启动场景Main.unity、StartMenu.unityAssets/Scripts全部 C# 逻辑GameManager.cs、Plant.cs、Zombie.cs、Bullet.cs、WaveManager.csAssets/Prefabs预制体向日葵/豌豆射手/坚果墙、不同僵尸、豌豆子弹、阳光收集物Assets/ScriptableObjects静态配置植物数值、波次配置、关卡配置Assets/Art美术资源Sprites、Animations、Animator ControllerAssets/Audio音效与音乐射击、被啃、爆炸、背景音乐我的习惯是先看 Scripts 目录里的命名判断这套工程是“教学型单场景工程”还是“模块化多人开发工程”。单场景教学工程通常一个 GameManager 挂满全局逻辑适合整体阅读模块化工程则是 PlantSystem、ZombieSystem 分开改起来更安全但上手成本高。植物大战僵尸源码绝大多数属于前者所以阅读主线就是 GameManager 里那几段阳光余额、卡片冷却、僵尸波次、游戏状态切换。2.2 Unity 版本选型与安装注意点第一步永远是看版本文件不是猜cat ProjectSettings/ProjectVersion.txt输出通常长这样m_EditorVersion: 2021.3.10f1这个字符串决定了你该在 Unity Hub 里安装哪个版本。植物大战僵尸这类 2D 塔防工程绝大多数是基于 2021.3 LTS 或 2022.3 LTS 写的如果项目版本低于你本地安装的版本Unity 会弹升级提示能升但升完一定要重新检查 Lighting 和动画状态机。如果项目版本高于你本地版本就别强行打开直接去 Unity Hub 的“Archive”列表装对应编辑器。安装时除了编辑器本体要勾选 Windows Build SupportMono和 Windows Build SupportIL2CPP做安卓就再加 Android Build Support。很多报错“FileNotFoundException”其实不是代码问题是打包模块没装。还有两个常见坑提前说清楚。第一个是 Unity Trial Version 水印Editor 右上角一直挂着的半透明水印说明当前许可证没激活成功打包出来也会带上。去 Unity Hub 里重新登录并激活 Personal License 就能消掉不是工程问题。第二个是平台宏定义——工程里如果出现类似“编辑器里刷新的辅助按钮打包后要隐藏”的需求标准做法是预编译指令#if UNITY_EDITOR Debug.Log(这段只在编辑器里执行打包进 Player 后被裁剪); #else Debug.Log(这段只在打包后运行); #endif参数说明宏定义是编译期行为不是运行期判断别指望它控制游戏内逻辑。UNITY_EDITOR 只在编辑器进程里定义UNITY_ANDROID、UNITY_IOS 对应发布平台。另外按需在 Package Manager 里补 2D Tilemap Editor 和 Input System。如果原工程用的是旧输入系统而你本地默认新建工程是新的 Input System打开后会出现“输入管理冲突”的红字——Package Manager 里禁用新输入系统或改 Player Settings 的 Active Input Handling 为 Both 即可。2.3 打开场景到第一帧运行版本对齐后按这个顺序走能避开八成的“打开即翻车”用 Unity Hub 打开工程等编译完成Console 里的报错先截图再处理。找到 Assets/Scenes/Main.unity双击加载主场景。在 Hierarchy 面板确认三个对象必须存在Main Camera、GameManager、Canvas。选中 GameManager看 Inspector 里阳光初始值、波次配置是否被正确引用。点 Play 前把 Console 面板打开过滤 Error再进入运行模式。补一段最常见的启动前自检直接放在场景里一个空物体上有用public class StartupCheck : MonoBehaviour { private void Awake() { var gm FindFirstObjectByTypeGameManager(); if (gm null) { Debug.LogError(当前场景缺少 GameManager阳光与波次逻辑不生效); return; } if (Camera.main null) { Debug.LogError(场景缺少 Main Camera玩家什么都看不到); } } }逻辑说明Awake 在场景加载时执行FindFirstObjectByType 是 Unity 2021.3 后的新查找 API老工程里如果写的是 FindObjectOfTypeUnity 会标过时警告不影响运行但建议顺手替换。参数说明这两行检查只做存在性验证不修数据——真正的修复还得回到场景里手动拖引用。跑通第一帧后再开始动业务代码不迟。3. 核心玩法拆解种植、寻路、碰撞三条主线的代码落点植物大战僵尸源码的价值不在美术在玩法循环阳光是货币、植物是防御塔、僵尸是敌人、胜负判定是终点线。这一章把三条主线拆开看每条都给到能直接抄的代码形态和参数建议。3.1 Plant 种植系统从点击卡片到格子生成种植的完整链路是点击卡片 → 检查阳光与冷却 → 鼠标位置转格子坐标 → 检查格子占用 → 扣阳光并生成植物。常见做法是把格子系统封装成 Board 单例Plant 脚本只负责生长和攻击。核心方法长这样public bool TryPlant(Vector2 worldPos, PlantData data) { if (currentSun data.sunCost) { AudioManager.Instance.PlayNotEnoughSun(); return false; } Vector2Int cell Board.Instance.WorldToCell(worldPos); if (Board.Instance.IsOccupied(cell)) { Debug.Log(该格子已被占用); return false; } currentSun - data.sunCost; UIManager.Instance.RefreshSunText(currentSun); GameObject plant Instantiate(data.prefab, Board.Instance.CellToWorld(cell), Quaternion.identity); plant.GetComponentPlant().Init(data, cell); StartCoroutine(CardCooldown(data.cooldown)); return true; }逻辑说明先扣阳光再实例化避免生成过程中再次点击造成重复扣费。WorldToCell 把鼠标的世界坐标映射到棋盘二维数组下标CellToWorld 再换算回生成位置——这两个转换内部要处理格子大小常见是 0.8x0.8 或 1x1改棋盘尺寸时只需要动 Board 里的 CellSize 参数。参数说明currentSun 是 GameManager 里的公共字段初始值一般给 200向日葵每 10 秒产 25 点阳光data.sunCost 来自 ScriptableObject 配置。CardCooldown 协程负责把 UI 上卡片的遮罩值从 1 扣到 0冷却结束才允许再次种植。这里最容易出问题的不是逻辑而是“点击被 UI 吃掉”和“格子坐标算偏”这两种情况放第 5 章单独说。3.2 Zombie 生成与寻路不用 NavMesh 的直线推进方案植物大战僵尸里的僵尸不需要寻路每行固定一条直线从左往右走。做 Unity 克隆工程时最稳的方案不是 NavMesh而是“设定行的 Y 坐标 向负 X 移动”。波次生成由 WaveManager 统一调度[System.Serializable] public class WaveConfig { public float delayBeforeWave; // 波次开始的等待时间 public int zombieCount; // 本波僵尸数量 public float spawnInterval; // 每只僵尸的生成间隔 public ZombieType zombieType; // 普通/路障/铁桶 } public IEnumerator SpawnRoutine(WaveConfig wave) { yield return new WaitForSeconds(wave.delayBeforeWave); for (int i 0; i wave.zombieCount; i) { float row Random.Range(0, Board.Instance.RowCount); Zombie zombie SpawnAtRow(row, wave.zombieType); zombie.Init(); yield return new WaitForSeconds(wave.spawnInterval); } }逻辑说明随机行用 Random.Range(0, RowCount)注意这个重载是左闭右开能取到 0 到 RowCount-1永远取不到 RowCount不会出现下标越界。SpawnAtRow 里负责实例化僵尸并设置初始 X 坐标在屏幕右边缘之外Z 轴固定为行索引对应值。参数说明zombieCount 和 spawnInterval 共同决定压力曲线。初学者常犯的错误是把生成间隔设成固定值导致每波节奏像节拍器一样平。想要自然波动可以在间隔里加随机Random.Range(1.5f, 3.5f)也可以用 Mathf.PerlinNoise 生成平滑的密度变化——把 Perlin 噪声的返回值映射到间隔区间波次进攻的火力就会有一阵松、一阵紧的起伏观感比纯随机好很多。“Unity Mathf.PerlinNoise”经常被人拿来做地形高度实际做波次随机密度一样顺手。僵尸移动本体不需要 AI一行代码transform.position speed * Time.deltaTime * Vector3.left;speed 暴露成公共字段每个 Zombie 预制体里单独调。普通僵尸建议 0.5路障僵尸 0.4铁桶僵尸 0.35具体数值以关卡手感为准。3.3 子弹碰撞与伤害结算把伤害收口到一个方法里豌豆子弹的碰撞处理看起来简单但实现方式直接决定后期好不好扩展冰冻、减速。常见做法是子弹带电时检查碰撞命中后调用僵尸身上的 TakeDamage 方法而不是在子弹里直接改生命值private void OnTriggerStay2D(Collider2D other) { if (isDead) return; if (!other.CompareTag(Zombie)) return; Zombie zombie other.GetComponentZombie(); if (zombie ! null) { zombie.TakeDamage(damage); isDead true; gameObject.SetActive(false); // 配合对象池回收而不是 Destroy } }逻辑说明用 CompareTag 而不是 GetComponent 判类型是因为标签比较开销更小。isDead 防止子弹在禁用前的一帧里重复结算多次伤害。SetActive(false) 是对象池标准回收方式对象池的内容放到第 6 章。参数说明damage 在豌豆子弹上是 20僵尸血量普通 200、路障 380、铁桶 600。伤害数值不要散落在各处脚本里统一放进 PlantData 或 BulletData 配置后期调关卡平衡只在 Inspector 里改。TakeDamage 内部要顺便处理受击动画、音效、死亡状态切换这些逻辑集中在僵尸脚本里而不是散在子弹里——否则以后加冰冻效果你得同时改子弹和僵尸两边。樱桃炸弹那类范围伤害也是走 TakeDamage只是遍历范围内所有僵尸。4. 视觉与交互补齐摄像机、UI 遮罩、音效动画的细节玩法主线跑通后离“能给别人展示”还差三块摄像机看着对不对、UI 反馈跟不跟手、音效动画有没有糊弄过去。这一章讲清楚参数怎么设以及常见偷懒做法和正式做法之间的差别。4.1 摄像机跟随与边界限制植物大战僵尸是固定棋盘游戏摄像机理论上不用跟随。但很多工程为了让关卡有“推近拉远”的演出效果会给摄像机加一个跟随逻辑跟随目标一般是决策点或 Corn Cannon 的落点。实现时最需要注意的是边界public float smooth 5f; public float minX, maxX, minY, maxY; private void LateUpdate() { float targetX Mathf.Clamp(followTarget.position.x, minX, maxX); float targetY Mathf.Clamp(followTarget.position.y, minY, maxY); Vector3 targetPos new Vector3(targetX, targetY, -10f); float t 1f - Mathf.Exp(-smooth * Time.deltaTime); transform.position Vector3.Lerp(transform.position, targetPos, t); }逻辑说明相机 Z 轴锁定 -10这是 2D 正交相机的标准做法——Z 决定相机在 XY 平面上的观察距离改这个值会影响正交投影的裁剪关系一般不用动。参数说明Mathf.Clamp 把目标位置限制在棋盘范围内minX 示例 -1.5maxX 示例 9.2minY -3.2maxY 3.2具体数值取决于你 Board 的尺寸。这里有个新手很容易忽略的细节Vector3.Lerp(a, b, t)的 t 是插值比例而不是速度系数直接写smooth * Time.deltaTime会在帧率不同时手感不一致。正确写法是1 - Mathf.Exp(-smooth * Time.deltaTime)这是帧率无关的平滑公式比乘 deltaTime 再钳制到 1 的做法稳定。摄像机跟随逻辑写在 LateUpdate 而不是 Update是因为要等目标对象移动完成后再跟随避免画面抖动。4.2 UI 布局阳光数量、卡片冷却与反向遮罩Canvas 下的层级按照清晰度排序底部是棋盘渲染层上面是 Canvas 的 Screen Space 层再往上是阳光计数器、卡片栏、铲子按钮、暂停按钮。一套合理的层级结构是这样Canvas (Screen Space - Camera) ├── SunCounter (Text) // 右上角阳光数量 ├── CardContainer (Horizontal Layout) │ ├── Card_0 (Button Image CooldownMask) │ ├── Card_1 │ └── Card_2 ├── ShovelButton └── StartPanel卡片冷却的 UI 实现有很多种最常见的是把冷却遮罩做成一张半透明 Image覆盖在卡片按钮上方用 Image.fillAmount 控制显示比例。这里大部分人第一次就栽在“遮罩方向不对”——Unity 的 Image Filled 类型默认从右往左填充你要的是“从有到无、从上到下消失”于是去调 Fill Origin 和 Clockwise调半天也不对。更省事的方案是反向遮罩组件思路给遮罩 Image 单独建一层用 RectMask2D 配合一个自定义反向遮罩脚本来裁剪或者直接用两层 Image下层显示冷却中的暗色上层用 fillAmount 控制亮色区域从 1 到 0视觉上就是卡片从亮到暗的完整冷却过程。代码控制就一行public Image cooldownOverlay; public void SetCooldownProgress(float current, float total) { float progress 1f - current / total; // 1 → 0 逐渐变暗 cooldownOverlay.fillAmount progress; cooldownOverlay.raycastTarget false; // 避免遮罩挡住按钮点击 }逻辑说明fillAmount 从 1 扣到 0刚好匹配冷却结束卡点亮起的效果。raycastTarget 必须设成 false否则遮罩 Image 会吃掉 Button 的点击事件出现“点了没反应、卡片却进冷却”的问题。参数说明current 是协程里记的已冷却时间total 是 PlantData 里的 cooldown 字段。如果工程用的是新 Input System还要确认 EventSystem 里 Input System UI Input Module 已经挂上老项目直接拖进去用旧输入系统的需要在 Player Settings 把 Active Input Handling 改成 Both。4.3 音效触发与动画事件克隆工程最常见的音效问题是“所有声音都挂在 Update 里用 Play 方法轮询”结果就是同一帧播放十几次射击声。正确做法是让动画帧来触发音效。拿豌豆射手举例生成子弹的瞬间应该播放射击声而不是靠碰撞触发。在 Animator 里给“Shoot”动画帧加 AnimationEvent事件函数写在 Plant 脚本上public void OnShootEvent() { audioSource.PlayOneShot(shootClip, 0.6f); }参数说明PlayOneShot 的第二个参数是音量系数0.6 表示在原始音量的 60% 播放避免每次射击声音太炸。别用 Play 方法直接播Play 会打断当前正在播的音频再重头放连续射击时声音会被反复切断听起来像卡碟。AnimationEvent 的时间点设置注意一个坑事件绑定的动画帧和实际视觉效果之间有一个帧的延迟如果声音比子弹生成早或者晚去 Animator 窗口里把事件往左或往右拖一格就行不用改代码。僵尸死亡音效同理放在“Die”动画的起始帧让声音先出来倒地动作跟进比反过来自然得多。5. 避坑指南跑不起来、种植失灵、水印与马赛克的五个真实翻车现场这套工程我前前后后也帮别人排过不少问题真正高频的坑集中在五个位置。每条都按“现象 → 原因 → 解决”的顺序写。5.1 场景打开全黑Game 视图看不到草地和植物现象双击 Main.unity 后Game 视图一片黑Scene 视图里能看到物体运行后却什么都看不见。原因Main Camera 的 Clear Flags 被改成了 Skybox而 2D 场景里没有天空盒或者相机 Position 被卡在某个有遮挡的位置。还有一种情况是之前有人调试时改了 Culling Mask把世界层屏蔽了。解决在 Hierarchy 里选中 Main Camera把 Clear Flags 设为 Solid ColorBackground 保持默认灰黑色即可。相机 Position 是正交 2D 的重置为 (0, 0, -10)。如果你不想每次手动检查可以在菜单里挂一键修复[MenuItem(Tools/Fix MainCamera)] static void FixCamera() { Camera cam Camera.main; cam.transform.position new Vector3(0f, 0f, -10f); cam.orthographic true; cam.orthographicSize 6f; // 6 是 2D 塔防常用的半高按棋盘尺寸调 cam.clearFlags CameraClearFlags.SolidColor; }逻辑说明Orthographic Size 表示相机可视范围的一半数值越大看到的世界越宽。棋盘高 5 格的工程Size 取 5.5 到 6 之间刚好留出上下边距。5.2 点击卡片后种植没反应卡片却进入冷却现象点击豌豆射手卡片卡片立刻变灰进入冷却但棋盘上没有任何植物生成。原因点击被 UI 层的 Image 挡住Board 的格子射线没触发或者 Physics2D.Raycast 没有按 Layer 过滤射线打在了 Collider 以外的物体上更隐蔽的原因是场景里生成了两个 EventSystem鼠标点击被第一个消费掉第二个收不到事件。解决先确认 Hierarchy 里只有一个 EventSystem。然后把 Board 的每个格子 Collider 单独放到 “PlantSlot” Layer种植检测用 LayerMask 过滤int plantableLayer 1 LayerMask.NameToLayer(PlantSlot); RaycastHit2D hit Physics2D.Raycast( Camera.main.ScreenToWorldPoint(Input.mousePosition), Vector2.zero, 0f, plantableLayer );逻辑说明Vector2.zero 作为方向Raycast 会做点检测只返回鼠标位置的 Collider0f 距离表示无限远也可以但结合 LayerMask 后效率更高。ScreenToWorldPoint 把屏幕坐标换算成世界坐标如果你忘了这一步射线会从相机位置出发永远打不到格子。参数说明LayerMask 的1 Layer写法是位运算不要直接写成LayerMask.GetMask(PlantSlot)以外的形式两种写法都行但要保证 Instance 的植物格子和 Board 检测用的是同一个 Layer。网格的 CellSize 和 Collider 尺寸不匹配也会出现“鼠标在格子上却点到隔壁”的偏移感这个靠调 Board Inspector 里的 CellSize 解决不用改代码。5.3 水印一直挂在右上角打包后还在现象编辑器里右上角有 Unity 水印打出来的包运行时也带着。原因Unity Trial Version 水印出现代表许可证未激活成功或者当前登录的账号是试用状态。还有一部分情况是你下载的资源包里自带的启动画面 Logo 是 Unity 默认图标这个属于 Player Settings 里的 Splash Screen 设置和水印不是一回事。解决Unity Hub 右上角头像里重新登录进入 Preferences → Licenses 确认状态是 Personal如果之前用过 Unity 2022 LTS 的试用许可过期了也会出现水印。打包前检查 Player Settings → Splash Screen → Show Splash Screen 取消勾选。克隆工程如果自带了一堆推广素材那些属于作者加的启动展示画面删除相关 Prefab 和 Splash 图片即可不需要重新连接任何资源。5.4 素材放大发糊像马赛克植物叶片背面透明穿帮现象精灵图放大后出现明显锯齿和模糊运行时植物背面能看穿或者叶子从背面看是透明的。原因两个问题叠加。锯齿是导入设置的问题Filter Mode 用了 Point而材质默认没有做双面渲染Unity 2D 精灵材质默认剔除背面Vertices 转过去的那一面直接消失。解决在 Project 窗口选中所有 Sprite打开 Import SettingsFilter Mode 改 BilinearCompression 改 High QualityMax Size 按原图尺寸设置。材质方面如果工程里用的是内置渲染管线可以直接改用双面 Sprite ShaderShader Custom/DoubleSideSprite { Properties { _MainTex (Sprite Texture, 2D) white {} } SubShader { Tags { QueueTransparent RenderTypeTransparent } Cull Off Blend SrcAlpha OneMinusSrcAlpha Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag // 标准 Sprite Shader 的顶点片元实现 ENDCG } } }逻辑说明Cull Off是关键告诉 GPU 不要裁剪背面。内置管线的默认 Sprite Shader 是单面渲染叶子模型转了 180 度后另一面变成透明就是这个原因。用 URP 的工程则直接换上 URP 的 Sprite-Lit-Default 并勾选 Double Sided 选项。这个现象就是“Unity 游戏去马赛克”问题最常见的源头不是代码问题是导入设置问题。改完导入设置后记得点 Apply All否则只改当前选中的一张图。5.5 僵尸倒地之后又站起来动画和状态机“打架”现象僵尸血量归零触发了死亡动画倒下后过了一两秒又站起来继续走。原因Zombie 脚本里没有做死亡状态锁定。TakeDamage 在最后一击后仍然被继续调用Animator 的 Death 状态没有设 Exit Time或者代码里反复 SetTrigger(Die) 把状态又拉回 Idle。解决给 Zombie 加一个 isDead 守卫字段public void TakeDamage(int amount) { if (isDead) return; health - amount; if (health 0) { isDead true; GetComponentCollider2D().enabled false; GetComponentAnimator().SetTrigger(Die); Destroy(gameObject, 5f); // 等死亡动画播完再销毁 return; } animator.SetTrigger(Hurt); }逻辑说明isDead 必须放在最前面防止同一帧内多颗子弹同时结算。禁用 Collider 是防止死亡后遮挡其他子弹射线。Destroy 延时 5 秒是给死亡动画留时间如果工程里用了对象池改成 ReturnToPool(gameObject) 并把延时逻辑放进池子里。参数说明Destroy 的延迟参数要和死亡动画长度匹配动画 3 秒就销毁 3.2 秒留 0.2 秒余量避免动画播到一半物体消失。Animator 里死亡状态的 Has Exit Time 保持勾选但“任何状态 → Death”的切换条件只需要 isDead 为真过渡时间和过渡偏移都调成 0这样死亡切换瞬时完成不会出现插帧导致的“鬼畜”。6. 从能玩到能改对象池与 ScriptableObject 两个扩展入口6.1 对象池优化子弹与僵尸的生成回收豌豆射手每 1.5 秒射一次满屏 20 株就是每秒十几发 Instantiate僵尸高峰波次同时 30 个预制体进出。这个量在 PC 上不痛不痒但一上手机立刻掉帧。对象池是成本最低的优化把 Instantiate 换成从池里取public class ObjectPoolT where T : Component { private readonly QueueT pool new(); private readonly T prefab; public T Get(Vector3 position) { if (pool.Count 0) { T item pool.Dequeue(); item.gameObject.SetActive(true); item.transform.position position; return item; } return Instantiate(prefab, position, Quaternion.identity); } public void Return(T item) { item.gameObject.SetActive(false); pool.Enqueue(item); } }逻辑说明取对象时先看池里有没有空闲实例有就激活并复用没有才实例化新对象。归还没用 Destroy 而是 SetActive(false)排到队尾等下次复用。这套工程里子弹、阳光、僵尸生成器都可以各建一个池游戏运行时生成的瞬时物体全部走池子速度比 Instantiate/Destroy 高一个量级。参数说明池子默认预热的数量建议和峰值并发量对齐——豌豆子弹池预热 50 发僵尸池预热 20 只。池容量不要设太大否则内存换帧率得不偿失。6.2 用 ScriptableObject 扩展自定义植物把植物数值写死在代码里是后期改起来最痛的地方。标准做法是建一个 PlantData 资产文件所有植物预制体共享这一份配置[CreateAssetMenu(menuName PvZ/PlantData)] public class PlantData : ScriptableObject { public string plantName; public int sunCost; public float cooldown; public int maxHealth; public float attackInterval; public GameObject bulletPrefab; public AudioClip attackClip; }逻辑说明每个植物做成一个 .asset 文件比如 Peashooter.asset、Sunflower.asset字段在 Inspector 里直接改不需要改代码。GameManager 的卡片列表只引用这些资产新增植物就是在 Assets 里右键创建资产拖好预制体引用再把它拖进卡槽配置数组里。PlantData 字段类型示例值说明sunCostint100种植消耗阳光数cooldownfloat7.5卡片冷却秒数maxHealthint300被僵尸啃食的生命上限attackIntervalfloat1.5两次攻击之间的间隔秒数bulletPrefabGameObjectPeaBullet子弹预制体引用想给樱桃炸弹做爆炸范围指示器时可以借鉴 Unity Skill Attack Indicators 那套思路把爆炸半径做成一个临时 Sprite 预制体种植时显示一圈红色范围1 秒后消失再引爆视觉效果比直接炸出来清楚得多。这套工程做完这些还能试着把 Canvas 改成 World Space 移植到 PICO 4 做 VR 桌面投影那时 UI 距离、射线交互和手部悬停判断会是新的排坑点。我的习惯是拿到任何克隆工程先花十分钟查 ProjectVersion、Camera、EventSystem 这三个黑匣子再动手改逻辑——这套流程至少帮我省掉了一半的“为什么我这里能跑你那里不行”。希望帮到你。本文还有配套的精品资源点击获取
返回列表