com.beamable 5.1.0-PREVIEW.RC7 → 5.1.0-PREVIEW.RC8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.attestation.p7m CHANGED
Binary file
package/CHANGELOG.md CHANGED
@@ -28,6 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
28
28
  - "Beam Library" Window is now "Beam Samples" Window and was updated with latest docs and samples
29
29
  - AdminFlow prefab (ConsoleFlow) was removed from the Beam Samples Window and is now considered Deprecated
30
30
  - Now Renamed content entries will be shown as Modified Renamed in the Content Manager Window rather than a New and Deleted entries.
31
+ - Default assets and content are imported manually on first visit to Content Manager
31
32
 
32
33
  ## [5.0.1] - 2026-04-02
33
34
  ### Fixed
@@ -83,6 +83,23 @@ namespace Beamable.Editor.ContentService
83
83
  public bool isReloading;
84
84
  public List<string> availableManifestIds;
85
85
 
86
+ /// <summary>
87
+ /// True once the most recent <see cref="Reload"/> has finished fetching the realm's remote
88
+ /// manifest list. Until this is true the realm's emptiness is unknown (still loading).
89
+ /// </summary>
90
+ public bool RemoteManifestsLoaded { get; private set; }
91
+ /// <summary>
92
+ /// True when the last remote-manifest fetch errored. Used so the Content Manager does not
93
+ /// misread a transient failure as "this realm is empty".
94
+ /// </summary>
95
+ public bool RemoteManifestsErrored { get; private set; }
96
+ /// <summary>
97
+ /// Number of remote manifests on the realm as of the last successful fetch. Zero means the
98
+ /// realm has never had content published — the signal used to offer the opt-in default
99
+ /// content import (see DefaultContentImporter).
100
+ /// </summary>
101
+ public int RemoteManifestCount { get; private set; }
102
+
86
103
  public Dictionary<string, LocalContentManifestEntry> EntriesCache { get; }
87
104
 
88
105
  private readonly Dictionary<string, ContentObject> _contentScriptableCache;
@@ -735,10 +752,10 @@ namespace Beamable.Editor.ContentService
735
752
  _contentWatcher.OnProgressStreamContentPsProgressMessage(dp => { LatestProgressUpdate = dp.data; });
736
753
  _ = _contentWatcher.Command.Run();
737
754
 
738
- bool addedAnyDefault = false;
755
+ RemoteManifestsLoaded = false;
756
+ RemoteManifestsErrored = false;
739
757
  var getAvailableManifestsPromise = _cli.ContentListManifests(new ContentListManifestsArgs()).OnStreamContentListManifestsCommandResults(dp =>
740
758
  {
741
-
742
759
  var manifestIds = new HashSet<string>();
743
760
  foreach (var id in dp.data.localManifests)
744
761
  {
@@ -749,41 +766,25 @@ namespace Beamable.Editor.ContentService
749
766
  {
750
767
  manifestIds.Add(id);
751
768
  }
752
-
753
- if (dp.data.remoteManifests.Count == 0)
754
- {
755
- // If no remote manifest on remote, it means that it is the first time that the customer is using this realm
756
- // If so, we need to create the default contents
757
- string[] guids = BeamableAssetDatabase.FindAssets<ContentObject>(new[] {Constants.Directories.DEFAULT_DATA_DIR});
758
- foreach (string guid in guids)
759
- {
760
- string path = AssetDatabase.GUIDToAssetPath(guid);
761
- ContentObject obj = AssetDatabase.LoadAssetAtPath<ContentObject>(path);
762
769
 
763
- if (obj == null)
764
- continue;
765
-
766
- string fileName = Path.GetFileNameWithoutExtension(path);
767
- obj.SetContentName(fileName);
768
- SaveContent(obj);
769
- addedAnyDefault = true;
770
- }
771
- }
770
+ // A realm with zero remote manifests has never had content published to it.
771
+ // Seeding default content is deferred to an explicit, user-initiated action
772
+ // (see DefaultContentImporter) so we never mutate the AssetDatabase / Addressables
773
+ // at init time. Here we only record the signal the Content Manager uses to offer
774
+ // the opt-in import prompt.
775
+ RemoteManifestCount = dp.data.remoteManifests.Count;
776
+ RemoteManifestsLoaded = true;
772
777
 
773
778
  availableManifestIds = manifestIds.ToList();
774
779
  }).OnError(dp =>
775
780
  {
781
+ RemoteManifestsErrored = true;
776
782
  Debug.LogError(dp.data.message);
777
783
  }).Run();
778
-
784
+
779
785
  await manifestIsFetchedTaskCompletion.Task;
780
786
  await getAvailableManifestsPromise;
781
787
 
782
- if (addedAnyDefault)
783
- {
784
- await PublishContents();
785
- }
786
-
787
788
  ManifestChangedCount++;
788
789
  }
