unity-monetization

Installation
SKILL.md

Unity Monetization

Wire ads into a casual iOS game without breaking the build or lying about whether ads serve. The deliverable is an AdsManager that no-ops safely off-device (behind an SDK-agnostic Ads facade), WHEN-to-show factored into a pure unit-tested cadence policy, and a build that carries whatever provisioning the SDK needs — plus an honest statement of what is provisioned vs coded. Built on unity-mcp-bridge (Editor actions) and cross-linked to unity-qa-release (the CocoaPods / pod-install / .xcworkspace build mechanics).

The path we ship now is Google AdMob (com.google.ads.mobile via OpenUPM) — see "Google AdMob (iOS)" below. The Unity Ads direct material that follows is still proven and kept as the alternative path (and as the source of the INTERNAL_ERROR / LevelPlay-conflict lore).

Doctrine (these override convenience)

  1. "Code compiles" ≠ "ads serve." Never claim ads work without seeing one render on-device. A clean build, a valid Game ID, and green listeners prove nothing about delivery. The only proof is an ad frame on a real device (or TestFlight). Until then say "integration wired, delivery unverified."
  2. A brand-new ad account/app/unit is almost certainly NOT serving real ads yet. Freshly created Unity Ads / AppLovin / ironSource accounts sit in "pending approval". AdMob is the same in a sharper form: a new AdMob app/unit returns no-fill for real ads until Google approves the app (often only once it's live / linked to a store listing) and the unit warms up (hours to a couple days). Flipping UseTestAds true→false too early makes ads disappear — that's expected, not a bug. Google test unit IDs always serve regardless of approval. When a valid ID yields no ad, suspect provisioning/no-fill first, not C#. Code must handle no-fill gracefully (log + retry next opportunity, never block gameplay).
  3. Pick ONE ad path. Mixing leaves conflicting CocoaPods. We evaluated three in one project; friction order was LevelPlay (com.unity.services.levelplay, mediation, account pending) → AppLovin MAX (.unitypackage, account pending) → Unity Ads direct (com.unity.ads), which is the path we shipped on. Two SDKs pulling the same pod at different versions breaks pod install (see the LevelPlay trap below).
  4. The iOS Xcode project is REGENERATED every build. Anything you'd hand-edit in Xcode (Info.plist ATT/SKAdNetwork keys, entitlements) must be re-applied by a [PostProcessBuild] editor script, or it's gone next build. Never hand-edit the generated project.
  5. Bootstrap ads on device only, and no-op until configured. The manager must do nothing in the Editor / off-device and nothing until a real Game ID + placement exist, so the rest of the game (and tests) never depends on an ad SDK being present or reachable.
  6. Gameplay never references the ad SDK — go through a tiny Ads facade. A static facade (Ads.NotifyGameFinished() → Ads.GameFinishedHandler?.Invoke()) that the concrete AdsManager registers at launch keeps gameplay assemblies SDK-free and makes swapping ad networks a one-file change. Off-device the handler is null → no-op. (The reference project swapped Unity Ads → AdMob by rewriting only AdsManager.cs.)
  7. WHEN to show is a pure, unit-tested policy — not logic buried in the MonoBehaviour. Put frequency capping in an engine-free class with injected time so it's deterministic and testable; the MonoBehaviour owns the real clock + SDK. Separate the decision from the commit (MarkShown only on real display) so a not-yet-loaded ad doesn't reset the counters.

Confirm the Editor is reachable (mcpforunity://editor/state, ready_for_tools==true) before driving any of this through MCP; for the CocoaPods build itself, route to unity-qa-release.

2025-2026 casual monetization strategy (hybrid by default)

Installs
14
GitHub Stars
3
First Seen
Jul 30, 2026