godot4 web 文件上传和下载


关于这个需求,之前在itch.io玩过一个web端游戏,可以导入导出存档,并且游戏同样是godot开发的

这次gamejam在确定了核心机制之后,就在思考如何构建编辑器,因为我个人设计能力偏弱,内置编辑器让玩家来参与设计是一个取巧的方法,玩家在设计出关卡之后,如何分享是一个问题,而由于之前活动租的云服务器早已到期,况且接入服务端是一件很费心力的事情,为了简化需求,在网页端导入导出关卡便是最后确定的设计。虽然说没有玩家来分享关卡也是很正常的结果,不过“可以不用,但是要有”这也是个人秉持的理念之一,谁知道冗余的设计在什么时候连成一个体系了呢
说归正题,在实现核心模块之后,我开了一个测试项目用来测试web段导入导出,测试项目和jam项目的版本都是godot4.4,在测试项目最开始,就是先问ai,如何实现,而ai给出的代码中,下载可以直接用,上传就是无法使用,我先给出下载的部分

func download_file(path: String, filename: String):

    var file = FileAccess.open(path, FileAccess.READ)

    var bytes = file.get_buffer(file.get_length())

    JavaScriptBridge.eval("""

        var blob = new Blob([new Uint8Array(%s)], {type: 'application/octet-stream'});

        var link = document.createElement('a');

        link.href = URL.createObjectURL(blob);

        link.download = '%s';

        link.click();

    """ % [str(bytes), filename])

func _on_download_button_pressed():

      download_file("user://savegame.json", "savegame.json")

下载部分很简单,读取user://savegame.json,转换为bytes,使用 JavaScriptBridge.eval生成html的下载链接,把数据写入blob并触发下载

关键在于上传,该部分调试了很久,主要是回调问题不好解决
上传的代码有2部分
一部分是在html中,这个部分建议修改导出模板将代码直接嵌入,否则就需要每次导出时都需要导入一次,我是将该段代码放在加载状态代码的前面

        <input id="godot_upload" type="file" accept=".json" style="display: none;">

        <script>

        window.ret_uint_array = []

        document.getElementById('godot_upload').addEventListener('change', function(e) {

            const file = e.target.files[0];

            const reader = new FileReader();

           
            reader.onload = function() {

                const uintArray = new Uint8Array(reader.result);

                window.ret_uint_array = uintArray

                window.on_receive_save_data(uintArray, file.name);

            };

            reader.readAsArrayBuffer(file);

        });

        </script>
简单来说就是一个隐藏的上传input,和一个回调函数,回调函数可将上传数据生成 Uint8Array传入window.on_receive_save_data

而另一部分代码,作用一个是触发上传input,另一个是将godot的回调函数挂载到web部分的window变量中

var receive_save_data_ref

func _ready():

    receive_save_data_ref = JavaScriptBridge.create_callback(_on_save_received)

    var window = JavaScriptBridge.get_interface("window")

    window.on_receive_save_data = receive_save_data_ref

func _on_save_received(args):

    var arr = JavaScriptBridge.get_interface("ret_uint_array")

    var byte_array = args[0]  # 接收 JavaScript 的 Uint8Array

    var file_name = args[1]

    # 转换为 PackedByteArray

    var bytes = PackedByteArray()

    for i in byte_array.length:

        bytes.append(byte_array[i])

    # 保存到用户目录

    var save_path = "user://%s" % file_name

    var file = FileAccess.open(save_path, FileAccess.WRITE)

    if file:

        file.store_buffer(bytes)

        print("存档已加载至:", save_path)

    #load_game_data(save_path)  # 调用你的存档加载逻辑

    else:

        push_error("文件写入失败")

func _on_upload_button_pressed():

    JavaScriptBridge.eval("""

        document.getElementById('godot_upload').click();

    """, true)

在_ready中将_on_save_received挂载到window.on_receive_save_data,引用地址为var receive_save_data_ref,同时也方便对于不同环境做差异化,如果不是web环境就不加载_ready处的内容
_on_save_received的部分,args为js回调函数传过来的参数

实现这个功能主要是使用ai + 文档,ai可以把第一步做好,但ai很容易胡说,所以要注意对照文档,要不然就被ai带进沟里了

Web - The JavaScriptBridge Singleton - 《Godot 游戏引擎 v4.3 中文文档》 - 书栈网 · BookStack

JavaScriptBridge — Godot Engine (4.x) 简体中文文档

Get echo

Download NowName your own price

Leave a comment

Log in with itch.io to leave a comment.