December 16, 2025

带可可学数学

可可三年级,前段老师说她数学成绩不好,需要在家加强一下。

这段时间我每天晚上给她讲一点点数学,都是课本上的内容,然后我再稍稍发挥一下。几次之后,我发现最大的问题是她觉得数学很无聊。

她似乎比较抗拒学新的知识,更喜欢用熟悉的方法。去年我发现她计算能力有问题,每天给她做加减法练习,总算不再用更早年我教她的 +1 法算加法了:即计算 7+8 的时候,算 8 次 +1 ,也就是数数。二年级学了乘法,乘法表也背了,但现在做应用题,本该用乘法的场合,她还是习惯连续算加法,一旦乘数太大就会出错。要用除法的时候就更混乱了,并不是用减法,而是靠猜测来试。大脑里完全没有建立乘除的概念。乘法表更像是独立的有背诵任务的诗词,还没古诗那么有趣。

我说:数学其实是这个世界上最有趣的东西。

她说:为什么呢?

我说:这个世界上有趣的东西很多,但数学是性价比最高的。一本数学书很便宜,但可以让你读很久。从中发现有那么多有趣的事实。原来解决不了的问题,知道方法突然就明白了。如果是自己找到的方法,就更让人兴奋了。

她说:我还是觉得数学没意思。

我说:慢慢来,不着急。首先不要排斥它。学数学其实不需要硬背那么多东西,我小时候最不喜欢背书了,所以才喜欢数学的。因为数学是最不需要背的,只要你从原理出发,一步步理解,最后什么问题都能解决,只是速度慢一点。多练习就快了。那些需要记住的知识,用的多就自动记住了,不需要专门背。

阅读全文 "带可可学数学" »

December 15, 2025

最近玩的几款卡牌构筑类电子游戏

最近玩了几个卡牌构筑类的电子游戏,觉得颇为有趣,值得记录一下。

首先是 Decktamer(训牌师)。我玩了十几个小时,把初级难度通关了。

它的新设计是用卡牌构筑的形式重新做了一个宝可梦。和杀戮尖塔开创的战斗结束后抽卡,用战斗胜利的奖励钱买卡、洗卡、升级的模式不同。它的战斗卡是不需要洗的,战斗中死亡就直接消失;新卡片是在战斗中捕获对手获得。加强战斗卡的方式主要是用道具卡杂交战斗卡:从一张战斗卡上抽取需要的技能,加到另一张战斗卡上。

战斗过程更像是万智牌那种更传统的卡牌战斗模式:摆放战斗卡都场上,再由上场的卡片发动能力。这种传统战斗模式不同,发动战斗技能没有额外的资源消耗,而修改成每回合必然从卡片上所有技能中选择一个。这可以避免给同一张卡片合成太多能力造成的不平衡。更多能力往往只是增加了容错性,可以应付更多场景。

玩家卡牌被分成了两个牌堆:战斗卡堆和道具卡堆。这种双卡堆的模式最近的卡牌构筑游戏中比较常见,下面还会再提到。不过这里道具卡堆并不是抽牌堆,更像是一个道具背包,可以随时使用。

我在简单难度通关的感受是:只有最终 boss 有挑战。而这种挑战更像是一个谜题。所以第一次面对最终 boss 我没有一次通过。而是熟悉了它的技能,第二次刻意针对这些技能来升级牌组,这样才通关。整个游戏给我的感觉是,解密成分更重一些,也就是该如何养卡才能解决对手。所以游戏里(简单模式下)有无限次的 undo 。我相信选择更高难度后会有不同的感受。不过暂时没有玩下去。

阅读全文 "最近玩的几款卡牌构筑类电子游戏" »

November 28, 2025

欧陆风云5的游玩笔记

最近一个月共玩了 270 小时的欧陆风云5 ,这两天打算停下来。最近在游戏后期打大战役时,交互已经卡得不行。我已经是 i9-14900K 的 CPU ,估计升级硬件已经无法解决这个问题,只能等版本更新优化了。

ps. 其实只要把游戏暂停下来立刻就不卡了。虽然我直到这个游戏需要的计算量非常大,但是卡交互操作肯定是实现的不对。因为这并不是因为渲染负荷造成的卡顿,可以让游戏时间流逝更慢一些,也不应该让鼠标点击后的界面弹出时间变长。

在暂置游戏前,我先把一些关于游戏设计上的理解先记录下来。也是对上一篇的补充