789
790
  finally
@@ -0,0 +1,248 @@
1
+ using Beamable.Common;
2
+ using Beamable.Common.Content;
3
+ using System.Collections.Generic;
4
+ using System.IO;
5
+ using System.Threading.Tasks;
6
+ using UnityEditor;
7
+ using UnityEditor.AddressableAssets;
8
+ using UnityEditor.AddressableAssets.Settings;
9
+ using UnityEditor.AddressableAssets.Settings.GroupSchemas;
10
+ using UnityEngine;
11
+
12
+ namespace Beamable.Editor.ContentService
13
+ {
14
+ /// <summary>
15
+ /// Opt-in, user-initiated seeding of Beamable's default content (currency gems/coins) into a
16
+ /// realm that has never had content published.
17
+ ///
18
+ /// This replaces the old behaviour where <see cref="CliContentService.Reload"/> silently seeded
19
+ /// and published default content at editor init time. That ran AssetDatabase / Addressables
20
+ /// mutation during initialization, which was risky (potential refresh loops). Seeding now only
21
+ /// happens when the user explicitly asks for it — via the Content Manager empty-state prompt or
22
+ /// the "Import Default Content" menu item.
23
+ ///
24
+ /// The currency icons are Addressable sprite references. The seed sprites ship in the
25
+ /// Unity-ignored <c>DefaultAssets~</c> folder with pinned-GUID meta files; importing copies them
26
+ /// into the project (where the pinned GUID registers for the first time, avoiding a collision),
27
+ /// marks them addressable, and only then seeds the content — whose serialized references already
28
+ /// point at the pinned GUIDs.
29
+ /// </summary>
30
+ public static class DefaultContentImporter
31
+ {
32
+ private const string DISMISS_PREF_PREFIX = "beamable.contentmanager.defaultcontent.dismissed";
33
+
34
+ /// <summary>Per-realm EditorPrefs key controlling whether the in-window prompt is shown.</summary>
35
+ public static string DismissKey(string cid, string pid) => $"{DISMISS_PREF_PREFIX}.{cid}.{pid}";
36
+
37
+ public static bool IsDismissed(string cid, string pid)
38
+ {
39
+ if (string.IsNullOrEmpty(cid) || string.IsNullOrEmpty(pid))
40
+ return false;
41
+ return EditorPrefs.GetBool(DismissKey(cid, pid), false);
42
+ }
43
+
44
+ public static void SetDismissed(string cid, string pid, bool dismissed = true)
45
+ {
46
+ if (string.IsNullOrEmpty(cid) || string.IsNullOrEmpty(pid))
47
+ return;
48
+ EditorPrefs.SetBool(DismissKey(cid, pid), dismissed);
49
+ }
50
+
51
+ [MenuItem(Constants.MenuItems.Windows.Paths.MENU_ITEM_PATH_WINDOW_BEAMABLE_UTILITIES + "/Import Default Content")]
52
+ public static async void ImportDefaultContent_MenuItem()
53
+ {
54
+ bool confirmed = EditorUtility.DisplayDialog(
55
+ "Import Default Content",
56
+ "This will create Beamable's default content (gems & coins currencies) as local content, " +
57
+ "copying their icon sprites into Assets/Beamable/DefaultAssets and registering them as " +
58
+ "Addressables.",
59
+ "Import",
60
+ "Cancel");
61
+
62
+ if (confirmed)
63
+ {
64
+ await ImportDefaultContent();
65
+ }
66
+ }
67
+
68
+ /// <summary>
69
+ /// Shared import routine used by both the menu item and the Content Manager prompt. Seeds the
70
+ /// default content as local content; the user can review and publish it themselves.
71
+ /// </summary>
72
+ public static async Task ImportDefaultContent()
73
+ {
74
+ var api = BeamEditorContext.Default;
75
+ await api.InitializePromise;
76
+ var contentService = api.ServiceScope.GetService<CliContentService>();
77
+
78
+ // 1. Ensure Addressables settings exist (GetSettings(true) creates them if missing).
79
+ var settings = AddressableAssetSettingsDefaultObject.GetSettings(true);
80
+ if (settings == null)
81
+ {
82
+ EditorUtility.DisplayDialog(
83
+ "Addressables Required",
84
+ "Beamable's default content uses Unity Addressables for its currency icons, but the " +
85
+ "Addressables settings could not be created. Please configure the Addressables package " +
86
+ "and try again.",
87
+ "OK");
88
+ return;
89
+ }
90
+
91
+ // 2. Copy seed sprites into the project and register them as addressables (idempotent).
92
+ if (!EnsureDefaultAssetsImported(settings))
93
+ {
94
+ return;
95
+ }
96
+
97
+ // 3. Seed the content locally. The default content's serialized icon references already
98
+ // point at the pinned GUIDs, so no per-asset remap is needed.
99
+ SeedDefaultContent(contentService);
100
+
101
+ // 4. Refresh the Content Manager view. The seeded content is left local for the user to
102
+ // review and publish on their own terms.
103
+ await contentService.Reload();
104
+ }
105
+
106
+ /// <summary>
107
+ /// Copies the seed sprites out of the Unity-ignored source folder into the project and
108
+ /// ensures each is an entry in the "Beamable Assets" Addressables group. Idempotent: sprites
109
+ /// already present (by pinned GUID) are reused rather than re-copied.
110
+ /// </summary>
111
+ private static bool EnsureDefaultAssetsImported(AddressableAssetSettings settings)
112
+ {
113
+ var sourceRoot = ResolveSourceRoot();
114
+ if (string.IsNullOrEmpty(sourceRoot) || !Directory.Exists(sourceRoot))
115
+ {
116
+ EditorUtility.DisplayDialog(
117
+ "Default Content Unavailable",
118
+ $"Could not locate the default content seed assets at '{Constants.Directories.DEFAULT_ASSET_SOURCE_DIR}'.",
119
+ "OK");
120
+ return false;
121
+ }
122
+
123
+ var knownGuids = new List<string>();
124
+ var copiedAnything = false;
125
+
126
+ foreach (var typeDir in Directory.GetDirectories(sourceRoot))
127
+ {
128
+ var typeName = Path.GetFileName(typeDir); // e.g. "currency"
129
+ var destDir = $"{Constants.Directories.ASSET_DIR}/{typeName}";
130
+
131
+ foreach (var src in Directory.GetFiles(typeDir))
132
+ {
133
+ if (src.EndsWith(".meta"))
134
+ continue;
135
+
136
+ var metaSrc = src + ".meta";
137
+ if (!File.Exists(metaSrc))
138
+ continue; // a pinned-GUID meta is required
139
+
140
+ var knownGuid = ReadGuidFromMeta(metaSrc);
141
+ if (string.IsNullOrEmpty(knownGuid))
142
+ continue;
143
+
144
+ knownGuids.Add(knownGuid);
145
+
146
+ // Short-circuit: already imported (by pinned GUID) -> reuse, don't duplicate.
147
+ var existingPath = AssetDatabase.GUIDToAssetPath(knownGuid);
148
+ if (!string.IsNullOrEmpty(existingPath) && File.Exists(existingPath))
149
+ continue;
150
+
151
+ Directory.CreateDirectory(destDir);
152
+ var fileName = Path.GetFileName(src);
153
+ var dest = Path.Combine(destDir, fileName);
154
+ File.Copy(src, dest, true);
155
+ // Copy the pinned-GUID meta so the imported asset keeps the known GUID.
156
+ File.Copy(metaSrc, dest + ".meta", true);
157
+ copiedAnything = true;
158
+ }
159
+ }
160
+
161
+ if (copiedAnything)
162
+ {
163
+ AssetDatabase.Refresh();
164
+ }
165
+
166
+ // Ensure the addressables group exists, then ensure each seed sprite is an entry in it.
167
+ var group = settings.FindGroup(Constants.BEAMABLE_ASSET_GROUP);
168
+ if (group == null)
169
+ {
170
+ group = settings.CreateGroup(
171
+ Constants.BEAMABLE_ASSET_GROUP,
172
+ setAsDefaultGroup: false,
173
+ readOnly: false,
174
+ postEvent: true,
175
+ schemasToCopy: new List<AddressableAssetGroupSchema>(),
176
+ typeof(ContentUpdateGroupSchema), typeof(BundledAssetGroupSchema));
177
+ }
178
+
179
+ var entries = new List<AddressableAssetEntry>();
180
+ foreach (var knownGuid in knownGuids)
181
+ {
182
+ var path = AssetDatabase.GUIDToAssetPath(knownGuid);
183
+ if (string.IsNullOrEmpty(path))
184
+ {
185
+ Debug.LogWarning($"[Default Content] Seed sprite with GUID {knownGuid} did not import; its currency icon may not resolve.");
186
+ continue;
187
+ }
188
+
189
+ var entry = settings.CreateOrMoveEntry(knownGuid, group);
190
+ if (entry != null)
191
+ entries.Add(entry);
192
+ }
193
+
194
+ if (entries.Count > 0)
195
+ {
196
+ settings.SetDirty(AddressableAssetSettings.ModificationEvent.EntryMoved, entries, true);
197
+ }
198
+
199
+ return true;
200
+ }
201
+
202
+ /// <summary>
203
+ /// Loads the default content objects shipped in the package and saves them as local content.
204
+ /// </summary>
205
+ private static void SeedDefaultContent(CliContentService contentService)
206
+ {
207
+ string[] guids = BeamableAssetDatabase.FindAssets<ContentObject>(new[] { Constants.Directories.DEFAULT_DATA_DIR });
208
+ foreach (string guid in guids)
209
+ {
210
+ string path = AssetDatabase.GUIDToAssetPath(guid);
211
+ ContentObject obj = AssetDatabase.LoadAssetAtPath<ContentObject>(path);
212
+ if (obj == null)
213
+ continue;
214
+
215
+ string fileName = Path.GetFileNameWithoutExtension(path);
216
+ obj.SetContentName(fileName);
217
+ contentService.SaveContent(obj);
218
+ }
219
+ }
220
+
221
+ /// <summary>
222
+ /// Resolves the seed-asset source folder to an absolute path. The folder is "~"-suffixed so
223
+ /// Unity ignores it; we read it with System.IO. <see cref="Path.GetFullPath"/> resolves the
224
+ /// "Packages/com.beamable/..." virtual path for embedded/local packages.
225
+ /// </summary>
226
+ private static string ResolveSourceRoot()
227
+ {
228
+ var relative = Constants.Directories.DEFAULT_ASSET_SOURCE_DIR;
229
+ if (Directory.Exists(relative))
230
+ return relative;
231
+
232
+ var full = Path.GetFullPath(relative);
233
+ return Directory.Exists(full) ? full : null;
234
+ }
235
+
236
+ private static string ReadGuidFromMeta(string metaPath)
237
+ {
238
+ foreach (var line in File.ReadLines(metaPath))
239
+ {
240
+ var trimmed = line.Trim();
241
+ if (trimmed.StartsWith("guid:"))
242
+ return trimmed.Substring("guid:".Length).Trim();
243
+ }
244
+
245
+ return null;
246
+ }
247
+ }
248
+ }
@@ -0,0 +1,2 @@
1
+ fileFormatVersion: 2
2
+ guid: a0e852440982b4314ad6589271754b95
@@ -0,0 +1,135 @@
1
+ fileFormatVersion: 2
2
+ guid: a7796303ede2490ea179355045c77b7e
3
+ TextureImporter:
4
+ internalIDToNameTable: []
5
+ externalObjects: {}
6
+ serializedVersion: 12
7
+ mipmaps:
8
+ mipMapMode: 0
9
+ enableMipMap: 1
10
+ sRGBTexture: 1
11
+ linearTexture: 0
12
+ fadeOut: 0
13
+ borderMipMap: 0
14
+ mipMapsPreserveCoverage: 0
15
+ alphaTestReferenceValue: 0.5
16
+ mipMapFadeDistanceStart: 1
17
+ mipMapFadeDistanceEnd: 3
18
+ bumpmap:
19
+ convertToNormalMap: 0
20
+ externalNormalMap: 0
21
+ heightScale: 0.25
22
+ normalMapFilter: 0
23
+ isReadable: 0
24
+ streamingMipmaps: 0
25
+ streamingMipmapsPriority: 0
26
+ vTOnly: 0
27
+ ignoreMasterTextureLimit: 0
28
+ grayScaleToAlpha: 0
29
+ generateCubemap: 6
30
+ cubemapConvolution: 0
31
+ seamlessCubemap: 0
32
+ textureFormat: 1
33
+ maxTextureSize: 2048
34
+ textureSettings:
35
+ serializedVersion: 2
36
+ filterMode: 1
37
+ aniso: 1
38
+ mipBias: 0
39
+ wrapU: 1
40
+ wrapV: 1
41
+ wrapW: 1
42
+ nPOTScale: 0
43
+ lightmap: 0
44
+ compressionQuality: 50
45
+ spriteMode: 1
46
+ spriteExtrude: 1
47
+ spriteMeshType: 1
48
+ alignment: 0
49
+ spritePivot: {x: 0.5, y: 0.5}
50
+ spritePixelsToUnits: 100
51
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
52
+ spriteGenerateFallbackPhysicsShape: 1
53
+ alphaUsage: 1
54
+ alphaIsTransparency: 1
55
+ spriteTessellationDetail: -1
56
+ textureType: 8
57
+ textureShape: 1
58
+ singleChannelComponent: 0
59
+ flipbookRows: 1
60
+ flipbookColumns: 1
61
+ maxTextureSizeSet: 0
62
+ compressionQualitySet: 0
63
+ textureFormatSet: 0
64
+ ignorePngGamma: 0
65
+ applyGammaDecoding: 0
66
+ cookieLightType: 0
67
+ platformSettings:
68
+ - serializedVersion: 3
69
+ buildTarget: DefaultTexturePlatform
70
+ maxTextureSize: 2048
71
+ resizeAlgorithm: 0
72
+ textureFormat: -1
73
+ textureCompression: 1
74
+ compressionQuality: 50
75
+ crunchedCompression: 0
76
+ allowsAlphaSplitting: 0
77
+ overridden: 0
78
+ androidETC2FallbackOverride: 0
79
+ forceMaximumCompressionQuality_BC6H_BC7: 0
80
+ - serializedVersion: 3
81
+ buildTarget: Standalone
82
+ maxTextureSize: 2048
83
+ resizeAlgorithm: 0
84
+ textureFormat: -1
85
+ textureCompression: 1
86
+ compressionQuality: 50
87
+ crunchedCompression: 0
88
+ allowsAlphaSplitting: 0
89
+ overridden: 0
90
+ androidETC2FallbackOverride: 0
91
+ forceMaximumCompressionQuality_BC6H_BC7: 0
92
+ - serializedVersion: 3
93
+ buildTarget: Server
94
+ maxTextureSize: 2048
95
+ resizeAlgorithm: 0
96
+ textureFormat: -1
97
+ textureCompression: 1
98
+ compressionQuality: 50
99
+ crunchedCompression: 0
100
+ allowsAlphaSplitting: 0
101
+ overridden: 0
102
+ androidETC2FallbackOverride: 0
103
+ forceMaximumCompressionQuality_BC6H_BC7: 0
104
+ - serializedVersion: 3
105
+ buildTarget: Android
106
+ maxTextureSize: 2048
107
+ resizeAlgorithm: 0
108
+ textureFormat: -1
109
+ textureCompression: 1
110
+ compressionQuality: 50
111
+ crunchedCompression: 0
112
+ allowsAlphaSplitting: 0
113
+ overridden: 0
114
+ androidETC2FallbackOverride: 0
115
+ forceMaximumCompressionQuality_BC6H_BC7: 0
116
+ spriteSheet:
117
+ serializedVersion: 2
118
+ sprites: []
119
+ outline: []
120
+ physicsShape: []
121
+ bones: []
122
+ spriteID: 0b1370abfc5b4c3b81bf007ca6b9664a
123
+ internalID: 0
124
+ vertices: []
125
+ indices:
126
+ edges: []
127
+ weights: []
128
+ secondaryTextures: []
129
+ nameFileIdTable: {}
130
+ spritePackingTag:
131
+ pSDRemoveMatte: 0
132
+ pSDShowRemoveMatteOption: 0
133
+ userData:
134
+ assetBundleName:
135
+ assetBundleVariant:
@@ -0,0 +1,135 @@
1
+ fileFormatVersion: 2
2
+ guid: 5a08d920469e4ee0a87a745623583807
3
+ TextureImporter:
4
+ internalIDToNameTable: []
5
+ externalObjects: {}
6
+ serializedVersion: 12
7
+ mipmaps:
8
+ mipMapMode: 0
9
+ enableMipMap: 1
10
+ sRGBTexture: 1
11
+ linearTexture: 0
12
+ fadeOut: 0
13
+ borderMipMap: 0
14
+ mipMapsPreserveCoverage: 0
15
+ alphaTestReferenceValue: 0.5
16
+ mipMapFadeDistanceStart: 1
17
+ mipMapFadeDistanceEnd: 3
18
+ bumpmap:
19
+ convertToNormalMap: 0
20
+ externalNormalMap: 0
21
+ heightScale: 0.25
22
+ normalMapFilter: 0
23
+ isReadable: 0
24
+ streamingMipmaps: 0
25
+ streamingMipmapsPriority: 0
26
+ vTOnly: 0
27
+ ignoreMasterTextureLimit: 0
28
+ grayScaleToAlpha: 0
29
+ generateCubemap: 6
30
+ cubemapConvolution: 0
31
+ seamlessCubemap: 0
32
+ textureFormat: 1
33
+ maxTextureSize: 2048
34
+ textureSettings:
35
+ serializedVersion: 2
36
+ filterMode: 1
37
+ aniso: 1
38
+ mipBias: 0
39
+ wrapU: 1
40
+ wrapV: 1
41
+ wrapW: 0
42
+ nPOTScale: 0
43
+ lightmap: 0
44
+ compressionQuality: 50
45
+ spriteMode: 1
46
+ spriteExtrude: 1
47
+ spriteMeshType: 1
48
+ alignment: 0
49
+ spritePivot: {x: 0.5, y: 0.5}
50
+ spritePixelsToUnits: 100
51
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
52
+ spriteGenerateFallbackPhysicsShape: 1
53
+ alphaUsage: 1
54
+ alphaIsTransparency: 1
55
+ spriteTessellationDetail: -1
56
+ textureType: 8
57
+ textureShape: 1
58
+ singleChannelComponent: 0
59
+ flipbookRows: 1
60
+ flipbookColumns: 1
61
+ maxTextureSizeSet: 0
62
+ compressionQualitySet: 0
63
+ textureFormatSet: 0
64
+ ignorePngGamma: 0
65
+ applyGammaDecoding: 1
66
+ cookieLightType: 1
67
+ platformSettings:
68
+ - serializedVersion: 3
69
+ buildTarget: DefaultTexturePlatform
70
+ maxTextureSize: 2048
71
+ resizeAlgorithm: 0
72
+ textureFormat: -1
73
+ textureCompression: 1
74
+ compressionQuality: 50
75
+ crunchedCompression: 0
76
+ allowsAlphaSplitting: 0
77
+ overridden: 0
78
+ androidETC2FallbackOverride: 0
79
+ forceMaximumCompressionQuality_BC6H_BC7: 0
80
+ - serializedVersion: 3
81
+ buildTarget: Standalone
82
+ maxTextureSize: 2048
83
+ resizeAlgorithm: 0
84
+ textureFormat: -1
85
+ textureCompression: 1
86
+ compressionQuality: 50
87
+ crunchedCompression: 0
88
+ allowsAlphaSplitting: 0
89
+ overridden: 0
90
+ androidETC2FallbackOverride: 0
91
+ forceMaximumCompressionQuality_BC6H_BC7: 0
92
+ - serializedVersion: 3
93
+ buildTarget: Server
94
+ maxTextureSize: 2048
95
+ resizeAlgorithm: 0
96
+ textureFormat: -1
97
+ textureCompression: 1
98
+ compressionQuality: 50
99
+ crunchedCompression: 0
100
+ allowsAlphaSplitting: 0
101
+ overridden: 0
102
+ androidETC2FallbackOverride: 0
103
+ forceMaximumCompressionQuality_BC6H_BC7: 0
104
+ - serializedVersion: 3
105
+ buildTarget: Android
106
+ maxTextureSize: 2048
107
+ resizeAlgorithm: 0
108
+ textureFormat: -1
109
+ textureCompression: 1
110
+ compressionQuality: 50
111
+ crunchedCompression: 0
112
+ allowsAlphaSplitting: 0
113
+ overridden: 0
114
+ androidETC2FallbackOverride: 0
115
+ forceMaximumCompressionQuality_BC6H_BC7: 0
116
+ spriteSheet:
117
+ serializedVersion: 2
118
+ sprites: []
119
+ outline: []
120
+ physicsShape: []
121
+ bones: []
122
+ spriteID: dda64ca25ea5446da563ba3807929558
123
+ internalID: 0
124
+ vertices: []
125
+ indices:
126
+ edges: []
127
+ weights: []
128
+ secondaryTextures: []
129
+ nameFileIdTable: {}
130
+ spritePackingTag:
131
+ pSDRemoveMatte: 0
132
+ pSDShowRemoveMatteOption: 0
133
+ userData:
134
+ assetBundleName:
135
+ assetBundleVariant:
@@ -16,7 +16,7 @@ MonoBehaviour:
16
16
  - base
