零基础开发 era 游戏 #15 SAV 存档文件

配置文件相关设置项#

使用专门的 ./sav/ 文件夹保存#

详见「1.7. セーブデータを sav フォルダ内に作成する」。

セーブデータをsavフォルダ内に作成する:YES

存档栏位默认显示的数量#

详见「1.10. 使用するセーブデータ数」。

表示するセーブデータ数:20

这个值有一个专门的行内函数 SAVENOS() 来读取,有需要的话可以直接使用。

和直接读取配置文件 GETCONFIG("表示するセーブデータ数") 的结果是一样的。

PRINTFORML SAVENOS()={SAVENOS()}
PRINTFORML GETCONFIG("表示するセーブデータ数")={GETCONFIG("表示するセーブデータ数")}

存档文件以二进制形式存储#

详见「5.12. セーブデータをバイナリ形式で保存する」。

セーブデータをバイナリ形式で保存する:YES

UTF-8-BOM 编码格式保存#

详见「5.13. セーブデータを UTF-8 で保存する」。

セーブデータをUTF-8で保存する:YES

通用的公共存档 global.sav#

内置公共存档变量 Global.csv / Globals.csv#

这里定义的索引名类似其他 *.csv 文件那样,可以用来作为内置数组变量的别名。

编辑 ./csv/Global.csv

0,公共存档数值1
1,公共存档数值2
2,公共存档数值3

之后 GLOBAL:0 就等于 GLOBAL:公共存档数值1,以此类推。

编辑 ./csv/Globals.csv

0,公共存档字符串A
1,公共存档字符串B
2,公共存档字符串C

之后 GLOBALS:0 就等于 GLOBALS:公共存档字符串A,以此类推。

保存公共存档 SAVEGLOBAL#

编辑 ./erb/全局存档变量.erh

#DIMS GLOBAL SAVEDATA GS_全局存档字符串变量, 4 = "初始值1", "初始值2", "初始值3", "初始值4"

运行 SAVEGLOBAL 命令会将内置的 GLOBAL / GLOBALS 数组,以及带有 SAVEDATA 关键词定义的全局存档变量变量保存到通用的公共存档文件 global.sav 中。

读取公共存档 LOADGLOBAL#

编辑 ./erb/游戏业务逻辑.erb

@SYSTEM_TITLE
    WHILE 1
        DRAWLINE
        PRINTL 〇 由 CSV 文件定义索引名称 - Global.csv / Globals.csv
        PRINTPLAINFORM GLOBAL:1  = [{GLOBAL:公共存档数值2}] (%GLOBALNAME:1%)
        PRINTL
        PRINTPLAINFORM GLOBALS:2 = [%GLOBALS:公共存档字符串C%] (%GLOBALSNAME:2%)
        PRINTL
        PRINTL
        PRINTL 〇 由 ERH 文件定义变量 - 头文件.erh
        PRINTL GS_全局存档字符串变量
        REPEAT 4
            PRINTPLAINFORM [%GS_全局存档字符串变量:COUNT%]
            PRINTS " "
        REND
        PRINTL
        PRINTL
        PRINTBUTTON "[1] 保存", 1
        PRINT_SPACE 200
        PRINTBUTTON "[2] 加载", 2
        PRINT_SPACE 200
        PRINTBUTTON "[3] 更新", 3
        INPUT
        SELECTCASE RESULT
            CASE 1
                SAVEGLOBAL
                PRINTL 已保存到 global.sav
            CASE 2
                LOADGLOBAL
                PRINTL 已从 global.sav 加载数据
            CASEELSE
                ; 将公共存档变量更新为当前时间
                GLOBAL:公共存档数值2 = GETTIME()
                GLOBALS:公共存档字符串C '= @"%GETTIMES()% 随机={RAND(1000, 10000)}"
                GS_全局存档字符串变量:0 = 我爱你
                GS_全局存档字符串变量:1 = 你一定要幸福啊
                GS_全局存档字符串变量:2 = 祝你身体健康
                GS_全局存档字符串变量:3 '= @"真没有阴阳怪气 {RAND:100}"
                PRINTL 已生成新的数据
        ENDSELECT
    WEND
    QUIT