在最初几十小时的游戏时间里,我一直想确认游戏经济系统的基础逻辑。和很多类似策略游戏不同,欧陆风云5 在游戏一开始,展现给玩家的是一个发展过(或者说是设定出来)的经济系统版图。玩家更需要了解的是他选择扮演的国家在当下到底面临什么问题,该怎样解决。这不只是经济,也包括政治、文化和军事。而很多游戏则是设定好规则,让玩家从零开始建设,找到合适的发展路径。

大多数情况下,EU5 玩家一开始考虑的并不是从头发展,所以在游戏新手期也没有强烈的理解游戏底层设计细节的动机。不过游戏也有开荒玩法,在游戏中后期势必会在远方殖民、开拓新大陆;甚至游戏还设计了让玩家直接转换视角以新殖民地为核心来继续游戏。但即使的重新殖民,在四周鸟无人烟的地方开荒,和在已有部分发展的区域附近拓展也完全不同。

我十分好奇这样一个复杂的经济系统是怎样启动起来的,所以仔细做了一点归纳笔记。不一定全对,但很多信息在游戏内的说明和目前的官方 wiki 都不完整,只能自己探索。

阅读全文 "欧陆风云5的游玩笔记" »

November 22, 2025

嵌入主线程消息循环的任务调度器

最近在网友协助下把 soluna port 到包括 wasm 在内的非 windows 平台。其间遇到很多难题,大多是多线程环境的问题。因为 soluna 的根基就是基于 ltask 的多线程调度器,如果用单线程实现它,整个项目的意义就几乎不存在,所以它是把项目维护下去必须解决的问题。

好在 lua 有优秀的 coroutine 支持,它可以把运行流程抽象成数据,而 Lua 本身并未限制数据的具体储存方式,所以完全可以存在于内存堆中,脱离于 C 栈存在,这为各种在 C 环境下的多线程难题开了后门。C 语言依赖栈运行代码逻辑,而栈绑定于线程,线程调度通常由操作系统完成,所以用常规方式无法让代码跨线程运行:即,无法通过常规手法让一段代码的流程前半段在一个线程运行,而用另一个线程运行后半段;但是,在 C 上建立一个 Lua 层,则很容易绕开这个限制,只用标准方法就可以自由控制程序运行流程。

上一次发现利用一些技巧就可以完成一些看似不可能却的确可行的调度方式是 多线程串行运行 Lua 虚拟机

简单复述一下当时的需求:

阅读全文 "嵌入主线程消息循环的任务调度器" »

November 05, 2025

欧陆风云 5 的经济系统

很早从“舅舅”那里拿到了《欧陆风云 5》的试玩版。因为开发期的缘故,更新版本后需要重玩,所以一开始只是陆陆续续玩了十几个小时。前段时间从阳朔攀岩回来,据说已经是发售前最后一版了,便投入精力好好玩了 50 小时,感觉非常好。

我没有玩过这个系列的前作,但有 800 小时《群星》的经验,还有维多利亚 2/3 以及十字军之王 2/3 的近百小时游戏时间,对 P 社的大战略游戏的套路还是比较了解的。这一作中有很多似曾相识的机制,但玩进去又颇为新鲜,未曾在其它游戏中体验过。

我特别喜欢 P 社这种在微观上使用简洁公式,宏观展现出深度的游戏设计。我试着对游戏的一小部分设计作一些分析,记录一下它的经济系统是如何构建的。

阅读全文 "欧陆风云 5 的经济系统" »

October 12, 2025

关于桌游设计大赛的介绍

这一篇是前几个月研究桌游规则期间的另一篇小结。因为最近两个多月都在制作 Deep Future 的数字版,没空整理笔记。现在闲下来,汇总整理这么一篇记录。

今年夏天,我迷上了 DIY 类型的桌游。这类桌游最显设计灵感。商业桌游固然被打磨的更好,但设计/制作周期也更长。通常,规则也更复杂,游戏时间更长。我经常买到喜欢的游戏找不到人开。阅读和理解游戏规则也是颇花精力的事情。所以,我近年更倾向于有单人模式的游戏。这样至少学会了规则就能开始玩。但为单人游玩的商业桌游并不算多(不太好卖),而我对多年前玩过的几款 PnP (打印出来即可玩)类单人桌游印象颇为深刻:比如 Delve 和同期的 Utopia Engine (2010)

在 7 月初我逛 bgg 时,一款叫做 Under Falling Skies 的游戏吸引了我。这是一个只需要 9 张自制卡片加几个骰子就可以玩的单人游戏,规则书很短几分钟就理解了游戏机制,但直觉告诉我在这套规则下会有很丰富的变化。我当即用打印机自制了卡片(普通 A4 纸加 9 个卡套)试玩,果然其乐无穷。尤其是高难度模式颇有挑战。进一步探索,我发现这个游戏还有一个商业版本,添加了更长的战役。当即在淘宝上下了单(有中文版本)。