17
17
  _serializedValidationGUID: 00000000-0000-0000-0000-000000000008
18
18
  icon:
19
- m_AssetGUID: 819869afaafce0144b1fba88a5e4b040
19
+ m_AssetGUID: a7796303ede2490ea179355045c77b7e
20
20
  m_SubObjectName:
21
21
  m_SubObjectType:
22
22
  m_EditorAssetChanged: 0
@@ -16,7 +16,7 @@ MonoBehaviour:
16
16
  - base
17
17
  _serializedValidationGUID: 00000000-0000-0000-0000-000000000009
18
18
  icon:
19
- m_AssetGUID: ddded4a9a76c8ff439eab5570826ad89
19
+ m_AssetGUID: 5a08d920469e4ee0a87a745623583807
20
20
  m_SubObjectName:
21
21
  m_SubObjectType:
22
22
  m_EditorAssetChanged: 0
@@ -33,6 +33,7 @@ namespace Beamable.Editor.UI.ContentWindow
33
33
  private int _lastManifestChangedCount;
34
34
  private int _lastProgressUpdateVersion;
35
35
  private EditorGUISplitView _mainSplitter;
36
+ private bool _importingDefaultContent;
36
37
 
37
38
  static ContentWindow()
38
39
  {
@@ -215,8 +216,20 @@ namespace Beamable.Editor.UI.ContentWindow
215
216
  return;
216
217
  }
