February 12, 2018

最近玩的几款游戏

最近和一位新同事一起在开发新的 3d engine 。还在构建基础的东西。从第一次提交到现在已经过去了 24 天,有 129 个 commits 。短期内还不太可能开源(即使开放仓库,估计也没几个人知道怎么构建出来)。提一句,只是说明这个项目正在进行中。

从 2017 年底到现在倒是玩到了不少非常不错的游戏。这些游戏没有用到什么华丽的技术,但它们都有一些能抓住玩家的不一样的东西。

阅读全文 "最近玩的几款游戏" »

February 07, 2018

向量库的一点改进

前段为 3d engine 写的向量运算库小伙伴在用,提了很多意见,所以这段时间一直在改进。

一开始觉得逆波兰表示法的运算表达式不太习惯,觉得需要绕个弯想问题,希望做一个表达式编译的东西,但是用了几天后,又觉得其实不是什么大问题,习惯了就好了。

但心智负担比较大的地方是那个 id 的正负号约定,也就是生命期管理。我想了一下,人为的去管理生命期,有些对象是要长期持有的,有些对象只在当前渲染帧使用,在使用的时候严格区分它们不太现实。

一开始的版本,我需要使用者在计算表达式中用一个 mark 'M' 指令,把一个临时对象转换成一个持久对象,这极大的增加了使用者的负担。尤其是更新一个对象的时候,需要先解除老对象的持久状态,再 mark 新生成的对象。使用的时候需要一直考虑这个对象是不是要更新,用起来太困难了。虽然有强检查,不会把程序弄混乱,但是稍不注意就会报告运行时错(对象 id 失效)。

今天,我做了极大的调整,去掉了之前 mark 语义,增加了引用语义。

阅读全文 "向量库的一点改进" »

January 31, 2018

lua 模块管理的一点改进

lua 从 5.2 开始,简化了 5.1 中的模块管理方式,然后一直保持到现在这个样子。

模块用 require 加载,同名模块在一个 vm 中只加载一次,第 2 次开始会返回上次加载的结果。加载模块时会利用 package.path 或 package.cpath 中定义的字符串模板,把模块名转换为文件名,依次尝试打开文件。

我在新项目中,由于整合了不少模块,感觉现有的这套机制有点点不够用。所以我做了一点点小改动,支持了类似 python 的模块管理那样的相对机制。当在一个模块中 require 另一个模块时,会先尝试加载相对路径上的模块,再尝试绝对路径。这样可以方便我们集成独立开始的模块,并放在独立的名字空间中。也方便给模块内置测试子模块。

例如,我独立开发了一个叫 foobar 的模块,它自己有一个子模块叫 foobar.baz ,在集成到系统中时,我希望把它们一起放在 common 名字空间下。使用的时候可以用 require "common.foobar" 来引用。

如果直接用 lua 原生的模块管理机制,我需要修改 foobar 主模块的代码,把里面的 require "foobar.baz" 改成 require "common.foobar.baz" 。同理,如果我不满意 foobar 这个名字,想换名也很麻烦。

阅读全文 "lua 模块管理的一点改进" »

January 21, 2018

提高 lua 处理向量运算性能的一点尝试

如果用纯 lua 来做向量/矩阵运算在性能要求很高的场合通常是不可接受的。但即使封装成 C 库,传统的方法也比较重。若把每个 vector 都封装为 userdata ,有效载荷很低。一个 float vector 4 ,本身只有 16 字节,而 userdata 本身需要额外 40 字节来维护;4 阶 float 矩阵也不过 64 字节。更不用说在向量运算过程中大量产生的临时对象所带来的 gc 负担了。

采用 lightuserdata 在内存额外开销方面会好一点点,但是生命期管理又会成为及其烦心的事。不像 C 中可以使用栈作临时储存,C++ 中有 RAII 。且使用 api 的时候也会变得比较繁琐。

我一度觉得在 lua 层面提供向量运算的基础模块是不是粒度太细了。曾经也想过许多方法来改善这方面。这两天实践了一下想了有一段时间的方案,感觉能初步满意。