从这个游戏开始,我了解到了 9 卡微型 PnP 游戏设计大赛。从 2008 年开始,在 bgg (boardgamegeek) 上每年都会举办 PnP 游戏设计大赛。这类游戏不限于单人模式,但显然单人可玩的游戏比例更高。毕竟比赛结果是由玩家票选出来,而单人游戏的试玩成本更低,会有更多玩家尝试。据我观察,历年比赛中,单人游戏可占一半。近几年甚至分拆出来单人游戏和双人游戏,多人游戏不同的设计比赛。

根据使用道具的限制条件,比赛又被细分。从 2016 年开始,开始有专门的 9 卡设计大赛。这是众多比赛中比较热门的一个。我想这是因为 9 张卡片刚好可以排版在一张 A4 纸上,只需要双面打印然后切开就完成了 DIY 制作。加上每个桌游玩家都有的少许米宝和骰子,阅读完说明书就可以游戏了。

如果嫌自己 DIY 麻烦或做出来的卡片不好看,在淘宝上有商家专门收集历年比赛中的优秀作品印出来卖,价格也非常实惠。比赛作品中特别优秀的,也会再完善和充实规则,制作大型的商业版本。例如前面介绍的坠空之下就是一例。我觉得,阅读规则书本身也很有意思。不要只看获奖作品,因为评奖只是少量活跃玩家的票选结果,每个玩家口味不同,你会有自己的喜好。而且我作为研究目的,更爱发现不同创作者的有趣灵感。

如果对这个比赛有兴趣,可以以关键词 2025 9-Card Nanogame Print and Play Design Contest 搜索今年的比赛历程。

阅读全文 "关于桌游设计大赛的介绍" »

October 05, 2025

深远未来开发总结

桌游 Deep Future(深远未来)开发告一段落,我为它创建了一个 itch.io 的页面 发布第一个试玩版本。接下来的 bugfix 会在 github 继续,等积累一定更新后再发布下一个小版本。

这是一个兴趣驱动的项目。正如上一篇 blog 中写到,驱使我写它的一大动力是在实践中探索游戏开发的难题。写这么一篇总结就是非常必要的了。

开发过程

我在 2025 年 7 月底写下了项目的第一行代码。在前三周并没有在实现游戏方面有太多进展。一开始的工作主要在思考实现这么一个游戏,底层需要怎样的支持。我使用的引擎 soluna 也只是一个雏形,只提供非常基础的功能。我想这样一个卡牌向桌游数字化程序,更好的文本排版功能比图形化支持更为迫切。固然,我可以先做一个 UI 编辑器,但那更适合和美术合作使用。而我现在只有一个开发者,应该用更适合自己的开发工具。应该更多考虑自己开发时的顺手,这样才能让开发过程保持好心情,这样项目才可能做完。所以我选择用结构化文本描述界面:容易在文本编辑器内编写,方便修改,易于跟踪变更和版本维护。

在 8 月的前两周,开发工作更多倾向于 soluna

  1. 维护 yoga 的集成,编写布局模块。
  2. 增加文本块的支持,支持简单的文字排版。
  3. 增加 icon 的支持,可以和文本混合排版。
  4. 增加单色无贴图矩形。这样可以视觉化布局的 box 。
  5. 增加嵌套图层,而不是之前的平坦化图元。

阅读全文 "深远未来开发总结" »

September 16, 2025

有惊无险的一次网站系统升级

好消息是:这个 blog 终于是 UTF-8 编码了。前些年老有人问我能不能把 RSS 输出改成 UTF-8 的,很多 RSS 阅读器不支持 gbk ,这次终于改过来了。

事情源于昨天下午的一次脑抽,我把网站机器的操作系统升级了。上次升级还是十多年前,真的是太老旧了。结果升完级一看,php 被强制升到了 7 ,我自己写的一些 php 程序(主要是留言板)坏掉了。

这些个程序是我在 2004 年重构 2002 年的代码完成的;而 2002 年是从网上随便找来的代码基础上改的。我正儿八经学习 PHP 是在 1997 年,2000 年后就没怎么更新 PHP 的知识了。上次网站升级的时候,PHP 从 4 强制升到 5 ,就乱改了一通,勉强让程序可以运行(开了一些兼容模式)。这次再看代码,简直是惨不忍睹。所以我在本地装了个 PHP8 ,打开 PHP 官网,好好学习了一下手册。然后把代码取下来,重新建了个 git 仓库,正儿八经的改了一下。把留言的部分删了,只留下了浏览旧信息的部分,勉强让它继续跑起来。等什么时候有空了,再用 PHP 或 Lua 重新做一个。