217
218
 
218
-
219
-
219
+ if (_importingDefaultContent)
220
+ {
221
+ DrawBlockLoading("Importing default content...");
222
+ return;
223
+ }
224
+
225
+ if (ShouldShowDefaultContentPrompt())
226
+ {
227
+ DrawDefaultContentPrompt();
228
+ return;
229
+ }
230
+
231
+
232
+
220
233
  EditorGUILayout.BeginVertical();
221
234
  _horizontalScrollPosition = EditorGUILayout.BeginScrollView(_horizontalScrollPosition);
222
235
 
@@ -356,6 +369,91 @@ namespace Beamable.Editor.UI.ContentWindow
356
369
  }
357
370
  }
358
371
 
372
+ /// <summary>
373
+ /// Whether to offer the opt-in default-content import. Only shown when the realm's remote
374
+ /// manifest is confirmed empty (not merely still loading or errored), there is no content
375
+ /// at all yet, and the user hasn't dismissed the prompt for this realm.
376
+ /// </summary>
377
+ private bool ShouldShowDefaultContentPrompt()
378
+ {
379
+ if (_contentService == null)
380
+ return false;
381
+ if (!_contentService.RemoteManifestsLoaded || _contentService.RemoteManifestsErrored)
382
+ return false;
383
+ if (_contentService.RemoteManifestCount > 0)
384
+ return false;
385
+ if (_contentService.EntriesCache.Count > 0)
386
+ return false;
387
+
388
+ var cid = _cli?.CurrentRealm?.Cid;
389
+ var pid = _cli?.CurrentRealm?.Pid;
390
+ if (string.IsNullOrEmpty(cid) || string.IsNullOrEmpty(pid))
391
+ return false;
392
+
393
+ return !DefaultContentImporter.IsDismissed(cid, pid);
394
+ }
395
+
396
+ private void DrawDefaultContentPrompt()
397
+ {
398
+ EditorGUILayout.BeginVertical(new GUIStyle(EditorStyles.helpBox)
399
+ {
400
+ padding = new RectOffset(12, 12, 12, 12),
401
+ margin = new RectOffset(10, 10, 10, 10)
402
+ });
403
+
404
+ EditorGUILayout.TextArea(
405
+ "This realm doesn't have any content yet. " +
406
+ "\n\n" +
407
+ "Would you like to import Beamable's default content? This creates the gems and coins " +
408
+ "currencies and copies their icon sprites into Assets/Beamable/DefaultAssets, " +
409
+ "registering them as Addressables. The content is added locally so you can review and " +
410
+ "publish it yourself.",
411
+ new GUIStyle(EditorStyles.label) { wordWrap = true });
412
+
413
+ EditorGUILayout.BeginHorizontal(new GUIStyle
414
+ {
415
+ margin = new RectOffset(0, 0, 12, 12)
416
+ });
417
+
418
+ EditorGUILayout.Space(5, true);
419
+ EditorGUILayout.Space(5, true);
420
+
421
+ var clickedNotNow = BeamGUI.CancelButton("Not now");
422
+ var clickedImport = BeamGUI.PrimaryButton(new GUIContent("Import"));
423
+
424
+ EditorGUILayout.EndHorizontal();
425
+ EditorGUILayout.EndVertical();
426
+
427
+ if (clickedNotNow)
428
+ {
429
+ DefaultContentImporter.SetDismissed(_cli?.CurrentRealm?.Cid, _cli?.CurrentRealm?.Pid);
430
+ Repaint();
431
+ }
432
+ else if (clickedImport)
433
+ {
434
+ StartDefaultContentImport();
435
+ }
436
+ }
437
+
438
+ private async void StartDefaultContentImport()
439
+ {
440
+ if (_importingDefaultContent)
441
+ return;
442
+
443
+ _importingDefaultContent = true;
444
+ Repaint();
445
+ try
446
+ {
447
+ // Awaited continuations resume on Unity's main-thread synchronization context.
448
+ await DefaultContentImporter.ImportDefaultContent();
449
+ }
450
+ finally
451
+ {
452
+ _importingDefaultContent = false;
453
+ Repaint();
454
+ }
455
+ }
456
+
359
457
  private List<LocalContentManifestEntry> GetCachedManifestEntries()
360
458
  {
361
459
  var localContentManifestEntries = new List<LocalContentManifestEntry>();
@@ -5,5 +5,5 @@
5
5
  "beamMongoExpressUrl": "https://storage.beamable.com",
6
6
  "dockerRegistryUrl": "https://microservices.beamable.com/v2/",
7
7
  "isUnityVsp": "false",
8
- "sdkVersion": "5.1.0-PREVIEW.RC7"
8
+ "sdkVersion": "5.1.0-PREVIEW.RC8"
9
9
  }
@@ -1,5 +1,5 @@
1
1
  fileFormatVersion: 2
2
- guid: ddded4a9a76c8ff439eab5570826ad89
2
+ guid: dac36b6d57bb4448398ce9232f5bde36
3
3
  TextureImporter:
4
4
  internalIDToNameTable: []
5
5
  externalObjects: {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "com.beamable",
3
- "version": "5.1.0-PREVIEW.RC7",
3
+ "version": "5.1.0-PREVIEW.RC8",
4
4
  "displayName": "Beamable",
5
5
  "description": "A better way to build games in Unity\n",
6
6
  "unity": "2021.3",