
这是一篇有关我如何编写8位熊猫的文章,该熊猫是 TIC-80 幻想游戏机 的简单经典风格平台游戏 。
您可以在 这里 玩完游戏 。
如果您是复古游戏迷并且喜欢编程,那么您很可能已经遇到了最新趋势:幻想游戏机。 如果您还没有,那么您应该考虑的两个主要方面可能是PICO-8和TIC-80。
我选择TIC-80是因为它是免费的,正在积极开发中,其纵横比(240×136)比PICO-8宽,并且可以输出到许多平台,如HTML,Android和桌面二进制文件。
在本文中,我将描述如何编写TIC-80的简单平台游戏8 Bit Panda。
主要人物
首先,我需要一个主角。 我并没有真正考虑太多:设计过程仅仅是“为什么不是熊猫?”这个问题,答案是“当然,为什么不这样做。”因此,我着手画些什么成为我使用TIC-80 Sprite编辑器的第一个Sprite:

在我停顿片刻,让您充分利用我令人印象深刻的缺乏艺术技巧时,请考虑一下:只有2²⁵⁶的16色8×8子画面。 其中只有少数是大熊猫。 如果您可以承认这不是最糟糕的情况,我认为我很受宠若惊。
我以此为基础,绘制了其他几种代表其他姿势的主要角色的精灵:走路,跳跃,进攻等。

现在,我肯定已经失去了所有来这里学习熊猫绘画课程的读者,让我们开始编写代码。
如果您觉得它有用,则可以跟随源代码。 但是您不必这样做,因为我将尽力在每个主题上提供尽可能多的上下文。 当然,我们不会遍历整个源代码,在此过程中,我只会指出一些有趣的部分。
建筑层
TIC-80具有内置的地图编辑器,您可以(并且应该)使用它来制作关卡。 使用地图编辑器非常容易:它只是一个大的图块矩阵,每个图块都可以是子画面图下半部分中的256个子图中的任何一个(上半部分,索引号256到511,可以在运行时绘制,但是不能在地图上,因为它们需要9位才能表示)。
雪碧与瓷砖: 在TIC-80中,“雪碧”仅表示墨盒中512个预定义的8×8图像之一。 地图图块只是精灵(每个地图图块可以是精灵图下半部分的256个精灵之一)。 因此,当我们要引用图形元素时,我们说“小精灵”,而当我们指的是地图上的一个单元格时,即使该单元格在技术上包含一个小精灵,我们也要说“平铺”。 底线:没关系,图块和精灵是同一回事。
使用地图编辑器,我想到了一个非常简单的“级别”:

我们需要观察的第一件事是有两种主要的图块类型:
- 玩家可以站在上面的实心砖 (脏土和草皮+草砖)会阻碍玩家的移动。
- 装饰瓷砖 (树木,草,灯笼,石头等)。 这些只是出于美学目的,对游戏没有影响。
稍后,我们将介绍实体图块,但暂时不要担心。 在代码中,我需要某种方法来判断图块是否为实心。 我选择了一种简单的方法,并决定了一个截止的精灵索引(80):如果精灵索引<80,则该图块为实心。 如果它≥80,则为装饰性的。 因此,在Sprite表中,我只绘制了索引80之前的所有实体砖,并绘制了80之后的所有装饰性砖:

但是请稍等,水不牢固! 实体部分在做什么? 好吧,我没有告诉您有关覆盖的问题:有一个瓦片实体覆盖的列表可以替代默认的实体,例如,告诉我们,尽管水瓦片在精灵板上的位置通常会使它变得不牢固,固体。 但这也不是装饰,因为它在游戏中具有作用。
玩家状态
如果我在编程生涯中了解到的一件事是,全局变量是坏的,但是只要您将它们称为“单身人士”,它们就可以了。 因此,我定义了一些“单子”来表示游戏的状态。 我松散地使用该术语,因为它不是OOP,它们更像是顶级结构,而不是实际的单例。
无论如何,它们的名字无关紧要。 让我们从Plr开始,它表示任何给定时间的播放器状态:
Plr = {
生活= 3,
x = 0,y = 0,
Grounded = false,
...
}
它具有许多其他字段,但要注意的最重要方面是此对象存储了当前级别中玩家的整个状态:玩家处于级别中(如果他们处于跳跃中间),站立在坚实的地面上,游泳,重铺路面,垂死,驾驶飞机(是的,这是那只飞飞机的熊猫),得分是多少,启动了哪些启动道具等。
然后是游戏状态,与玩家状态不同。 例如,
游戏 = {
m = M.BOOT,-当前模式
lvlNo = 0,-我们正在玩的级别
...
}
它存储诸如当前模式是什么(我们将介绍),当前级别是什么以及一些在运行时计算的游戏范围数据之类的东西。
将游戏状态与玩家状态分开非常有用,因为这样很容易启动/重启级别:您所要做的就是重置并清除玩家状态而不是游戏状态。
渲染关卡和玩家
在TIC-80中渲染关卡和播放器非常容易。 您真正要做的就是调用map()绘制地图(一部分) ,并调用spr()在任意位置绘制精灵。 由于我是从地图的左上角开始绘制关卡的,因此我可以像这样简单地绘制它:
COLS = 30
ROWS = 17
函数 Render()
地图 (0,0,COLS,ROWS)
结束
然后添加播放器:
PLAYER_SPRITE = 257
spr(PLAYER_SPRITE,Plr.x,Plr.y)
这使我们得到:

而且,当然,熊猫只是呆在角落里,只是熊猫而无所事事。 尚无足够的游戏,但我们正在实现目标。
当然,一旦要实现侧面滚动效果,当播放器四处移动时,相机会伴随播放器,事情就会变得更加复杂。 我这样做的方法是拥有一个名为Game.scr的变量,该变量指示屏幕向右滚动了多远。 因此,在绘制地图时,我会将地图向左平移那么多像素,并且在绘制任何东西时,我总是减去Game.scr以在屏幕上的正确位置绘制它,例如:
spr(S.PLR.STAND,Plr.x-Game.scr,Plr.y)
另外,为了提高效率,我还确定了该关卡的哪一部分在任何时候都可见,并且仅在屏幕上绘制地图的矩形,而不是全部绘制。 您可以在RendMap()函数中找到丑陋的细节。
接下来,我们必须编写逻辑以响应玩家而移动熊猫。
移动那只熊猫
我从没想过我会写这篇文章作为副标题,但生活充满了惊喜。 熊猫是我们的主要角色,这是一款与移动和跳跃有关的平台游戏,因此可以说“移动熊猫”确实是该游戏的核心。
“移动”部分非常简单:您只需修改Plr.x和Plr.y ,熊猫就会出现在不同的位置。 因此,我们可以执行的运动的最基本实现将是:
如果btn (2)然后
Plr.x = Plr.x-1
elseif btn (3)然后
Plr.x = Plr.x +1
结束
请记住,在TIC-80中, btn(2)是左键,而btn(3)是右键。 但这只会水平移动,不会与事物发生碰撞。 我们需要更详尽的东西,要考虑重力和障碍。
函数 UpdatePlr()
如果不是 IsOnGround()
-秋天
Plr.y = Plr.y + 1
结束
如果btn (2)然后
Plr.x = Plr.x-1
elseif btn (3)然后
Plr.x = Plr.x +1
结束
结束
假设我们正确实现了IsOnGround() ,这将是一个巨大的改进:播放器将左右移动,并且只要它们不在坚实的地面上,它就会自动掉落。 所以有了这个,您已经可以走动,也可以从悬崖上掉下来。 令人兴奋!
但这并没有考虑到障碍:如果您尝试(水平)进入有障碍的实心砖,会发生什么? 您不应该能够搬到那里。 因此,通常,我们注意到一个模式开始出现:移动需要两个步骤:
- 确定玩家想要移动的位置(包括重力等外部因素)。
- 确定是否允许玩家移动(由于障碍)。
“想要移动”的概念被广义地定义,包括自愿和非自愿的移动:站在坚实的地面上时,玩家“希望”向下移动(由于重力),但不允许移动,因为向下移动会与地面。
因此,对于我们来说,编写一个编码整个逻辑“是否允许玩家移动到给定的x,y位置”的逻辑是有意义的。 但是,在部署敌人时我们也将需要它,因为我们还将不得不问“这个敌人能移到x,y位置吗?”。 因此,概括地说,最好编写一个函数,该函数采用x,y和任意碰撞矩形(这样,我们可以为玩家或敌方实体适当地传递正确的x,y和碰撞矩形):
C = 8- 瓦片大小的常量(TIC-80中始终为8)
-检查是否有碰撞矩形的实体
-cr = {x,y,w,h}可以移动到位置x,y
函数 CanMove(x,y,cr)
本地 x1 = x + cr.x
局部 y1 = y + cr.y
局部 x2 = x1 + cr.w -1
本地 y2 = y1 + cr.h-1
-检查矩形接触的所有瓷砖
本地开始C = x1 // C
本地端C = x2 // C
本地 startR = y1 // C
本地端R = y2 // C
对于 c = startC,endC做
对于 r = startR,endR
如果 IsTileSolid(mget(c,r)) 然后返回false end
结束
结束
结束
逻辑很简单:只需找到矩形的边界,然后遍历该矩形接触的所有图块,然后检查其中是否有固定的图块(IsTileSolid()仅执行我们的“≥80”测试以及覆盖)。 如果找不到妨碍您前进的实体砖,则返回true ,表示“好,您可以移动到那里” 。 如果找到一个,则返回false ,表示“不,您不能移动到那里。”下面说明了两种情况。

