几个代码小习惯,让你越写越顺

04/2189 浏览开发心得
一个人做游戏,代码写到后面最怕什么?不是新功能写不出来,是改一个地方崩三个地方,是三个月后打开项目看不懂自己写了啥。
分享几个我写游戏时养成的小习惯,不依赖特定引擎,纯思路层面的东西。
一、状态不要用字符串,用枚举
刚开始写玩家状态,很容易写成这样:
player.state = "idle"
player.state = "running"
player.state = "jumping"
然后到处写 if player.state == "jumping" then 不能二段跳 end。
项目大了之后,一不小心写成"Jumping"或者"jump",查bug查到怀疑人生。
后来学乖了,用数字枚举:
PLAYER_STATE = {
IDLE = 1,
RUNNING = 2,
JUMPING = 3,
ATTACKING = 4
}
player.state = PLAYER_STATE.IDLE
判断的时候写 if player.state == PLAYER_STATE.JUMPING then。
好处三个:不会拼错、编辑器能自动补全、以后想加新状态直接在枚举里加数字就行,不用全局搜索字符串。
二、别让UI直接改数据,中间加一层
新手最容易写出这种代码:
-- 按钮点击时
player.gold = player.gold + 100
goldLabel.text = player.gold
看起来没毛病,但当你有了十种获得金币的方式——任务奖励、出售物品、看广告、兑换码——每个地方都要写一遍更新UI的逻辑,迟早漏一个。
我的习惯是写一个统一的函数:
function addGold(amount)
player.gold = player.gold + amount
updateGoldUI()
-- 顺便检查成就
checkGoldAchievement()
-- 顺便存档
markDirty("gold")
end
任何地方需要加金币,只调用 addGold(100),别直接改 player.gold。
这条叫“单一出口原则”,省下的调试时间远超写函数那几行代码。
三、数据存读单独一个文件管
存档代码散落在各个模块,是后期维护的噩梦。今天改一下存档格式,得满世界找哪里写了Save和Load。
我现在的项目里,所有存档逻辑集中在两个函数里:
function saveGame()
local data = {
gold = player.gold,
level = player.level,
items = inventory.getSaveData(),
settings = settings.getSaveData()
}
return encodeJSON(data)
end
function loadGame(data)
player.gold = data.gold
player.level = data.level
inventory.loadSaveData(data.items)
settings.loadSaveData(data.settings)
end
各个模块只需要提供自己的 getSaveData 和 loadSaveData,主存档函数负责拼装和解析。
哪天想加一个新字段,只改这一个文件就行。而且一眼就能看清整个游戏存了哪些东西,不会漏。
四、写配置表,别硬编码数值
掉落概率、升级经验、伤害公式——这些数值直接写在代码里,调起来要人命。
我习惯把数值单独放配置文件里,哪怕是一个简单的Lua表:
Config = {
dropRate = {
common = 0.6,
rare = 0.3,
epic = 0.1
},
levelExp = {
1, 2, 4, 7, 11, 16, 22, 29, 37, 46
}
}
代码里只用 Config.dropRate.common 去读取。
好处是调数值不用翻代码,直接在配置文件里改;更关键的是,你可以把配置文件单独发给朋友帮你测数值,不用把整个工程打包过去。
五、给每个功能留一个“开关”
在开发阶段,我习惯在配置文件里留一堆开关:
Config.debug = {
godMode = false,
infiniteGold = false,
skipTutorial = true,
showFPS = true
}
测试的时候打开无限金币,跑流程不用慢慢攒资源。录视频或者给朋友试玩的时候关掉。
还有一个妙用:功能做到一半但暂时不想让玩家看到,用开关直接关掉,不用删代码也不用注释。等想继续做的时候,打开开关就行。
六、事件系统代替直接调用
两个模块之间直接调用函数,初期省事,后期耦合得拆不开。
比如玩家死亡时,要通知UI弹窗、音效播放、任务系统检测、存档标记……如果每个地方都写一遍,改死亡逻辑的时候得动所有模块。
我习惯用一个简单的事件系统:
Event = {
listeners = {}
}
function Event.on(eventName, callback)
-- 注册监听
end
function Event.emit(eventName, data)
-- 触发所有监听者
end
玩家死亡时只发一个 Event.emit("playerDied"),谁关心这个事件谁自己注册。
音效模块注册一下,UI模块注册一下,任务模块注册一下。哪天想加一个“死亡统计”,直接注册监听就行,不用改玩家死亡那块代码。
七、注释写“为什么”,不写“是什么”
看代码最怕这种注释:
-- 循环十次
for i = 1, 10 do
-- 血量减50
hp = hp - 50
这叫废话注释,代码本身已经说明“是什么”了。
真正有用的注释是写“为什么”:
-- 因为教程阶段不允许切换武器,所以这里强制返回
if isTutorial then return end
-- BOSS第二阶段伤害系数调高30%,测试下来原数值太简单
damage = damage * 1.3
三个月后回来看代码,你会感谢当初写清楚“为什么”的自己。
八、每做完一个功能就备份整个文件夹
版本控制软件当然好,但对于个人开发者来说,最实在的保险是:
把整个项目文件夹复制一份,改名为“游戏名_日期_刚做完XX功能”。
就这么土,就这么管用。
某天改崩了又不想回滚Git,直接打开昨天的文件夹,复制粘贴回来。心理压力小很多,做实验性功能时也敢大胆改。
九、先跑通最小闭环,再加细节
不要一上来就做完整的角色控制器、背包系统、技能树。
先做一个能“看到东西在动”的最小版本——一个方块能左右移动,一个按钮点了能加数字。
这个版本跑通了,再往上加跳跃、加攻击、加UI。每一步都有能跑的东西,出问题了也知道是刚才加的哪一步导致的。
“能跑的简陋版本”比“跑不起来的精美框架”有价值一百倍。
十、把玩家操作记录到日志里
单机游戏调试,最怕玩家说“有个bug但我不知道怎么触发的”。
我会在关键操作处写一行日志:点击按钮、进入场景、触发事件,都记下来,输出到控制台或者写到一个log文件里。
出bug时看日志,能还原玩家的操作路径。这条对于一个人测试的独立游戏尤其有用,因为你自己测的时候永远按“正确路径”走,测不出边缘情况。
这些都是我一个人写游戏时慢慢磨出来的习惯,没什么高深技术,但每条都帮我省过大量时间。独立开发最贵的不是代码写多快,是调试时能多快定位问题,是三个月后还能不能看懂自己的项目。希望这些小习惯对你有用。
9
4
5