diff --git a/Assets/Scenes/ArbiterScene.unity b/Assets/Scenes/ArbiterScene.unity new file mode 100644 index 0000000..c3153d5 --- /dev/null +++ b/Assets/Scenes/ArbiterScene.unity @@ -0,0 +1,1547 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 10 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 0 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 4890085278179872738, guid: b9ae659ad95074ed28609bd7545ffb90, + type: 2} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &277484667 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 277484670} + - component: {fileID: 277484669} + - component: {fileID: 277484668} + m_Layer: 0 + m_Name: EventSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &277484668 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 277484667} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4f231c4fb786f3946a6b90b886c48677, type: 3} + m_Name: + m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 + m_HorizontalAxis: Horizontal + m_VerticalAxis: Vertical + m_SubmitButton: Submit + m_CancelButton: Cancel + m_InputActionsPerSecond: 10 + m_RepeatDelay: 0.5 + m_ForceModuleActive: 0 +--- !u!114 &277484669 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 277484667} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 76c392e42b5098c458856cdf6ecaaaa1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_FirstSelected: {fileID: 0} + m_sendNavigationEvents: 1 + m_DragThreshold: 10 +--- !u!4 &277484670 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 277484667} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &519420028 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 519420032} + - component: {fileID: 519420031} + - component: {fileID: 519420029} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &519420029 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 +--- !u!20 &519420031 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.15686275, g: 0.26666668, b: 0.6509804, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 0 + m_HDR: 1 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 0 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &519420032 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &524696371 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 524696372} + - component: {fileID: 524696374} + - component: {fileID: 524696373} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &524696372 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 524696371} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 846627341} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 2.5} + m_SizeDelta: {x: -40, y: -15} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &524696373 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 524696371} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 40 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Show Banner +--- !u!222 &524696374 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 524696371} + m_CullTransparentMesh: 0 +--- !u!1 &636086475 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 636086478} + - component: {fileID: 636086477} + - component: {fileID: 636086476} + m_Layer: 5 + m_Name: InterstitialStatusText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &636086476 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 636086475} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 36 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Loading... +--- !u!222 &636086477 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 636086475} + m_CullTransparentMesh: 0 +--- !u!224 &636086478 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 636086475} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.71} + m_AnchorMax: {x: 1, y: 0.77} + m_AnchoredPosition: {x: 105, y: 0} + m_SizeDelta: {x: -230, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &694260087 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 694260088} + - component: {fileID: 694260090} + - component: {fileID: 694260089} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &694260088 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694260087} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1665391391} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 2.5} + m_SizeDelta: {x: -40, y: -15} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &694260089 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694260087} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 40 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Show Rewarded +--- !u!222 &694260090 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 694260087} + m_CullTransparentMesh: 0 +--- !u!1 &846627337 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 846627341} + - component: {fileID: 846627340} + - component: {fileID: 846627339} + - component: {fileID: 846627338} + m_Layer: 5 + m_Name: ShowBannerButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &846627338 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 846627337} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 846627339} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &846627339 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 846627337} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 482bb6becec9f49fda9230607c1e1097, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &846627340 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 846627337} + m_CullTransparentMesh: 0 +--- !u!224 &846627341 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 846627337} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 524696372} + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.5} + m_AnchorMax: {x: 0.5, y: 0.58} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1024874062 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1024874066} + - component: {fileID: 1024874065} + - component: {fileID: 1024874064} + - component: {fileID: 1024874063} + - component: {fileID: 1024874068} + - component: {fileID: 1024874069} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1024874063 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 +--- !u!114 &1024874064 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 1 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!223 &1024874065 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!224 &1024874066 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1734153390} + - {fileID: 1034179796} + - {fileID: 636086478} + - {fileID: 1665391391} + - {fileID: 1382241166} + - {fileID: 846627341} + - {fileID: 1170807040} + - {fileID: 1972437132} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!114 &1024874068 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c4d7e2857b734401a7835410dd65776b, type: 3} + m_Name: + m_EditorClassIdentifier: + showBannerButton: {fileID: 846627338} + showMrecButton: {fileID: 1170807041} + showInterstitialButton: {fileID: 1034179793} + showRewardedButton: {fileID: 1665391392} + initializationStatusText: {fileID: 1972437130} + interstitialStatusText: {fileID: 636086476} + rewardedStatusText: {fileID: 1382241164} +--- !u!114 &1024874069 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1024874062} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: acafde15ea31470e81e2be191a938627, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &1034179792 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1034179796} + - component: {fileID: 1034179795} + - component: {fileID: 1034179794} + - component: {fileID: 1034179793} + m_Layer: 5 + m_Name: ShowInterstitialButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1034179793 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1034179792} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1034179794} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1034179794 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1034179792} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 482bb6becec9f49fda9230607c1e1097, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1034179795 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1034179792} + m_CullTransparentMesh: 0 +--- !u!224 &1034179796 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1034179792} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1590164947} + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.7} + m_AnchorMax: {x: 0.5, y: 0.78} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1170807039 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1170807040} + - component: {fileID: 1170807043} + - component: {fileID: 1170807042} + - component: {fileID: 1170807041} + m_Layer: 5 + m_Name: ShowMRecButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1170807040 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1170807039} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1341077592} + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.4} + m_AnchorMax: {x: 0.5, y: 0.48} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1170807041 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1170807039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1170807042} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1170807042 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1170807039} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 482bb6becec9f49fda9230607c1e1097, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1170807043 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1170807039} + m_CullTransparentMesh: 0 +--- !u!1 &1341077591 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1341077592} + - component: {fileID: 1341077594} + - component: {fileID: 1341077593} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1341077592 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1341077591} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1170807040} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 2.5} + m_SizeDelta: {x: -40, y: -15} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1341077593 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1341077591} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 40 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Show MREC +--- !u!222 &1341077594 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1341077591} + m_CullTransparentMesh: 0 +--- !u!1 &1382241163 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1382241166} + - component: {fileID: 1382241165} + - component: {fileID: 1382241164} + m_Layer: 5 + m_Name: RewardedStatusText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1382241164 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1382241163} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 36 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 3 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Loading... +--- !u!222 &1382241165 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1382241163} + m_CullTransparentMesh: 0 +--- !u!224 &1382241166 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1382241163} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.61} + m_AnchorMax: {x: 1, y: 0.67} + m_AnchoredPosition: {x: 105, y: 0} + m_SizeDelta: {x: -230, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1 &1590164946 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1590164947} + - component: {fileID: 1590164949} + - component: {fileID: 1590164948} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1590164947 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590164946} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1034179796} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 2.5} + m_SizeDelta: {x: -40, y: -15} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1590164948 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590164946} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 40 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: Show Interstitial +--- !u!222 &1590164949 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1590164946} + m_CullTransparentMesh: 0 +--- !u!1 &1665391390 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1665391391} + - component: {fileID: 1665391394} + - component: {fileID: 1665391393} + - component: {fileID: 1665391392} + m_Layer: 5 + m_Name: ShowRewardedButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1665391391 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1665391390} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 694260088} + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.6} + m_AnchorMax: {x: 0.5, y: 0.68} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 400, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1665391392 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1665391390} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Highlighted + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1665391393} + m_OnClick: + m_PersistentCalls: + m_Calls: [] +--- !u!114 &1665391393 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1665391390} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 21300000, guid: 482bb6becec9f49fda9230607c1e1097, type: 3} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1665391394 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1665391390} + m_CullTransparentMesh: 0 +--- !u!1 &1734153389 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1734153390} + - component: {fileID: 1734153392} + - component: {fileID: 1734153391} + m_Layer: 5 + m_Name: AppTitle + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1734153390 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1734153389} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.8} + m_AnchorMax: {x: 0.5, y: 0.88} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 600, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1734153391 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1734153389} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 50 + m_FontStyle: 1 + m_BestFit: 1 + m_MinSize: 10 + m_MaxSize: 60 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: CloudX Arbiter/TPA Demo +--- !u!222 &1734153392 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1734153389} + m_CullTransparentMesh: 1 +--- !u!1 &1972437129 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1972437132} + - component: {fileID: 1972437131} + - component: {fileID: 1972437130} + m_Layer: 5 + m_Name: InitializationStatusText + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1972437130 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1972437129} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 12800000, guid: ff72041106c504e268ea0ba778b962ee, type: 3} + m_FontSize: 36 + m_FontStyle: 0 + m_BestFit: 1 + m_MinSize: 0 + m_MaxSize: 300 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: 'Status: Not initialized' +--- !u!222 &1972437131 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1972437129} + m_CullTransparentMesh: 1 +--- !u!224 &1972437132 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1972437129} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 1024874066} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0.5, y: 0.22} + m_AnchorMax: {x: 0.5, y: 0.28} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 500, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 519420032} + - {fileID: 1024874066} + - {fileID: 277484670} diff --git a/Assets/Scenes/ArbiterScene.unity.meta b/Assets/Scenes/ArbiterScene.unity.meta new file mode 100644 index 0000000..eb7064e --- /dev/null +++ b/Assets/Scenes/ArbiterScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 0365ea9803b44c7da3c9fc6ebe403f63 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Arbiter.meta b/Assets/Scripts/Arbiter.meta new file mode 100644 index 0000000..55a5faa --- /dev/null +++ b/Assets/Scripts/Arbiter.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5ff48743f31740239414917f7126a831 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs b/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs new file mode 100644 index 0000000..d28ce43 --- /dev/null +++ b/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs @@ -0,0 +1,492 @@ +using System; +using System.Collections.Generic; +using CloudX; +using GoogleMobileAds.Api; +using GoogleMobileAds.Common; +using UnityEngine; + +/* + * Trusted Arbiter for interstitials. CloudX and AdMob load in parallel, the + * candidates that fill become bids, and CloudXSdk.Arbiter picks the platform + * to show. + * + * This is the prepare-ahead rule from docs.cloudx.io -> Trusted Arbiter. The + * arbiter runs as soon as both candidates have settled (loaded or failed) and + * stores the result. Show() then shows the stored winner immediately, with no + * arbiter call and no network call on the show path. It returns false when no + * winner is prepared, in which case the caller carries on with the game. The + * cycle restarts after the ad closes. + * + * AdMob bids carry no price. CloudX prices them from the revenue this + * controller forwards after every AdMob impression through ReportRevenueData. + * That forwarding is a required part of the integration, not telemetry. + * + * Copy this file to take the complete interstitial integration. It needs the + * CloudX SDK package and Google Mobile Ads Unity plugin com.google.ads.mobile + * 11.3.0. The demo ids live in Assets/Scripts/DemoConfig.cs; copy that file or + * pass your own ids to the constructor. ArbiterScreen.cs brings both SDKs up + * and is demo-only. Rewarded follows this controller with the rewarded SDK + * calls substituted and one reward event added. + */ +public sealed class ArbiterInterstitialController : IDisposable +{ + private const string TAG = "CloudXUnityDemo"; + private const string AdFormatName = "interstitial"; + + private readonly string _cloudXAdUnitId; + private readonly string _adMobAdUnitId; + private readonly bool _includeCloudXInArbitration; + + private bool _isDisposed; + private bool _isLoadingCloudX; + private bool _isLoadingAdMob; + private bool _cloudXSettled; + private bool _adMobSettled; + private bool _arbiterInFlight; + private bool _isShowing; + private CloudXAd _loadedCloudXAd; + private InterstitialAd _adMobInterstitial; + private CloudXArbiterResult _nextWinner; + + public event Action AdLoaded; + public event Action AdLoadFailed; + public event Action ArbiterCompleted; + public event Action AdShown; + public event Action AdShowFailed; + public event Action AdClosed; + public event Action AdClicked; + public event Action RevenueReported; + + public ArbiterInterstitialController( + string cloudXAdUnitId, + string adMobAdUnitId, + bool cloudXAvailable) + { + _cloudXAdUnitId = cloudXAdUnitId; + _adMobAdUnitId = adMobAdUnitId; + _includeCloudXInArbitration = cloudXAvailable; + _cloudXSettled = !cloudXAvailable; + + SubscribeCloudXCallbacks(); + } + + /* The platform Show() would use right now; null when no winner is stored. */ + public CloudXArbiterPlatform? PreparedWinner => + _nextWinner == null || _nextWinner.Platform == CloudXArbiterPlatform.None + ? null + : _nextWinner.Platform; + + /* True between a show call and its close or show-failure callback. */ + public bool IsShowing => _isShowing; + + /* + * True when nothing is loaded, loading, decided or showing, so only a fresh + * Load() can move things forward. The screen's backoff retry keys off this. + */ + public bool NeedsRetry => + !_isDisposed && !_arbiterInFlight && !_isShowing && PreparedWinner == null + && !_isLoadingCloudX && !_isLoadingAdMob && !CloudXHasBid && !AdMobCanShow(); + + private bool CloudXHasBid => + _loadedCloudXAd != null && CloudXSdk.IsInterstitialReady(_cloudXAdUnitId); + + private static void Log(string message) => Debug.Log($"[{TAG}][Arbiter] {message}"); + + /* + * Loads whichever network does not hold a fill. After one side failed, a + * retry reloads only that side and re-arbitrates when it settles. While an + * ad is showing nothing is loaded because the shown AdMob object must not + * be destroyed under the user. The close callback restarts the cycle. + */ + public void Load() + { + if (_isDisposed || _arbiterInFlight || _isShowing) + { + return; + } + + if (_includeCloudXInArbitration && !CloudXHasBid && !_isLoadingCloudX) + { + _cloudXSettled = false; + _loadedCloudXAd = null; + _isLoadingCloudX = true; + CloudXSdk.LoadInterstitial(_cloudXAdUnitId); + } + + if (!AdMobCanShow() && !_isLoadingAdMob) + { + _adMobSettled = false; + _isLoadingAdMob = true; + DestroyAdMobAd(); + AdMobLoad(); + } + + /* + * Both sides may already hold a fill with no winner stored after a None + * result or after a stale winner was dropped. Nothing loads in that case, + * so run the round again from here. + */ + if (_nextWinner == null) + { + MaybePrepareWinner(); + } + } + + /* + * Shows the stored winner. A stale winner is dropped and false is returned, + * so the caller can continue and reload. A second call while an ad is + * showing also returns false; use IsShowing to distinguish those cases. + */ + public bool Show() + { + if (_isDisposed || _isShowing || _nextWinner == null) + { + return false; + } + + var winner = _nextWinner.Platform; + _nextWinner = null; + + switch (winner) + { + case CloudXArbiterPlatform.CloudX when CloudXHasBid: + _isShowing = true; + CloudXSdk.ShowInterstitial(_cloudXAdUnitId); + return true; + case CloudXArbiterPlatform.CloudX: + _loadedCloudXAd = null; + return false; + case CloudXArbiterPlatform.AdMob when AdMobCanShow(): + _isShowing = true; + _adMobInterstitial.Show(); + return true; + case CloudXArbiterPlatform.AdMob: + DestroyAdMobAd(); + return false; + default: + return false; + } + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + UnsubscribeCloudXCallbacks(); + + if (_includeCloudXInArbitration) + { + CloudXSdk.DestroyInterstitial(_cloudXAdUnitId); + } + + DestroyAdMobAd(); + } + + /* + * Runs one arbiter round once both networks have settled and stores the + * result. The SDK owns the timeout and fallback. One bid wins without a + * service call; several bids use the arbiter service or its local fallback. + */ + private void MaybePrepareWinner() + { + if (_isDisposed || _arbiterInFlight || !_cloudXSettled || !_adMobSettled) + { + return; + } + + var bids = new List(); + + if (_includeCloudXInArbitration && CloudXHasBid) + { + bids.Add(new CloudXArbiterBid.CloudX(_loadedCloudXAd)); + } + + if (AdMobCanShow()) + { + bids.Add(new CloudXArbiterBid.AdMob( + _adMobAdUnitId, + NetworkName: AdMobNetworkName(_adMobInterstitial.GetResponseInfo()))); + } + + if (bids.Count == 0) + { + return; + } + + RunArbiter(bids); + } + + private void RunArbiter(List bids) + { + _arbiterInFlight = true; + Log($"Arbiter: {bids.Count} bid(s) for {_cloudXAdUnitId}"); + + void OnResult(CloudXArbiterResult result) + { + _arbiterInFlight = false; + + if (_isDisposed) + { + return; + } + + Log($"Arbiter result: platform={result.Platform} platformName={result.PlatformName} " + + $"id={result.Id} bidId={result.BidId ?? "-"} bids={bids.Count}"); + + /* None is not a winner; storing it would leave the slot wedged. */ + _nextWinner = result.Platform == CloudXArbiterPlatform.None ? null : result; + ArbiterCompleted?.Invoke(result, bids.Count); + } + + if (_includeCloudXInArbitration) + { + CloudXSdk.Arbiter(bids, OnResult); + return; + } + + /* + * CloudX never initialized, so AdMob is the only possible candidate. + * Decide locally instead of calling an uninitialized SDK bridge. + */ + OnResult(new CloudXArbiterResult( + Id: "local", + Platform: CloudXArbiterPlatform.AdMob, + PlatformName: "AdMob", + BidId: null, + Extras: new Dictionary())); + } + + private void SubscribeCloudXCallbacks() + { + CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess += CloudXOnLoadSuccess; + CloudXAdsCallbacks.Interstitial.OnAdLoadFailed += CloudXOnLoadFailed; + CloudXAdsCallbacks.Interstitial.OnAdShowSuccess += CloudXOnShowSuccess; + CloudXAdsCallbacks.Interstitial.OnAdShowFailed += CloudXOnShowFailed; + CloudXAdsCallbacks.Interstitial.OnAdHidden += CloudXOnHidden; + CloudXAdsCallbacks.Interstitial.OnAdClicked += CloudXOnClicked; + } + + private void UnsubscribeCloudXCallbacks() + { + CloudXAdsCallbacks.Interstitial.OnAdLoadSuccess -= CloudXOnLoadSuccess; + CloudXAdsCallbacks.Interstitial.OnAdLoadFailed -= CloudXOnLoadFailed; + CloudXAdsCallbacks.Interstitial.OnAdShowSuccess -= CloudXOnShowSuccess; + CloudXAdsCallbacks.Interstitial.OnAdShowFailed -= CloudXOnShowFailed; + CloudXAdsCallbacks.Interstitial.OnAdHidden -= CloudXOnHidden; + CloudXAdsCallbacks.Interstitial.OnAdClicked -= CloudXOnClicked; + } + + private void CloudXOnLoadSuccess(CloudXAd ad) + { + if (_isDisposed || ad.AdUnitId != _cloudXAdUnitId) + { + return; + } + + _isLoadingCloudX = false; + _loadedCloudXAd = ad; + _cloudXSettled = true; + Log($"CloudX loaded: {ad.NetworkName} ${ad.Revenue:0.0000}"); + AdLoaded?.Invoke(CloudXArbiterPlatform.CloudX); + MaybePrepareWinner(); + } + + private void CloudXOnLoadFailed(string adUnitId, CloudXError error) + { + if (_isDisposed || adUnitId != _cloudXAdUnitId) + { + return; + } + + _isLoadingCloudX = false; + _loadedCloudXAd = null; + _cloudXSettled = true; + AdLoadFailed?.Invoke(CloudXArbiterPlatform.CloudX, FailureText(error)); + MaybePrepareWinner(); + } + + private void CloudXOnShowSuccess(CloudXAd ad) + { + if (!_isDisposed && ad.AdUnitId == _cloudXAdUnitId) + { + AdShown?.Invoke(CloudXArbiterPlatform.CloudX); + } + } + + private void CloudXOnShowFailed(CloudXAd ad, CloudXError error) + { + if (_isDisposed || ad.AdUnitId != _cloudXAdUnitId) + { + return; + } + + _loadedCloudXAd = null; + _isShowing = false; + AdShowFailed?.Invoke(CloudXArbiterPlatform.CloudX, FailureText(error)); + } + + private void CloudXOnHidden(CloudXAd ad) + { + if (_isDisposed || ad.AdUnitId != _cloudXAdUnitId) + { + return; + } + + _loadedCloudXAd = null; + _isShowing = false; + AdClosed?.Invoke(CloudXArbiterPlatform.CloudX); + } + + private void CloudXOnClicked(CloudXAd ad) + { + if (!_isDisposed && ad.AdUnitId == _cloudXAdUnitId) + { + AdClicked?.Invoke(CloudXArbiterPlatform.CloudX); + } + } + + private bool AdMobCanShow() => + _adMobInterstitial != null && _adMobInterstitial.CanShowAd(); + + private void AdMobLoad() + { + /* + * Google callbacks do not run on the Unity main thread. Move each whole + * callback body onto it so controller state and UI events stay ordered. + */ + InterstitialAd.Load( + _adMobAdUnitId, + new AdRequest(), + (ad, error) => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + if (_isDisposed) + { + ad?.Destroy(); + return; + } + + if (error != null || ad == null) + { + _isLoadingAdMob = false; + _adMobSettled = true; + AdLoadFailed?.Invoke( + CloudXArbiterPlatform.AdMob, + error?.GetMessage() ?? "AdMob returned no ad"); + MaybePrepareWinner(); + return; + } + + _adMobInterstitial = ad; + RegisterAdMobEvents(ad); + _isLoadingAdMob = false; + _adMobSettled = true; + AdLoaded?.Invoke(CloudXArbiterPlatform.AdMob); + MaybePrepareWinner(); + })); + } + + private void RegisterAdMobEvents(InterstitialAd ad) + { + ad.OnAdFullScreenContentOpened += () => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + if (!_isDisposed) + { + AdShown?.Invoke(CloudXArbiterPlatform.AdMob); + } + }); + + ad.OnAdFullScreenContentClosed += () => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + DestroyAdMobAd(); + _isShowing = false; + + if (!_isDisposed) + { + AdClosed?.Invoke(CloudXArbiterPlatform.AdMob); + } + }); + + ad.OnAdFullScreenContentFailed += error => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + DestroyAdMobAd(); + _isShowing = false; + + if (!_isDisposed) + { + AdShowFailed?.Invoke(CloudXArbiterPlatform.AdMob, error.GetMessage()); + } + }); + + ad.OnAdClicked += () => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + if (!_isDisposed) + { + AdClicked?.Invoke(CloudXArbiterPlatform.AdMob); + } + }); + + /* Required: this is how CloudX learns what the AdMob bid was worth. */ + ad.OnAdPaid += adValue => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + if (!_isDisposed) + { + ReportAdMobPaidEvent(adValue, AdSourceName(ad.GetResponseInfo())); + } + }); + } + + private void DestroyAdMobAd() + { + _adMobInterstitial?.Destroy(); + _adMobInterstitial = null; + } + + private void ReportAdMobPaidEvent(AdValue adValue, string adSourceName) + { + if (!CloudXSdk.IsInitialized()) + { + Log("AdMob paid event not forwarded: CloudX is not initialized"); + return; + } + + var revenueData = new CloudXRevenueData( + Platform: CloudXRevenuePlatform.AdMob, + Revenue: adValue.Value / 1_000_000.0, + AdFormat: AdFormatName, + CurrencyCode: adValue.CurrencyCode, + Precision: ToCloudXRevenuePrecision(adValue.Precision), + NetworkName: adSourceName, + AdUnitId: _adMobAdUnitId); + var accepted = CloudXSdk.ReportRevenueData(revenueData); + + Log($"ReportRevenueData({adValue.Value} micros {adValue.CurrencyCode}, " + + $"{adValue.Precision}) accepted={accepted}"); + RevenueReported?.Invoke(revenueData, accepted); + } + + private static CloudXRevenuePrecision ToCloudXRevenuePrecision( + AdValue.PrecisionType precision) => + precision switch + { + AdValue.PrecisionType.Precise => CloudXRevenuePrecision.Exact, + AdValue.PrecisionType.Estimated => CloudXRevenuePrecision.Estimated, + AdValue.PrecisionType.PublisherProvided => CloudXRevenuePrecision.PublisherDefined, + _ => CloudXRevenuePrecision.Undefined, + }; + + private static string AdMobNetworkName(ResponseInfo responseInfo) + { + var adSourceName = AdSourceName(responseInfo); + return string.IsNullOrWhiteSpace(adSourceName) ? "admob" : adSourceName; + } + + private static string AdSourceName(ResponseInfo responseInfo) => + responseInfo?.GetLoadedAdapterResponseInfo()?.AdSourceName; + + private static string FailureText(CloudXError error) => + $"{error.Message} ({error.ErrorCodeName}[{error.ErrorCode}])"; +} diff --git a/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs.meta b/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs.meta new file mode 100644 index 0000000..63a4d18 --- /dev/null +++ b/Assets/Scripts/Arbiter/ArbiterInterstitialController.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7dbdaf7c30934f1ba8a0ad7eb95e8d94 diff --git a/Assets/Scripts/Arbiter/ArbiterScreen.cs b/Assets/Scripts/Arbiter/ArbiterScreen.cs new file mode 100644 index 0000000..eb5870e --- /dev/null +++ b/Assets/Scripts/Arbiter/ArbiterScreen.cs @@ -0,0 +1,267 @@ +using System.Collections; +using CloudX; +using CloudX.Demo; +using GoogleMobileAds.Api; +using GoogleMobileAds.Common; +using UnityEngine; + +/* + * Demo-only host for the Trusted Arbiter interstitial controller. Nothing in + * this file is meant to be copied into a publisher integration. It brings both + * SDKs up, turns controller events into status text, and retries terminal + * failures with backoff. Every ad SDK call lives in + * ArbiterInterstitialController.cs. + */ +[RequireComponent(typeof(AdScreenUi))] +public class ArbiterScreen : MonoBehaviour +{ + private const string TAG = "CloudXUnityDemo"; + private const float InitializationUiTimeoutSeconds = 15f; + private const float RetryBaseDelaySeconds = 2f; + private const float RetryMaxDelaySeconds = 60f; + + private AdScreenUi _ui; + private ArbiterInterstitialController _interstitial; + private bool _cloudXInitAnswered; + private int _interstitialRetries; + private string _cloudXStatus = "CloudX: Initializing"; + private string _adMobStatus = "AdMob: Initializing"; + + private static void Log(string message) => Debug.Log($"[{TAG}][Arbiter] {message}"); + + void Awake() + { + _ui = GetComponent(); + } + + IEnumerator Start() + { + Application.SetStackTraceLogType(LogType.Log, StackTraceLogType.None); + + _ui.Bind(new AdScreenUi.Actions + { + ShowBanner = () => { }, + ToggleMrec = () => { }, + ShowInterstitial = ShowInterstitial, + ShowRewarded = () => { }, + OnOrientationChanged = _ => { }, + }); + _ui.SetButtonVisible(_ui.showBannerButton, false); + _ui.SetButtonVisible(_ui.showMrecButton, false); + _ui.SetButtonVisible(_ui.showRewardedButton, false); + _ui.SetRewardedStatus(string.Empty); + _ui.SetActionsInteractable(false); +#if UNITY_IOS && !UNITY_EDITOR + PublishInitializationStatus("Requesting tracking permission"); +#endif + /* + * Resolve ATT before either SDK initializes. CloudX never prompts and + * treats an undetermined status as opted out, which suppresses fill on + * physical devices. + */ + yield return DemoAppTrackingiOS.EnsureRequested(); + + if (!DemoAppTrackingiOS.IsUsable(DemoAppTrackingiOS.Status)) + { + Log($"Tracking not authorized ({DemoAppTrackingiOS.Status}), leaving the UI disabled"); + _ui.SetInitializationStatus("Status: Tracking not authorized - ads cannot load"); + yield break; + } + + InitializeAdMob(); + InitializeCloudX(); + StartCoroutine(ReleaseActionsIfInitStalls()); + } + + void OnDestroy() + { + CloudXInitializationCallbacks.OnSdkInitializedEvent -= OnCloudXInitialized; + CloudXInitializationCallbacks.OnSdkInitializationFailedEvent -= OnCloudXInitializationFailed; + + _interstitial?.Dispose(); + _interstitial = null; + } + + private void InitializeAdMob() + { + /* Google callbacks that touch UI are moved onto the Unity main thread. */ + MobileAds.Initialize(_ => MobileAdsEventExecutor.ExecuteInUpdate(() => + { + _adMobStatus = "AdMob: Ready"; + PublishInitializationStatus(); + })); + } + + private void InitializeCloudX() + { + PublishInitializationStatus(); + + if (CloudXSdk.IsInitialized()) + { + OnCloudXInitialized(new CloudXSdkConfiguration()); + return; + } + + CloudXSdk.SetMinLogLevel(CloudXLogLevel.Verbose); + CloudXSdk.SetHasUserConsent(true); + CloudXSdk.SetDoNotSell(false); + + CloudXInitializationCallbacks.OnSdkInitializedEvent += OnCloudXInitialized; + CloudXInitializationCallbacks.OnSdkInitializationFailedEvent += OnCloudXInitializationFailed; + + CloudXSdk.Initialize(CloudXInitializationConfiguration.Create(DemoConfig.AppKey).Build()); + } + + private void OnCloudXInitialized(CloudXSdkConfiguration _) + { + _cloudXInitAnswered = true; + + if (_interstitial != null) + { + /* + * The watchdog already built an AdMob-only controller. Swapping it + * now could destroy an ad that is on screen, so keep this session. + */ + Log("CloudX initialized after the watchdog; this session stays AdMob-only"); + _cloudXStatus = "CloudX: Initialized late - AdMob only this session"; + PublishInitializationStatus(); + return; + } + + Log("CloudX initialized"); + _cloudXStatus = "CloudX: Initialized"; + PublishInitializationStatus(); + CreateController(cloudXAvailable: true); + } + + private void OnCloudXInitializationFailed(CloudXError error) + { + Log($"CloudX initialization failed: {error}"); + _cloudXInitAnswered = true; + _cloudXStatus = $"CloudX: Failed ({error.ErrorCodeName})"; + PublishInitializationStatus(); + + /* AdMob is the only candidate and wins each round locally. */ + CreateController(cloudXAvailable: false); + } + + private IEnumerator ReleaseActionsIfInitStalls() + { + yield return new WaitForSecondsRealtime(InitializationUiTimeoutSeconds); + + if (_cloudXInitAnswered) + { + yield break; + } + + Log($"No CloudX initialization result within {InitializationUiTimeoutSeconds}s, continuing with AdMob only"); + _cloudXStatus = "CloudX: No response"; + PublishInitializationStatus(); + CreateController(cloudXAvailable: false); + } + + private void CreateController(bool cloudXAvailable) + { + if (_interstitial != null) + { + return; + } + + _interstitial = new ArbiterInterstitialController( + DemoConfig.InterstitialAdUnitId, + DemoConfig.AdMobInterstitialAdUnitId, + cloudXAvailable); + _interstitial.AdLoaded += platform => + { + _interstitialRetries = 0; + SetInterstitialStatus($"{platform} loaded"); + }; + _interstitial.AdLoadFailed += (platform, message) => + { + if (!_interstitial.NeedsRetry) + { + SetInterstitialStatus($"Load failed ({platform}): {message}"); + return; + } + + var delay = NextRetryDelay(ref _interstitialRetries); + SetInterstitialStatus($"Load failed ({platform}): {message}\nRetrying in {delay:0}s..."); + Invoke(nameof(LoadInterstitial), delay); + }; + _interstitial.ArbiterCompleted += (result, bidCount) => + SetInterstitialStatus(ArbiterSummary(result, bidCount)); + _interstitial.AdShown += platform => SetInterstitialStatus($"Showing ({platform})"); + _interstitial.AdShowFailed += (platform, message) => + { + var delay = NextRetryDelay(ref _interstitialRetries); + SetInterstitialStatus($"Show failed ({platform}): {message}\nRetrying in {delay:0}s..."); + Invoke(nameof(LoadInterstitial), delay); + }; + _interstitial.AdClosed += platform => + { + SetInterstitialStatus($"Closed ({platform})"); + LoadInterstitial(); + }; + _interstitial.AdClicked += platform => Log($"Interstitial clicked ({platform})"); + _interstitial.RevenueReported += (data, accepted) => + SetInterstitialStatus( + $"Revenue -> CloudX: {data.Revenue:0.000000} {data.CurrencyCode} " + + $"(accepted={accepted})"); + + LoadInterstitial(); + _ui.SetActionsInteractable(true); + } + + private void ShowInterstitial() + { + var winner = _interstitial.PreparedWinner; + + if (_interstitial.Show()) + { + Log($"Showing the interstitial ({winner})"); + return; + } + + if (_interstitial.IsShowing) + { + return; + } + + /* + * A real app continues the game when no winner is prepared. The demo + * reloads as well so another round becomes visible without a restart. + */ + SetInterstitialStatus("No winner prepared; reloading"); + LoadInterstitial(); + } + + private static float NextRetryDelay(ref int retries) + { + var delay = Mathf.Min(RetryBaseDelaySeconds * Mathf.Pow(2f, retries), RetryMaxDelaySeconds); + retries++; + return delay; + } + + private static string Bids(int bidCount) => bidCount == 1 ? "1 bid" : $"{bidCount} bids"; + + private static string ArbiterSummary(CloudXArbiterResult result, int bidCount) => + result.Platform == CloudXArbiterPlatform.None + ? $"Arbiter: no winner ({Bids(bidCount)})" + : $"Arbiter: {result.Platform} ({Bids(bidCount)})"; + + private void LoadInterstitial() + { + _interstitial?.Load(); + } + + private void PublishInitializationStatus(string overrideText = null) + { + _ui.SetInitializationStatus(overrideText ?? $"{_cloudXStatus} | {_adMobStatus}"); + } + + private void SetInterstitialStatus(string text) + { + Log($"Interstitial: {text.Replace('\n', ' ')}"); + _ui.SetInterstitialStatus($"Inter: {text}"); + } +} diff --git a/Assets/Scripts/Arbiter/ArbiterScreen.cs.meta b/Assets/Scripts/Arbiter/ArbiterScreen.cs.meta new file mode 100644 index 0000000..1ed73c8 --- /dev/null +++ b/Assets/Scripts/Arbiter/ArbiterScreen.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: acafde15ea31470e81e2be191a938627 diff --git a/Assets/Scripts/DemoConfig.cs b/Assets/Scripts/DemoConfig.cs index d80d42d..fbdd217 100644 --- a/Assets/Scripts/DemoConfig.cs +++ b/Assets/Scripts/DemoConfig.cs @@ -2,6 +2,12 @@ * Demo dashboard IDs so this sample runs without a CloudX account. * In your game, replace these with the app key and ad unit IDs from your * CloudX dashboard. Use one app key per process. + * + * The AdMob ids are Google's official TEST ad units, shared by the First Look + * and Arbiter/TPA demos. Replace them with your own AdMob ad units in a real + * integration, and set Automatic refresh to Disabled on the banner unit in + * the AdMob console: the Unity plugin cannot control it, and a + * refreshing AdMob banner would replace the ad the demo flow decided to show. */ public static class DemoConfig { @@ -12,6 +18,9 @@ public static class DemoConfig public const string InterstitialAdUnitId = "9SizbPM3Dctz71WM2BKpi"; public const string AppOpenAdUnitId = "3evNMg9P4E1pgRPyAYk9O"; public const string RewardedAdUnitId = "7T2i4VWjsG2I4PM5vircU"; + + public const string AdMobInterstitialAdUnitId = "ca-app-pub-3940256099942544/4411468910"; + public const string AdMobBannerAdUnitId = "ca-app-pub-3940256099942544/2934735716"; #else public const string AppKey = "0qE4q2MoJzoOkFQQKAtkt"; public const string BannerAdUnitId = "guDml31r4Ys6O6HroPJia"; @@ -19,5 +28,8 @@ public static class DemoConfig public const string InterstitialAdUnitId = "PwIOPhOD0KMCB_aqz8c89"; public const string AppOpenAdUnitId = "BI0Whd5_o8ZIxkdHBS7X_"; public const string RewardedAdUnitId = "LZrqb2oz47LMG_TaaVtaR"; + + public const string AdMobInterstitialAdUnitId = "ca-app-pub-3940256099942544/1033173712"; + public const string AdMobBannerAdUnitId = "ca-app-pub-3940256099942544/6300978111"; #endif } diff --git a/Assets/Scripts/FirstLook/FirstLookConfig.cs b/Assets/Scripts/FirstLook/FirstLookConfig.cs deleted file mode 100644 index c2f6aef..0000000 --- a/Assets/Scripts/FirstLook/FirstLookConfig.cs +++ /dev/null @@ -1,24 +0,0 @@ -/* - * The AdMob ad unit ids the First Look demo falls back to. The CloudX ad unit - * ids come from DemoConfig; these are Google's official AdMob TEST ad unit ids. - * Replace them with your own AdMob ad units in a real integration. - * - * When you do, set Automatic refresh to Disabled on the banner unit in the - * AdMob console. The Unity plugin cannot control it, and a refreshing AdMob - * banner would replace the ad that won the First Look pass. - * - * The banner pass cooldown is not here; it lives in FirstLookBannerCycle, - * which paces it. - * - * https://docs.cloudx.io/en/unity/integrations/first-look - */ -public static class FirstLookConfig -{ -#if UNITY_IOS - public const string AdMobInterstitialAdUnitId = "ca-app-pub-3940256099942544/4411468910"; - public const string AdMobBannerAdUnitId = "ca-app-pub-3940256099942544/2934735716"; -#else - public const string AdMobInterstitialAdUnitId = "ca-app-pub-3940256099942544/1033173712"; - public const string AdMobBannerAdUnitId = "ca-app-pub-3940256099942544/6300978111"; -#endif -} diff --git a/Assets/Scripts/FirstLook/FirstLookConfig.cs.meta b/Assets/Scripts/FirstLook/FirstLookConfig.cs.meta deleted file mode 100644 index 2719335..0000000 --- a/Assets/Scripts/FirstLook/FirstLookConfig.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 2e6f9d5b672fa491d9309917aaacecc2 \ No newline at end of file diff --git a/Assets/Scripts/FirstLook/FirstLookScreen.cs b/Assets/Scripts/FirstLook/FirstLookScreen.cs index 329c0b3..0bd76a3 100644 --- a/Assets/Scripts/FirstLook/FirstLookScreen.cs +++ b/Assets/Scripts/FirstLook/FirstLookScreen.cs @@ -217,7 +217,7 @@ private void CreateControllers(bool cloudXAvailable) _interstitial = new FirstLookInterstitialController( DemoConfig.InterstitialAdUnitId, - FirstLookConfig.AdMobInterstitialAdUnitId, + DemoConfig.AdMobInterstitialAdUnitId, cloudXAvailable); _interstitial.AdLoaded += source => { @@ -273,7 +273,7 @@ private void CreateControllers(bool cloudXAvailable) LoadInterstitial(); _banner.Begin( DemoConfig.BannerAdUnitId, - FirstLookConfig.AdMobBannerAdUnitId, + DemoConfig.AdMobBannerAdUnitId, cloudXAvailable); _ui.SetActionsInteractable(true); } diff --git a/Assets/Scripts/OptionsScreen.cs b/Assets/Scripts/OptionsScreen.cs index c4afbb2..af101c7 100644 --- a/Assets/Scripts/OptionsScreen.cs +++ b/Assets/Scripts/OptionsScreen.cs @@ -5,14 +5,16 @@ /* * Demo-only launch screen. Picks which demo flow to enter. General routes to * GeneralScene, where the CloudX integration sample lives; First Look routes to - * FirstLookScene, the CloudX-first-with-AdMob-fallback template. Arbiter/TPA is - * not implemented yet, so its button stays hidden. + * FirstLookScene, the CloudX-first-with-AdMob-fallback template; Arbiter/TPA + * routes to ArbiterScene, where CloudX and AdMob load in parallel and Trusted + * Arbiter picks the winner. */ public class OptionsScreen : MonoBehaviour { private const string TAG = "CloudXUnityDemo"; private const string GeneralSceneName = "GeneralScene"; private const string FirstLookSceneName = "FirstLookScene"; + private const string ArbiterSceneName = "ArbiterScene"; public Button generalButton; public Button firstLookButton; @@ -22,9 +24,7 @@ private void Start() { generalButton.onClick.AddListener(OpenGeneral); firstLookButton.onClick.AddListener(OpenFirstLook); - - /* Nothing to route to yet; hide the button rather than leave it dead. */ - arbiterTpaButton.gameObject.SetActive(false); + arbiterTpaButton.onClick.AddListener(OpenArbiter); } private void OpenGeneral() @@ -38,4 +38,10 @@ private void OpenFirstLook() Debug.Log($"[{TAG}] Opening the First Look demo"); SceneManager.LoadScene(FirstLookSceneName); } + + private void OpenArbiter() + { + Debug.Log($"[{TAG}] Opening the Arbiter/TPA demo"); + SceneManager.LoadScene(ArbiterSceneName); + } } diff --git a/ProjectSettings/EditorBuildSettings.asset b/ProjectSettings/EditorBuildSettings.asset index df65263..50b9d20 100644 --- a/ProjectSettings/EditorBuildSettings.asset +++ b/ProjectSettings/EditorBuildSettings.asset @@ -14,5 +14,8 @@ EditorBuildSettings: - enabled: 1 path: Assets/Scenes/FirstLookScene.unity guid: 33c712e03aba7427695d0bd645dcd1f8 + - enabled: 1 + path: Assets/Scenes/ArbiterScene.unity + guid: 0365ea9803b44c7da3c9fc6ebe403f63 m_configObjects: {} m_UseUCBPForAssetBundles: 0 diff --git a/README.md b/README.md index 3187ac8..b222c56 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ Our complete CloudX Unity SDK integration guide is available on our docs site, [ ## Demo app This repository is also a runnable Unity demo project. It shows a working CloudX integration for -banner, MREC (the 300x250 medium rectangle), interstitial and rewarded ads, plus a First Look flow -that gives CloudX the first chance and falls back to AdMob. +banner, MREC (the 300x250 medium rectangle), interstitial and rewarded ads, plus two ways of running +CloudX next to AdMob: a First Look flow that falls back to AdMob, and an interstitial Arbiter/TPA +flow where both load and Trusted Arbiter picks the winner. Requirements: @@ -27,8 +28,9 @@ choose, which is deliberate: the iOS tracking prompt and `CloudXSdk.Initialize` you picked, not to app start. ``` -OptionsScene ── General ──> GeneralScene (the full CloudX surface) - └─ First Look ──> FirstLookScene (CloudX first, AdMob fallback) +OptionsScene ── General ──> GeneralScene (the full CloudX surface) + ├─ First Look ──> FirstLookScene (CloudX first, AdMob fallback) + └─ Arbiter/TPA ──> ArbiterScene (CloudX and AdMob in parallel, arbiter picks) ``` There is no back navigation; relaunch the app to pick the other flow. @@ -38,19 +40,18 @@ There is no back navigation; relaunch the app to pick the other flow. | `Assets/Scenes/OptionsScene.unity` | `Assets/Scripts/OptionsScreen.cs` | Picking a flow. No SDK calls. | | `Assets/Scenes/GeneralScene.unity` | `Assets/Scripts/GeneralScreen.cs` | Every ad format, straight CloudX. | | `Assets/Scenes/FirstLookScene.unity` | `Assets/Scripts/FirstLook/` | CloudX first, AdMob as the fallback. | +| `Assets/Scenes/ArbiterScene.unity` | `Assets/Scripts/Arbiter/` | CloudX and AdMob interstitials in parallel, Trusted Arbiter picks. | -`Assets/Scripts/AdScreenUi.cs` is the layout shared by the two ad screens. It is demo-only: it wires +`Assets/Scripts/AdScreenUi.cs` is the layout shared by the three ad screens. It is demo-only: it wires buttons and reflows on rotate, and contains no SDK calls. Ignore it when reading the integration. ### Options screen -Options screen with General and First Look buttons +Options screen with General, First Look and Arbiter/TPA buttons `OptionsScene` is index 0 in the build settings, so it is what launches. Each button calls `SceneManager.LoadScene` with a scene name, which only resolves for scenes listed in -File > Build Settings, so all three are listed there. - -A third flow, Arbiter/TPA, is not implemented yet and its button stays hidden. +File > Build Settings, so all four are listed there. ### General screen @@ -136,7 +137,6 @@ screen: | `FirstLookBannerController.cs` | Which SDK fills a banner pass, self-contained. | | `FirstLookBannerCycle.cs` | When the next pass starts: the clock the controller has no way to keep. | | `FirstLookSource.cs` | The `CloudX` / `AdMob` enum every event reports. | -| `FirstLookConfig.cs` | The AdMob fallback ad unit ids. | | `FirstLookScreen.cs` | Initializes both SDKs, wires the controller and the cycle to the buttons. | **To integrate the interstitial, copy two files:** `FirstLookInterstitialController.cs` and @@ -197,13 +197,75 @@ to `StopBannerAutoRefresh` - so the controller calls that before create and neve > The demo's Google test units are configured by Google, not by this project, so treat them only > as a way to see the fallback render; the setting above is about the units you replace them with. +### Arbiter/TPA screen + +Arbiter/TPA screen with an interstitial arbiter result + +Trusted Arbiter (TPA, third-party arbitration) is the other way to run CloudX next to an existing +mediation SDK. Where First Look asks CloudX first and touches AdMob only on a CloudX miss, the +Arbiter flow loads **both** at the same time and lets CloudX's arbiter decide which loaded ad is +shown. The full pattern is documented at +[https://docs.cloudx.io/en/unity/trusted-arbiter](https://docs.cloudx.io/en/unity/trusted-arbiter); +this screen is a working copy of it against AdMob, meant to be lifted into a publisher app. + +This demo covers the interstitial only. Rewarded follows the same controller with the rewarded SDK +calls substituted and one reward event added. Banner and MREC use a different shape because they +arbitrate and render on a refresh cycle; they are not part of this demo yet. + +The rules the controller implements: + +- CloudX and AdMob load in parallel. Once both have settled (loaded or failed), the loaded ones + become bids and `CloudXSdk.Arbiter` returns the platform to show. Nothing compares prices or times + the call out locally: the SDK owns both, and always completes. A single bid wins without a service + call; with no arbiter service the SDK falls back to the highest locally comparable price. +- **The interstitial prepares the winner ahead of the placement.** The arbiter runs as soon as the + candidates settle and the result is stored; tapping Show displays the stored winner with no network + call. `Show()` returns `false` when no winner is prepared, so the game carries on. The cycle restarts + after the ad closes by reloading what is missing and running a new round. +- **AdMob bids carry no price.** CloudX prices them from the revenue the app forwards after each AdMob + impression: every AdMob ad's `OnAdPaid` goes into `CloudXSdk.ReportRevenueData`. This is a required + part of the integration, not telemetry; without it CloudX never learns what AdMob pays and its + estimate for the AdMob bid never improves. +- If CloudX initialization fails outright, the controllers skip the CloudX leg; AdMob is the only + candidate and wins every round locally. + +The interstitial status line shows which side loaded, what the arbiter returned and over how many bids +(`Arbiter: CloudX (2 bids)`), and which platform is showing. After an AdMob impression, it also shows +the revenue forwarded to CloudX (`Revenue -> CloudX: 0.000000 USD (accepted=True)` for a Google test +unit, which pays zero). + +Everything the flow needs lives in `Assets/Scripts/Arbiter`, and none of it calls into the other +screens: + +| File | Role | +| --- | --- | +| `ArbiterInterstitialController.cs` | The complete integration: both SDKs' calls, parallel loading, arbitration, show flow and revenue forwarding. | +| `ArbiterScreen.cs` | Demo-only host that initializes both SDKs and turns controller events into status lines. | + +To integrate the interstitial, copy `ArbiterInterstitialController.cs`. It compiles alone once the +CloudX SDK and Google Mobile Ads Unity plugin are installed. The demo gets its app key and ad unit ids +from `Assets/Scripts/DemoConfig.cs`; copy it too, or pass your own ids to the controller constructor. +`ArbiterScreen.cs` is demo layout and initialization plumbing, so taking it without the controller does +not build an integration. + +Read the files in this order: + +1. `Assets/Scripts/DemoConfig.cs`, where the app key and ad unit ids live. +2. `Assets/Scripts/Arbiter/ArbiterInterstitialController.cs`, which contains the integration. + +> **Dashboard setup is outside the code.** +> +> Trusted Arbiter has to be enabled for your app in the CloudX dashboard. Until it is, the SDK still +> answers every `Arbiter` call, but from its local fallback: the highest locally comparable price +> among the bids, in which an AdMob bid with no reported revenue history yet cannot win. + ### Google Mobile Ads dependency -The First Look flow needs the Google Mobile Ads Unity plugin, which this project pulls in as a +The First Look and Arbiter/TPA flows need the Google Mobile Ads Unity plugin, which this project pulls in as a package (`Packages/manifest.json`) along with the External Dependency Manager it requires. Unity resolves both on open, so no manual import step is needed. -If you copy the First Look folder into your own project, add the same plugin there; the CloudX SDK +If you copy the First Look or Arbiter folder into your own project, add the same plugin there; the CloudX SDK itself does not depend on it. ### Using your own CloudX app @@ -221,8 +283,8 @@ Project Settings > Player > Signing. It ships empty on purpose. Bid requests are authorized per app key and bundle identifier, so both have to match your dashboard app or the SDK gets no fill. -The AdMob ad units in `FirstLookConfig.cs` are Google's official test units and stay valid as they -are; replace them with your own AdMob units when you take this into production, and set +The AdMob ad units in `DemoConfig.cs` are Google's official test units and stay valid as they are; +replace them with your own AdMob units when you take this into production, and set **Automatic refresh** to Disabled on the banner one (see the First Look section for why). ### iOS target SDK diff --git a/docs/images/arbiter-screen.png b/docs/images/arbiter-screen.png new file mode 100644 index 0000000..94cc5e5 Binary files /dev/null and b/docs/images/arbiter-screen.png differ diff --git a/docs/images/options-screen.png b/docs/images/options-screen.png index ca915de..a137240 100644 Binary files a/docs/images/options-screen.png and b/docs/images/options-screen.png differ