让我们编写另一个便利函数,如果允许,它尝试移动一定的位移:
PLAYER_CR = {x = 2,y = 2,w = 5,h = 5}
函数 TryMoveBy(dx,dy)
如果 CanMoveEx(Plr.x + dx,Plr.y + dy,PLAYER_CR) 然后
Plr.x = Plr.x + dx
Plr.y = Plr.y + dy
返回真
结束
返回假
结束
现在,我们对move函数的实现更加简洁:首先确定我们要去的地方,然后检查是否被允许。 如果是,那就搬到那里。
函数 UpdatePlr()
-“在地面上”意味着“我们不能向下移动”
Plr.grounded = not CanMove(Plr.x,Ply.y +1)
如果没有接地
-如果不在地面上,请跌倒。
Plr.y = Plr.y + 1
结束
本地 dx = btn(2) 和 -1 或 (btn(3) 和 1或0)
局部 dy = 0-我们稍后将实现跳转
TryMoveBy(dx,dy)
结束
到了那里,我们有了意识障碍的运动。 稍后,当我们添加实体(移动平台等)时,我们将不得不对其进行复杂化以检查与实体的碰撞,但是原理是相同的。
熊猫动画
如果我们始终使用相同的Sprite(#257),则游戏将很无聊,因为熊猫将始终处于该站立姿势。 相反,我们希望熊猫走路/跳跃/攻击等。因此,我们必须根据玩家的状态来改变精灵。 为了更容易引用精灵编号,让我们声明一些常量:
-S是一张包含所有精灵的表格
S = {
-S.PLR是只有玩家精灵的桌子
PLR = {
STAND = 257,
WALK1 = 258,WALK2 = 259,JUMP = 273,SWING = 276,
SWING_C = 260,HIT = 277,HIT_C = 278,DIE = 274,
SWIM1 = 267,SWIM2 = 268,
}
}
这些对应于我们在Spritesheet中的几个Panda Sprite:

因此,在渲染功能中,我们决定要使用哪个精灵。 这是RendPlr()函数,您将在其中找到以下内容:
本地间谍
如果 Plr.grounded 则
如果 btn (2) 或btn (3)
spid = S.PLR.WALK1 + time()%2
其他
spid = S.PLR.STAND
结束
其他
spid = S.PLR.JUMP
结束
...
spr(spid,Plr.x,Plr.y)
从本质上讲,这意味着:如果玩家在稳固的地面上行走,则进行行走动画交替显示精灵S.PLR.WALK1和S.PLR.WALK2。 否则,如果在坚实的地面上而不是步行,请使用S.PLR.STAND。 如果不在坚固的地面上(跌落或跳跃),请使用S.PLR.JUMP。
还有另外一种逻辑,可以弄清楚玩家面对哪种方式并进行动画序列(如攻击序列或跳跃序列),并添加一些Sprite叠加层来表示加电。
跳跃的
人类有一个奇怪的期望:当我们跳入现实生活时,实际上我们几乎无能为力,无法改变跳跃中的轨迹,但是当我们玩平台游戏时,我们期望(实际上,我们要求 )角色能够做出在空中任意改变其跳跃轨迹。 因此,就像许多其他游戏主角一样,我们的熊猫也将具有在空中自由移动的抗物理能力。
实际上,这使实现跳转变得更加简单。 跳跃本质上是改变玩家Y坐标的序列。 X坐标由箭头键自由控制,就像玩家在地面上一样。
因此,我们通过“跳跃序列”表示跳跃的迭代:
JUMP_DY = {-3,-3,-3,-3,-2,-2,-2,-2,1,1,0,0,0,0,0}
随着玩家的跳跃,他们的Y位置在每一帧上按顺序显示的数量变化。 跟踪我们在跳转序列中的位置的变量是Plr.jmp 。
开始/结束跳转的逻辑大约是:
- 如果在坚实的地面上并按下跳转按钮( btn(4) ),则开始跳转(设置Plr.jmp = 1 )。
- 如果正在进行跳跃( Plr.jmp > 0),则继续进行跳跃,并尝试通过JUMP_DY [Plr.jmp]更改玩家的Y位置(如果允许的话)(根据CanMove()函数)。
- 如果在跳跃的某个时刻,玩家的动作受到阻碍( CanMove()返回false),则中断跳跃(设置Plr.jmp = 0并开始下降)。
由此产生的跳跃轨迹远非完美的抛物线,但足以满足我们的目的。 跳跃后的下降是一条直线,因为我们在下降的过程中并未实现加速。 我试着去,感觉很奇怪,所以我不加理会。 同样,具有1个像素/滴答滴答的下降速度,使我们可以在碰撞检测方面做一些技巧。

实体
磁贴很好,但它们是静态的。 跳过无生命的尘土只能有那么多兴奋。 为了使我们的平台游戏栩栩如生,我们需要一些敌人,力量等等。所有这些“移动或交互的事物”都称为实体。
首先,我画了一些可怕的敌人。 它们主要是由于图纸的质量而恐怖,而不是因为它们以任何方式都令人恐惧:

我只是将它们添加到Sprite工作表中,并为每个动画制作了动画。 我还建立了一个新的临界点:索引≥128的精灵表示实体 ,而不是静态图块。 因此,我可以在地图编辑器中将敌人添加到我的关卡中,如下所示,由于它们的精灵索引,我将知道他们是敌人:

同样,许多其他事物都是实体:箱子,碎块,基于时间的平台,电梯,入口等。
加载关卡时,我会检查地图上的每个图块。 如果≥128,则删除该地图图块并在其位置创建一个实体。 实体的ID(EID)表示它是什么。 我们将什么用作EID? 容易:我只是重复使用精灵编号! 因此,如果绿色史莱姆敌人是sprite#180,那么绿色史莱姆的EID就是180。
所有实体都存储在全局Ents结构中。
实体动画
实体可以设置动画。 我没有为每个特定的实体类型对动画进行硬编码,而是通过EID索引创建了一张大型动画表,该表指示要循环播放的精灵:
-每个EID的动画周期。
ANIM = {
[EID.EN.SLIME] = {S.EN.SLIME,295},
[EID.EN.DEMON] = {S.EN.DEMON,292},
[EID.EN.BAT] = {S.EN.BAT,296},
[EID.FIREBALL] = {S.FIREBALL,294},
[EID.FOOD.LEAF] = {S.FOOD.LEAF,288,289},
[EID.PFIRE] = {S.PFIRE,264},
...
}
请注意,当它们还与实体的子画面重合时,它们中的一些是符号常量(例如S.EN.DEMON),而一些则是硬编码的整数(292),因为第二个只是动画中的次要帧,我们永远不需要在其他地方引用。
渲染时,我们可以简单地在此表上查找正确的动画,并为每个实体渲染正确的精灵。
元标记:地图注释
有时我们需要在地图上添加一些注释,以便在运行时使用。 例如,如果有一个百宝箱,我们需要某种方式来表示它的内部以及其中有多少。 对于这些情况,我们使用地图注释标记,即编号为0-12的特殊图块,这些图块从不实际显示(它们在运行时从地图中删除):

当水平装载机看到箱子时,它会在箱子上方查看以了解箱子中的物品,并寻找指示要产生多少物品的特殊数字标记。 因此,当玩家敲击胸部时,所有物品都会生成:

元标记还可以帮助表示例如电梯必须在哪里启动和停止,玩家在关卡中的起始位置是什么,定时平台的相位信息等。
它们还用于电平压缩。 接下来我们将讨论。
等级
游戏有17个关卡。 它们存储在哪里? 好吧,如果您查看地图内存,将会看到以下内容:

TIC-80具有64个地图“页面”,每个页面都是内容的一个“屏幕”(30×17个图块)。 页面编号为0到63。
在我们的布局中,我们保留了前8个供运行时使用。 打开关卡后,我们便在这里存储了关卡(稍后将对此进行详细介绍)。 然后,每个级别是映射存储器中2或3页的序列。 我们也有教程屏幕,获胜屏幕,世界地图和标题屏幕的页面。 这是地图的带注释的版本:

如果您玩游戏,您可能会注意到水平实际上比2或3屏幕上显示的水平更长 。 但是在墨盒的地图内存中,它们要小得多。 这是怎么回事
您猜对了(我也宠坏了它):水平已压缩! 在地图内存中,每列的第一行都带有一个meta标签 ,该标签指示该列重复了多少次。 换句话说,我们实现了RLE压缩的一种简单形式:

因此,当该页面未压缩时,它实际上代表了该级别更长的部分(几乎是该级别的两倍,有时在某些级别上更大)。
这就是我们在运行时使用的前8个地图页面:当我们要玩关卡时,我们将其解压缩到0-7页进行游戏。 因此,这是开始一个级别的逻辑:
- 从地图内存中的正确位置读取打包的级别。
- 根据打包级别中每列的重复计数,将其解压缩到前8页。
- 查找实体(精灵≥128)并实例化它们。
- 查找播放器的起始位置(元标记“ A”),然后将播放器放在此处。
- 开始播放。
实体行为
是什么导致敌人以特定方式行事? 以游戏中的红色恶魔为例。 向玩家投掷火球的天生愿望来自何处? 他们为什么不能只是朋友?

每个实体的行为都不同。 恶魔投掷火球并跳跃。 绿色的粘液只是来回徘徊。 蓝色怪物定期跳跃。 当玩家离得足够近时,冰柱会掉落。 粉碎块崩溃。 电梯提升。 箱子只是放在箱子里,直到被玩家击中为止,然后箱子打开并产生物品。
编写所有这些逻辑的简单方法是:
如果敌人.eid == EID.DEMON 然后
ThrowFireballAtPlayer()
elseif敌人.eid == EID_SLIME 然后
-做其他事情
elseif敌人.eid == EID_ELEVATOR 然后
-做其他事情
-...依此类推,在每种情况下...
起初很容易。 但是随后事情开始变得复杂和重复。 以移动为例:当敌人移动时,我们必须做一堆检查以查看敌人是否可以移动到目标位置,我们必须检查它是否会掉落,等等。但是有些敌人不会秋天,它们飞(例如蝙蝠)。 还有一些游泳(鱼)。 有些敌人想要面对玩家,有些则不想。 一些敌人想要跟随玩家。 一些敌人只是继续前进,而没有意识到玩家在哪里。 弹丸呢? 火球,等离子球,雪球。 有些受到重力的影响,有些则没有。 有些与固体物体发生碰撞,有些则没有。 当这些都击中玩家时会发生什么? 当玩家击中它们时会发生什么? 这么多的变量和组合!
过了一段时间,为每种情况编写if / else块开始变得很麻烦。 因此,我们没有行为系统,而是行为系统,这是游戏开发中非常普遍且有用的模式。 首先,我们定义某些事物可能具有的所有可能行为以及必要的参数。 例如,它可以:
- 移动(多少?它如何处理悬崖边缘?当它撞到坚固的物体时会发生什么?)
- 秋季
- 更改方向(多久一次?始终面对玩家?)
- 跳(多久?跳多高?)
- 射击(射击什么?朝哪个方向?瞄准玩家?)
- 脆弱(受到玩家伤害)。
- 碰撞时受伤的球员
- 碰撞时崩溃(多长时间?)
- 在给定时间后自动销毁(多长时间?)
- 授予通电(哪个通电?)
- …等等…
在定义了所有可能的行为之后,我们只需将它们用正确的参数分配给正确的实体,即EBT(实体行为表)。 例如,这是红色恶魔的条目:
-实体行为表
EBT = {
...
[EID.EN.DEMON] = {
-行为:
beh = {BE.JUMP,BE.FALL,BE.SHOOT,
BE.HURT,BE.FACEPLR,BE.VULN},
-行为参数:
数据= {hp = 1,moveDen = 5,clr = 7,
aim = AIM.HORIZ,
ShootEid = EID.FIREBALL,
ShootSpr = S.EN.DEMON_THROW,
lootp = 60,
战利品= {EID.FOOD.C,EID.FOOD.D}},
},
...
}
这说明恶魔具有以下行为:跳跃,跌倒,射击,伤害,面对球员,易受攻击。 此外,参数表明它有1个生命点,每5跳动一次,底色为7(红色),发射火球(EID.FIREBALL),水平瞄准玩家(AIM.HORIZ),占60%战利品掉落的概率,可以掉落食物C和D(寿司)。 请注意,如何通过组合不同的行为,仅用几行就可以定义敌人的整个行为!
那那些背景山脉呢?
啊,您注意到了背景中的群山! 如果您在地图内存中查找,它们无处可寻。 它们也不会完全随播放器移动:它们具有视差效果,移动速度比前景慢,给人以为它们在背景中距离很远。
如何实现这种效果? 实际上,我们在运行时生成了山脉。 加载关卡时,我们会随机(但使用固定的随机种子)生成一个高程图,每个单元格具有一个值,并使其比该关卡大一点(总计约300个值左右)。
渲染场景时,我们使用玩家的位置来找出高程图的位移(将其除以常数以获得视差效果),然后使用精灵表中的山峰精灵来渲染山脉。 这是快速的,因为我们仅渲染在特定时刻可见的山脉(通过查看Game.scr并进行一些数学运算很容易确定)。
调色板替代
TIC-80的重要功能之一是它可以通过在RAM中插入一些内存地址来覆盖调色板。 这意味着我们每个级别都可以在开始时设置一个“调色板覆盖”:
函数 SetPal(重写)
对于 c = 0,15做
本地 clr = PAL [c]
如果覆盖并覆盖[c], 则
clr = overrides [E]
结束
戳 (0x3fc0 + c * 3 + 0,(clr >> 16)&255)
戳 (0x3fc0 + c * 3 + 1,(clr >> 8)&255)
戳 (0x3fc0 + c * 3 + 2,clr&255)
结束
结束
RAM地址0x3fc0是TIC-80保留其调色板的区域的开始,因此我们只需要向该存储区写入字节来更改调色板。
世界地图
世界地图是该游戏的较晚版本,可以使玩家了解他们的进度。 它显示了分布在6个岛屿上的游戏的17个级别:

玩家可以使用箭头键四处移动,并通过按Z键输入级别。世界地图在两个地图页面中实现: 前景页面和背景页面。
后台页面(地图内存中的第62页)仅包含地图的静态部分:

在运行时,它被前景页面(页面#61)覆盖:

该页面指示级别。 “ 1”,“ 2”和“ 3”图块是关卡,游戏通过在图块周围寻找元标记(1–6)来知道它们属于哪个岛。在岛3(地图的右侧)上的“ 2”图块上,它注意到附近有一个“ 3”元标记,因此它知道它位于3–2级。
恢复已保存的游戏时,“ A”标记指示玩家的开始位置,“ B”标记指示每个孤岛的开始位置。
音效和音乐
我使用TIC-80的内置编辑器编写了所有声音效果和音乐。 对于音效,我希望能告诉您我有一个很好的创建方法,但是实际上我只是单击按钮并随机调整数字,直到获得所需的东西,我认为这是创建复古音效的合理方法。

对于背景音乐,我实际上阅读了一些有关如何作曲的知识,因为我之前没有做过任何作曲。 事实证明,只要具备非常基本的知识,您就可以将听起来似乎可以接受的东西组合在一起(或者至少可以像我那只可怕的熊猫图形在耳朵上可以接受的那样)。

我为此游戏撰写了8条音轨(TIC-80限制):
- 一级音乐A,用于1–5岛。
- 级别B的音乐,在1-5岛中使用。
- 电平结束(短)。
- 级别C,用于1–5岛。
- 世界地图音乐。
- 标题屏幕主题。
- 岛屿6的关卡音乐(最终2个关卡)
- 游戏主题结束(“ The End”屏幕)
结论
编写这款游戏非常有趣,对于TIC-80(Vadim Grigoruk)的创造者提出了这个出色的平台,我深表感谢。 我希望您喜欢玩游戏(如果您愿意,也可以使用源代码进行破解!),就像我喜欢制作它一样!
我期待着将来为TIC-80和类似游戏机编写更多游戏。