从0到1开发生存游戏《caveman》:程序化地图与系统设计完整复盘
发布时间:2026/10/28 12:22:50 锦皓数字建站

我把一个游戏项目代号定成“caveman”的时候身边的同事第一反应都是笑。他们说这名字太直白了像是给幼儿园绘本起的名字。但等我把玩法和核心循环讲清楚之后他们发现这个名字其实贴切得过分——你扮演的就是一个穴居人要在洞穴和荒野之间求生白天采集资源、制作工具晚上回到洞穴里抵御野兽。这个简单到有些原始的核心循环恰恰是整个项目最有吸引力的地方。“caveman”这个名字从一开始就决定了这款游戏的基调不搞花哨的剧情不堆复杂的数值就用最原始的生存本能去驱动玩家。这篇文章我想从设计思路、系统拆解、实际操作到问题排查完整复盘一下这个项目的开发过程。适合正在做独立游戏、尤其是想做生存类或模拟经营类的开发者参考。如果你还没开始做游戏只是想了解一个生存游戏从0到1是怎么被搭出来的也可以放心读下去。我会把地图生成、日夜循环、AI行为、存档管理这些核心模块的原理和实操细节都讲清楚包括那些只在踩坑之后才能总结出来的经验。1. 项目雏形为什么想做一款“穴居人”生存游戏1.1 玩法定位不是挖矿而是“活下去”刚开始构思这个项目时我面临一个选择是做一个类似《星露谷物语》的轻松采集玩法还是做一个偏硬核的生存游戏。反复权衡之后我把玩法核心锁定在“活下去”这三个字上。整个游戏没有等级系统没有技能树唯一的目标就是让这个穴居人熬过每一个夜晚。白天的每一分钟都要用来采集石头、木材、浆果制作火把、石斧和长矛到了晚上野兽会从黑暗中靠近火光你需要守着火堆、握着武器撑到天亮。这个定位带来一个非常直接的好处玩家的目标极其清晰不需要任何新手引导就能上手。另一个隐藏的好处是数值设计变得特别好做因为所有系统都围绕“生存压力”这个单一维度展开。食物值影响行动速度体力值影响采集效率火光范围影响夜晚安全感每项数值都直接关系到能不能活过下一天。相比那些动辄几十个数值相互纠缠的生存游戏这种克制反而让玩家更容易进入心流状态。我见过太多独立游戏死在“什么都想做”上加了战斗系统又要加装备系统加了装备又要加技能树最后变成一个缝合怪。caveman的定位从一开始就砍掉了这些枝蔓只保留了生存体验的核心骨架。事实证明这个决定是对的开发周期大幅缩短玩家反馈也集中在“压迫感强”“节奏好”上而不是抱怨系统太多玩不懂。1.2 美术方向像素风与有限调色板美术风格这块我一开始就被像素风吸引但真正让我下定决心的是“有限调色板”这个概念。整个游戏只用16种颜色所有场景、角色、物品都从这16个颜色中取色。这个概念听起来像是自讨苦吃实际执行下来却有三个好处一是美术工作量被压缩到一个人也能完成的程度二是画面风格因为统一性而显得高级不会出现不同素材拼贴感很强的问题三是渲染性能开销很小优化起来很轻松。具体的操作方法是先固定一套调色板主色调以棕褐色和土黄色为主模拟洞穴和荒野的质感。火光区域用橙色和黄色系夜晚用深蓝和紫色系这样玩家不需要任何UI提示就能从色彩上判断当前区域的安全程度。像素尺寸固定为16乘16每个物体占一格人物占两格高度。这个规格配合Tilemap做地图非常适合2D网格类生存游戏的开发。还有一个细节是角色动画。穴居人的动作只有四种闲置、走路、采集、攻击。为了让动作看起来不僵硬我在每种动作里加了5到8帧的关键帧动画并配合一个很小的位移补偿——比如采集时身体会向前倾一点攻击时武器会有一个延迟回摆。这些小细节单独看不出来组合在一起却能让角色在像素画质下显得有“肉感”。1.3 技术方案选型Unity 2D与C#的取舍技术选型方面我用的是Unity 2D加C#。其实市面上可以选的引擎不少比如Godot、GameMaker甚至RPG Maker但Unity在2D网格游戏这块生态最成熟资料也最好找。Tilemap系统非常成熟Cinemachine处理摄像机跟随很方便Addressables做资源管理也很顺手。对于独立开发者来说节省时间就是节省生命成熟的工具链比炫酷的技术更有价值。还有一个实际原因是我在开发过程中需要频繁做原型验证——比如地图生成算法的迭代Unity的编辑器扩展能力让我可以在Inspector面板里直接调整参数点击Play就能看到效果。这种“改参数—跑场景—看结果”的循环效率非常高我用一个下午就把地图生成脚本调试到了满意的效果。如果我当时选了其他引擎光是搭建编辑器调试环境可能就要多花一倍时间。当然选Unity不等于无脑堆插件。我保留了最精简的插件列表Cinemachine负责摄像机Input System负责输入处理配置文件全部手写成ScriptableObject。这种方式在项目初期可能慢一点但后期带来的可控性和可维护性远超使用一堆黑盒插件。2. 核心系统拆解生存玩法的三根支柱2.1 程序化地图生成细胞自动机的实际调参地图生成是整个项目最早做的模块因为它直接决定玩家体验的第一印象。我采用的是细胞自动机算法也就是从一张随机噪声图开始多次迭代地应用“邻居统计规则”来生成洞穴般的连通区域。这个算法本身不复杂但参数调试才是真正的关键。具体实现是这样的先用Uniform随机生成一张50乘50的网格每个格子有45%的概率是墙。然后迭代4次每次迭代时统计每个格子周围8个邻居中墙的数量如果超过4个就保持为墙否则变为空地。这个迭代过程会让地图逐渐从“胡椒面”一样的随机噪声聚合成几块像样的洞穴区域。参数调整的经验是初始密度剔率在40%到50%之间效果最好。低于40%地图太空旷洞穴之间的通道过多探索没有重点高于50%地图太堵玩家可能被一圈墙围住动弹不得。迭代次数4到5次是比较稳定的区间少于3次地图还带着大片噪点多于6次地图会退化成几块孤立的大空场。我在Unity的Inspector里把这些参数全暴露出来方便反复试。生成之后的地图并不是直接就能玩还需要二次处理。我用广度优先搜索找出所有连通的空地区域把最大的那块区域保留下来作为活动区域其余的小孤岛直接填成墙。接着在地图边缘固定放置洞穴入口在洞穴深处固定放置一个火把刷新点。这套流程下来地图既能保证连通性又能保证每局都有一定的随机差异。顺带一提地图生成一定要做“种子”的记录。玩家创建世界时随机生成一个种子字符串世界创建后种子固定之后所有地图数据都由这个种子按确定性算法生成。这样做的价值在后续版本中特别明显——支援读取、Bug复现、分享世界种子给朋友看都离不开这个设计。2.2 日夜循环与事件驱动用状态机管理节奏生存游戏的核心节奏感来自日夜循环。caveman的一天被分成四个阶段清晨、白天、黄昏、夜晚每个阶段的时长和事件类型都不同。清晨是短暂的过渡期野兽开始退回巢穴白天时间最长大约占一天的50%是采集和建造的黄金期黄昏是一个“提醒阶段”屏幕上会冒出“天快黑了”的视觉警示夜晚则进入生存压力最高的环节野兽刷新频率翻倍视野范围大幅缩减。技术实现上我没有用简单的计时器而是用了一个状态机加事件器。每个阶段都有明确的进入和退出事件比如进入夜晚时触发“野兽福灵”事件所有地图外的野兽会重新定位到距离玩家5格内的随机空地上。事件系统用的是C#的event委托各模块之间通过订阅事件来响应而不是一个脚本轮询检查当前时间段。这么做让代码的耦合度大幅降低后来加季节系统的时候我只新增了几个订阅者就完成了扩展。有一个细节特别值得分享阶段切换用的不是直线计时而是带一点随机偏差。比如“白天”理论时长是120秒实际每次生成的时长在110到130秒之间浮动。这一点随机性让玩家无法完全背板每一局的压力曲线都不完全一样。测试时很多老玩家反馈“感觉每一天都不一样”很大程度上就是靠这个随机偏差实现的。夜晚的视觉效果也做了专门设计。摄像机周围加了一个暗角遮罩只有火光半径内的区域保持明亮。这个效果不是简单的图片叠加上去而是用Unity的光照系统配合Sprite Mask实现让光源可以真正被障碍物遮挡。夜晚站在山洞里和站在开阔地上视野表现完全不同这种差异感让玩家对“躲进洞穴”这个行为产生本能的依赖。2.3 AI行为与生态张力让野兽成为压力源而不是笑话生存游戏的AI不需要多聪明但一定要让玩家感到威胁。caveman的敌人目前有三种野猪、灰狼和洞穴熊。野猪是新手期最常见的对手行为模式是“感知玩家进入自身4格范围后发起冲锋”但冲锋有1秒的前摇玩家可以走位躲开。灰狼则更沉得住气会在6格距离上尾随玩家等玩家体力偏低或者被地形卡住时才扑上来。洞穴熊是夜晚才会出现的精英怪血量高、攻击力强抬手动作明显但出手极快。AI的实现思路是有限状态机加感知半径。每个野兽都维护一个状态状态只有“闲置、警戒、追踪、攻击”四种切换条件从感知系统获得。感知系统每0.2秒做一次距离检测和视线检测——距离检测查的是直线距离视线检测则用射线判断中间有没有墙体阻挡。这种方案在性能上非常友好一个场景同时存在20只野兽时依然能稳定在60帧。真正让野兽成为“压力源”的是它们之间的协同错觉。虽然每只野兽都是独立运行的但我在代码里加了一条规则当一只野兽进入攻击状态后它周围的同族野兽会有概率被“惊动”自动从闲置切换为追踪状态。从玩家视角看这就像狼群商量好了一起扑过来压迫感一下子拉满。其实它们只是简单响应了同一个事件但在游戏体验上是质变。为了让猎杀行为有正反馈野兽被击败后会掉落肉和兽皮。肉保证食物值恢复兽皮则是制作衣物类工具的基础材料。这里我刻意设计了“击杀成本高于收益”的曲线——前期打一只野猪要消耗约30%的体力而掉落的肉只恢复25%的食物值多出来的部分实际上靠的是玩家对资源的长期规划。这个数值设计让“无脑猎杀”变得不可持续潜在地引导玩家学会权衡和避战。3. 从零搭建到第一版demo的实操记录3.1 基础工程结构Tilemap与摄像机设置拿到一个新项目我习惯先把工程结构搭好再写代码。caveman的Tilemap结构用了两层一层是地形层用来放置地面、岩壁、水洼这些静态元素另一层是道具层用来存放浆果丛、石头堆、木材堆这些可采集物体。道具层在玩家采集后会被实时改写这样就能避免在运行时动态修改地形层带来的各种麻烦。Tilemap的瓦片制作也走了捷径。我不打算逐格手绘全部瓦片而是用Unity的Tile Palette配合自动轮廓功能让瓦片边缘根据相邻瓦片自动补边。洞穴墙体的选角效果就是这样生成的画一张墙角的基础贴图、配置好Rule TileUnity会自动根据上下左右四种邻居关系拼接出完整的墙体样式。用这个方案我只花了两个晚上就完成了200多格地形瓦片的适配如果手绘的话估计要画一周。摄像机这块我用的是Cinemachine的Framing Transposer目标跟随玩家同时开启软边界限制让摄像机不会移动到地图之外。还有一个细节是视野缩放白天FOV保持默认值到黄昏时用DOTween做一个0.2倍缓动缩小模拟“压迫感逼近”的效果。这个变化不需要玩家刻意觉察但它确实在潜移默化地影响情绪。3.2 物件系统与背包一个数据结构引发的连锁调整物件系统在最早期是我写得最乱的部分。每个可采集物体都直接挂了一个自定义脚本各自维护自己的血量、掉落物和动画状态。最开始用着还好但等到加入草绳、树皮、石片这些新材料之后每个脚本都要改一遍维护成本直线上升。后来我看了一次资深开发的分享才意识到应该用“物件定义”加“实例状态”的模式来重写。我定义了一堆硬编码的ScriptableObject作为物件模板比如“石头堆”模板里配置了采集耗时3秒、掉落1到3块石头、需要石镐或更强工具。实际场景里摆放的只是一个只有坐标和模板引用的轻量级实例。实例在场景里只有几个变量具体的属性全部从模板读取。这个改动让新增一种材料只需要新建一个模板、配置几个字段就行代码不用再碰。背包系统也因此重新设计。玩家背包只有12格每种物品可堆叠到10个食物、建材、工具各占一部分空间。不要把格子数给太多生存游戏的背包本身应该是玩家需要管理的资源。格子一多玩家就不会认真思考要带什么出门游戏节奏就散了。这个设计当时挨了测试玩家不少骂但最终效果大家都认可——跑出洞穴前收拾背包的紧张感本身就是生存游戏魅力的来源。3.3 探索、制作与建造三个界面如何做到一体制作和建造在caveman里不是两套独立的界面而是共用一套“配方树”UI。玩家按Tab打开制作面板左侧是分类列表分为工具、武器、建筑、照明四大类右侧是当前可解锁的合成配方。每个配方会显示所需材料及当前持有数量持有不足时图标置灰且缺的材料会高亮标红。这套界面我在设计时最纠结的是“靠近物件自动弹出交互”还是“手动打开面板”。试了几轮之后选择了后者因为生存游戏的节奏需要玩家自己掌握主动。自动弹出面板看着方便实际中会频繁打断玩家——每次经过一个采集点就要弹一次窗口非常干扰沉浸感。后期版本中加入的建造模式也跟制作面板做了联动。玩家点选建筑配方后场景中会半透明显示一个弧形预览框示意建筑的占地范围确认放置后才扣除材料。这个交互在手机平台上也算很成熟了但在PC端使用鼠标操作反而有一种“搭积木”的快感。编辑器里我把预览颜色改成根据地形类型适配比如岩地上显示绿色、水中显示红色视觉信息非常干净。3.4 对象池与性能让同屏上百个物件不卡的方案随着地图变大、采集物增多性能问题很快就暴露了。第一次整合后测试时一片树林里同时有上百棵树木实例每个树木实例都需要更新动画、掉落判定和碰撞体帧率直接掉到30以下。我一开始以为要上ECS才能解决后来发现需求还没到那个级别普通对象池就够了。对象池的方案很简单场景启动时预先创建一批树木实例比如30个全部隐藏。当玩家靠近一片刷新区域时从池子里取出一个实例、摆放到目标位置、激活即可。玩家离开后实例又被还回池子重用而不是反复销毁创建。这个改动处理了大约70%的运行时卡顿GC压力也大幅度下降。另外还有一个容易被忽视的性能杀手——Tilemap的物理更新。我一开始把地形层的每个格子都添加了碰撞体这导致地图上几千格瓦片都在参与物理运算。后来改成只在地形边缘层使用碰撞体中间大片的“安全区域”不参与碰撞只用来做视觉和网格寻路。这个改动极其简单但当场景里格数过万时收益是全方位的。灯光性能也有讲究。Unity的2D光照系统如果同时开启几十个实时光源开销依然很大。我把火把、火堆这些光源全部改成了“延迟渲染”模式并且控制同时激活的光源数量最多不超过8个多余的用烘焙完成的“光影贴花”代替。官方对这个功能的优化已经很好用了用了烘焙方案后即使场景里摆了100处火把帧率也纹丝不动。3.5 存档系统用JSON记录世界用版本号保平安存档系统是最容易在开发后期引发灾难的模块。caveman用了最经典的JSON方案一个存档文件包含世界种子、玩家的坐标与背包数据、地图上已发生改动的模块列表。因为地图本身是程序生成的存档不需要保存整张地图的原始数据只需要保存“增量变化”——比如这棵树的浆果已经摘了、那个墙被挖掉了、某个建筑放在了哪里。每写一次存档我都强制写入schemaVersion字段当前版本是3。每次数据结构有变动时必须写一个独立的迁移函数将旧版本数据升级到新版本。这个习惯救了我两次一次是背包增加“耐久度”字段一次是地图模块结构重写没有版本控制的话这些改动会造成所有老存档直接损坏测试玩家的进度就会白费。存档的写入时机选在“玩家入睡”和“玩家死亡”两个节点。前者是主动保存后者是死亡后的重置。平时运行中不做自动存储因为频繁写磁盘会导致短暂的卡顿破坏沉浸感。很多玩家反映“晚上点入睡的时候有明显卡顿”其实就是因为保存操作在处理一整天的数据。后来我改成异步保存用线程池执行写入卡顿基本消失了。4. 常见问题与排查技巧实录4.1 地图生成得太碎怎么办每次跑种子如果地图生成了几个互不相连的小洞穴而不是一整块连贯区域玩家就会被堵在出生点旁边。这个问题最开始很频繁排查后发现主要是两个原因第一是初始墙密度定太高了导致细胞自动机的迭代过程很难“打通”洞穴之间的狭长通道第二是连通性检查没有做二次缝合。解决方案是两步走。第一步把初始墙密度从45%下调到40%同时把迭代次数从4次改为5次让空地区域有更多机会连成片。第二步是在连通性检查之后增加一个“桥梁”步骤——用A星算法找出距离最近的几个空洞穴区域然后在它们之间填出一条2格宽的地下走廊。走廊的位置不是随机的我让它经过地图上的铁矿刷新点这样玩家去探索走廊时自然发现资源一举两得。4.2 夜晚掉帧抖动尤其是一堆火把同时点亮虽然用了对象池和烘焙光源但夜晚场景在开阔地同时出现十几只野兽加多个光源时帧率还是会掉到40多。排查后定位到原因野兽AI的感知系统每0.2秒对周围所有对象做一次射线检测十几只野兽叠加起来每秒钟要发上百条物理射线请求。我的解法是给感知系统做“分批错峰”。每只野兽在生成时会被赋予一个0到199毫秒的随机偏移量更新感知的帧时间从“全局固定0.2秒”改成“基础间隔加上自己的偏移量”。这样虽然单只野兽的感知时机不完全精确但整体分布在时间上均匀了不再出现每0.2秒的一次同步波峰。修复之后哪怕同屏30只野兽帧率也能稳在60。4.3 存档损坏与报错排查测试过程中曾经出现过“存档能读取但地图错位”的问题表现为玩家的位置和地图岩壁重叠。深挖后发现是mapChanges列表的长度没有做校验版本迭代后旧存档里的改动记录和新世界的地图尺寸对不上读取时直接越界出错。这个问题的根源是早期写存档时偷懒没校验每个改动块的合法性。后来的方案是在读取存档时先核对地图尺寸如果改动块的坐标超出范围就静默丢弃同时增加一个Checksum字段每次保存时对整个存档内容做一遍轻量哈希载入时校验不一致就直接提示玩家“存档已损坏”并提供选项重置。虽然让代码量多了几百行但崩溃率几乎降到了零。4.4 问题速查表现象可能原因解决方案生成地图太散玩家困在孤岛初始墙密度过高或迭代次数不足初始墙密度降至40%迭代次数改为5次夜晚同屏多光源时掉帧多个实时光源同时参与渲染使用烘焙光源贴花限制实时光源数量AI过多时帧率突降感知射线请求集中造成物理波峰给感知系统加随机偏移让更新分散在不同帧存档读取后地图错位存档内改动块与当前地图尺寸不匹配加载时校验坐标范围非法数据静默丢弃角色被墙体卡住无法移动Tilemap碰撞体与视觉层不一致检查物理层设置统一边缘层与碰撞体边界浆果丛采集后不刷新增量存档记录丢失或未写入确保采集点在存档时写入mapChanges列表背包格子溢出且物品丢失背包容量在运行中被外部修改在存档写入时动态计算背包容量并做合法性校验这些经验想告诉后来者两件事第一生存类游戏的核心引擎不在某个奇特的技术技巧上而在于“每个系统的资源循环是否自洽”第二开发独立游戏的过程中“看起来复杂的技术”往往是隐藏的性能杀手和安全炸弹能用简单方案完成的模块就不要去炫技。细胞自动机加对象池加JSON存档到现在依然是很多成熟独立游戏项目里的黄金组合没必要一开始就去追求那些更花哨的框架。5. 一些没有写进代码里的心得项目做到后期我发现真正花时间的不是系统实现而是数值和节奏的打磨。野兽的刷新率怎么调、白天和夜晚的时长比怎么算、背包容量定多少格这些参数每调整一次都要重新跑几十局测试才能确认体验是否达到预期。测试玩家经常问我“这个数值是怎么定的”其实没有公式全凭“大致合理加反复试错”硬跑出来的。但从结果看这种方式反而最适合独立游戏因为体验这东西很难用纯逻辑推导。这个caveman项目带给我最大的收获是让我重新理解了“限制”的价值。像素风格限制了角色细节但让美术风格高度统一背包格子限制了资源囤积但让每次出门的决策都充满张力程序生成的地图限制了可预测性但让每次开局都有新鲜感。很多好的游戏设计不是“做加法”而是“在合理的限制中找到自由”。玩家感受到的自由反而是设计者在背后克制的产物。我猜这也是一种“穴居人”的哲学——在狭窄的洞穴里反而能活得更专注。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。