阅读全文 "有惊无险的一次网站系统升级" »

September 09, 2025

立即模式下的鼠标交互处理

最近在做游戏时,发现在立即模式下鼠标的交互部分实现的比较混乱。

在做引擎时,我简单留出了鼠标相关事件的 callback 接口。一开始写游戏时,也就是对付一下写了几行代码,大致可以工作。做了大半个月后,随着交互界面越来越复杂,那些应付用的代码明显不堪重负。有越来越多的边界情况无法正确处理。等到最近想在交互上加一种长按鼠标确认的操作,发现不能再这样对付下去了,就花了一晚上重构了所有和鼠标交互相关的代码。

之前的问题出在哪里?

如果从系统的鼠标消息出发,我们可以从引擎获得鼠标移动、按下、抬起等事件。或许还可以利用系统发送来的点击、双击等等复合事件,但我选择自己用按下和抬起来判断这些。但是,消息机制本身和立即模式是相悖的。采用立即模式编写游戏业务以及交互,获得当下的状态是最自然的,而“消息”不是状态。它是一个队列:在每个游戏帧中,可能没有消息,也可能有多条消息。如果只是处理鼠标的位置和按键状态,那么保留最后一个状态也可以;但是,像点击这种行为,明显不是瞬间的状态,而是过去一段时间的状态叠加后的事件。

阅读全文 "立即模式下的鼠标交互处理" »

August 26, 2025

在 Lua 中定义类型的简单方法

我通常用 Lua 定义一个类型只需要这样做:

-- 定义一个 object 的新类型
local object = {}; object.__index = object

-- 定义构建 object 的函数
local function new_object(self)
  return setmetatable(self or {}, object)
end

-- 给 object 添加一个 get 方法
function object:get(what)
  return self[what]
end

-- 测试一下
local obj = new_object { x = "x" }
assert(obj:get "x" == "x")

阅读全文 "在 Lua 中定义类型的简单方法" »

August 23, 2025

编写游戏程序的一些启示

这个月我开了个新项目:制作 deep future 的电子版。

之所以做这个事情,是因为我真的很喜欢这个游戏。而过去一年我在构思一个独立游戏的玩法时好像进入了死胡同,我需要一些设计灵感,又需要写点代码保持一下开发状态。思来想去,我确定制作一个成熟桌游的电子版是一个不错的练习。而且这个游戏的单人玩法很接近电子游戏中的 4x 类型,那也是我喜欢的,等还原了原版桌游规则后,我应该可以以此为基础创造一些适合电子游戏特性的东西来。

另一方面,我自以为了解游戏软件从屏幕上每个像素点到最终游戏的技术原理,大部分的过程都亲身实践过。但我总感觉上层的东西,尤其是游戏玩法、交互等部分开发起来没有底层(尤其是引擎部分)顺畅。我也看到很多实际游戏项目的开发周期远大于预期,似乎开发时间被投进了黑洞。

在 GameJam 上两个晚上可以做出的游戏原型,往往又需要花掉 2,3 年时间磨练成成品。我想弄清楚到底遇到了怎样的困难,那些不明不白消耗掉的开发时间到底去了哪里。

阅读全文 "编写游戏程序的一些启示" »

August 09, 2025

SetWindowText 引起的死锁

最近发现我在写的小游戏在启动时有很小的概率黑屏。我使用的是 ltask 多线程框架,在黑屏时感觉 ltask 并没有停止工作,似乎只是管理窗口的部分(线程/服务)卡死了。

窗口管理使用的是 sokol_app 做的多平台封装,这只是一个很浅的封装层,但已经够用。我觉得美中不足的是,sokol_app 的 frame 回调函数是放在 WinProc 中,由 Windows 的消息循环被动调度,而不是放在外层的主动 GetMessage 循环中。

即,在 Windows 程序中,线程通常会在最外面写一个这样的 while 循环:

阅读全文 "SetWindowText 引起的死锁" »

July 20, 2025

慢跑

我这两年攀岩时总是体力不够用,出去野攀如果需要先爬山接近的话,往往爬到岩壁下就累个半死不想动了。而我那帮 50 多岁的岩友一个个都比我有活力的多。所以我想通过有氧运动改善一下心肺功能。岩友建议我试试慢跑。

去年底痛风发作 后也考虑过减少一些体重,据说有利于降低尿酸。但有点懒就一直没有开始跑。

阅读全文 "慢跑" »

Misc

Categories

Archives

Recent Comments