这个 demo 演示了完整的读写公共存档 global.sav 的流程,请自行体验效果。

重置公共存档相关变量 RESETGLOBAL#

GLOBAL 初始化为 0
GLOBALS 初始化为 ""(空字符串)。

专属的特定存档 save*.sav#

检查特定存档 CHKDATA#

注意执行 CHKDATA(无论是 命令 还是 行内函数)之后,会将 存档名 赋值给 RESULTS
如果有需要,最好及时打印自行转存(以免执行其他命令后又覆盖了 RESULTS)。

@SYSTEM_TITLE
    #LOCALSIZE 1
    WHILE 1
        PRINTL 请输入要检查的存档栏位:
        INPUT
        SIF RESULT < 0
            CONTINUE
        LOCAL = RESULT
        SELECTCASE CHKDATA(LOCAL)
            CASE 0
                PRINTPLAINFORM 检测到合法的存档文件 [{LOCAL}] %RESULTS%
                PRINTL
            CASE 1
                PRINTFORML {LOCAL} 号存档栏位没有数据
            CASE 2
                PRINTFORML {LOCAL} 号存档与本游戏不匹配
            CASE 3
                PRINTFORML {LOCAL} 号存档的游戏版本已不再受支持
            CASE 4
                PRINTFORML {LOCAL} 号存档文件存在其他问题
            CASEELSE
                PRINTFORML {LOCAL} 号存档发生未知错误
        ENDSELECT
    WEND
    QUIT

保存特定存档 SAVEDATA#

很弱智的一点是 SAVEDATA 无论成功还是失败都是没有返回值的。
你必须自行结合 CHKDATA 来判断存档文件是否存在(从无到有说明保存成功,理论上)。

编辑 ./erb/特定存档变量.erh

#DIMS SAVEDATA GS_特定存档, 4 = "初始时间", "初始地点", "初始人物", "初始金钱"

编辑 ./erb/游戏业务逻辑.erb

@SYSTEM_TITLE
    #LOCALSIZE 1
    #LOCALSSIZE 1
    WHILE 1
        DRAWLINE
        PRINTFORML 时间=%GS_特定存档:0% 地点=%GS_特定存档:1% 人物=%GS_特定存档:2% 金钱=%GS_特定存档:3%
        PRINTL 请输入要保存的存档栏位:
        INPUT
        SIF RESULT < 0
            CONTINUE
        LOCAL = RESULT
        GS_特定存档:0 '= GETTIMES()
        GS_特定存档:1 '= ""
        REPEAT 5
            GS_特定存档:1 += @"%UNICODE(RAND(97, 123))%"
        REND
        GS_特定存档:2 '= ""
        REPEAT 3
            GS_特定存档:2 += @"%UNICODE(RAND(65, 91))%"
        REND
        GS_特定存档:3 '= @"{RAND(1000, 10000)}"
        LOCALS '= @"%GS_特定存档:0% %GS_特定存档:1% %GS_特定存档:2% %GS_特定存档:3%"
        IF SAVE_DATA(LOCAL, LOCALS)
            PRINTFORML 存档到 {LOCAL} 栏位成功
        ELSE
            PRINTFORML 存档到 {LOCAL} 栏位失败
        ENDIF
    WEND
    QUIT


@SAVE_DATA(save_id, save_name)
#FUNCTION
    #DIM save_id
    #DIMS save_name
    SAVEDATA save_id, save_name
    SELECTCASE CHKDATA(save_id)
        CASE 0
            PRINTPLAINFORM 保存新存档 [{save_id, 2}] %RESULTS%
            PRINTL
            RETURNF 1
        CASE 1
            PRINTFORML {save_id} 号存档栏位没有数据
        CASE 2
            PRINTFORML {save_id} 号存档与本游戏不匹配
        CASE 3
            PRINTFORML {save_id} 号存档的游戏版本已不再受支持
        CASE 4
            PRINTFORML {save_id} 号存档文件存在其他问题
        CASEELSE
            PRINTFORML {save_id} 号存档发生未知错误
    ENDSELECT
    RETURNF 0

