chunk 1: core gameplay scripts scenes runtime assets

This commit is contained in:
2026-04-06 11:02:34 +03:00
parent fa0388bc79
commit 0d11a097d8
703 changed files with 2292651 additions and 0 deletions

View File

@@ -0,0 +1,86 @@
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using System.IO;
using System.Linq;
/// <summary>
/// Pre-build check to ensure Addressables bundles are built and up-to-date
/// before creating an Android build. Prevents "Invalid serialized file version" errors.
/// </summary>
public class AddressablePreBuildCheck : IPreprocessBuildWithReport
{
public int callbackOrder => -100; // Run early in the build process
public void OnPreprocessBuild(BuildReport report)
{
if (report.summary.platform != BuildTarget.Android)
return;
CheckAddressablesBundles();
}
private void CheckAddressablesBundles()
{
string bundlePath = "ServerData/Android";
string fullBundlePath = Path.Combine(Application.dataPath, "..", bundlePath);
// Check if bundles directory exists and has content
if (!Directory.Exists(fullBundlePath))
{
LogWarningAndPromptBuild("Addressables bundle directory not found!");
return;
}
var bundles = Directory.GetFiles(fullBundlePath, "*.bundle");
if (bundles.Length == 0)
{
LogWarningAndPromptBuild("No Addressables bundles found!");
return;
}
// Check bundle age - warn if older than 1 day
var oldestBundle = bundles
.Select(f => new FileInfo(f))
.OrderBy(f => f.LastWriteTime)
.FirstOrDefault();
if (oldestBundle != null)
{
var age = System.DateTime.Now - oldestBundle.LastWriteTime;
if (age.TotalHours > 24)
{
Debug.LogWarning($"⚠️ [Addressables Check] Bundles are {age.TotalHours:F1} hours old.");
Debug.LogWarning($" Oldest bundle: {Path.GetFileName(oldestBundle.FullName)}");
Debug.LogWarning($" Last modified: {oldestBundle.LastWriteTime}");
Debug.LogWarning(" Consider rebuilding Addressables to ensure version compatibility.");
Debug.LogWarning(" Go to: Window > Asset Management > Addressables > Groups > Build > New Build");
}
else
{
Debug.Log($"✓ [Addressables Check] Bundles are recent ({age.TotalMinutes:F0} minutes old)");
}
}
// Check catalog files
var catalogs = Directory.GetFiles(fullBundlePath, "catalog_*.bin");
if (catalogs.Length == 0)
{
Debug.LogWarning("⚠️ [Addressables Check] No catalog files found! Addressables may not load properly.");
}
}
private void LogWarningAndPromptBuild(string message)
{
Debug.LogWarning($"⚠️ [Addressables Check] {message}");
Debug.LogWarning(" Addressables content must be built before building the Android app.");
Debug.LogWarning(" Go to: Window > Asset Management > Addressables > Groups > Build > New Build");
Debug.LogWarning(" Or the app will fail to initialize with 'Invalid serialized file version' error!");
// Optionally stop the build - uncomment if you want to enforce this
// throw new BuildFailedException("Addressables bundles not found. Please build Addressables first.");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0b497cb2e406b1ffba3d66e65c7c757c

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 26392c925a05ffd1a88d7d1b79ebdfd0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,214 @@
package com.unity3d.player.appui;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.util.DisplayMetrics;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import com.unity3d.player.UnityPlayerActivity;
public class AppUIActivity extends UnityPlayerActivity {
int m_CurrentUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
float m_CurrentFontScale = 1;
float m_CurrentScaledDensity = 1;
float m_CurrentDensity = 1;
int m_CurrentDensityDpi = DisplayMetrics.DENSITY_DEFAULT;
Vibrator m_Vibrator;
public boolean IsNightModeDefined() {
return m_CurrentUiMode != Configuration.UI_MODE_NIGHT_UNDEFINED;
}
public boolean IsNightModeEnabled() {
return m_CurrentUiMode == Configuration.UI_MODE_NIGHT_YES;
}
public float FontScale() {
return m_CurrentFontScale;
}
public float ScaledDensity() {
return m_CurrentScaledDensity;
}
public float Density() {
return m_CurrentDensity;
}
public int DensityDpi() {
return m_CurrentDensityDpi;
}
void Vibrate(long[] timings, int repeat) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
m_Vibrator.vibrate(VibrationEffect.createWaveform(timings, repeat));
} else {
m_Vibrator.vibrate(timings, repeat);
}
}
void Vibrate(long[] timings) {
Vibrate(timings, -1);
}
void Vibrate(long timing, int amplitude) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
m_Vibrator.vibrate(VibrationEffect.createOneShot(timing, amplitude));
}
else {
m_Vibrator.vibrate(timing);
}
}
void Vibrate(long timing) {
Vibrate(timing, -1);
}
public void RunHapticFeedback(int hapticFeedbackType) {
switch (hapticFeedbackType) {
case HapticFeedback.LIGHT: {
Vibrate(HapticFeedback.lightTiming, HapticFeedback.lightAmplitude);
break;
}
case HapticFeedback.MEDIUM: {
Vibrate(HapticFeedback.mediumTiming, HapticFeedback.mediumAmplitude);
break;
}
case HapticFeedback.HEAVY: {
Vibrate(HapticFeedback.heavyTiming, HapticFeedback.heavyAmplitude);
break;
}
case HapticFeedback.SUCCESS: {
Vibrate(HapticFeedback.successTimings);
break;
}
case HapticFeedback.ERROR: {
Vibrate(HapticFeedback.errorTimings);
break;
}
case HapticFeedback.WARNING: {
Vibrate(HapticFeedback.warningTimings);
break;
}
case HapticFeedback.SELECTION: {
Vibrate(HapticFeedback.selectionTiming, HapticFeedback.lightAmplitude);
break;
}
default:
break;
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Resources resources = getResources();
Configuration configuration = resources.getConfiguration();
m_CurrentUiMode = configuration.uiMode & Configuration.UI_MODE_NIGHT_MASK;
m_CurrentFontScale = configuration.fontScale;
m_Vibrator = resolveVibrator();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
m_CurrentScaledDensity = displayMetrics.scaledDensity;
m_CurrentDensity = displayMetrics.density;
m_CurrentDensityDpi = displayMetrics.densityDpi;
Log.d("APP UI", "Initial Night Mode: " + m_CurrentUiMode);
Log.d("APP UI", "Initial Font Scale: " + m_CurrentFontScale);
Log.d("APP UI", "Initial Density: " + displayMetrics.density);
Log.d("APP UI", "Initial Scaled Density: " + displayMetrics.scaledDensity);
Log.d("APP UI", "Initial Density DPI: " + displayMetrics.densityDpi);
}
private Vibrator resolveVibrator() {
Vibrator fallback = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
if (Build.VERSION.SDK_INT >= 31) {
try {
Object manager = getSystemService("vibrator_manager");
if (manager != null) {
java.lang.reflect.Method method = manager.getClass().getMethod("getDefaultVibrator");
Object result = method.invoke(manager);
if (result instanceof Vibrator) {
return (Vibrator) result;
}
}
} catch (Throwable throwable) {
Log.w("APP UI", "Unable to access VibratorManager", throwable);
}
}
return fallback;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
boolean configurationChanged = false;
int currentNightMode = newConfig.uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (m_CurrentUiMode != currentNightMode) {
m_CurrentUiMode = currentNightMode;
configurationChanged = true;
switch (m_CurrentUiMode) {
case Configuration.UI_MODE_NIGHT_NO:
Log.d("APP UI", "Light Mode enabled");
break;
case Configuration.UI_MODE_NIGHT_YES:
Log.d("APP UI", "Dark Mode enabled");
break;
case Configuration.UI_MODE_NIGHT_UNDEFINED:
Log.d("APP UI", "Dark Mode is Undefined");
break;
}
}
float currentFontScale = newConfig.fontScale;
if (m_CurrentFontScale != currentFontScale) {
m_CurrentFontScale = currentFontScale;
configurationChanged = true;
Log.d("APP UI", "Changed Font Scale : " + m_CurrentFontScale);
}
float currentScaledDensity = displayMetrics.scaledDensity;
if (m_CurrentScaledDensity != currentScaledDensity) {
m_CurrentScaledDensity = currentScaledDensity;
configurationChanged = true;
Log.d("APP UI", "Changed Scaled Density : " + m_CurrentScaledDensity);
}
float currentDensity = displayMetrics.density;
if (m_CurrentDensity != currentDensity) {
m_CurrentDensity = currentDensity;
configurationChanged = true;
Log.d("APP UI", "Changed Density : " + m_CurrentDensity);
}
int currentDensityDpi = newConfig.densityDpi;
if (m_CurrentDensityDpi != currentDensityDpi) {
m_CurrentDensityDpi = currentDensityDpi;
configurationChanged = true;
Log.d("APP UI", "Changed Density DPI : " + m_CurrentDensityDpi);
}
if (configurationChanged) {
UnityPlayer.UnitySendMessage("AppUIUpdater", "OnAndroidNativeMessageReceived", "configurationChanged");
}
}
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9c5699037016407d5a4ec18d7f0f5f83
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
namespace DeviantMobile.Editor.Android
{
[InitializeOnLoad]
internal static class AppUIActivityVibratorPatch
{
private const string PackageName = "com.unity.dt.app-ui";
private const string RelativeJavaPath = "Runtime/Core/Platform/Android/Plugins/Android/AppUIActivity.java";
private const string PackageAssetPath = "Packages/" + PackageName + "/" + RelativeJavaPath;
static AppUIActivityVibratorPatch()
{
EditorApplication.update += ApplyOnceWhenEditorReady;
}
private static void ApplyOnceWhenEditorReady()
{
EditorApplication.update -= ApplyOnceWhenEditorReady;
TryApplyPatch();
}
[MenuItem("Tools/Android/App UI/Reapply Vibrator Compatibility Patch")]
private static void ReapplyPatchFromMenu()
{
if (TryApplyPatch(force: true))
{
Debug.Log("Reapplied App UI vibrator compatibility patch.");
}
else
{
Debug.LogWarning("App UI vibrator compatibility patch could not be applied. See console for details.");
}
}
private static bool TryApplyPatch(bool force = false)
{
try
{
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/" + PackageName);
if (packageInfo == null)
{
Debug.LogWarning($"Package '{PackageName}' could not be located. Skipping App UI vibrator patch.");
return false;
}
var javaFilePath = Path.Combine(packageInfo.resolvedPath, RelativeJavaPath);
if (!File.Exists(javaFilePath))
{
Debug.LogWarning($"AppUIActivity.java not found at '{javaFilePath}'.");
return false;
}
var currentSource = File.ReadAllText(javaFilePath);
if (!force && currentSource.Contains("Unable to access VibratorManager"))
{
return true; // already patched
}
var templatePath = Path.Combine(Application.dataPath, "Editor/AndroidTemplates/AppUIActivity.java.txt");
if (!File.Exists(templatePath))
{
Debug.LogWarning($"Template Java source missing at '{templatePath}'.");
return false;
}
var patchedSource = File.ReadAllText(templatePath);
File.WriteAllText(javaFilePath, patchedSource);
AssetDatabase.ImportAsset(PackageAssetPath);
Debug.Log("Applied App UI vibrator compatibility patch.");
return true;
}
catch (Exception exception)
{
Debug.LogException(exception);
return false;
}
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d0d69b3c420214735a5d14802dee4af9

View File

@@ -0,0 +1,292 @@
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using CustomAssetBundleSystem;
/// <summary>
/// Unity Editor tool to build AssetBundles and generate catalog.json
/// Menu: Tools > AssetBundles > Build for Android
/// </summary>
public class AssetBundleBuilder : EditorWindow
{
private static string outputPath = "ServerData/AssetBundles/Android";
private static BuildTarget buildTarget = BuildTarget.Android;
[MenuItem("Tools/AssetBundles/Build for Android")]
public static void BuildAssetBundles()
{
Debug.Log("=== Custom AssetBundle Builder ===");
// Create output directory
string fullOutputPath = Path.Combine(Application.dataPath, "..", outputPath);
if (!Directory.Exists(fullOutputPath))
{
Directory.CreateDirectory(fullOutputPath);
Debug.Log($"Created output directory: {fullOutputPath}");
}
// Build options
BuildAssetBundleOptions options = BuildAssetBundleOptions.ChunkBasedCompression |
BuildAssetBundleOptions.StrictMode;
// Build the bundles
Debug.Log($"Building AssetBundles to: {fullOutputPath}");
Debug.Log($"Target platform: {buildTarget}");
AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(
fullOutputPath,
options,
buildTarget
);
if (manifest == null)
{
Debug.LogError("✗ AssetBundle build failed!");
EditorUtility.DisplayDialog("Build Failed", "AssetBundle build failed! Check console for errors.", "OK");
return;
}
Debug.Log("✓ AssetBundles built successfully!");
// Generate the JSON catalog
GenerateCatalog(fullOutputPath, manifest);
Debug.Log("=== Build Complete ===");
EditorUtility.DisplayDialog("Build Complete",
$"AssetBundles built successfully!\n\nOutput: {fullOutputPath}\n\nNext: Upload to VPS",
"OK");
}
/// <summary>
/// Generate catalog.json from built AssetBundles
/// </summary>
private static void GenerateCatalog(string outputPath, AssetBundleManifest manifest)
{
Debug.Log("Generating catalog.json...");
var catalog = new RemoteAssetCatalog
{
version = Application.version,
timestamp = System.DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
baseUrl = "https://assetbundle.uncaptured.co",
bundles = new List<RemoteAssetCatalog.BundleEntry>()
};
// Get all bundle names
string[] allBundles = manifest.GetAllAssetBundles();
foreach (string bundleName in allBundles)
{
string bundlePath = Path.Combine(outputPath, bundleName);
if (!File.Exists(bundlePath))
{
Debug.LogWarning($"Bundle file not found: {bundlePath}");
continue;
}
// Get file info
FileInfo fileInfo = new FileInfo(bundlePath);
// Calculate hash
string hash = CalculateMD5(bundlePath);
// Get scenes in this bundle
var scenePaths = AssetDatabase.GetAssetPathsFromAssetBundle(bundleName);
var sceneNames = new List<string>();
foreach (var scenePath in scenePaths)
{
if (scenePath.EndsWith(".unity"))
{
string sceneName = Path.GetFileNameWithoutExtension(scenePath);
sceneNames.Add(sceneName);
}
}
// Get dependencies
var dependencies = new List<string>();
string[] bundleDeps = manifest.GetAllDependencies(bundleName);
dependencies.AddRange(bundleDeps);
// Create bundle entry
var entry = new RemoteAssetCatalog.BundleEntry
{
bundleName = Path.GetFileNameWithoutExtension(bundleName),
fileName = bundleName,
hash = hash,
size = fileInfo.Length,
scenes = sceneNames,
dependencies = dependencies
};
catalog.bundles.Add(entry);
Debug.Log($" ✓ Added bundle: {bundleName}");
Debug.Log($" - Size: {fileInfo.Length / 1024f / 1024f:F2} MB");
Debug.Log($" - Hash: {hash}");
Debug.Log($" - Scenes: {sceneNames.Count}");
Debug.Log($" - Dependencies: {dependencies.Count}");
}
// Save catalog as JSON
string catalogPath = Path.Combine(outputPath, "catalog.json");
string json = JsonUtility.ToJson(catalog, true);
File.WriteAllText(catalogPath, json);
Debug.Log($"✓ Catalog generated: {catalogPath}");
Debug.Log($" Total bundles: {catalog.bundles.Count}");
Debug.Log($" Catalog size: {json.Length / 1024f:F2} KB");
// Also save a pretty-printed version for debugging
string prettyPath = Path.Combine(outputPath, "catalog_readable.json");
File.WriteAllText(prettyPath, json);
Debug.Log($"✓ Readable catalog: {prettyPath}");
}
/// <summary>
/// Calculate MD5 hash of a file
/// </summary>
private static string CalculateMD5(string filePath)
{
using (var md5 = MD5.Create())
using (var stream = File.OpenRead(filePath))
{
byte[] hash = md5.ComputeHash(stream);
StringBuilder sb = new StringBuilder();
foreach (byte b in hash)
{
sb.Append(b.ToString("x2"));
}
return sb.ToString();
}
}
[MenuItem("Tools/AssetBundles/Open Output Folder")]
public static void OpenOutputFolder()
{
string fullPath = Path.Combine(Application.dataPath, "..", outputPath);
if (Directory.Exists(fullPath))
{
EditorUtility.RevealInFinder(fullPath);
}
else
{
Debug.LogWarning($"Output folder doesn't exist: {fullPath}");
}
}
[MenuItem("Tools/AssetBundles/Clear Cache")]
public static void ClearCache()
{
if (EditorUtility.DisplayDialog("Clear AssetBundle Cache?",
"This will delete all cached AssetBundles. Continue?",
"Yes", "Cancel"))
{
Caching.ClearCache();
Debug.Log("✓ AssetBundle cache cleared");
}
}
[MenuItem("Tools/AssetBundles/Settings")]
public static void ShowSettings()
{
var window = GetWindow<AssetBundleBuilder>("AssetBundle Builder");
window.Show();
}
// Editor Window GUI
void OnGUI()
{
GUILayout.Label("Custom AssetBundle Builder", EditorStyles.boldLabel);
GUILayout.Space(10);
EditorGUILayout.HelpBox(
"This tool builds AssetBundles and generates a JSON catalog for remote loading.\n\n" +
"1. Mark your scenes/assets with AssetBundle names\n" +
"2. Click 'Build for Android'\n" +
"3. Upload ServerData/AssetBundles/Android/ to your VPS",
MessageType.Info);
GUILayout.Space(10);
// Output path
GUILayout.Label("Output Path:", EditorStyles.boldLabel);
outputPath = EditorGUILayout.TextField(outputPath);
GUILayout.Space(5);
// Build target
GUILayout.Label("Build Target:", EditorStyles.boldLabel);
buildTarget = (BuildTarget)EditorGUILayout.EnumPopup(buildTarget);
GUILayout.Space(10);
// Build button
if (GUILayout.Button("Build AssetBundles", GUILayout.Height(40)))
{
BuildAssetBundles();
}
GUILayout.Space(5);
// Utility buttons
using (new EditorGUILayout.HorizontalScope())
{
if (GUILayout.Button("Open Output Folder"))
{
OpenOutputFolder();
}
if (GUILayout.Button("Clear Cache"))
{
ClearCache();
}
}
GUILayout.Space(20);
// Info section
GUILayout.Label("Current Settings:", EditorStyles.boldLabel);
EditorGUILayout.LabelField("Unity Version:", Application.unityVersion);
EditorGUILayout.LabelField("App Version:", Application.version);
EditorGUILayout.LabelField("Company:", Application.companyName);
GUILayout.Space(10);
// Quick guide
if (GUILayout.Button("Show Quick Guide"))
{
ShowQuickGuide();
}
}
private static void ShowQuickGuide()
{
EditorUtility.DisplayDialog("AssetBundle Quick Guide",
"STEP 1: Mark Assets for Bundling\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"• Select your Game scene in Project window\n" +
"• In Inspector, find 'AssetBundle' dropdown (bottom)\n" +
"• Select 'New...' and name it 'gamescene'\n\n" +
"STEP 2: Build AssetBundles\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"• Tools → AssetBundles → Build for Android\n" +
"• Wait for build to complete\n" +
"• Files created in ServerData/AssetBundles/Android/\n\n" +
"STEP 3: Upload to VPS\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"• scp -r ServerData/AssetBundles/Android/* user@server:/var/www/AssetBundles/Android/\n\n" +
"STEP 4: Update Your Code\n" +
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" +
"• Replace Addressables calls with CustomAssetBundleSceneLoader\n" +
"• Build APK and test!",
"Got it!");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8bb3b29337992a0e180b70df6883c57e

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4316661c7d34ec384b8b9b9f0bb914a0

View File

@@ -0,0 +1,128 @@
using UnityEditor;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEngine;
using System.IO;
using System.Text.RegularExpressions;
/// <summary>
/// WORKAROUND for Unity 6 bug where m_EnableJsonCatalog is ignored
/// This script patches settings.json to force JSON catalog loading
/// </summary>
public class ForceJsonCatalogWorkaround
{
[MenuItem("Addressables/Fix/Patch settings.json for JSON Catalogs")]
public static void PatchSettingsForJsonCatalog()
{
Debug.Log("[ForceJsonCatalog] WORKAROUND: Patching settings.json to use .json catalogs instead of .bin");
// Find all settings.json files
string[] settingsFiles = Directory.GetFiles("Library/com.unity.addressables", "settings.json", SearchOption.AllDirectories);
if (settingsFiles.Length == 0)
{
Debug.LogWarning("[ForceJsonCatalog] No settings.json found. Build Addressables content first!");
return;
}
foreach (string settingsPath in settingsFiles)
{
PatchSettingsFile(settingsPath);
}
// Also patch ServerData if it exists
string[] serverSettingsFiles = Directory.GetFiles("ServerData", "settings.json", SearchOption.AllDirectories);
foreach (string settingsPath in serverSettingsFiles)
{
PatchSettingsFile(settingsPath);
}
// Rename .bin catalogs to .json
RenameBinCatalogsToJson();
Debug.Log("[ForceJsonCatalog] ✓ Patch complete! settings.json now references .json catalogs");
Debug.Log("[ForceJsonCatalog] Next steps:");
Debug.Log(" 1. Rebuild your APK");
Debug.Log(" 2. Upload ServerData/Android/ contents to your server");
Debug.Log(" 3. Test on device");
}
private static void PatchSettingsFile(string settingsPath)
{
try
{
string content = File.ReadAllText(settingsPath);
// Replace all references from .bin to .json in catalog URLs
string patchedContent = content.Replace("catalog.bin", "catalog.json");
patchedContent = patchedContent.Replace("catalog_2.0.0.bin", "catalog_2.0.0.json");
// Also patch the m_IsLocalCatalogInBundle if needed
patchedContent = Regex.Replace(patchedContent,
@"""m_IsLocalCatalogInBundle"":\s*true",
"\"m_IsLocalCatalogInBundle\":false");
File.WriteAllText(settingsPath, patchedContent);
Debug.Log($"[ForceJsonCatalog] ✓ Patched: {settingsPath}");
}
catch (System.Exception ex)
{
Debug.LogError($"[ForceJsonCatalog] Error patching {settingsPath}: {ex.Message}");
}
}
private static void RenameBinCatalogsToJson()
{
// Rename catalog.bin files to catalog.json
string[] paths = new string[] { "Library/com.unity.addressables", "ServerData" };
foreach (string basePath in paths)
{
if (!Directory.Exists(basePath)) continue;
string[] binFiles = Directory.GetFiles(basePath, "catalog*.bin", SearchOption.AllDirectories);
foreach (string binFile in binFiles)
{
string jsonFile = binFile.Replace(".bin", ".json");
try
{
// Copy (don't delete .bin yet for safety)
File.Copy(binFile, jsonFile, true);
Debug.Log($"[ForceJsonCatalog] ✓ Created: {Path.GetFileName(jsonFile)}");
}
catch (System.Exception ex)
{
Debug.LogError($"[ForceJsonCatalog] Error renaming {binFile}: {ex.Message}");
}
}
}
AssetDatabase.Refresh();
}
[MenuItem("Addressables/Fix/Build + Patch (Complete Fix)")]
public static void BuildAndPatchForJsonCatalog()
{
Debug.Log("[ForceJsonCatalog] Starting complete build with JSON catalog fix...");
// 1. Build Addressables normally
Debug.Log("[ForceJsonCatalog] Step 1/3: Building Addressables content...");
AddressableAssetSettings.BuildPlayerContent();
// 2. Patch settings.json files
Debug.Log("[ForceJsonCatalog] Step 2/3: Patching settings.json files...");
PatchSettingsForJsonCatalog();
// 3. Done
Debug.Log("[ForceJsonCatalog] ============================================");
Debug.Log("[ForceJsonCatalog] ✓ BUILD COMPLETE WITH JSON CATALOG FIX!");
Debug.Log("[ForceJsonCatalog] ============================================");
Debug.Log("[ForceJsonCatalog] Next steps:");
Debug.Log("[ForceJsonCatalog] 1. Rebuild APK in Unity (File → Build Settings → Build)");
Debug.Log("[ForceJsonCatalog] 2. Upload ServerData/Android/ to server");
Debug.Log("[ForceJsonCatalog] 3. Install APK and test");
Debug.Log("[ForceJsonCatalog] ============================================");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a75aa305a40bff91ab51e23adc8a79a2