An opinionated, all-in-one game development framework for Unity. UGFW ships as a single Assets/UGFW folder and is designed to be adopted as a whole -- every module works together.
Add as a git submodule into your Unity project:
git submodule add https://github.com/invertibleMatrix/unity-game-framework.git Assets/UGFWInstall these via Unity Package Manager or OpenUPM:
- UniTask -
https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask#2.5.10 - Reflex -
https://github.com/gustavopsantos/reflex.git?path=/Assets/Reflex/#14.3.0 - DOTween - Install from Asset Store or OpenUPM
- Addressables -
com.unity.addressables2.11+ - Cinemachine -
com.unity.cinemachine3.1+
These enable additional service implementations. Toggle them via Tools > UGFW > Define Symbols:
- Unity Purchasing - Enables IAP service (
IAP) - Google Mobile Ads - Enables AdMob provider (
ADMOB_ENABLED) - Firebase Core - Enables Firebase initialization (
FIREBASE_INITIALIZATION) - Firebase Analytics - Enables Firebase analytics provider (
FIREBASE_ANALYTICS) - Firebase Remote Config - Enables remote config service (
FIREBASE_REMOTE_CONFIG) - GameAnalytics - Enables GameAnalytics provider (
GAME_ANALYTICS) - Unity Notifications - Enables notification service
Assets/UGFW/Runtime/
Core/ - Foundation: state machines, persistence, UID, events, camera, resource loading
GameplayCore/ - Game data: MetaData system, GameModel, currencies, rewards, IAP definitions
Services/ - SDK integrations: ads, analytics, IAP, remote config, notifications
UISystem/ - Full UI framework: screens, fragments, animations, pooling
Assets/UGFW/Editor/ - Editor tools: define symbols window, UI visualizer, UID editor, scene loader
Each runtime module has its own assembly definition (AK.Core, AK.GameplayCore, AK.Services, AK.UISystem).
The foundation layer. Everything else builds on top of these systems.
A generic, pure-C# finite state machine with no Unity dependency.
// Define states
public class IdleState : BaseState<Player>
{
public override void OnEnter() { /* ... */ }
public override void Tick() { /* ... */ }
public override void OnExit() { /* ... */ }
}
// Create and use
var sm = new StateMachine<Player, BaseState<Player>>(player);
sm.ChangeState(new IdleState());
sm.Tick(); // called every frameBaseState<TMediator>-- abstract base withOnEnter(),Tick(),OnExit(),Dispose()StateMachine<TMediator, TBaseState>-- manages transitions, auto-enters initial state- States can re-enter themselves (no same-instance guard) -- useful for restart flows
- Used by
StateEntityandBaseCameraunder the hood
Application-level state machine using ScriptableObject states. Supports a pause/resume stack for navigation between coarse-grained app states (Boot, MainMenu, Gameplay, LevelEditor).
// Define a state
[CreateAssetMenu(menuName = "AppStateMachine/BootState")]
public class BootState : AppState
{
[Inject] private readonly GameModel _gameModel;
[Inject] private readonly IMetaDataRepository _metaDataRepository;
public override void OnEnter()
{
_gameModel.Initialize(_metaDataRepository, out bool isFirstLaunch);
// ... boot sequence
}
}
// Transition between states
AppStateMachine.ChangeState(_mainMenuState); // replace current
AppStateMachine.ChangeState(_gameplayState, pauseCurrent: true); // push (pause current)
AppStateMachine.TryGoBack(); // pop (resume previous)Key types:
AppState-- abstract ScriptableObject with lifecycle:OnEnter(),OnExit(),OnPause(),OnResume(),Tick()AppState<TTransitionContext>-- typed context subclassTransitionContext-- base class for passing data between states (extend with your own fields)IAppStateMachine-- interface withChangeState(),TryGoBack(),PreviousStateAppStateMachine-- MonoBehaviour implementation, boots from a configured_bootStateinStart(), ticks the current state every frame
DI: States receive Reflex [Inject] dependencies automatically. The Container is also available as _container.
Two-layer persistence built on top of PlayerPrefs.
UniPrefs -- static wrapper over PlayerPrefs that adds JSON serialization for any [Serializable] type:
UniPrefs.Set("player_name", "Alice");
UniPrefs.Set("high_score", myScoreObject); // any serializable type
string name = UniPrefs.Get<string>("player_name");
var score = UniPrefs.Get<ScoreData>("high_score");
UniPrefs.Delete("player_name");
UniPrefs.DeleteAll(); // fires OnReset eventPrefsProperty -- instance-based wrapper with lazy caching:
// Declare
private readonly PrefsProperty<int> _highScore = new("high_score", 0);
private readonly PrefsProperty<GameModel> _save = new("UGFW_GAME_MODEL");
// Read (lazy-loads from prefs on first access, then cached)
int score = _highScore.Read();
// Save (writes to prefs immediately)
_highScore.Save(100);
// Reset (deletes from prefs, reverts to default)
_highScore.Reset();
// Implicit conversion
int val = _highScore; // same as _highScore.Read()Unique identifiers as ScriptableObject assets. The backbone of the MetaData system.
// UID is a ScriptableObject with an auto-generated GUID
// Equality is value-based (by GUID string), not reference-based
uidA == uidB; // true if same GUID
uidA == "some-guid"; // compare with string
string id = uidA; // implicit conversion to stringRegistry lookup:
UIDRegistry-- global registry of all UID assets, lookup by GUID or asset nameTypedUIDRegistry<T>-- maps UID to typed objects (whereT : UID), bidirectional lookupTypedUIDRegistryAsset<T>-- ScriptableObject wrapper with editor validation buttons
The MetaData identity chain:
UID (ScriptableObject with GUID)
-> MetaDataAsset (UID + Name, DisplayName, Description, Icon)
-> CurrencyDefinition, RewardDefinition, etc. (game-specific definitions)
Every definition extends MetaDataAsset which extends UID. This means every definition has a stable GUID identity AND display metadata.
High-performance, allocation-conscious event bus with priority-based dispatch and event consumption.
// Define events
public struct DamageEvent : IEvent
{
public int Amount;
public bool IsCritical;
}
// Subscribe
bus.SubscribeTo<DamageEvent>(OnDamage, priority: 10); // higher = earlier
// Raise
var evt = new DamageEvent { Amount = 50, IsCritical = true };
bus.Raise(in evt);
// Consume (stop propagation to lower-priority listeners)
bus.ConsumeCurrentEvent();GenericEventBus<TBaseEvent>-- simple event bus with priority and consumptionTargetedGenericEventBus<TBaseEvent, TObject>-- adds target/source filtering for object-scoped events- Recursive raise is safe (queued and dispatched after current event finishes)
- Uses internal object pooling for enumerators and queued events
Async resource loading facade over Unity Addressables.
// Load single asset
var texture = await UniResources.LoadAssetAsync<Texture2D>("my_texture");
// Load multiple assets as a group
var group = await UniResources.LoadAssetsAsync<Sprite>(new[] { "icon1", "icon2" });
// Spawn prefab
var instance = await UniResources.SpawnAsync("enemy_prefab", transform);
// Sync (blocking) variants also available
var tex = UniResources.LoadAsset<Texture2D>("my_texture");
// Cleanup
UniResources.DisposeAsset(texture);
UniResources.DisposeInstance(instance.gameObject);UniResources-- static facade, delegates toIResourceLoadingStrategyAddressablesLoadingStrategy-- default implementation using Unity's AddressablesAssetsGroup<T>-- tracks a batch of loaded assets for group release- Sprite loading components:
ImageSpriteLoader,SpriteRendererLoader-- drop on UI Image or SpriteRenderer to auto-load from Addressables
Multi-camera management with URP camera stacking.
// Get a camera
var mainCam = cameraSystem.Get<MainCamera>();
// Enable/disable
cameraSystem.EnableCamera<MainMenuCamera>();
cameraSystem.DisableCamera<GameplayCamera>();
// Camera shake
mainCam.Shake(intensity: 0.5f, duration: 0.3f);ICameraSystem-- get/enable/disable cameras, reorder stacksCameraRole--Base(renders to screen) orOverlay(stacks on top)BaseCamera-- abstractStateEntityimplementingIGameCamera, auto-registers withICameraSystemCinemachineBaseCamera-- adds Cinemachine integration with impulse-based shake
| Utility | Description |
|---|---|
| Timer | UniTask-based countdown/stopwatch/interval with pause/resume and UI binding |
| TimeFormatter | Format durations, arrival times, relative times ("5m ago"), smart dynamic formatting |
| NumberFormatter | Abbreviate large numbers (1500 -> "1.5K"), parse back |
| AudioSpawner | Pooled audio spawner with registry-based configs, fade in/out, pitch randomization |
| ParticleSpawner | Pooled particle spawner with registry-based configs, sync/async instantiation |
| JobDispatcher | Multi-threaded job dispatch with double-buffered lock-free main/worker thread communication |
| DataStructures | FreeList<T> (generational handles), PriorityQueue<T,P> (quaternary min-heap port) |
| Extensions | Shuffle<T> (Fisher-Yates), SafeInvoke, TweenExt (DOTween value tweening), enum caching |
| PropertyProxy<T> | Reactive property wrapper with UnityEvent<T> OnChange |
| Haptics | IHapticsPlayer interface (8 haptic methods), HapticPlayerComponent with DI resolution |
| Profiling | ScopedTimeProfiler (IDisposable), StopwatchTimeProfiler |
| Visual | PingPongRotator, LineRendererScroller, DiscoEffect |
Game data layer built around the MetaData system -- a ScriptableObject-driven architecture for defining all game content as data assets.
Every game domain follows a consistent four-part pattern:
[Domain]Meta -- ScriptableObject container (e.g., CurrencyMeta, RewardsMeta)
-> [Domain]Registry -- TypedUIDRegistry<Definition> for UID-based lookup
-> [Domain]Definition -- The actual data asset (extends MetaDataAsset extends UID)
-> [Domain]Type -- Enum categorizing the domain (e.g., CurrencyType, RewardType)
Example: The Currency domain
CurrencyMeta (ScriptableObject)
-> CurrencyRegistry (TypedUIDRegistry<CurrencyDefinition>)
-> CurrencyDefinition : MetaDataAsset // fields: Type, MaxAmount, StartingAmount, etc.
-> CurrencyType enum // SoftCurrency, HardCurrency, Energy, etc.
MetaDataRepository is a single ScriptableObject that holds references to ALL domain metas:
public class MetaDataRepository : MonoBehaviour, IMetaDataRepository
{
public UIDRegistry UIDRegistry;
public CurrencyMeta CurrencyMeta;
public RewardsMeta RewardsMeta;
public AdsMeta AdsMeta;
public AnalyticsMeta AnalyticsMeta;
public IAPMeta IAPMeta;
public ShopMeta ShopMeta;
public NotificationsMeta NotificationsMeta;
public ProgressionMeta ProgressionMeta;
public AchievementsMeta AchievementsMeta;
public DailyChallengesMeta DailyChallengesMeta;
public DailyRewardsMeta DailyRewardsMeta;
public DifficultyMeta DifficultyMeta;
public GameModesMeta GameModesMeta;
public SeasonsMeta SeasonsMeta;
public SpinWheelMeta SpinWheelMeta;
public TutorialsMeta TutorialsMeta;
public RemoteConfigMeta RemoteConfigMeta;
// ...
}Place ONE MetaDataRepository in your bootstrap scene. It's registered in DI and injected everywhere.
CurrencyDefinition-- type, max amount, starting amount, exchange ratesCurrencyType-- SoftCurrency, HardCurrency, Energy, Premium, Token, etc.CurrencyModel-- runtime model withAdd(),Deduct(),DeductPartial(), respects MaxAmount capCurrencyExchangeRate-- conversion rates between currencies
RewardDefinition-- amount, type, linked currency/bundle/gacha dataRewardType-- Star, Currency, Bundle, Gacha, Subscription, Unlockable, Powerup, Booster, Live, NoAdsRewardBundle-- ordered/weighted list of sub-rewards (recursive), bundle types: Sequential, Random, Weighted, RandomWeighted, AllGachaBundle-- weighted random reward pool withEvaluateRewards()for gacha pullsCheckpointReward-- rewards tied to progression milestonesSubscriptionReward-- time-based rewards for subscribers
AdPlacementDefinition-- defines an ad placement with: placement ID, ad unit ID, ad type (Rewarded/Interstitial/Banner/AppOpen/RewardedInterstitial), frequency caps (MaxPerSession, MaxPerDay, CooldownSeconds), level gating, loading strategies (PreloadOnInitialize, AutoReloadAfterShow, AutoReloadOnFail, MaxRetryAttempts, GetRetryDelay())AdTypeenumAdLoadingStrategyenumAdsMeta/AdsRegistry-- container and registry
IAPProductDefinition-- store product ID, product type (Consumable/NonConsumable/Subscription)IAPProductTypeenumShopCategoryDefinition-- categories of shop items with cost type, product UIDsShopItemDefinition-- individual shop items with rarity, cost, rewardsPurchasableItemDefinition-- bridge between shop items and the purchase system: cost type, currency type, price, product ID, reward/bundle referencesCostType-- None, Free, Currency, Gem, Ad, Resource, InAppPurchase
| Domain | Definitions | Description |
|---|---|---|
| Analytics | AnalyticsEventDefinition, ParameterName | Typed event and parameter definitions for analytics tracking |
| Progression | ProgressionLevel, MilestoneDefinition | Level progression and milestone tracking |
| Achievements | AchievementDefinition, AchievementType | Achievement definitions |
| DailyChallenges | DailyChallengeDefinition, ChallengeType | Daily challenge definitions |
| DailyRewards | DailyRewardSlot, StreakBonusDefinition | Daily reward calendar with streak bonuses |
| Difficulty | DifficultyDefinition, DifficultyType | Difficulty settings |
| GameModes | GameModeDefinition, GameModeType | Game mode definitions |
| Seasons | EventDefinition, EventType | Seasonal/live event definitions |
| SpinWheel | SpinWheelSlot | Spin wheel prize definitions |
| Tutorials | TutorialDefinition, TutorialType | Tutorial step definitions |
| Notifications | NotificationDefinition, NotificationType | Local notification templates |
| RemoteConfig | RemoteVariable, RemoteBool/Int/Float/String | Remote variables with Remote > Cached > Default priority |
RemoteVariable<T> provides a three-tier value resolution: Remote > Cached > Default.
// Define in a RemoteConfigMeta ScriptableObject
public RemoteInt DailyCoinReward = new() { DefaultValue = 100 };
// At runtime, after remote config fetch:
int reward = DailyCoinReward.Value; // remote value if available, else cached, else 100
// Each RemoteVariable automatically:
// 1. Uses the remote value if fetched and non-default
// 2. Falls back to the last cached value (persisted locally)
// 3. Falls back to the DefaultValue set in the inspectorThe central runtime game state model. Persisted via PrefsProperty<GameModel>.
// Load saved game (or get fresh instance)
var gameModel = GameModel.Load();
// Initialize (resolves UIDs, credits pending transactions, detects first launch)
gameModel.Initialize(metaDataRepository, out bool isFirstLaunch);
// Persist any time
gameModel.Commit(); // writes to prefs via PrefsProperty
// Access currencies
var coins = gameModel.GetCurrencyModel(CurrencyType.SoftCurrency);
coins.Add(100); // respects MaxAmount cap
gameModel.Commit(); // persist
// Queue and credit rewards
gameModel.AppendLevelCompleteRewards(rewardUIDs);
gameModel.CreditPendingTransactions(TransactionType.LevelCompleteTransaction);
// Check for save
bool hasSave = GameModel.HasSave();
GameModel.DeleteSave(); // wipe all dataKey models:
GameModel-- player level, session, currencies, pending transactions, settings, dirty trackingCurrencyModel-- runtime currency withAdd(),Deduct(),DeductPartial(), UID resolution for deserializationGameSettingsModel-- audio/vibration preferencesGameStateModel-- gameplay stateTransaction-- UID + timestamp for pending rewards/purchases, with deserialization resolution
Provider-based service layer for SDK integrations. Each service has a public interface and swap-able provider implementations guarded by preprocessor symbols.
Priority-based waterfall mediation with frequency capping and auto-reload.
// Initialize with metadata
await adsService.InitializeAsync(metaDataRepository.AdsMeta, playerLevel: 5);
// Show a rewarded ad
bool rewardGranted = await adsService.ShowRewardedAdAsync(placementDefinition);
// Check readiness and frequency caps
bool canShow = adsService.CanShowPlacement(placement);
int sessionCount = adsService.GetSessionShowCount("placement_id");
// Consent
adsService.SetUserConsent(canTrack: true);
adsService.SetUserUnderAge(isUnderAge: false);AdPlacementDefinition controls everything about an ad slot:
- Frequency caps:
MaxPerSession,MaxPerDay,CooldownSeconds - Level gating:
IsAvailable(playerLevel)with min/max level - Loading:
PreloadOnInitialize,AutoReloadAfterShow,MaxRetryAttempts
Providers: AdMob (ADMOB_ENABLED), NullProvider (testing)
Builder: AdServiceBuilder for fluent construction, or IMetaDataRepository.CreateAdService()
Multi-provider analytics facade with metadata-driven event definitions.
// Track raw events
analyticsService.TrackEvent("level_complete", new Dictionary<string, object> { { "level", 5 } });
// Track metadata-driven events (validates parameters, maps provider names)
analyticsService.TrackEvent(eventUID, parameters);
// Track monetization
analyticsService.TrackPurchase("com.game.coinpack", 0.99, "USD");
analyticsService.TrackAdImpression("rewarded_level_end", "admob");Providers: Firebase Analytics (FIREBASE_ANALYTICS), GameAnalytics (GAME_ANALYTICS), DebugAnalyticsProvider (console logging)
Two-layer system: IIAPService (raw store operations) and IPurchaseService (bridges IAP with MetaData and GameModel).
// High-level purchase (handles currency deduction and reward delivery)
var status = await purchaseService.Purchase(purchasableItemDefinition, immediateCredit: true);
// Check IAP ownership
bool owned = purchaseService.IAPService.IsProductOwned("no_ads");
bool subscribed = purchaseService.IAPService.IsSubscribed("vip_monthly");Purchase flow:
CostType.InAppPurchase-> delegates toIIAPService.PurchaseAsync(), then credits rewardsCostType.Currency-> checks/deducts fromCurrencyModel, queues rewards- Currency-type rewards from IAP are always credited immediately
Must be initialized first. Other Firebase services depend on IFirebaseInitializationService.
// Initialize Firebase
bool available = await firebaseInit.InitializeAsync();
// Remote Config
await remoteConfigService.InitializeAsync(metaDataRepository.RemoteConfigMeta);
await remoteConfigService.FetchAndActivateAsync();
// RemoteVariables on the meta now reflect server valuesLocal push notifications with UID-based scheduling from MetaData definitions.
// Request permission
notificationService.RequestPermission(status => { /* ... */ });
// Schedule from MetaData definition
notificationService.ScheduleNotification(welcomeUID, delaySeconds: 86400);
// Schedule custom
notificationService.ScheduleNotification("Title", "Message", fireTime, "id", data, repeatInterval);A unified UI framework where one class (UIView) serves as both screens and fragments. The distinction is determined by a UIViewChannel component on the prefab -- no separate class hierarchies.
Screen (has UIViewChannel) -- gets its own Canvas, pushed onto a channel-based stack, sorted by UIChannel (HUD=0, Menu=100, Overlay=200).
Fragment (no UIViewChannel) -- lives inside a parent view's FragmentContainer, tracked in per-parent history stacks.
// Show a screen (fire-and-forget or async)
uiSystem.Show<UIMainMenuScreen>();
var screen = await uiSystem.ShowAsync<UIMainMenuScreen>(context: myData);
// Show a fragment inside a specific parent
uiSystem.Show<UISettingsFragment>(parent: screen);
// Close
uiSystem.Close(view);
await uiSystem.CloseAsync(view);
// Navigate back (fragment history)
uiSystem.GoBack(parentView);
// Convenience
uiSystem.DisplayToast("Saved!");
uiSystem.DisplayBanner("Special Offer!", variantId: "sale");public class UIMyScreen : UIView<MyContext>
{
public override void SetContext(MyContext ctx) { /* receive data */ }
public override void RegisterResources() { /* subscribe to events */ }
public override void UnRegisterResources() { /* unsubscribe */ }
public override void OnPrepareShow() { /* before animation */ }
public override void OnShow() { /* after animation */ }
public override void OnPrepareHide() { /* before hide animation */ }
public override void OnHide() { /* after hide animation */ }
public override void OnPause() { /* covered by another view */ }
public override void OnResume() { /* uncovered */ }
public override void OnReset() { /* returned to pool */ }
}Control what happens to the view below when a new view is pushed:
| Behavior | Effect |
|---|---|
DoNothing |
Below view unaffected (toasts, overlays) |
HideBelow |
Below view hidden (full-screen takeover) |
PauseOnlyBelow |
Below view input-blocked but visible (popups, dialogs) |
PauseAndHideBelow |
Below view fully paused + hidden (replacement screens) |
CloseBelow |
Below view closed/destroyed (no-return navigation) |
30+ animation strategy ScriptableObjects for show/hide transitions:
- Core: Fade, Slide, Scale, Composite
- Bouncy/Elastic: Bounce, Boing, Elastic, PopSnap
- Card-themed: CardArc, CardDeal, CardFan, CardFlipDeal, CardFlyIn, CardPop, CardSpread, CardStack
- Rotation: Flip, RotateIn, SlideRotate, ZoomRotate, Spiral
- Special: Cascade, ConfettiBurst, DropBounce, OrganicGrowth, PartyPopper, Pulse, Reward, Shake, WobblyLife
Assign via UIAnimationConfig ScriptableObject on each UIView.
Ready-made views: UIViewToast, UIViewBanner, UIFragButton, UIFragTooltip, UIFragLoadSpinner, UITutorialArrow
- Static -- pre-placed as child GameObjects. Survive
Close()withNormalcontext (just hidden). - Dynamic -- instantiated at runtime. Always destroyed (or pooled) on close.
| Tool | Menu Path | Description |
|---|---|---|
| Define Symbols Window | Tools > UGFW > Define Symbols | Toggle preprocessor symbols for optional SDKs |
| View Stack Visualizer | AK > UI > V2 - View Stack Visualizer | Inspect live UI channel/fragment stacks, validate consistency |
| UID Editor | Context menu on null UID fields | Create UID assets in-place from inspector |
| Missing Scripts Finder | Tools > Missing Scripts | Find and remove missing script references |
| Always Start From Scene 0 | Tools > AK > AlwaysStartsFromScene0 | Force Play mode to start from bootstrap scene |
| Inspector Ping Button | Automatic on all inspectors | Ping button in every Inspector header |
The glue is a GameBindings MonoBehaviour implementing Reflex's IInstaller. It lives in your bootstrap scene and wires everything into the DI container.
public sealed class GameBindings : MonoBehaviour, IInstaller
{
[SerializeField] private AppStateMachine.AppStateMachine _appStateMachine;
[SerializeField] private BootState _bootState;
[SerializeField] private MetaDataRepository _metaDataRepository;
// ... other scene references
public GameModel GameModel;
public void InstallBindings(ContainerBuilder builder)
{
// App states
builder.AddSingleton(_appStateMachine, typeof(AppStateMachine.AppStateMachine), typeof(IAppStateMachine));
builder.AddSingleton(_bootState, typeof(BootState));
// MetaData
builder.AddSingleton(_metaDataRepository, typeof(MetaDataRepository), typeof(IMetaDataRepository));
_metaDataRepository.UIDRegistry.Initialize();
// Game model - load from save
GameModel = GameModel.Load();
builder.AddSingleton(GameModel, typeof(GameModel));
// Services
builder.AddSingleton(_metaDataRepository.CreateAdService(), typeof(IAdsService));
builder.AddSingleton(container =>
new PurchaseService(_metaDataRepository, container.Resolve<GameModel>(), new UnityIAPService()),
typeof(IPurchaseService));
// Firebase
builder.AddSingleton(new FirebaseInitializationService(), typeof(IFirebaseInitializationService));
builder.AddSingleton(container =>
new FirebaseRemoteConfigService(_metaDataRepository, container.Resolve<IFirebaseInitializationService>()),
typeof(IRemoteConfigService));
}
private void OnApplicationPause(bool pauseStatus)
{
GameModel.Commit();
}
}Your BootState ScriptableObject is where initialization happens:
[CreateAssetMenu(menuName = "AppStateMachine/BootState")]
public class BootState : AppState
{
[Inject] private readonly IMetaDataRepository _metaDataRepository;
[Inject] private readonly GameModel _gameModel;
[Inject] private readonly IRemoteConfigService _remoteConfigService;
[Inject] private readonly IFirebaseInitializationService _firebaseInit;
public override void OnEnter()
{
_metaDataRepository.InitializeRegistries();
_gameModel.Initialize(_metaDataRepository, out bool isFirstLaunch);
BootAsync().Forget();
}
private async UniTask BootAsync()
{
bool firebaseAvailable = await _firebaseInit.InitializeAsync();
await _remoteConfigService.InitializeAsync();
// ... more async initialization
AppStateMachine.ChangeState(_mainMenuState);
}
}Toggle via Tools > UGFW > Define Symbols:
| Symbol | Enables |
|---|---|
ADMOB_ENABLED |
Google Mobile Ads SDK (AdMob provider) |
FIREBASE_INITIALIZATION |
Firebase Core SDK initialization |
FIREBASE_ANALYTICS |
Firebase Analytics provider |
FIREBASE_REMOTE_CONFIG |
Firebase Remote Config service |
GAME_ANALYTICS |
GameAnalytics SDK provider |
IAP |
Unity In-App Purchasing (UnityIAPService) |
Each Firebase capability is a separate symbol because each requires its own Firebase SDK package (they are NOT a single SDK).
cd Assets/UGFW
git pull origin mainOr from project root:
git submodule update --remote Assets/UGFWcd Assets/UGFW
git add -A
git commit -m "your change"
git push