BasilYes / godot-yandex-games-sdk

Yandex games SDK implementation for godot
MIT License
85 stars 7 forks source link

save_data не сохраняет array и dict (Godot (3.5) #18

Open AlexLouder opened 3 months ago

AlexLouder commented 3 months ago

В документации яндекса https://yandex.ru/dev/games/doc/ru/sdk/sdk-player В описании метода player.setData приводят пример со списком: { achievements: ['trophy1', 'trophy2', 'trophy3'], }

У меня есть списки и словари, которые я хотел бы сохранить.

data = {}
data['test_array'] = ['sword', 'axe']
data['test_dict'] = {'points' : 12, 'health' : 100}
data['level'] = 10

При сохранении и загрузки из json все в порядке. Но если сохранять в Godot через YandexSDK.save_data(data, true), то в Яндексе в консоли отобразится:

test_array: undefined
test_dict: undefined
level: 10

Я посмотрел код синглтона yandex_sdk (gd и js) и не понимаю, почему не работает. Пока приходится костылить, превращая списки в строку при сохранении и сплитить строку в список при загрузке.

BasilYes commented 3 months ago

Я могу сказать, почему это происходит, все из-за того, что в SDK подается годотовский массив или словарь а не JS массив или Object. Тоже самое у меня было когда подавался не правильный int (годотовский int превращается в какой-то не правильный int в js, который не обрабатывает яндекс). Это можно обойти, примерно как это я делаю тут

func save_data(data: Dictionary, flush: bool = false) -> void:
    if not OS.has_feature("yandex"):
        return
    if not is_player_initialized:
        init_player()
        await player_initialized
    var saves = JavaScriptBridge.create_object("Object")
    for i in data.keys():
        if data[i] is int:
            saves[i] = float(data[i])
        else:
            saves[i] = data[i]
    window.SaveData(saves, flush)

превращая исходный словарь в Object с заменой интов (надо проверять на словари/массивы также как и на инты и преобразовывать их тоже, а лучше, вообще, рекусрсивно заменять все массивы и словари). Справишься, сделай пул реквест, было бы замечательно. Не справишься, проблему локализовали, позже исправлю

AlexLouder commented 3 months ago

Спасибо, забавно, но с интами у меня проблем не было. Я поковырялся и создал 2 вспомогательные рекурсивные функции для массива и словаря и 1 функция, которая превращает обычный словарь в словарь с javascriptobjects. Эти функции работают и если перед сохранением через них прогонять, то сохраняется все как надо, в консоли все ок.

func recursive_array_to_jsarray(array: Array) -> JavaScriptObject:
    var jsArray:JavaScriptObject = JavaScript.create_object("Array")
    for item in array:
        if item is Array:
            jsArray.push(recursive_array_to_jsarray(item))
            continue
        if item is Dictionary:
            jsArray.push(recursive_dict_to_jsmap(item))
            continue
        jsArray.push(item)
    return jsArray

func recursive_dict_to_jsmap(dict: Dictionary) -> JavaScriptObject:
    var jsMap:JavaScriptObject = JavaScript.create_object('Map')
    for key in dict:
        if dict[key] is Array:
            jsMap.set(key, recursive_array_to_jsarray(dict[key]))
            #jsMap[key] = recursive_array_to_jsarray(dict[key])
            continue
        if dict[key] is Dictionary:
            jsMap.set(key, recursive_dict_to_jsmap(dict[key]))
            continue

        jsMap.set(key, dict[key])

    return jsMap

func dict_make_jsobjects(dict: Dictionary) -> Dictionary:
    var out_dict = {}
    for key in dict:
        if dict[key] is Array:
            out_dict[key] = recursive_array_to_jsarray(dict[key])
            continue
        if dict[key] is Dictionary:
            out_dict[key] = recursive_dict_to_jsmap(dict[key])
            continue
        out_dict[key] = dict[key]

    print('dict_make_jsobjects', out_dict)
    return out_dict

Но столкнулся я с другой проблемой =) Проблема считать в godot сохраненные данные из этих JavaScriptObjects. По логике нужны функции recursive_jsarray_to_array() и recursive_jsmap_to_dict(), но я столкнулся со сложностями:

  1. Если использовать var jsarr = JavaScript.get_interface("Array").from(jsArray), то не получается по нему итерировать с помощью for. Консоль ругается:

Error calling method _iter_init on: Array(4)0: "тест1"1: "iasd"2: "wobble"3: (4) [1, 2, 3, 4]length: 4[[Prototype]]: Array(0) error: TypeError: obj[method] is not a function at _godot_js_wrapper_object_call (index.js:9:275121)

Т.е. по сути массив уже виден, но как из него что-то вытащить - не понятно.

  1. Как провести проверку JavaScriptObjects по типу (Array он или Map), чтобы вызвать соответствующую функцию?
BasilYes commented 3 months ago

С JavaScriptObject можно использовать функции из JS. Надо просто погуглить как в JS понять массив это или словарь. И как в JS получить размер массива и тогда уже цикл по размеру массива сделать. (П.С. проблемы с интами только в 4 годо)

Maxikc commented 3 months ago

Я обошёл это более костыльным методом, который показался мне более удобным

func save_data(data: Dictionary, flush: bool = false) -> void:
    if not OS.has_feature("yandex"):
        return
    if not is_player_initialized:
        init_player()
        await player_initialized
    var saves = JavaScriptBridge.create_object("Object")
    for i in data.keys():
        if data[i] is int:
            saves[i] = float(data[i])
        else:
            saves[i] = data[i]
        #Добавил ещё ветку if с преобразованием массива в строчку
        if data[i] is Array:
            saves[i] = JSON.stringify(data[i]) 
    window.SaveData(saves, flush)

После чего использую JSON.parse_string(). Это позволяет обрабатывать

#своя функция в коде, вызываемая с сигналом data_loaded в SDK
func _on_data_loaded(data):
    saves = {}
    for i in data.keys():
        saves[i] = JSON.parse_string(data[i])