到Poom的旅程(Pico8 Doom)

2021-05-09 06:58:39

由于一个(仍未发布的)项目,一个突击席位 - 一场攻击 - 我曾经像孩子一样玩的游戏(双棍!出色的音乐!超低低音爆炸!)。

2020年初,为我的丝般流畅的旋转孔发动机骄傲完全在记忆中运行,远低于50%的CPU与敌方单位。

引擎看起来不错,是时候导出到HTML和演示它。我在我的家用电脑和手机上运行了几次测试......

它没有顺利,表现遍布各地,需要一个强大的PC以全速运行。

向Zep(Joseph White,Pico8作者)报告错误(?),显而易见的是,游戏在二进制操作上依赖太多并删除了Web播放器。模拟API成本的微妙平衡并没有考虑到这么多"低级"每帧的操作。

Poke4( mem, BOR( BOR( BOR(ROTR(频带(SHL(MX(频带(MX,0xFFFF), 频段(LSHR(SRCY,16),0x0.FFFF))],SHL(频段(SRCX,7),2)),0xF000),28), rotr(频段(shl(mx(bor(mx + mdx1,0xffff), 频段(LSHR(SRCY-DDY1,16),0x0.ffff))],SHL(带(SRCX + DDX1,7),2)),0xF000),24)), BOR(ROTR(频带(SHL(MX(MX + MDX2,0xFFFF), 频段(LSHR(SRCY-DDY2,16),0x0.ffff))],SHL(频段(SRCX + DDX2,7),2)),0xF000),20), ROTR(频段(SHL(M [BOR(BAND(MX + MDX3,0xFFFF), 频段(LSHR(SRCY-DDY3,16),0x0.FFFF))],SHL(带(SRCX + DDX3,7),2)),0xF000),16))) ), BOR( BOR(ROTR(频带(SHL(MX(频段)(mx + mdx4,0xffff), 频段(LSHR(SRCY-DDY4,16),0x0.ffff))],SHL(频段(SRCX + DDX4,7),2)),0xF000),12), rotr(频段(shl(mx(mx + mdx5,0xffff), 频段(LSHR(SRCY-DDY5,16),0x0.FFFF)),SHL(频段(SRCX + DDX5,7),2)),0xF000),8)), BOR(ROTR(频段(SHL(MX(频带(MX + MDX6,0xFFFF), 频段(LSHR(SRCY-DDY6,16),0x0.ffff))],SHL(带(SRCX + DDX6,7),2)),0xF000),4), 频段(SHL(M [BOR(BAND(MX + MDX7,0xFFFF), 频段(LSHR(SRCY-DDY7,16),0x0.FFFF))],SHL(频段(SRCX + DDX7,7),2)),0xF000)) ) )

长话短说,Pico8 0.2不久 - 二元运营商和Tline("纹理线")是一件事......

tline x0 y0 x1 y1 mx my [mdx mdy] [图层] 将纹理线从(x0,y0)绘制到(x1,y1),从地图采样颜色值。 半开玩笑,我告诉Zep关于我们将在任何时候都有的洪水克隆...... 如果说,我会挖掘自己有点进入二进制空间分区(BSP树)& 门户? 善良的事情接近(非常成功的)第30岁的比赛是文件和amp; 工具是顶级陷阱! 由便携性和amp驱动; 可扩展性,现在使用开放文本格式(UDMF)表示级别几何,从而节省了WAD结构的繁琐二元打开包装(在某种程度上......)并完美地快速黑客。 5月9日至10日:武装从埃里斯顿计数埃里斯顿的实时碰撞检测,为UDMF解析的Antlr和良好的Python我得到了我的第一个BSP编译器,并运行Pico8渲染器: 接下来的2周都花了挖掘了WAD结构(我想我现在知道一只Linef是什么......)并越过Zoom Wiki一百万次。