阅读全文 "提高 lua 处理向量运算性能的一点尝试" »

January 12, 2018

通过斜切变换 2d sprite 提高装箱率

现代 2d 游戏的图形地层绝大多数也是基于 3d api 实现的。为了提高性能,通常要把若干图元 (sprite) 装箱在整张贴图中。这个装箱过程可以是在线下完成,也可以是在运行期来做。

TexturePacker 就是做这件事的优秀商业工具。不过我认为把它放在开发工具链中还有一些不足。图元的装箱和根据装箱结果合成贴图是两件事情,如果我们是手工操作,合在一起完成当然方便;但如果是在自动化流程中,分开独立完成更好。因为迭代开发过程中,每次重新打包资源,都只会修改少部分图元,且图元的大小未必会改变。如果大小不变,就不必重新做装箱运算。

如果部分修改图元,则合成贴图的过程有可能能减少运算过程。通常我们需要对最终的贴图做一次压缩,生成类似 ETC 的压缩贴图类型,这是极消耗 cpu 的。而 ETC 压缩格式是基于 4x4 的区块独立压缩,只要保证图元尺寸是 4 的倍数、就可以先压缩,再合成。这样,没有修改过的图元就可以不必重新运算,直接从文件 cache 中读回。

有些时候不合成、仅保存装箱结果更适用。可以在运行时根据 altas 数据把分离的图元装载在贴图中。分开打包独立的图元资源更适合游戏更新。

第二,在提高装箱利用率上面 TexturePacker 做了很多的努力。很多 sprite 的边角会有大量的空白,仅仅按轴对齐的四边形包围盒裁剪还是浪费太大。它的最新版本支持了多边形装箱、即尽可能的把边角都裁剪下来。这种做法的代价是增加了运行时的多边形数量(对 2d 游戏来说,通常不太重要),但让装箱边角余料可能多填一些小 sprite 进去。

但我认为其实还可以找到更多方法。

这篇 blog 就想谈谈最近我在为公司新的 2d 项目完善 ejoy2d 的工具链,编写装箱工具时,做的一些工作。

阅读全文 "通过斜切变换 2d sprite 提高装箱率" »

December 20, 2017

资源文件系统的设计

上次说到,我们的引擎打算在 PC 上开发,设备上直接调试。如果是按传统的开发方式:运行前将 app 打包上载然后再运行,肯定是无法满足开发需要的。所以必须建立一套资源的同步机制。

目前,我们已经实现了基本的资源文件系统,大致是这样工作的:

所有的资源文件,包括程序代码本身(基于 Lua),都是放在开发 PC 上的。开发环境会缓存每个文件的 md5 值,文件系统将用此 md5 值为标准,默认 md5 相同的文件,其内容也是一致的,暂不考虑 md5 冲突的问题。

在设备上,用设备的本地文件系统做一个 cache ,cache 中分为两个区间,一是资源文件区,按所有资源文件的 md5 值为文件名(按 md5 的 16 进制码的前三字节散列在不同子目录中,防止单个目录文件数量过多)保存。二是目录结构区,每个子目录用一个递增数字 id 做文件名,内容则是可读文件名以及其内容对应的 md5 值或子目录编号。其中 id 0 是资源根目录。

阅读全文 "资源文件系统的设计" »

December 19, 2017

BenQ WiT ScreenBar 试用记录

前段时间,BenQ 的同学送了盏屏幕灯给我,我看着不占桌面空间就收下了。

作为一个从小学开始就整天盯着屏幕的程序员,30 多年一直没有近视,应该和一贯重视用眼时的环境光有关。大部分时间我在晚上作业,所以我对灯光的要求挺高的,办公室装修时还特别关照灯光要足。但面对屏幕的时候还好,在要写写画画的时候,还是感觉光线不太够。一直没特别在办公桌上摆个台灯,主要还是嫌占空间。而且我的办公桌比较大,一般台灯也照不到整个桌面。

阅读全文 "BenQ WiT ScreenBar 试用记录" »

Misc

Categories

Archives

Recent Comments