Python (ipin_vr/export.py): - generate_bank(): levels 1–2 enumerated exhaustively (120/720 combos), level 3 random with dedup; all deduplicated per-level - run_export(): CLI --per-level / --out / --seed flags per ONDEVICE §4.1 - cli.py routes "export" subcommand before session args (backward-compat) - 14 new tests: format, dedup for all 3 levels, space caps (L1=120, L2=720), seed determinism, file output Unity scaffold (ondevice/Scripts/): - CommandBank.cs: loads commands.json from StreamingAssets (WebRequest on Android) - TtsManager.cs: sherpa-onnx integration with [SHERPA] stubs, StreamingAssets→ persistentDataPath copy, defensive Speak() with onDone callback - SessionController.cs: passthrough flow §6, level switching, busy guard on Next - ondevice/README.md: full manual Editor steps §5 (Unity setup, Meta XR SDK, sherpa-onnx install, scene wiring, font, build, metrics) pytest: 37/37 passed Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
119 lines
3.7 KiB
C#
119 lines
3.7 KiB
C#
// CommandBank.cs — ładuje commands.json z StreamingAssets, udostępnia losowe
|
|
// polecenia per-level. Samoistny MonoBehaviour; podepnij do dowolnego GameObject.
|
|
//
|
|
// Plik commands.json generuj offline:
|
|
// python -m ipin_vr export --per-level 300 --out commands.json
|
|
// Następnie umieść go w Assets/StreamingAssets/commands.json.
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
|
|
namespace IpinVr
|
|
{
|
|
[Serializable]
|
|
public class CommandEntry
|
|
{
|
|
public int level;
|
|
public string text;
|
|
}
|
|
|
|
[Serializable]
|
|
internal class BankMeta
|
|
{
|
|
public string version;
|
|
public int per_level;
|
|
}
|
|
|
|
[Serializable]
|
|
internal class BankData
|
|
{
|
|
public BankMeta meta;
|
|
public List<CommandEntry> commands;
|
|
}
|
|
|
|
public class CommandBank : MonoBehaviour
|
|
{
|
|
[SerializeField] private string jsonFileName = "commands.json";
|
|
|
|
private readonly Dictionary<int, List<string>> _byLevel = new();
|
|
private bool _loaded;
|
|
|
|
public bool IsLoaded => _loaded;
|
|
|
|
/// <summary>Wczytaj bank asynchronicznie. onDone wywoływane po załadowaniu.</summary>
|
|
public void Load(Action onDone = null)
|
|
{
|
|
StartCoroutine(LoadCoroutine(onDone));
|
|
}
|
|
|
|
private IEnumerator LoadCoroutine(Action onDone)
|
|
{
|
|
string srcPath = Path.Combine(Application.streamingAssetsPath, jsonFileName);
|
|
string json;
|
|
|
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
|
// Na Androidzie StreamingAssets są wewnątrz APK — czytamy przez WebRequest.
|
|
using var req = UnityWebRequest.Get(srcPath);
|
|
yield return req.SendWebRequest();
|
|
if (req.result != UnityWebRequest.Result.Success)
|
|
{
|
|
Debug.LogError($"[CommandBank] Błąd wczytywania {jsonFileName}: {req.error}");
|
|
yield break;
|
|
}
|
|
json = req.downloadHandler.text;
|
|
#else
|
|
if (!File.Exists(srcPath))
|
|
{
|
|
Debug.LogError($"[CommandBank] Plik nie istnieje: {srcPath}");
|
|
yield break;
|
|
}
|
|
json = File.ReadAllText(srcPath);
|
|
yield return null;
|
|
#endif
|
|
|
|
var data = JsonUtility.FromJson<BankData>(json);
|
|
if (data?.commands == null)
|
|
{
|
|
Debug.LogError("[CommandBank] Nie można sparsować commands.json");
|
|
yield break;
|
|
}
|
|
|
|
_byLevel.Clear();
|
|
foreach (var entry in data.commands)
|
|
{
|
|
if (!_byLevel.TryGetValue(entry.level, out var list))
|
|
_byLevel[entry.level] = list = new List<string>();
|
|
list.Add(entry.text);
|
|
}
|
|
|
|
int total = data.commands.Count;
|
|
Debug.Log($"[CommandBank] Wczytano {total} poleceń (v{data.meta?.version})");
|
|
_loaded = true;
|
|
onDone?.Invoke();
|
|
}
|
|
|
|
/// <summary>Zwraca losowe polecenie dla danego poziomu, lub null jeśli brak.</summary>
|
|
public string GetRandom(int level)
|
|
{
|
|
if (!_loaded)
|
|
{
|
|
Debug.LogWarning("[CommandBank] Bank nie jest jeszcze wczytany");
|
|
return null;
|
|
}
|
|
if (!_byLevel.TryGetValue(level, out var list) || list.Count == 0)
|
|
{
|
|
Debug.LogWarning($"[CommandBank] Brak poleceń dla poziomu {level}");
|
|
return null;
|
|
}
|
|
return list[UnityEngine.Random.Range(0, list.Count)];
|
|
}
|
|
|
|
public int CountForLevel(int level) =>
|
|
_byLevel.TryGetValue(level, out var l) ? l.Count : 0;
|
|
}
|
|
}
|