多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

从零开发2D原始生存游戏:caveman完整技术解析

从零开发2D原始生存游戏:caveman完整技术解析 “caveman”这个项目标题我第一次看到的时候脑子里蹦出来的不是“原始人”三个字而是一连串问题这是什么类型的项目是游戏是工具还是某个开源库的代号直到我在自己的开发者群里问了圈才确认大家的第一反应几乎都一样——想做一个原始人生存题材的东西。于是我决定把这个“只有一个单词”的项目落成一个可以玩的原型一款名叫 caveman 的 2D 原始生存游戏。玩家扮演一个在石器时代醒来的原始人天亮采集果子白天砍树打石做工具傍晚搭庇护所夜里点火取暖防野兽。项目不大但五脏俱全。这篇文章把完整开发过程、数值设计思路和踩过的坑写在这里给想动手做同类小游戏的朋友作个参考。1. 项目概述与设计思路caveman 到底做什么1.1 选题背后的理由为什么是洞穴时代原始生存题材在独立游戏圈一直有稳定的受众但大部分同类作品都堆得很重部落发展、科技树、外交、多文明对战……一套下来光策划文档就能写几百页。caveman 想做减法只保留“活过第一天”这个最小核心。洞穴时代的好处是资源种类少、规则直觉化——玩家不需要学复杂教程看到石头知道能敲看到树知道能砍看到果子知道能吃。这种天然的认知匹配对小型原型项目来说极其友好。另一个理由是美术和声音成本低。原始题材不需要现代UI的复杂图标石头、木头、火堆、动物都是几何形状就能表达的东西。我在项目里用了一组 32x32 的手绘像素图全部素材加起来不到80张几乎是一个人两周能画完的量。这对独立开发或者业余项目来说是一个非常现实的起跑线。1.2 核心体验定位夜晚恐慌感caveman 的核心体验不是“种田养成”而是“夜晚倒计时”带来的压力。白天所有行为都在为夜晚做准备太阳一落山视野收缩气温下降野兽刷新率提高。玩家必须提前确认三件事有一个能挡住风向的庇护所、有一堆烧着的火、手里至少有一根能防身的石矛。这个循环的节奏用“分钟”来拆的话大约是游戏中一天16分钟白天11分钟黄昏2分钟夜晚3分钟。白天表面很宽裕但如果玩家浪费前5分钟乱跑后面就会明显吃紧。这个“前期松弛、后期紧张”的节奏曲线是我在测试中反复调出来的初始版本白天只有8分钟结果玩家根本来不及建庇护所挫败感极高。后来放长到11分钟压力点从“来不及”变成了“要不要冒险多采一轮果子”这个决策感才是生存类游戏真正好玩的地方。1.3 目标玩家与项目边界我给自己定了一个很克制的范围单人开发、2D、单场景地图、三天可通关。目标不是做长线运营产品而是让玩家在20分钟内完成一次“从裸奔到住进洞穴”的完整体验。如果把概念比作一顿饭caveman 不是满汉全席是一道用料扎实的主菜——能吃出核心味道但没有配餐和甜点。如果你正准备入门游戏开发或者想在一个月内把一个想法做成可玩的Demo这个项目非常适合作为练手。它覆盖了游戏开发里最常用的几个基础系统角色控制、资源采集、背包、建造、生存数值、敌人生成和存档每一个单拆出来都不难但把它们串成一个体感顺滑的循环才是真正的价值所在。2. 技术选型与引擎方案如何把洞穴人生跑起来2.1 2D还是3D我为什么选了2D最初我也纠结过要不要上3D。毕竟现在很多独立游戏都喜欢用 Low Poly 风格做原始题材视觉冲击力更强。但认真核算之后我发现3D方案会带来一串连锁成本需要建模、骨骼绑定、动画状态机、更复杂的光照烘培还有更加繁琐的碰撞调试。这些成本对于“一个人 一个月”的项目来说太奢侈了。2D方案的优势是可以把全部精力集中到玩法循环本身。角色是一个简单的胶囊体树是圆形的碰撞体石头是方形碰撞体人走过去按交互键就能触发挥砍动画和掉落物。我用的是一套比较经典的2D俯视角实现角色持续播放根据移动方向切换的8方向动画但实际碰撞逻辑用圆形触发器统一处理避免动画朝向影响判定范围。这个方案鲁棒性很高后期加新物品、新地形不需要动框架。技术栈清单引擎Unity 2021.3 LTS稳定且文档齐全渲染URP 2D Renderer语言C#地图Tilemap 手摆物件数据存储JSON 存档版本管理Git GitHub2.2 引擎对比Unity、Godot、还有自研这个环节我想多说几句。很多新人在选引擎时会被“哪个更强”带偏caveman 的实际经验是选引擎不是选上限而是选你迭代的速度。我对比过这三个方向Unity生态最成熟Tilemap、Animator、Physics2D 都是现成的网上教程密度极高。遇到问题基本都能搜到答案适合第一款作品。Godot轻量、开源、脚本上手快2D支持非常好。但团队协作资料偏少部分插件和第三方库还在快速变化中如果项目要发布到多个平台需要自己踩一遍平台适配的坑。自研除非你有明确的技术追求比如要做超大规模同屏单位否则不推荐。生存游戏的核心是系统数量多且相互耦合自研引擎会把大量时间消耗在绘图、输入、物理这些“地基工作”上而不是游戏内容本身。我给 caveman 的结论是Unity 的 Tilemap 和 2D Physics 配合得最舒服而且我要用的 UI Toolkit、Localization、JsonUtility 都是标准能力不需要额外找第三方依赖。项目规模不大版本锁定在 LTS 即可没必要追最新版本。2.3 核心数据模型与坐标系设计在写任何业务逻辑之前我先把游戏世界的数据模型想清楚了。这里的关键原则是数据与表现分离。比如一棵浆果树地图上看到的是“树的样子”但背后实际是一个 类的实例记录它的坐标、剩余采集次数、再生计时器。这样做的直接好处是存档恢复时可以只存数据列表场景重新加载时根据数据重新生成物件保证存读档后世界状态一致。核心类结构如下[System.Serializable] public class WorldObjectData { public string type; // 物件类型tree / stone / berry / ore public float x; public float y; public float remaining; // 剩余可采集次数 public float respawnTime; // 再生计时器 } [System.Serializable] public class PlayerData { public float hunger; public float warmth; public float stamina; public int inventoryIndex; public ListItemData inventory; }坐标系统采用 Tilemap 网格对齐。地图上一切可交互物件都放在“物件层”坐标必须是 tileSize 的整数倍。采集得到的掉落物则放进玩家背包的 UI 网格而不是直接扔到场景里避免满地图飞物品带来的物理计算压力。3. 核心系统拆解与实现细节3.1 资源采集系统敲石头不难难在反馈感资源采集是第一个被实现的核心功能。它的逻辑一眼能看懂靠近物件、长按或者连按交互键、播放挥砍动画、生成掉落物、减少剩余次数。但真正让玩家觉得“手感好”的其实是三个细节第一交互范围判定不是单纯的圆形碰撞而是看玩家朝向和物件位置的点积。简单说就算你在目标旁边如果背对着它按键不会生效。这个设定很反直觉但它显著提升了操作的方向感玩家会下意识转身面对采集目标动作看起来更真实。第二掉落物不能“凭空出现”在背包里。我加了短暂的“掉落物飞行弧线”效果物品从采集点向玩家方向抛一个小抛物线落进脚下后自动入包。这个0.3秒的动画看似多余实际上给了大脑一个“我获得了东西”的完成信号比直接弹提示窗舒服得多。第三不同工具对应不同产出效率。徒手摘果子一次只能拿1个石斧砍树一次掉2个木材石镐敲石头一次掉3个碎石。每件工具都有耐久耐久归零时工具碎裂并播放一个音效提示。这个设计让“先去采石头做工具”变成玩家的第一优先级而不需要任何文字引导。采集系统的参数资源工具需求每次获得耐久消耗再生时间浆果丛不需要1-2个果子05分钟树木不需要徒手1个/石斧2个1/2木材28分钟岩石不需要徒手1个/石镐3个1/3碎石210分钟数值上我刻意把徒手收益压得很低逼迫玩家做工具升级但又不至于空手完全采不了——留一条“裸奔路线”给喜欢挑战的玩家。3.2 制作与工具升级链从石器到火把制作系统没有做成复杂的配方树而是用了一个很直接的“合成台”方案。玩家靠近工作台打开制作界面左侧是配方列表右侧是材料需求材料满足时按钮高亮点击即制作。配方用 JSON 配置{ recipes: [ { id: stone_axe, name: 石斧, materials: { wood: 2, stone: 3 }, result: stone_axe, craftTime: 2.5 }, { id: torch, name: 火把, materials: { wood: 1, resin: 1 }, result: torch, craftTime: 1.0 }, { id: shelter, name: 简易庇护所, materials: { wood: 6, rope: 2 }, result: shelter, craftTime: 5.0 } ] }这里有个容易踩坑的设计点制作不能瞬间完成。如果没有制作耗时玩家会变成“材料够了一遍全点完”完全失去决策空间。我加的craftTime是真实秒数制作期间角色无法移动可以被野兽攻击。这个“脆弱窗口”让玩家必须掂量是趁天亮多做几把工具还是留出时间找安全地方过夜。工具升级链我控制在三层以内石质工具基础→ 木质/骨制工具进阶→ 火系工具夜间保命。三层以上会让玩家在20分钟的游戏时长内感到疲惫。升级的收益也要拉开梯度比如火把除了照明还能在离开火堆时提供额外的2分钟体温保暖效果这直接改变了夜间的策略权重。3.3 生存状态机饥饿、体温与体力怎么算生存数值是这类游戏的灵魂也是最容易翻车的地方。caveman 用了三个互相耦合的数值饥饿度、体温、体力。它们不是三个独立的进度条而是一个互相影响的状态机。饥饿度初始100每分钟自然下降2点。当饥饿度低于50时体力上限减半低于20时移动速度降低20%。吃果子能恢复25点饥饿度吃肉能恢复50点但需要先烤熟。体温由环境温度和靠近火源共同决定。白天环境温度20°C玩家体温恒定100黄昏气温降到10°C体温以每分钟0.5点速率下降夜晚气温0°C下降速率提高到每分钟1.5点。隔热衣物和火源会抵消下降。体力跑步、采集、制作都消耗体力每秒消耗2-5点。体力为0时无法跑步只能走。进食和小睡能恢复体力观众反馈里“看着体力一点点见底”是压力感最强的时刻。每个数值的下降速率都不是定死不可调但修改时必须看完整体时间线如果我白天把饥饿速率调快玩家就必须采更多果子那采果子占用的时间会让庇护所的建造更紧张。这种“牵一发动全身”的感受是数值设计的核心乐趣。核心更新逻辑void UpdateSurvival() { float dt Time.deltaTime; // 饥饿影响体力 hunger Mathf.Max(0, hunger - dt * 2f); float hungerModifier hunger 50f ? 1f : (hunger 20f ? 1.5f : 2f); // 体温受环境和火源影响 float envTemp GetEnvironmentTemperature(); float fireBonus nearFire ? 25f : (hasTorch ? 8f : 0f); float effectiveTemp envTemp fireBonus; if (effectiveTemp 15f) { warmth - dt * (15f - effectiveTemp) * 0.1f; } else { warmth Mathf.Min(100f, warmth dt * 1f); } // 体力 if (isRunning) { stamina - dt * 5f * hungerModifier; } }要特别强调一点温度系统里“近火”的判定不是矩形区域而是以火堆为中心的衰减半径距离越远加温越弱。这样玩家不会站在火堆边缘就全程体温暖暖必须贴身靠近才有安全感——这也会促使玩家把庇护所建得小而紧凑恰好符合原始洞穴的场景气质。4. 实操过程从零搭一个能过夜的游戏4.1 第一步场景搭建与角色控制我先把场景的底子搭出来。用 Tilemap 画了3张地图草地、岩石地、沙地再加一组树木、灌木、石块作为地图物件。地图尺寸我控制在 80x80 格每格16像素总范围不大放完所有物件后场景文件约5MB加载时间控制在1秒内。角色控制用的是 Unity 的 Rigidbody2D 自定义移动脚本。没有用 CharacterController是因为在2D俯视角里它的碰撞检测不如Physics2D直接。移动采用归一化方向向量乘以速度避免斜向移动比水平移动更快float moveX Input.GetAxisRaw(Horizontal); float moveY Input.GetAxisRaw(Vertical); Vector2 dir new Vector2(moveX, moveY).normalized; rb.velocity dir * moveSpeed;这个阶段我只测试了一件事角色能不能顺畅地穿过树林之间的缝隙并且碰撞体不会频繁抖动。2D物理在高帧率下偶尔会有抖动问题解决办法是把 Rigidbody2D 的插值模式从 None 改成 Interpolate同时把碰撞检测模式改成 Continuous。4.2 第二步物品与背包系统背包系统我做得比较“传统”底部一条可展开的快捷栏 一个全屏背包界面。所有物品都是 ItemData 实例通过工厂方法创建public class Item { public string id; public string displayName; public int stackSize; public Sprite icon; public bool isTool; public int durability; }背包的核心逻辑是入包和丢弃。入包时先看现有格子能否堆叠不能堆叠再找空位。这个判断逻辑要写得严谨否则会出现“明明有空位但物品就是捡不起来”的Bug。实际操作中我花了最多时间的是拖拽交互鼠标按住物品拖动到目标格子目标格子有物品时要做交换交换后要更新两侧的UI。Unity UI 的 EventSystem 在多Canvas场景下有个坑如果你的背包面板和快捷栏是两个独立的 Canvas拖拽事件会经常“丢消息”。我最终把背包面板和快捷栏放进了同一个 Canvas 下并且用 Canvas Group 控制显隐问题才根治。4.3 第三步洞穴庇护所与火焰系统庇护所是夜晚安全的必要条件。建造时玩家需要选择一个2x2的格子区域系统检测区域内没有障碍物后生成一个带屋顶的洞穴入口图块。庇护所的核心逻辑不是“无敌房”而是“温度缓冲”和“挡风”庇护所内环境温度比外界高5°C默认值。庇护所内无法被野兽锁定为目标但野兽依然会在门口徘徊。庇护所内允许点燃火堆火堆在室内时热效率提高50%。火焰系统我用的是一个粒子发射器加一个光照组件。火焰的粒子参数需要调得“像火而不过度”——粒子初始大小20速度3颜色从亮白渐变到橙色生命周期0.8秒。这部分参数我调了好几次一开始火焰粒子太浓密在低端手机上掉帧严重后来把粒子发射率从每秒80降到50视觉效果几乎无损性能压力却减了一半。火堆的状态有三种点燃、燃烧中、熄灭。点燃需要消耗1个木材和1个火种燃烧中会持续消耗木材速度为每30秒1个木材。如果玩家库存木材为0火堆会进入警告状态屏幕边缘泛红配一个心跳音效。这个“资源焦虑”的设计目的很直接逼玩家在白天囤木材而不是等到天黑了再去找。4.4 第四步存档与性能优化存档我选择了 JSON 而非二进制。原因有三个第一调试方便记事本打开就能看内容第二兼容性风险低改版本号字段就能做迁移第三对小型项目来说 JSON 序列化的性能损耗完全可忽略。存档文件只记录三个核心数据块玩家属性、背包物品、世界物件状态大约3KB保存耗时不超过10毫秒在每5分钟和一个关键节点入睡/死亡自动写入。性能优化方面caveman 最耗性能的部分是格子的碰撞检测和火焰光照动态烘焙。我做了三个针对性优化静态碰撞体合并地图上所有树木和石头的碰撞体标记为 StaticUnity 物理引擎会自动做空间划分碰撞查询开销很低。动态光照数量限制全地图最多允许同时存在4个动态光源火堆、火把、洞穴口投影灯超过数量的光源自动降级为静态光照贴图。对象池采集掉的树叶、碎石粒子、攻击特效全部用对象池回收避免运行过程中频繁实例化和销毁物体导致的 GC 卡顿。这三板斧做完以后在地图中高密度区域奔跑的帧生成时间从12ms稳定降到6ms换算下来从60帧提升到接近160帧的缓冲量至少不会让低端手机发热。5. 常见问题与排查技巧实录5.1 问题一采集判定失灵明明站在旁边却按不出交互这是我调试过程中第一个让人抓狂的Bug。排查步骤是先在 Update 日志里打印交互距离和角度发现有些物件能检测到、有些不能。最后定位到问题出在坐标对齐上物件层是1x1格但树木类物件的碰撞体是稍微大于1格的为了让木材看起来更茂密导致玩家朝向点积计算时判定角度被碰撞体边缘“挤”偏。解决办法是在计算交互方向时忽略物件的视觉碰撞体改用独立的、不可见的小交互Trigger。这一套“视觉层、碰撞层、交互层分离”的做法后来也沿用到了其他物件上。现在每次加新物件都默认建好三层结构反而不容易再出类似问题。5.2 问题二地图物品太多存档结构失控第一版存档完全按“地图坐标列表”记录结果测试两小时后存档文件膨胀到400KB读档后地图上还会出现两个重叠的树——因为世界物件多了一轮刷新但旧记录没有正确删除。后来我把存档改成按键分区PlayerData、InventoryData、WorldObjectData[]并且为 WorldObjectData 增加唯一ID。每次世界物件刷新生效前系统先按ID清理旧实例。这样文件体积稳定在60KB左右含所有环境物件的坐标和ID读档后的世界状态精确匹配上一次存档时点。错误做法问题表现正确做法存档只存坐标列表物件重复、存档膨胀按唯一ID区分并清理旧实例存档格式用二进制版本升级后无法读取JSON 包含版本号字段未启用迁移函数自动存档间隔太短每次存档读档卡顿设为5分钟一次关键节点额外存档5.3 问题三夜间野兽刷得太无规律一开始野兽是随机位置生成玩家经常在毫无预兆的情况下被扑倒晚上被打扰得没法出去补石头挫败感爆棚。我调整了两层逻辑第一野兽只在离玩家15格外、45格内的环状区域生成保证“有风险但可见”。第二刷新前0.5秒在地面生成一个红色预警圈。预警圈淡入、声音低吼、然后野兽扑出。这个预警机制并没有降低野兽难度但它把“随机被杀”变成了“玩家可以主动回避的风险”观感完全不同。5.4 避坑速查表坑位现象建议环境温度参数拍脑袋设置玩家一夜被冷死以为是Bug画一张全天温度时间线对照数值表检查制作没加耗时节奏失去决策空间所有重要配方加1-5秒制作时间采集动画过长玩家交互体验拖沓单次采集动作控制在0.6秒左右光源数量无上限火把加火堆全亮起后卡顿限制动态光源为4个或用对象池UI 分多个 Canvas拖拽事件失效相关UI放同一Canvas用Group控制显隐存档覆盖写入无备份一次坏档全盘清零写入前先备份一份 .bak读头校验版本6. 我最后想多说的一点如果你也想做一个类似的项目我诚心建议不要在第一个版本里追求极致真实感。原始人题材容易让人沉迷于“真实生存模拟”的幻觉里然后每个系统都想加细节——真实的狩猎行为、真实的季节变化、真实的火堆点燃方式。这些东西单看都很酷但它们会迅速膨胀成一场灾难像我第一次做原型时一样一个月了连一个完整夜晚都跑不顺畅。caveman 后期被我砍掉的功能包括饮水系统、土质耕地、动物驯化、多季节切换、部落交易……砍完之后游戏反而更快地变得好玩了。原因很简单玩家接收到的信息变少注意力就能集中在“天要黑了我到底准没准备好”这个核心焦虑上。如果你从这个项目展开我建议你按这个顺序扩展先做一个多滚动的中心村庄让玩家可以建立多个成员并分配任务再引入事件系统比如冬季第一场雪来临前玩家得提前囤够十天的柴火和食物。这种“时间压迫感”和核心循环完全同构会比无脑加新物品系统更能突出原始题材的魅力。做游戏是一回事做完游戏是另一回事。希望这篇记录能帮你少走我绕过的路。
返回列表