add loadscene

This commit is contained in:
CuongNV
2026-05-21 17:42:02 +07:00
parent 3dc0255eae
commit 5d4cc73cc8
11 changed files with 205 additions and 20 deletions
+2 -2
View File
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:59a38344245b9d816e9ee3749664edf1ced3bb23a176c93562f0e7ef78ac22aa
size 311690
oid sha256:118edff57ce13d46edc50acb1387bca1d79d66795958be304ef1e9d017cff634
size 309966
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a5ca144887d8b4ad62b05ed4ee96ef2ff817460dd87d9a70a30ca6a1f750fe30
size 5293
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7231194cdbf312f4ea3bc0583146e93f
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -13,7 +13,10 @@ using UnityEngine.U2D;
namespace BrewMonster.Scripts
{
/// <summary>Runs after <see cref="GameContentBootstrap"/> (-2000) so bootstrap Awake creates the gate first.</summary>
/// <summary>
/// Scene game (Bootstrap): chờ gate chỉ khi <see cref="GameContentBootstrap"/> cùng scene.
/// Nếu đã chạy scene GameContentBootstrap trước → <see cref="GameContentBootstrapSession.IsContentReady"/>.
/// </summary>
[DefaultExecutionOrder(-1990)]
public class AddressableManager : MonoSingleton<AddressableManager>
{
@@ -85,20 +88,28 @@ namespace BrewMonster.Scripts
/// </summary>
async UniTaskVoid StartAddressablesInitAfterBootstrapGate()
{
var gateState = GameContentBootstrap.GetGateDebugState();
Debug.Log(
$"[Cuong] AddressableManager: Đang chờ GameContentBootstrap (version / URL rewrite)... | " +
$"id={GetInstanceID()} scene={SceneManager.GetActiveScene().name} gate={gateState}");
var waited = await WaitForBootstrapGateWithTimeoutAsync();
if (!waited)
if (GameContentBootstrapSession.IsContentReady || AddressablesInitService.IsInitialized)
{
Debug.LogWarning(
$"[Cuong] AddressableManager: Bootstrap gate timeout ({_bootstrapGateWaitTimeoutSeconds:F0}s) — " +
"InitializeAsync anyway. Check GameContentBootstrap lifecycle logs.");
Debug.Log(
$"[Cuong] AddressableManager: Content bootstrap đã chạy ở scene trước — init Addressables (scene={SceneManager.GetActiveScene().name}).");
}
else
{
var gateState = GameContentBootstrap.GetGateDebugState();
Debug.Log(
$"[Cuong] AddressableManager: Đang chờ GameContentBootstrap (version / URL rewrite)... | " +
$"id={GetInstanceID()} scene={SceneManager.GetActiveScene().name} gate={gateState}");
var waited = await WaitForBootstrapGateWithTimeoutAsync();
if (!waited)
{
Debug.LogWarning(
$"[Cuong] AddressableManager: Bootstrap gate timeout ({_bootstrapGateWaitTimeoutSeconds:F0}s) — " +
"InitializeAsync anyway. Nên dùng scene GameContentBootstrap riêng (index 0).");
}
}
Debug.Log("[Cuong] AddressableManager: Bootstrap gate xong — đang InitializeAsync Addressables...");
Debug.Log("[Cuong] AddressableManager: Đang InitializeAsync Addressables...");
try
{
await AddressablesInitService.EnsureInitializedAsync();
@@ -52,8 +52,12 @@ namespace BrewMonster.Scripts
try
{
await GameContentBootstrap.WaitForPreAddressablesSetupIfAnyAsync();
Debug.Log("[Cuong] AddressablesInitService: Bootstrap gate OK — InitializeAsync...");
if (!GameContentBootstrapSession.IsContentReady)
await GameContentBootstrap.WaitForPreAddressablesSetupIfAnyAsync();
else
Debug.Log("[Cuong] AddressablesInitService: Content scene đã sync — bỏ chờ gate.");
Debug.Log("[Cuong] AddressablesInitService: InitializeAsync...");
var handle = Addressables.InitializeAsync();
await handle.ToUniTask();
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f5cbe16c40166b34c9a3f9e967dfc694
@@ -8,10 +8,8 @@ using UnityEngine.SceneManagement;
namespace BrewMonster.Scripts
{
/// <summary>
/// Bootstrap: lấy contentVersion + (tuỳ chọn) assetsBaseUrl qua server hoặc hardcode, gắn URL rewrite trước khi Addressables init,
/// rồi nếu lần đầu hoặc contentVersion khác đã lưu thì cập nhật catalog và tải toàn bộ entry gắn label.
/// First run / version bump: resolve version + optional assetsBaseUrl (server or hardcoded), apply URL rewrite before Addressables init,
/// then if first launch or stored version differs, update catalogs and download all entries under the configured label.
/// Scene riêng (khuyến nghị index 0 trong Build Settings): version + URL rewrite Addressables init → catalog/bulk.
/// Khi xong, load scene game (<see cref="_nextSceneName"/>, thường <c>Bootstrap</c>) — scene đó không cần component này.
/// </summary>
[DefaultExecutionOrder(-2000)]
public class GameContentBootstrap : MonoBehaviour
@@ -87,6 +85,19 @@ namespace BrewMonster.Scripts
[SerializeField]
string _editorFakeContentVersion = "editor";
[Header("Scene flow (dedicated content scene)")]
[Tooltip("Bật: sau khi sync content thành công, LoadScene Single sang scene game. Tắt: chỉ chạy sync (dùng khi gắn chung scene Bootstrap).")]
[SerializeField]
bool _loadNextSceneAfterSuccess = true;
[Tooltip("Tên scene trong Build Settings (vd Bootstrap). BootstrapSceneController trong scene đó sẽ load LoginScene.")]
[SerializeField]
string _nextSceneName = "Bootstrap";
[Tooltip("Khi sync thất bại, không load scene game (ở lại scene content để xử lý / retry).")]
[SerializeField]
bool _stayOnSceneWhenSyncFails = true;
public event Action<BootstrapResult> Finished;
/// <summary>Trạng thái gate tĩnh — dùng log chẩn đoán từ <see cref="AddressableManager"/>.</summary>
@@ -110,6 +121,8 @@ namespace BrewMonster.Scripts
void Awake()
{
GameContentBootstrapSession.ResetForNewRun();
s_bootstrapRunStarted = false;
s_activeBootstrapInstanceId = GetInstanceID();
_holdAddressablesInitConfigured = _holdAddressablesInitUntilVersionChecked;
LogLifecycle("Awake");
@@ -209,7 +222,32 @@ namespace BrewMonster.Scripts
async UniTaskVoid RunAsync()
{
var result = await RunInternalAsync();
if (result.Success)
GameContentBootstrapSession.MarkContentReady();
Finished?.Invoke(result);
await HandleSceneFlowAfterBootstrapAsync(result);
}
async UniTask HandleSceneFlowAfterBootstrapAsync(BootstrapResult result)
{
if (!result.Success)
{
if (_stayOnSceneWhenSyncFails)
Debug.LogError("[Cuong] GameContentBootstrap: Sync thất bại — ở lại scene content (không load game).");
return;
}
if (!_loadNextSceneAfterSuccess || string.IsNullOrWhiteSpace(_nextSceneName))
{
Debug.Log("[Cuong] GameContentBootstrap: Sync OK — không load scene tiếp (_loadNextSceneAfterSuccess tắt hoặc tên scene trống).");
return;
}
var next = _nextSceneName.Trim();
await UniTask.Yield();
Debug.Log($"[Cuong] GameContentBootstrap: Sync OK — LoadScene '{next}' (Single)...");
SceneManager.LoadScene(next, LoadSceneMode.Single);
}
public async UniTask<BootstrapResult> RunInternalAsync()
@@ -0,0 +1,21 @@
namespace BrewMonster.Scripts
{
/// <summary>
/// Trạng thái phiên sau khi scene <c>GameContentBootstrap</c> hoàn tất (Addressables + catalog/bulk nếu cần).
/// Scene game (<c>Bootstrap</c>) đọc flag này — không cần component <see cref="GameContentBootstrap"/> trong scene đó.
/// </summary>
public static class GameContentBootstrapSession
{
public static bool IsContentReady { get; private set; }
public static void MarkContentReady()
{
IsContentReady = true;
}
public static void ResetForNewRun()
{
IsContentReady = false;
}
}
}
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 1c7e6838c2213a9478bf29bb3f248949
@@ -0,0 +1,95 @@
#if UNITY_EDITOR
using BrewMonster.Scripts;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace BrewMonster.Editor
{
/// <summary>
/// Tạo scene GameContentBootstrap và đưa lên đầu Build Settings (trước Bootstrap).
/// </summary>
public static class GameContentBootstrapSceneSetup
{
const string ContentScenePath = "Assets/PerfectWorld/Scene/GameContentBootstrap.unity";
const string BootstrapScenePath = "Assets/PerfectWorld/Scene/Bootstrap.unity";
[MenuItem("Perfect World/Addressables/Setup Two-Scene Bootstrap")]
public static void SetupTwoSceneBootstrap()
{
EnsureContentBootstrapScene();
RemoveGameContentBootstrapFromBootstrapScene();
ReorderBuildSettings();
AssetDatabase.SaveAssets();
Debug.Log(
"[Cuong] Two-scene bootstrap: (0) GameContentBootstrap → (1) Bootstrap. " +
"Gỡ GameContentBootstrap khỏi Bootstrap.unity; cấu hình _nextSceneName trên component trong scene Content.");
}
static void EnsureContentBootstrapScene()
{
if (System.IO.File.Exists(ContentScenePath))
return;
var scene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
var root = new GameObject("GameContentBootstrap");
var bootstrap = root.AddComponent<GameContentBootstrap>();
var so = new SerializedObject(bootstrap);
so.FindProperty("_loadNextSceneAfterSuccess").boolValue = true;
so.FindProperty("_nextSceneName").stringValue = "Bootstrap";
so.ApplyModifiedPropertiesWithoutUndo();
EditorSceneManager.SaveScene(scene, ContentScenePath);
Debug.Log($"[Cuong] Created {ContentScenePath}");
}
static void RemoveGameContentBootstrapFromBootstrapScene()
{
if (!System.IO.File.Exists(BootstrapScenePath))
{
Debug.LogWarning($"[Cuong] Missing {BootstrapScenePath}");
return;
}
var scene = EditorSceneManager.OpenScene(BootstrapScenePath, OpenSceneMode.Single);
var targets = Object.FindObjectsByType<GameContentBootstrap>(FindObjectsInactive.Include, FindObjectsSortMode.None);
if (targets == null || targets.Length == 0)
{
Debug.Log("[Cuong] Bootstrap.unity: no GameContentBootstrap found (already clean).");
return;
}
foreach (var t in targets)
Object.DestroyImmediate(t.gameObject);
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene);
}
static void ReorderBuildSettings()
{
var scenes = new System.Collections.Generic.List<EditorBuildSettingsScene>(EditorBuildSettings.scenes);
EditorBuildSettingsScene contentEntry = null;
for (int i = scenes.Count - 1; i >= 0; i--)
{
if (scenes[i].path == ContentScenePath)
{
contentEntry = scenes[i];
scenes.RemoveAt(i);
break;
}
}
if (contentEntry == null)
contentEntry = new EditorBuildSettingsScene(ContentScenePath, true);
contentEntry.enabled = true;
scenes.Insert(0, contentEntry);
EditorBuildSettings.scenes = scenes.ToArray();
Debug.Log("[Cuong] Build Settings: GameContentBootstrap is index 0.");
}
}
}
#endif
@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 3a3ab2943a6c3ab44aee0633dc42a560