零基础开发 era 游戏 #17 图像处理

配置文件相关设置项#

使用的渲染方式#

详见「2.1. 描画インターフェース」。

描画インターフェース:GRAPHICS
  • WINAPI:不会混合 Alpha 通道(不支持透明度)。
  • Graphics:混合 Alpha 通道。
  • TextRenderer:混合 Alpha 通道。

除此之外,WINAPI 的「缩放」也会其他两种渲染方式有一些区别。

允许读取子目录下的图片文件#

从本家的 1823 版本开始,可以在 ./resources/* 的子文件夹中直接读取,不需要配置:

サブディレクトリを検索する:YES

但读取子目录下的 erb 文件还是必需的,总之启用检索子目录一般都没错。

本家用法#

  1. 在 era 游戏的项目根目录建立一个 ./resources/ 文件夹,将图片文件放入其中。
  2. 新建 .csv 文件按照规定的格式填写引入图片的参数。
  3. .erb 代码文件中直接使用引入图片sprite的名称。

加载常规格式图片#

原生支持 .bmp / .jpg / .png 三种图片格式。

对于 Emuera,图片文件不能直接读取,需要根据 *.csv 数据文件的描述进行加载。
用来索引的 csv 文件需要和对应的图片文件在同一个目录下。

索引文件名类似 ./resources/_index.csv./resources/二级目录/支持中文文件名.csv

; 注释
图像资源名, 图像文件名, x, y, width, height, posX, posY

动画资源名, ANIME, width, height
动画资源名, 图像文件名, x, y, width, height, posX, posY, delay
动画资源名, 图像文件名, x, y, width, height, posX, posY, delay

再强调一遍,文件名没多大所谓,但图片文件和引用它的索引文件一定要在同一目录下。

这里的 资源名 就是图像资源(sprite)名称,跟之后的 SPRITE 系命令是一样的性质。
所有的图像资源名 sprite name 都是唯一的。

PRINT_IMG "图像资源名"
HTML_PRINT "<img src='图像资源名'>"

详见 HTML_PRINT 命令的图片用法

指定的 x, y, width, height 是取目标图片文件的区域。
以左上角为起始点,从 x, y 座标开始,取宽 width、高 height 的区域。
可以省略,省略时默认为整幅图片。

指定的 posX, posY 是取到图像加载时放置的座标(即偏移量)。
(可以由下文会讲解的 SPRITEPOS / SPRITEMOVE 命令改变。)
可以省略,省略时默认为 0, 0(从左上角开始放置)。

仅限动图 / 动画(标记为 ANIME),指定的 delay 是帧延迟,单位为毫秒 ms
可以省略,省略时默认为 1000 ms(1 秒)。

注意 Emuera 默认情况下不会在等待输入的时候重新渲染,因此动画图像在特定帧中看起来会是静止的。
可以执行 SETANIMETIMER 命令,让动图在询问玩家输入期间也会重绘。

G(Graphics)系列命令#

使用 G 系命令必须将绘制方式设置为 GRAPHICSTEXTRENDERER
如果设置为 WINAPI 会报错。

创建图像

GCREATE int ID, int width, int height
GCREATEFROMFILE int ID, str filepath
  • GCREATE:创建一个空白的图像,宽度 / 高度自定义为 1 ~ 8192 之间的值。
    注意是真正的空白(void),不存在任何图层(完全透明),而不是「白板」。
  • GCREATEFROMFILE:直接指定一个格式受支持的图片文件,从现有文件创建。
    图像宽高自动取加载的图片文件的尺寸。

创建失败时返回 0

销毁图像

GDISPOSE int ID
  • GDISPOSE:销毁指定 ID 的 GDI 图像。

销毁失败时返回 0

清除图像

GCLEAR int ID, int cARGB
  • GCLEAR:清除指定 ID 的图像,可以视为用指定的颜色覆盖。
    颜色格式为 0xaarrggbb,代表 Alpha、Red、Green、Blue 四个通道的十六进制值。

处理失败时返回 0

绘制矩形

GFILLRECTANGLE int ID, int x, int y, int width, int height
  • GFILLRECTANGLE:在指定 ID 的图像上绘制矩形区域。

颜色可以用 GSETBRUSH 自定义;如果没有提前设定,则默认为背景颜色:背景色:0,0,0
没错,我确定(反复测试过)是 背景颜色,而不是官方文档说的 文字颜色 文字色

描画の色は GSETBRUSH 命令によってあらかじめ指定しなければ Emuera コンフィグの文字色で描画されます。
会写 文档 就多写点。

叠加图像

万众期待的「叠图」功能,可谓重点中的重点。

GDRAWG int destID, int srcID, int destX, int destY, int destWidth, int destHeight, int srcX, int srcY, int srcWidth, int srcHeight
GDRAWG int destID, int srcID, int destX, int destY, int destWidth, int destHeight, int srcX, int srcY, int srcWidth, int srcHeight, var cm

对于愿意阅读本文的大部分人来说,最需要的其实就这一个功能而已:
把一张图像(可以先裁剪)按照指定的尺寸 / 位置,叠到另一张图片之上。

用途就多了去了,比如最典型的「纸娃娃」系统,或者自行实现 tilemap 系统。

具体用法是 GDRAWG 命令后面依次指定:

  1. 目标图像 ID
  2. 源图像 ID
  3. 目标图像参数(起始点座标、缩放后宽度、缩放后高度)
  4. 源图像参数(起始点座标、裁剪宽度、裁剪高度)
  5. (可选)指定一个至少 5 × 5 大小的二维数值数组作为颜色矩阵Color Matrix应用

注意是将 源图像 叠到 目标图像 上,然后使用 目标图像,不要弄反了。

一般来说,源图像是没有变化的。
唯一的例外是「源图像和目标图像是同一个图像(ID 相同)」。
没错,你真的可以这样做。多循环几次可以达到套娃的效果。

初心者可以忽略最后一个参数 var cm(颜色矩阵)。
简单来说可以理解为「滤镜」,或者用游戏开发术语叫做「着色器非常原始的」。

使用蒙版

GDRAWGWITHMASK int destID, int srcID, int maskID, int destX, int destY

源图像 叠加 蒙版图像 后叠加到 目标图像 的指定位置上。
因此 源图像蒙版图像 的尺寸必须一致,并且要小于 目标图像 的尺寸。

结合 文档源码 共同理解。
应该是将「蒙版图像 的蓝色通道」作为「源图像 的不透明度」。

  • 当每一个像素的蓝色通道都为 ff 时,源图像 完全不透明,就像没有蒙版一样。
  • 当每一个像素的蓝色通道都为 00 时,源图像 完全透明,就像没有 源图像 一样。

注意这个绘制过程完全由 CPU 单线程处理,不会调用 GPU,不要对运行效率抱有期待。

随手写的测试用例,仅供参考:

@SYSTEM_TITLE
    #LOCALSIZE 1
    #DIM CONST img_size = 1000
    #DIM mask = 0x010000ff
    GCREATE 0, 200, 200
    GCREATE 1, 100, 100
    GCREATE 2, 100, 100
    WHILE 1
        CLEARLINE LINECOUNT
        GCLEAR 0, 0xff888888
        GCLEAR 1, 0xffffffff
        GCLEAR 2, mask
        GDRAWGWITHMASK 0, 1, 2, 50, 50
        SPRITECREATE "图图", 0
        HTML_PRINT @"<img src='图图' height='{img_size}'>"
        REPEAT img_size / 100
            PRINTL
        REND
        PRINTFORML 当前蒙版颜色为 0x%TOSTR(mask, "x8")%
        PRINTL
        PRINTL 请输入新的蒙版颜色(手动输入不需要加 0x 前缀):
        PRINTBUTTON "0x00000000", "零"
        PRINT_SPACE 100
        PRINTBUTTON "0x000000ff", "000000ff"
        PRINT_SPACE 100
        PRINTBUTTON "0xff0000ff", "ff0000ff"
        PRINT_SPACE 100
        PRINTBUTTON "0xffffffff", "ffffffff"
        $wait_mask_input
        INPUTS
        SELECTCASE RESULTS
            CASE ""
                CONTINUE
            CASE "零"
                mask = 0
            CASEELSE
                LOCAL = TOINT("0x" + RESULTS)
                IF LOCAL
                    IF LOCAL <= 0xffffffff
                        mask = LOCAL
                    ELSE
                        PRINT 输入的值超出上限,请重新输入
                        GOTO wait_mask_input
                    ENDIF
                ELSE
                    PRINT 格式错误,请重新输入十六进制数值(如 ffffffff)
                    GOTO wait_mask_input
                ENDIF
        ENDSELECT
    WEND
    QUIT

从 sprite(雪碧图)导入

这里先解释一下「gid 图像」和「sprite 图像」之间的区别:

  • 能被 PRINT_IMGHTML_PRINT 直接调用的是 sprite 图像,由 sprite_name 标识。
  • 能被 Emuera 在内部进行 图像后期 处理的是 gid 图像,由 gid 标识。
GDRAWSPRITE int ID, str sprName
GDRAWSPRITE int ID, str sprName, int destX, int destY
GDRAWSPRITE int ID, str sprName, int destX, int destY, int destWidth, int destHeight
GDRAWSPRITE int ID, str sprName, int destX, int destY, int destWidth, int destHeight, var cm

一般来说,sprite 是根据图片文件和 .csv 索引加载的。
如果想要对其进一步操作,就需要导入为 gid 图像,利用 G 系命令进行后期处理。

具体用法是 GDRAWSPRITE 命令后面依次指定:

  1. 目标 gid 图像的 ID
  2. 源 sprite 图像的 Name
  3. (可选)目标图像被覆盖区域的起始点座标
  4. (可选)目标图像被覆盖区域的宽度、高度
  5. (可选)指定一个至少 5 × 5 大小的二维数值数组作为颜色矩阵Color Matrix应用

设置图像参数

GSETCOLOR int ID, int cARGB, int x, int y

将指定座标的像素设置为指定颜色。(注意效率堪忧)

GSETFONT int ID, str fontName, int fontSize

设置目标图像的默认字体,会一直保存到该图像从内存中销毁(GDISPOSE)。
这个设置在本家 / 私家改造版(截至 1824 版本)里没有用处。
但 EE+EM 改版里有用,详见下文。

GSETBRUSH int ID, int cARGB

设置目标图像的默认笔刷颜色,会一直保存到该图像从内存中销毁(GDISPOSE)。
这个颜色会被 GFILLRECTANGLE(以及 EE+EM 的 GDRAWTEXT)应用。

GSETPEN int ID, int cARGB, int penWidth

设置目标图像的默认画笔颜色,会一直保存到该图像从内存中销毁(GDISPOSE)。
这个颜色会被 GDRAWRECTANGLE(该函数存在于源码中,但并没有导出)应用。
截至目前为止,这个命令在本家 / 私家改造版(1824 版本)里均没有用处。

查询图像参数

GCREATED int ID

查询指定 ID 的图像是否存在。

GWIDTH int ID
GHEIGHT int ID

查询指定 ID 的图像尺寸。

GGETCOLOR int ID, int x, int y

查询指定 ID 的图像指定位置的像素颜色。
预期的返回值为 8 位十六进制数值 0xaarrggbb 的整型形式(0 ~ 4,294,967,295)。
(可以通过 TOSTR(GGETCOLOR(gid, x, y), "x8") 格式化。)
当指定 ID 的图像不存在,或指定的座标超出边界时,返回 -1

文件 I/O

GSAVE int ID, int fileNo

将指定 ID 的图像保存为图片文件。
比如 GSAVE 1, 9527 就是将 gid 为 1 的图像保存到 ./sav/img9527.png

这里的参数 fileNo 实测为 int32 类型(带符号位的 long)。
所以最大值为 21474836472^31 - 1),配合时间格式最多只能 mmddHHMM

GSAVE 图像gid, TOINT(SUBstrU(TOSTR(GETTIME()), 4, 8))
GLOAD int ID, int fileNo

从现有的图片文件读取为指定 ID 的图像(加载到内存中),行为类似 GCREATEFROMFILE
只是图片文件的来源不同而已,一个来自 ./sav/,一个来自 ./resources/

官方文档 错拼成 CREATEFORMFILE 是真滴离谱,手挺快的哈。

SPRITE(雪碧图)系列命令#

所谓的 sprite,常被译为雪碧图 / 图像精灵。
总之就是泛指「一个游戏里可以直接渲染、显示给玩家的图像资源」。

导出为 sprite

之前讲过了 gid 图像和 sprite 图像之间的区别,总之就是:

  • gid 图像可以进行后期处理,但不能直接输出。
  • sprite 图像不能直接进行处理,但可以直接输出(PRINT_IMG / HTML_PRINT)。

所以需要使用 GDRAWSPRITESPRITECREATE 命令根据实际需求互相转化:

SPRITECREATE str spriteName, int gID
SPRITECREATE str spriteName, int gID, int x, int y, int width, int height

可以使用相关参数(座标 + 尺寸)裁剪需要的 sprite 图像的指定区域。

注意 SPRITECREATE 创建的 sprite 图像只是对 gid 图像的「引用」。
如果你使用同一个 gid 创建了多个 sprite:

  • 修改 gid 图像,所有的 sprite 都会随之改变。
  • 删除 gid 图像,所有的 sprite 都会变为一张透明图像。

动画 sprite

Emuera 的图片原生支持动画是怎么回事呢?Emuera 的图片相信大家都很熟悉,但是 Emuera 的图片原生支持动画是怎么回事呢,下面就让小编带大家一起了解吧。
Emuera 的图片原生支持动画,其实就是可以实现动图,大家可能会很惊讶 Emuera 的图片怎么会原生支持动画呢?但事实就是这样,小编也感到非常惊讶。
这就是关于 Emuera 的图片原生支持动画的事情了,大家有什么想法呢,欢迎在评论区告诉小编一起讨论哦!

SPRITEANIMECREATE str spriteName, int width, int height
SPRITEANIMEADDFRAME str spriteName, int gID, int x, int y, int width, int height, int offsetX, int offsetY, int delay

说是动画,其实就是手动编辑每一帧,然后 Emuera 可以循环连续播放。

SETANIMETIMER int time

设定重绘定时器间隔,单位为毫秒(ms)。

一般来说,Emuera 不会在等待输入(INPUT)期间重绘图像。
但你可以通过设置 SETANIMETIMER 来让图像在等待输入期间进行重绘。

由于性能原因,实际重绘的间隔会比预设的数值稍高一些。

如果「定时器间隔」大于或等于「帧延时」,会频繁发生跳帧现象。
比如帧延时 200ms 共 5 帧,定时器间隔长达 500ms,那么:

  1. 第一次绘制(0ms)第 1 帧(0ms ~ 200ms),正常
  2. 第二次绘制(500ms)第 3 帧(400ms ~ 600ms),第二帧被跳过了
  3. 第三次绘制(1000ms)第 1 帧(>= 1000ms),第四、五帧被跳过了

此处本来有一个甘特图演示,但 mermaid.js 并不支持秒级的粒度,毫秒级更不用说。

所以推荐 SETANIMETIMER 设置的间隔时间小于 SPRITEANIMEADDFRAME 设置的帧延时。

注意:使用超时处理(TINPUT 等命令)时此命令无法生效。
合着全 Emuera 共用这一个定时器是吧。

此外,动图的播放重绘跟设置里的 フレーム毎秒 没有关系,同样也不受 REDRAW 命令影响。

@SYSTEM_TITLE
    #LOCALSIZE 1
    #DIM CONST img_size = 200
    #DIM CONST delay = 200
    {
    #DIM CONST gid = 1001, 1002, 1003, 1004, 1005, 1006,
                     1007, 1008, 1009, 1010, 1011, 1012
    }
    ;#DIM CONST gary = 0xff8e8e93, 0xff636366, 0xff48484a, 0xff3a3a3c, 0xff2c2c2e, 0xff1c1c1e
    {
    #DIM CONST color = 0xffff453a, 0xffff9f0a, 0xffffd60a, 0xff30d158,
                       0xff66d4cf, 0xff40c8e0, 0xff64d2ff, 0xff0a84ff,
                       0xff5e5ce6, 0xffbf5af2, 0xffff375f, 0xffac8e68
    }
    REDRAW 0
    PRINTFORML 此时 CURRENTREDRAW()={CURRENTREDRAW()},即被动刷新画面
    SPRITEANIMECREATE "动图", img_size, img_size
    FOR LOCAL, 0, VARSIZE("color")
        GCREATE gid:LOCAL, img_size, img_size
        GCLEAR gid:LOCAL, color:LOCAL
        SPRITEANIMEADDFRAME "动图", gid:LOCAL, 0, 0, img_size, img_size, 0, 0, delay
    NEXT
    HTML_PRINT @"<img src='动图' height='1000'>"
    SETANIMETIMER delay / 2
    REPEAT 10
        PRINTL
    REND
    PRINTW 输入任意内容停止播放(自动重绘)动图
    QUIT

销毁 sprite

SPRITEDISPOSE str spriteName

销毁指定名称的 sprite 图像。

设置 sprite 参数

SPRITESETPOS str spriteName, int posX, int posY

设置指定 sprite 图像的偏移位置(以 (0, 0) 为原点的绝对座标)。

SPRITEMOVE str spriteName, int moveX, int moveY
; 相当于
SPRITESETPOS spriteName, SPRITEPOSX(spriteName) + moveX, SPRITEPOSY(spriteName) + moveY

设置指定 sprite 图像的偏移位置(根据当前的位置相对计算座标)。

查询 sprite 参数

SPRITECREATED str spriteName

查询指定 sprite 图像是否存在。

SPRITEWIDTH str spriteName
SPRITEHEIGHT str spriteName

查询指定 sprite 图像尺寸。

SPRITEGETCOLOR str spriteName, int x, int y

查询指定 sprite 图像指定位置的像素颜色。
预期的返回值为 8 位十六进制数值 0xaarrggbb 的整型形式(0 ~ 4,294,967,295)。
当指定 sprite 图像不存在,或指定的座标超出边界时,返回 -1

SPRITEPOSX str spriteName
SPRITEPOSY str spriteName

查询指定 sprite 图像当前的相对位置。

CBG(Client Background)系命令#

以 CBG 开头的 Client Background Graphics 系列命令,是与客户端渲染区域的背景图像相关的指令。

绘制 CBG 图像

CBGSETG int ID, int x, int y, int zDepth
CBGSETSPRITE str spriteName, int x, int y, int zDepth

注意 CBG 系列命令的 (x, y) 位置计算逻辑非常奇怪。
当为 (0, 0) 时,目标图像 的「左下角」会与 渲染区域 的「左下角」对齐。
然后随着 x / y 增大,图片会从「左上角」向「右下角」偏移。

YZX

至于 Z 轴深度(zDepth),从「屏幕外部」往「屏幕内部(深处)」为正方向。
通常的文字显示相当于深度为 0,设置为 -1 时图像会覆盖在文字之上。

清除 CBG 图像

CBGCLEAR

清除所有 CBG 系列命令渲染的图像。

CBGREMOVERANGE int zMin, int zMax

清除深度在指定范围(zMin ~ zMax)内的 CBG 系列命令(CBGSETG / CBGSETSPRITE / CBGSETBUTTONSPRITE)渲染的图像。

这是一个非常有用的功能,因为取值范围是「包含边界在内的闭区间」。
也就是说,你可以像 CBGREMOVERANGE 42, 42 这样移除「指定深度」的 CBG 图像。
如此一来就可以实现基于图层分层显示的超大型叠图系统。

比如深度 1000 是背景、100 是环境、10 是人物、-1 是按钮……等等。
需要切换场景时,想删哪层都行(CBGREMOVERANGE),然后重绘新的场景就好了。

设置 CBG 按钮映射

CBGSETBMAPG int ID

将指定 ID 的 gid 图像设置为客户端渲染窗口区域的 CBG 按钮的「颜色映射」。
这里设置的「颜色映射」会影响 CBGSETBUTTONSPRITEINPUTMOUSEKEY 两个命令。

该图像的定位逻辑和 CBGSETG / CBGSETSPRITE 一样:
初始情况下((0, 0)),目标图像 的「左下角」会与 渲染区域 的「左下角」对齐。

被设定为颜色映射的那张图像默认不会显示。
但「鼠标指向的像素」正下方对应的颜色会被捕获到,这就是所谓「颜色映射」的含义。

具体来说就是:

  • CBGSETBUTTONSPRITE 的第一个参数:int button 按钮颜色映射值
  • INPUTMOUSEKEY 的第五个返回值:RESULT:4 鼠标点击时该位置的颜色映射值

会受到 CBGSETBMAPG 使用的那张图像的影响。

注意,如果想让颜色映射生效,某个部位使用的颜色必须为「完全不透明」。
即 α 通道(透明度通道)必须为 255(0xff)。
其他情况,不管是 完全透明 还是任何程度的 半透明 都会被视为 没有颜色映射

这是 AWT 的,主要界面的,用于 CBG 按钮组的,颜色映射的,图片文件:
(文件路径为 eraAWT\RESOURCES\qv_color_936_648_color.png

据我所知,AWT 是唯一一个大量使用 CBG 系列命令绘制图像作为演出方式的 era 游戏。
而上图就是 AWT 中最重要的一张颜色映射图。

这张图最天才的地方在于中间的「渐变色」菱形方块网格(仔细观察,它是一张网格图)。
这意味着,你可以通过识别点击的「映射颜色」的范围,进而判断具体是哪个按钮。

哪里天才了?天才在于这巧妙地规避了「按钮范围判定」的复杂问题。
不用你计算(斜向的)座标,你只需要直接看鼠标指向的颜色落在哪个范围里就行了。

在「游戏开发技艺」这个方向上,我愿称之为最强 era 游戏。

理论上,使用同样的原理,你可以轻松地制作基于「六边形网格」的战旗类游戏。

重置 CBG 按钮映射

CBGREMOVEBMAP

清除 CBGSETBMAPG 命令设定的 CBG 按钮颜色映射。

绘制 CBG 按钮

类似「直接输入值」之于

PRINTBUTTON 按钮文本, 按钮值
HTML_PRINT "<button value='按钮值'>按钮文本</button>"

其实你不需要「打印 出一个可以用鼠标点击的按钮」;
只要你有 INPUT / INPUTS 等待输入,就可以直接用键盘手动输入。

同样的道理,并不是必须使用 CBGSETBUTTONSPRITE 打印出 CBG 按钮才行。
只要你 CBGSETBMAPG 设置过按钮颜色映射,INPUTMOUSEKEY 就已经可以捕获到输入了。

我们绘制 CBG 按钮只是为了更友好的人机交互(提升用户体验)。

不知道你们拆过薄膜键盘没有,或者 FC(红白机)时代的手柄。
颜色映射 CBGSETBMAPG 就是那层薄膜。理论上你不需要「键帽」,也可以让按下薄膜上的按键,让输入生效;但有了「键帽」,操作(按下按键)起来才会舒服得多。
这里的「键帽」就是 CBGSETBUTTONSPRITE 绘制的 CBG 按钮。

CBGSETBUTTONSPRITE int button, str spriteName, str spriteNameB, int x, int y, int zDepth
CBGSETBUTTONSPRITE int button, str spriteName, str spriteNameB, int x, int y, int zDepth, str tooltipMessage

具体用法是 CBGSETBUTTONSPRITE 命令后面依次指定:

  1. 按钮颜色映射值
  2. 按钮使用的 sprite 图像
  3. 鼠标指向按钮(悬浮)时,使用的 sprite 图像
  4. 相对原点(再强调一遍,初始原点是图像和渲染区域左下角对齐时的位置)偏移的座标
  5. 按钮显示深度(数字越大越深,越位于「底层」)
  6. (可选)鼠标指向按钮(悬浮)时,弹出的 tooltip 文本提示信息

着重说一下第一个参数:数值类型的所谓 button
前面反复讲过很多次了,实际输入判定跟「CBG 按钮」本身没有双向的强关联。
那这个参数——「按钮颜色映射值」有什么用呢?其实它是用来检测玩家的鼠标状态的。

通过 INPUTMOUSEKEY,我们可以检测到玩家的鼠标位置(RESULT:2, RESULT:3)。
加上 CBGSETBMAPG,我们可以检测到玩家的鼠标位置指向的颜色(RESULT:4)。
此时可以按下的「按钮」就已经生效了,玩家点得到,但是看不到。
所以我们要 CBGSETBUTTONSPRITE 给玩家画出来。按钮在哪里?按钮在这里。

光是画出来还不够,我们最好增加一些动态反馈,告诉玩家:
你指向的这个区域是一个按钮哦,可以点击哦。
如何实现呢?通过设置「按钮颜色映射值」,赋予 CBG 按钮一个颜色映射。
INPUTMOUSEKEY 获取到的 RESULT:4 等于这个颜色映射值时,说明:
玩家的鼠标指向了这个按钮。
然后可以把按钮使用的图像换成另外一张图,或是弹出 tooltip 悬浮提示文本。

你可以 CBGSETBUTTONSPRITE 设置多个 CBG 按钮,且位于不同位置,都指向同一个颜色。
这样所有对应那个颜色映射值的 CBG 按钮都会和 CBGSETBMAPG 指定的映射颜色联动。
总之就是一句话:CBG 按钮只是「键帽」而已,只有外观功能。

最后,CBG 按钮的显示逻辑也是和 CBGSETG / CBGSETSPRITE 一样:
初始情况下((0, 0)),目标图像 的「左下角」会与 渲染区域 的「左下角」对齐。

@SYSTEM_TITLE
    #LOCALSIZE 2
    #DIM CONST button_size, 2 = 100, 141  ; 按钮尺寸
    #DIM CONST button_gap = 20            ; 按钮之间的间隔
    #DIM CONST button_hover_offset = 40   ; 鼠标覆盖按钮时的偏移量
    #DIM CONST z_depth = -1               ; 按钮深度,-1 为覆盖文本
    #DIM CONST buttons_map_gid = 12345    ; 颜色映射图像的 gid
    #DIM CONST buttons_map_color, 3 = 0xff0000, 0x00ff00, 0x0000ff
    #DIMS CONST buttons_tooltip, 3 = "红色按钮\n这是一个红色按钮", "绿色按钮\n这是一个绿色按钮", "蓝色按钮\n这是一个蓝色按钮"
    ; 多维数组不能在定义时直接赋初值,之后手动初始化
    #DIM buttons_gid, 3, 2
    #DIM buttons_pos, 3, 2
    #DIM buttons_echo_color, 3, 2

    ; 初始化按钮图像的 gid
    FOR LOCAL:0, 0, VARSIZE("buttons_gid")
        FOR LOCAL:1, 0, 2
            buttons_gid:(LOCAL:0):(LOCAL:1) = 1000 + LOCAL:0 * 10 + LOCAL:1
        NEXT
    NEXT

    ; 初始化 CBG 按钮位置座标
    buttons_pos:1:0 = (CLIENTWIDTH() - button_size:0) / 2
    buttons_pos:1:1 = CLIENTHEIGHT() - button_size:1
    buttons_pos:0:0 = buttons_pos:1:0 - button_gap - button_size:0
    buttons_pos:0:1 = buttons_pos:1:1
    buttons_pos:2:0 = buttons_pos:1:0 + button_gap + button_size:0
    buttons_pos:2:1 = buttons_pos:1:1

    ; 初始化 CBG 按钮颜色,随便用什么颜色都行
    ; 甚至实际应用中大多数时候都是使用图片,而不是纯色
    buttons_echo_color:0:0 = 0xffff453a
    buttons_echo_color:1:0 = 0xff30d158
    buttons_echo_color:2:0 = 0xff0a84ff
    buttons_echo_color:0:1 = 0xffff6961
    buttons_echo_color:1:1 = 0xff30db5b
    buttons_echo_color:2:1 = 0xff409cff

    ; 初始化 CBG 按钮颜色映射
    GCREATE buttons_map_gid, CLIENTWIDTH(), CLIENTHEIGHT()
    GCLEAR buttons_map_gid, 0xff333333  ; 也可以不使用默认颜色,点击空白区域会被忽略

    FOR COUNT, 0, VARSIZE("buttons_gid")
        ; 渲染 CBG 按钮颜色映射图像
        GSETBRUSH buttons_map_gid, buttons_map_color:COUNT | 0xff000000
        GFILLRECTANGLE buttons_map_gid, buttons_pos:COUNT:0, buttons_pos:COUNT:1, button_size:0, button_size:1

        ; 渲染 CBG 按钮本身的颜色
        FOR LOCAL, 0, 2
            GCREATE buttons_gid:COUNT:LOCAL, button_size:0, button_size:1
            GCLEAR buttons_gid:COUNT:LOCAL, buttons_echo_color:COUNT:LOCAL
        NEXT

        ; 将 CBG 按钮从 gid 导出到 sprite 备用
        SPRITECREATE @"button_{COUNT}", buttons_gid:COUNT:0
        SPRITECREATE @"button_{COUNT}_hover", buttons_gid:COUNT:1
        SPRITESETPOS @"button_{COUNT}_hover", 0, -button_hover_offset
    NEXT

    ; 绘制 CBG 按钮颜色映射图像,预览按钮位置
    CBGSETG buttons_map_gid, 0, 0, 1
    DRAWLINE
    PRINTL 这里是 CBG 按钮的颜色映射预览
    PRINTW 只展示一次,看清楚了再继续
    ; 删除 CBG 按钮颜色映射图像,的预览图
    CLEARLINE 2
    CBGREMOVERANGE 1, 1

    ; 正式进行 CBG 按钮颜色映射
    CBGSETBMAPG buttons_map_gid

    ; 绘制 CBG 按钮
    FOR COUNT, 0, VARSIZE("buttons_gid")
        CBGSETBUTTONSPRITE buttons_map_color:COUNT, @"button_{COUNT}", @"button_{COUNT}_hover", buttons_pos:COUNT:0, 0, z_depth, buttons_tooltip:COUNT
    NEXT

    ; 输入检测
    WHILE 1
        INPUTMOUSEKEY
        IF RESULT:0 == 1 && RESULT:1 == 0x100000 && RESULT:4 != -1
            SELECTCASE RESULT:4
                CASE buttons_map_color:0
                    SETCOLOR buttons_map_color:0
                    PRINTL 点击了红色按钮
                    RESETCOLOR
                CASE buttons_map_color:1
                    SETCOLOR buttons_map_color:1
                    PRINTL 点击了绿色按钮
                    RESETCOLOR
                CASE buttons_map_color:2
                    SETCOLOR buttons_map_color:2
                    PRINTL 点击了蓝色按钮
                    RESETCOLOR
                CASEELSE
                    PRINTFORML 检测到未捕获的鼠标点击: 0x%TOSTR(RESULT:4, "x6")%
            ENDSELECT
        ENDIF
    WEND
    QUIT

清除 CBG 按钮

CBGCLEARBUTTON

清除 CBGSETBUTTONSPRITE 命令设定的图片按钮。

EE+EM 扩展功能#

强力安利这个 EM+EE 改进版。
这是目前功能最强大、设计最先进、更新最勤奋的 Emuera 版本,没有之一。
大家快来用啊。
两位作者都在我们的 Discord 里,有任何问题随时可以反馈。

支持 WebP 格式图片#

通过 ImageProcessor + libwebp 实现。
编译后的动态链接库就是随包附带的 libwebp.dll

绘制文字#

在图像中绘制文本

GDRAWTEXT int gid, str text(, int x, int y)

在指定 ID 的图像上绘制文本,可以进一步指定位置。

查询图像绘制文本使用的参数

字体名称
GGETFONT int gid

返回指定 ID 的图像当前使用的字体名称(由 GSETFONT 设定)。

字体尺寸
GGETFONTSIZE int gid

返回指定 ID 的图像当前使用的字体尺寸(由 GSETFONT 设定)。

字体样式
GGETFONTSTYLE int gid

返回指定 ID 的图像当前使用的字体样式标记(由 GSETFONT 设定)。
注意 GSETFONT 可以使用第四个参数设置「字体样式」是 EE+EM 新增的功能。

推荐在头文件里定义可读性更好的常量:

#DIM CONST FONT_B = 1p0  ; 1 粗体 Bold
#DIM CONST FONT_I = 1p1  ; 2 斜体 Italic
#DIM CONST FONT_S = 1p2  ; 4 删除线 Strikethrough
#DIM CONST FONT_U = 1p3  ; 8 下划线 Underlined

判定可以:

@SYSTEM_TITLE
    GCREATE 0, 100, 100
    GSETFONT 0, "Arial", 100, FONT_B|FONT_U

    GCREATE 1, 100, 100
    GSETFONT 1, "MS ゴシック", 100, FONT_I|FONT_S

    CALL PRINT_FONT_STYLE(GGETFONTSTYLE(0))
    CALL PRINT_FONT_STYLE(GGETFONTSTYLE(1))
    QUIT


@PRINT_FONT_STYLE(font_style)
    #LOCALSSIZE 1
    #DIM font_style
    LOCALS '= GETBIT(font_style, 0) ? "加粗" # "未加粗"
    LOCALS += " "
    LOCALS += GETBIT(font_style, 1) ? "是斜体" # "不是斜体"
    LOCALS += " "
    LOCALS += GETBIT(font_style, 2) ? "有删除线" # "无删除线"
    LOCALS += " "
    LOCALS += GETBIT(font_style, 3) ? "有下划线" # "无下划线"
    PRINTSL LOCALS

预期结果:

加粗 不是斜体 无删除线 有下划线
未加粗 是斜体 有删除线 无下划线
预测文本尺寸
GGETTEXTSIZE str text, str fontName, int fontSize(, int fontStyle)
@SYSTEM_TITLE
    GGETTEXTSIZE "有一说一", "Arial", 150
    PRINTFORML Width:{RESULT:0} Height:{RESULT:1}

    GGETTEXTSIZE "Indeed", "Arial", 150
    PRINTFORML Width:{RESULT:0} Height:{RESULT:1}

    GGETTEXTSIZE "有一说一", "MS Pゴシック", 150
    PRINTFORML Width:{RESULT:0} Height:{RESULT:1}

    GGETTEXTSIZE "Indeed", "MS Pゴシック", 150
    PRINTFORML Width:{RESULT:0} Height:{RESULT:1}

    QUIT

预期结果:

Width:660 Height:172
Width:458 Height:167
Width:600 Height:150
Width:410 Height:150

旋转图片#

GDRAWGWITHROTATE int destID, int srcID, int angle(, int x, int y)

可选参数 (x, y) 是指定旋转的中心点座标,省略时默认为 (x/2, y/2) 图像正中心。

@SYSTEM_TITLE
    #DIM CONST grid_count = 8   ; 网格数量
    #DIM CONST grid_size = 400  ; 网格大小
    #DIM CONST canvas_gid = 123
    #DIM step

    GCREATE canvas_gid, grid_count * grid_size, grid_size
    REPEAT grid_count
        GCREATE COUNT, grid_size, grid_size
    REND
    GSETFONT 0, "Arial", grid_size / 4
    GDRAWTEXT 0, "Emuera", grid_size / 8, grid_size * 3 / 8

    step = 360 / grid_count
    REPEAT grid_count
        SIF COUNT > 0
            GDRAWGWITHROTATE COUNT, 0, step * COUNT
        GDRAWG canvas_gid, COUNT, COUNT * grid_size, 0, grid_size, grid_size, 0, 0, grid_size, grid_size
    REND

    SPRITECREATE @"rotated_image", canvas_gid
    HTML_PRINT @"<img src='rotated_image' width='{grid_count * grid_size}' height='{grid_size}'>"
    REPEAT grid_size / 100
        PRINTL
    REND
    QUIT

lackbfun © 2021 - 2024