加载特定存档 LOADDATA#

编辑 ./erb/特定存档变量.erh

#DIMS SAVEDATA GS_特定存档, 4 = "初始时间", "初始地点", "初始人物", "初始金钱"

编辑 ./erb/游戏业务逻辑.erb

@SYSTEM_TITLE
    WHILE 1
        DRAWLINE
        PRINTFORML 时间=%GS_特定存档:0% 地点=%GS_特定存档:1% 人物=%GS_特定存档:2% 金钱=%GS_特定存档:3%
        PRINTL
        PRINTBUTTON "[1] 保存", 1
        PRINT_SPACE 200
        PRINTBUTTON "[2] 加载", 2
        PRINT_SPACE 200
        PRINTBUTTON "[3] 更新", 3
        INPUT
        SELECTCASE RESULT
            CASE 1
                SAVEDATA 0, ""
                PRINTL 已存档到 save00.sav
            CASE 2
                IF !CHKDATA(0)
                    PRINTL 从 save00.sav 加载存档
                    LOADDATA 0
                ELSE
                    PRINTL 从 save00.sav 加载存档失败
                ENDIF
            CASEELSE
                GS_特定存档:0 '= GETTIMES()
                GS_特定存档:1 '= ""
                REPEAT 5
                    GS_特定存档:1 += @"%UNICODE(RAND(97, 123))%"
                REND
                GS_特定存档:2 '= ""
                REPEAT 3
                    GS_特定存档:2 += @"%UNICODE(RAND(65, 91))%"
                REND
                GS_特定存档:3 '= @"{RAND(1000, 10000)}"
                PRINTL 已生成新的数据
        ENDSELECT
    WEND
    QUIT


@EVENTLOAD
    BEGIN TITLE

删除特定存档 DELDATA#

很弱智的一点是无论成功与否(在删除之前,指定的存档是否存在)都不会报错。
建议搭配使用 CHKDATA 进行判断。

@SYSTEM_TITLE
    #LOCALSIZE 1
    WHILE 1
        DRAWLINE
        PRINTL 请输入想要删除的存档栏位:
        INPUT
        LOCAL = RESULT
        SELECTCASE LOCAL
            CASE 0 TO 99
                IF !CHKDATA(LOCAL)
                    DELDATA LOCAL
                    PRINTFORML 成功删除 {LOCAL} 号存档
                ELSE
                    PRINTFORML 指定的 {LOCAL} 号存档不存在
                ENDIF
            CASEELSE
                PRINTFORML 非法的存档编号 {LOCAL}
        ENDSELECT
    WEND
    QUIT

重置非公共存档变量 RESETDATA#

将除了 GLOBALGLOBALS 之外的全部变量都初始化。
有初始值的赋初值,没有初始值的赋值为 0 或空字符串。

角色数据 chara_*.dat#

保存角色数据 SAVECHARA#

ADDVOIDCHARA 0
SAVECHARA "角色备份文件名", "这是一个角色数据文件的备注", 0

会把序号为 0 的角色数据存储到 ./dat/chara_角色备份文件名.dat 文件。

检测角色数据 CHKCHARADATA#

@SYSTEM_TITLE
    #LOCALSSIZE 1
    LOCALS = 角色备份文件名
    IF !CHKCHARADATA(LOCALS)
        PRINTFORML 可以读取 ./dat/chara_%LOCALS%.dat
        PRINTFORML 该角色备份的备注为:%RESULTS%
    ELSE
        PRINTFORML 没有找到 ./dat/chara_%LOCALS%.dat
    ENDIF
    QUIT

读取角色数据 LOADCHARA#