我的自定义编译器很快被删除,支持"官方" ZBSP编译器(没有什么比错误修复多年!),Python代码完全解码二进制WAD文件,支持复杂的几何,包括纹理墙壁&楼层:

由于纹理在内,发动机离开了"佳能"前后厄运渲染到前面。

无论如何,我真的从未想过前后Woudl工作 - 对于Pico8的编程更像是针对一个非常低端的GPU。

根据厄运标准,透视正确纹理&只要世界由平面墙和宽大的墙壁制成,遮荫为便宜的工作地板。

使用标准调色板交换完成快速深度遮荫(您的样子越远,它越暗)。深度信息索引渐变表,存储在Pico8 RAM中。它使完整的调色板交换速度比常规方法PAL()呼叫,节省核心墙壁和amp的宝贵循环;地板光栅化循环:

回到前面的渲染意味着失去自然"缩小"标准厄运水平扫描(参见黑书以进行广泛的解释!)和较大级别的性能影响有限。

我知道地震有同样的问题,并使用潜在可见的集(PVS)解决了它。在编译时生成的PVS是来自任何给定的子扇区的所有潜在可见的凸子扇区的集合(慢慢读取两次!)。

令人惊讶的是,如何产生适当的PVS,我发现的最值得注意的来源很少有文献:

PVS被编码为位域,存储为32bits块以保留控制中的内存使用(比Quake PV的RLE编码更简单):

- PVS(包装为位数) unpack_array(function() - 可见子扇区ID(16位) local id = unpack_variant() - 包装作为位域 PVS [ID \ 32] = BOR(PVS [ID \ 32],0x0.0001<(ID& 31)) 结尾)

更多地在将旋转精灵进入游戏引擎的情况下工作,这是明显的精灵房地产将成为一个重大痛点。供参考,单个士兵姿势正在吃几乎一半的可用空间。和#34;小"精灵......

我知道游戏将是多车,也就是说,存储可能不是我的主要关注点,Pico8有一个大型2MB的Lua RAM玩。

如果精灵可以适应内存,如何最好使用内置Pico8 Sproite缩放功能(读取,SSPR)?

如果我有某种快速内存分配器怎么办?上次循环使用的缓存是这种情况的良好设计模式,如果块的大小是固定的,则足够简单。

如果我拆分精灵略微分为小(但不是太小)块,比如16x16,从中获取他们的实际精灵位置和#34;虚拟内存"分配器?

7月15日,这确实是一种非常可行的方法 - Memcpy足够快,可以在飞行中换流所需的精灵瓷砖。精灵可以高达16瓦,例如16瓦。 64像素上升64像素,允许大型怪物等春天的原始尺寸的1/2。

虚拟精灵引擎(TM!)集成在游戏中 - 怪物可能不仅仅是模糊的像素化乱七八糟!

基于其半径,在每个子扇区都注册了演员。假设&#34的数量,使用基本插入排序对给定的子扇区上的多个演员进行排序。演员通常很低:

- 所有事物中的所有事物 对于东西,_成对(segs.things)做 本地x,y = thing [1],事情[2] 本地轴,AZ = M1 * X + M3 * Y + M4,M9 * x + M11 * Y + M12 - todo:考虑到半径 如果az> 8和az< 854和ax和az和-ax< az然后 - 默认值:在排序阵列结尾插入 本地w,thingi = 128 / az,#imest + 1 - 基本插入排序 对于我,在iPairs(事物)中的其他工作 如果(其他[1]> w)thingi =我打破了 结尾 - 透视缩放 添加(事物,{w,thing,63.5 + ax * w,63.5-(thing [3] + m8)* w},thingi) 结尾 结尾

请参阅凸面侧面的前正面渲染是如何删除GIF下面的一部分Cacodaemon。 Sprite呈现多次,最后渲染(在最近的子扇区中)修复了图像:

每次子部门注册演员(例如BSP叶子)也用于加速玩家/演员&演员/演员碰撞检测。