@SYSTEM_TITLE
    #LOCALSSIZE 1
    LOCALS = 角色备份文件名
    PRINTFORML 读取之前 角色数量为 {CHARANUM}
    IF !CHKCHARADATA(LOCALS)
        PRINTFORML 读取角色数据成功 %RESULTS%
        LOADCHARA LOCALS
    ELSE
        PRINTFORML 没有找到 ./dat/chara_%LOCALS%.dat
    ENDIF
    PRINTFORML 读取之后 角色数量为 {CHARANUM}
    QUIT

搜索角色数据 FIND_CHARADATA#

会搜索 ./dat/ 目录下的角色数据文件。
可以使用 * 通配符,比如 *数据* 可以匹配到 有一个数据 / 数据备份

@SYSTEM_TITLE
    #LOCALSIZE 1
    LOCAL = FIND_CHARADATA("*角色*")
    IF LOCAL
        REPEAT LOCAL
            PRINTFORML 匹配到角色数据 ./dat/chara_%RESULTS:COUNT%.dat
        REND
    ELSE
        PRINTFORML 没有匹配到角色数据
    ENDIF
    QUIT

内置存档系统#

保存游戏(写入存档)界面 SAVEGAME#

执行 SAVEGAME 命令就会调用内置的存档保存系统。
无论「保存成功」还是「取消保存」都会返回到调用的地方。

@SYSTEM_TITLE
    SAVEGAME
    PRINTFORML 存档结束 上次存档的备注为 SAVEDATA_TEXT=%SAVEDATA_TEXT%
    QUIT

加载游戏(读取存档)界面 LOADGAME#

简单对默认的 @SYSTEM_TITLE(标题界面)和 @TITLE_LOADGAME(存档加载界面)进行一个刻的复:

@SYSTEM_TITLE
    #LOCALSSIZE 1
    WHILE 1
        DRAWLINE
        PRINTL 这里是 @SYSTEM_TITLE
        ALIGNMENT CENTER
        SIF STRLENSU(GAMEBASE_TITLE)
            PRINTFORML %GAMEBASE_TITLE%
        IF GAMEBASE_VERSION > 0
            LOCALS '= TOSTR(GAMEBASE_VERSION, "D8")
            PRINTFORML {TOINT(SUBSTRINGU(LOCALS, 0, 5))}.%SUBSTRINGU(LOCALS, 5)%
        ENDIF
        SIF STRLENSU(GAMEBASE_AUTHOR)
            PRINTFORML %GAMEBASE_AUTHOR%
        PRINTFORML (%GAMEBASE_YEAR%)
        PRINTL
        SIF STRLENSU(GAMEBASE_INFO)
            PRINTFORML %GAMEBASE_INFO%
        ALIGNMENT LEFT
        DRAWLINE
        FONTSTYLE 1p2
        PRINTPLAINFORM [0] 开始新游戏
        FONTSTYLE 0
        PRINT_SPACE 100
        PRINTBUTTON "[0] 保存存档", 0
        PRINTL
        PRINTBUTTON "[1] 读取存档", 1
        INPUT
        SELECTCASE RESULT
            CASE 0
                SAVEGAME
            CASE 1
                TRYCCALL TITLE_LOADGAME
                    ; 相当于优先 CALL TITLE_LOADGAME
                    ; 如果想看原生的 LOADGAME 是什么样子
                    ; 把下面的 @TITLE_LOADGAME 函数的名字稍作修改即可
                CATCH
                    LOADGAME
                ENDCATCH
            CASEELSE
                CONTINUE
        ENDSELECT
    WEND
    PRINTL 只有 BEGIN FIRST 的话,理论上不需要死循环也永远不会运行到这一行,因为 BEGIN 的本质是 JUMP
    PRINTW 永远无法到达的「游戏结束」
    QUIT