夏季时间,此时,我的目标很清楚:使发动机尽可能轻松地与艺术家合作,我' ll需要一个团队来实现视力。

每个"事情"运行它'自己的小状态机,可以参考精灵和呼叫游戏功能:

演员Zombieman:怪物3004 { 健康20. 半径20. 高度56. 速度8. 状态 { 产卵: poss一个10 a_look; 环形 看: poss a 8 poss b 8 a_chase; 环形 导弹: poss e 10 a_facetarget; poss f 8 a_firebullets(22.5,0,1,9," bulletpuff"); poss e 8. 去看 死亡: POSS H 5. poss i 5 // a_scream poss j 5 // a_noblocking poss k 5. poss l 60. 停止 } }

Python编译器支持有限的功能集,但足以支持关键的DOOM游戏元素(状态,动画,使用参数调用)。

运行时部分足够简单,以符合少于20行代码:

- VM更新 勾选=函数(self) 蜱虫!= - 1做 - 等待(+重置随机启动延迟) if(ticks> 0)滴答+ = delay-1 delay = 0返回true - 完成,下一步 if(ticks == 0)i + = 1 ::环形:: 地方州=州[i] - 停止(或VM指令的结尾) 如果(不是状态或extend.jmp == -1)del_thing(self)返回 - 循环或转到 如果(state.jmp)self:hump_to(state.jmp)goto循环 - 有效状态 self.state =状态 - 获得蜱虫 蜱=州[1] - 触发功能(如果有的话) - 提供所有者和自我(例如,武器) if(state.fn)state.fn(self.owner或self,self) 结尾 结尾

总之,游戏中的所有内容都使用相同的语法描述:武器,子弹,项目&怪物!

到8月,我有一个可以轻松安装的Python包。编译器支持许多关键的厄运功能(门,平台,怪物,Infigthing,多种武器,拾取,难度级别......)。

八月结束,我伸手去偏执狂仙人掌(X-Zero的名声和我的工作总粉丝!),让'等等...

偏执狂仙人掌(Simon Huleinga Irl)回复了几天后几天,似乎有兴趣 - 好消息!

这就是这样,西蒙是一个多级Gamevev,掌握代码,艺术,音乐&游戏(是的,生活是不公平的!)。

偏执狂仙人掌在游戏玩法方面取得了铅,我们都进入了挑战性目前的功能的例程,重新加工发动机支持在保持令牌的同时支持越来越多的细节。要命名一些:

代码进入大规模的重构,始终接近危险区域,始终找到挤压我们最后一个想法的新方法!

示例令牌优化技术,其中项目标识符,属性名称和解压缩功能被声明为大型文本块:

- 布局: - 财产掩码 - 属性类名称 - 房产解压缩功能 本地属性_factory = split(" 0x0.0001,Health,Unpack_variant,0x0.0002,Armor,Unpack_variant,0x0.0004,unpack_variant,0x0.0008,maxamount,Unpack_variant,0x0.0010,图标,Unpack_chr,0x0 .0020,slot,mpeek,0x0.0040,Ammouse,Unpack_variant,0x0.0080,速度,unpack_variant,0x0.0100,损坏,unpack_variant,0x0.0200,Ammotype,Unpack_ref,0x0.0800,Mass,Unpack_variant,0x0.1000 ,Pickupsound,Unpack_variant,0x0.2000,攻击,unpack_variant,0x0.4000,Hudcolor,0x0.8000,Deathsound,Unpack_variant,0x1,Meleerange,Unpack_variant,0x2,MaxtargetRange,Unpack_variant,0x4,Ammogive,Unpack_variant,0x8,Tradtype ,unpack_ref,0x10,拖动,unpack_fixed",",",1) - 属性:当前演员的属性位域 对于i = 1,#properties_factory,3做 如果属性_factory [i]&属性!= 0然后 - 解压缩&为演员分配价值 演员[属性_factory [i + 1]] = _ env [properties_factory [i + 2]](演员) 结尾 结尾

碰撞经历了多次重构,使世界感觉坚实 - 其中一个关键点。

我真的很惊讶地发现厄运使用不同的数据模型来处理碰撞(众所周知的BlockMap),当BSP将具有完美的匹配(黑书和amp; Carmack Notes确认这是一个错过的机会)。

Poom碰撞代码生成沿着光线遍历的Linefs列表,使用BSP按顺序遍历世界。

有一天,你有一个超级练习例程,第二天你得到了那个gif:

根本原因是BSP树松散的扇区空间关系,例如, 2墙可能在树的2个不同的一部分上,留下一种" GAP"按顺序处理碰撞时。

溶液是将每个壁视为胶囊(例如,带半径的射线),确保球播路径不能落入壁段之间。

在所有情况下,PVS计算也非常棘手。在失败时,整个部门最终会从屏幕上消失(被称为"黑色扇区"由我和西蒙)。

来自给定的子扇区(凸区): 迭代所有双面狐 寄存器连接的子扇区("其他子扇区和#34;) #查找所有反门户网站 对于来自其他子行业的所有双面Linefef: 如果其他leinef是前面或跨跨的电流leinef: 将leinef对注册为门户 #剪辑&找到可见的几何 虽然门户集不是空: 创建剪辑区域("反半影") 对于目的地门户的所有两侧: 如果侧面是背部或跨越: 剪辑段 如果有的话: 注册一个新的门户网站 标记子部门可见

在游戏Deviopement之前不支持压缩,我已经成为一个LZW编码器,用于攻击相当良好的攻击。

事情是,我很快意识到LZW解压缩是一个记忆猪,当然与已经伸展可用RAM的游戏不兼容......

Python中的压缩代码,解压缩代码足够小以适应令牌预算,最好的全部,固定内存帽!

压缩比率约为50%,在图片和世界数据上有足够的工作。

那个障碍是一个较大的一级播放层面回到后面会在2MB限制上提示游戏。

以下是我用来了解我是否正在讨论垃圾收集错误或依赖周期或其他一些错误的图表...... 大多数数据结构转换为阵列,代码令人享受,但内存用法返回安全区域 没有正确像素的Poom吸引力就没有 - 我们需要许多像素...... 左右:原始厄运Imp | 自动50%缩放+调色板转换| 手固定 9月至12月的最佳部分是重写选定的演员达到正确的质量水平。 Pico8游戏都是关于制作选择,即使使用如此大的游戏,也不是每个想法都可以制作它。 有趣& 游戏玩法主要是第一个,第二岁和#34;嘿,它适合" 我相信西蒙在播放游戏设计决策时,仍然试图让他用新的发动机特征,如透明纹理!

似乎显而易见,一旦你花了几个月在比赛上工作,有一双新鲜的眼睛是宝贵的。

向Tom Hall(厄运设计名人谱和居民Pico8 Discord成员)发出邀请,他们迅速提出了众多的生活质量调整&熟悉水平进步。

最值得注意的添加是鼠标支持(引用Tom"我想要鼠标支持"),接线"鼠标锁定"浏览器处理程序到Pico8 GPIO将坐标沟通回游戏。

向zep的转发当然有助于将鼠标锁作为天然pico8特征(在夜晚的小小时内提供的封面修补程序下!) - 谢谢!

Behond这项技术,主要带走的是,如此大的比赛将难以拔出没有团队合作,善良的话语&扩展PICO8社区的贡献(嘿@Farbs,@nucertide,@sam,@valeradhd ...!)。

游戏是我们最大的成功,迄今为止总计10k +下载,100k +网页课程,数百名评论......

阅读给了你一些新的游戏观念,想要返工精灵或试图改善发动机吗?不再犹豫,一个完整的吹嘘SDK就在那里 - 我们用来制作游戏的同一个工具! POOM SDK.