@TITLE_LOADGAME
    DRAWLINE
    PRINTL 这里是 @TITLE_LOADGAME
    WHILE 1
        PRINTL 要读取哪个存档?
        REPEAT SAVENOS()  ; 相当于 GETCONFIG("表示するセーブデータ数")
            IF CHKDATA(COUNT)
                PRINTFORML [{COUNT, 2}] ----
            ELSE
                PRINTFORML [{COUNT, 2}] %RESULTS%
            ENDIF
        REND
        IF CHKDATA(99)
            PRINTFORML [99] ----
        ELSE
            PRINTFORML [99] %RESULTS%
        ENDIF
        PRINTFORML [100] 返回
        INPUT
        SELECTCASE RESULT
            CASE 0 TO 19
                SELECTCASE CHKDATA(RESULT)
                    CASE 0
                        LOADDATA RESULT
                    CASE 1
                        PRINTL 该存档栏位没有数据
                    CASE 2
                        PRINTL 该存档与本游戏不匹配
                    CASE 3
                        PRINTL 该存档的游戏版本已不再受支持
                    CASE 4
                        PRINTL 存档文件存在其他问题
                    CASEELSE
                        PRINTL 读取存档时发生未知错误
                ENDSELECT
            CASE 99
                IF CHKDATA(99)
                    PRINTL 读取自动存档失败
                ELSE
                    LOADDATA 99
                ENDIF
            CASE 100
                BREAK
            CASEELSE
                PRINTL 输入的值无效
        ENDSELECT
    WEND
    PRINTL 放弃读取存档 返回标题界面


@SYSTEM_LOADEND
    DRAWLINE
    PRINTL 这里是 @SYSTEM_LOADEND
    PRINTL 存档加载完成
    PRINTL 上次读取的存档相关数据:
    PRINTFORML %"", 4%游戏版本号为 {LASTLOAD_VERSION}
    PRINTFORML %"", 4%存档栏位为 {LASTLOAD_NO}
    PRINTFORML %"", 4%存档名为 %LASTLOAD_TEXT%


@EVENTLOAD
    DRAWLINE
    PRINTL 这里是 @EVENTLOAD
    PRINTL 接下来本来会自动跳转到 SHOP 界面
    PRINTL 即 BEGIN SHOP(需要自行定义 @SHOW_SHOP 函数) 等价于 JUMP SHOW_SHOP
    PRINTL 我们在这里劫持游戏流程 让它回到标题界面
    BEGIN TITLE

EE+EM 扩展功能#

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

将 XML 和 MAP 加入存档保存#

启用本功能必须先设置 セーブデータをバイナリ形式で保存する:YES
即启用「将存档文件使用二进制格式存储」。

编辑 ./csv/VarExt*.csv

; 可以在一行内设置多个(用逗号分开)
; 也可以使用多行、多个文件(例如 VarExt1.csv / VarExt2.csv / VarExt3.csv)
; 空格只是为了排版便于阅读,实际读取时会自动删除开头 / 末尾的空格

; 设置希望保存在 global.sav 中的 MAP 的 ID
GLOBAL_MAPS, MyMap, MyMap2
GLOBAL_MAPS, MyMap3

; 设置希望保存在 global.sav 中的 XmlDocument 的 ID
GLOBAL_XMLS, 0, MyXml

; 设置希望保存在 save*.sav 中的 MAP 的 ID
SAVE_MAPS, MyMap4

; 设置希望保存在 save*.sav 中的 XmlDocument 的 ID
SAVE_XMLS, 1, MyXml2

注意事项:

  1. 不会存储空值,设置之后还要内存中确实存在这个变量,才会进行保存。
  2. 如果存档中已经保存的数据,没有在 VarExt*.csv 中设定,则会被丢弃。
  3. 保存 MAP / XML 的新存档兼容本家 / 私家改造版 / 旧版本的 EE+EM。

存储存档文件时进行压缩#

启用此功能需要 EMv12+EEv20 及以上版本。

启用本功能必须先设置 セーブデータをバイナリ形式で保存する:YES
即启用「将存档文件使用二进制格式存储」。

然后可以启用 EmueraEE+EM 的专属配置项 セーブデータを圧縮して保存する:YES
会进一步将二进制的存档文件压缩保存。

注意:压缩保存后的存档文件不再兼容本家 / 私家改造版 / 旧版本的 EE+EM。

lackbfun © 2021 - 2024