release: 1.4.7

This commit is contained in:
2026-04-22 17:44:49 +08:00
parent 79f478dd5f
commit e38e066a2d
37 changed files with 2598 additions and 78 deletions

View File

@@ -229,6 +229,20 @@ namespace AnyThinkAds.Api
client.setLocation(longitude, latitude);
}
public static void start()
{
client.start();
}
public static bool isCnSDK()
{
return client.isCnSDK();
}
public static void setLocalStrategyAssetPath(string assetPath)
{
client.setLocalStrategyAssetPath(assetPath);
}
}
}

View File

@@ -31,5 +31,8 @@ namespace AnyThinkAds.Common
void getArea(ATGetAreaListener listener);
void setWXStatus(bool install);
void setLocation(double longitude, double latitude);
void start();
bool isCnSDK();
void setLocalStrategyAssetPath(string assetPath);
}
}

View File

@@ -162,6 +162,9 @@ namespace AnyThinkAds
public void getArea(ATGetAreaListener listener) { }
public void setWXStatus(bool install) { }
public void setLocation(double longitude, double latitude) { }
public void start() { }
public bool isCnSDK() { return false; }
public void setLocalStrategyAssetPath(string assetPath) { }
public void showDebuggerUI() {}
public void showDebuggerUI(string debugKey) {}
}
@@ -490,4 +493,4 @@ namespace AnyThinkAds
public void entryScenarioWithPlacementID(string placementId, string scenarioID) {}
}
}
}

View File

@@ -9,11 +9,13 @@ namespace AnyThinkAds.Android
public class ATSDKAPIClient : AndroidJavaProxy, IATSDKAPIClient
{
private AndroidJavaObject sdkInitHelper;
private AndroidJavaClass sdkClass;
private ATSDKInitListener sdkInitListener;
public ATSDKAPIClient () : base("com.anythink.unitybridge.sdkinit.SDKInitListener")
{
this.sdkInitHelper = new AndroidJavaObject(
"com.anythink.unitybridge.sdkinit.SDKInitHelper", this);
this.sdkClass = new AndroidJavaClass("com.anythink.core.api.ATSDK");
}
public void initSDK(string appId, string appKey)
@@ -390,5 +392,59 @@ namespace AnyThinkAds.Android
Debug.Log("ATSDKAPIClient : error." + e.Message);
}
}
public void start()
{
try
{
if (this.sdkClass != null)
{
this.sdkClass.CallStatic("start");
}
}
catch (System.Exception e)
{
System.Console.WriteLine("Exception caught: {0}", e);
Debug.Log("ATSDKAPIClient : start error." + e.Message);
}
}
public bool isCnSDK()
{
try
{
if (this.sdkClass != null)
{
return this.sdkClass.CallStatic<bool>("isCnSDK");
}
}
catch (System.Exception e)
{
System.Console.WriteLine("Exception caught: {0}", e);
Debug.Log("ATSDKAPIClient : isCnSDK error." + e.Message);
}
return false;
}
public void setLocalStrategyAssetPath(string assetPath)
{
try
{
if (this.sdkClass != null)
{
using (AndroidJavaClass unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
using (AndroidJavaObject currentActivity = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
{
this.sdkClass.CallStatic("setLocalStrategyAssetPath", currentActivity, assetPath);
}
}
}
catch (System.Exception e)
{
System.Console.WriteLine("Exception caught: {0}", e);
Debug.Log("ATSDKAPIClient : setLocalStrategyAssetPath error." + e.Message);
}
}
}
}

View File

@@ -193,6 +193,21 @@ namespace AnyThinkAds.iOS {
Debug.Log("Unity:ATSDKAPIClient::setLocation()");
ATManager.setLocation(longitude, latitude);
}
public void start()
{
Debug.Log("Unity:ATSDKAPIClient::start() noop on iOS");
}
public bool isCnSDK()
{
return false;
}
public void setLocalStrategyAssetPath(string assetPath)
{
Debug.Log("Unity:ATSDKAPIClient::setLocalStrategyAssetPath() noop on iOS");
}
//iOS显示Debugger UI
public void showDebuggerUI()

14
Assets/CHANGELOG.md Normal file
View File

@@ -0,0 +1,14 @@
# [1.4.7]
### 新增
* 支持中国区 `ATSDK.start()`、本地策略资源路径和自动加载相关配置。
* 新增激励/插屏广告场景显式上报能力,播放链不再自动补记场景到达。
* 增加 Android IMGUI 调试样例场景、日志导出和官方 DebugUI 手动入口。
### 修复
* 激励和插屏切换为更稳的自动加载/预热策略,补强首次播放容错。
* 修复插屏关闭、展示失败和播放失败回调链不完整的问题。
* 调整 debug 行为:`topon.debug=true` 仅开启日志,不再自动弹出官方 DebugUI。

View File

@@ -1,32 +0,0 @@
fileFormatVersion: 2
guid: 89da22c2144f47ea823de749873367b4
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Android: Android
second:
enabled: 1
settings: {}
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,32 +0,0 @@
fileFormatVersion: 2
guid: 417c2272592f46b0b62da859413203f3
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Android: Android
second:
enabled: 1
settings: {}
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Samples.meta Normal file
View File

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

View File

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

View File

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

View File

@@ -0,0 +1,48 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 98b31b835f394db4a51957c3f0f3ac32, type: 3}
m_Name: IAAAdDebugSampleConfig
m_EditorClassIdentifier:
ConfigName: IAA Ad Debug Sample
Id: your_taku_app_id
Key: your_taku_app_key
Key2:
BaseAwardAdKeyValue:
key: rewarded
value: your_rewarded_placement_id
BaseInteractionAdKeyValue:
key: interstitial
value: your_interstitial_placement_id
BaseSplashAdKeyValue:
key: splash
value: optional_splash_placement
CommonKeyValues:
- key: topon.debug
value: true
- key: topon.auto_open_debugger_ui
value: false
- key: topon.rewarded_auto_load
value: true
- key: topon.rewarded_prewarm_on_init
value: true
- key: topon.rewarded_max_load_attempts
value: 3
- key: topon.rewarded_load_retry_delay_ms
value: 1000
- key: topon.interstitial_auto_load
value: true
- key: topon.interstitial_prewarm_on_init
value: true
- key: topon.interstitial_max_load_attempts
value: 2
- key: topon.interstitial_load_retry_delay_ms
value: 500

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5acf750d60576674597ee314d6a39cf8
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,112 @@
# IAA Ad Debug Sample
## 文件位置
- 场景:
- `Assets/Samples/IAAAdDebugSample/Scenes/IAAAdDebugSample.unity`
- 示例配置:
- `Assets/Samples/IAAAdDebugSample/Configs/IAAAdDebugSampleConfig.asset`
## 使用步骤
1. 打开示例配置资产 `IAAAdDebugSampleConfig.asset`
2. 填入以下字段:
- `Id` = Taku AppId
- `Key` = Taku AppKey
- `BaseAwardAdKeyValue.value` = 激励视频广告位ID
- `BaseInteractionAdKeyValue.value` = 插屏广告位ID
3. 打开示例场景 `IAAAdDebugSample.unity`
4. 运行到 Android 手机测试
## 当前默认策略
- SDK Debug
- `topon.debug=true`
- `topon.auto_open_debugger_ui=false`
- 激励视频
- 自动加载:开启
- 初始化预热:开启
- 最大加载尝试次数3
- 首轮失败补偿1000ms
- 插屏广告
- 自动加载:开启
- 初始化预热:开启
- 最大加载尝试次数2
- 首轮失败补偿500ms
## 场景里的 IMGUI 面板支持
- 初始化 ADManager
- 打开官方 DebugUI
- 手动 Enter Rewarded / Interstitial Scene
- 手动 Load 激励 / 插屏
- AsyncPlay 激励 / 插屏
- 查看当前屏幕分辨率和 SafeArea
- 查看当前策略状态
- 手动刷新状态快照,避免高频状态查询刷满日志
- 查看 Rewarded / Interstitial 的状态 JSON
- 一键复制全部日志
- 自动写入本地 session log 文件
- 一键导出当前面板可见日志到单独文件
- 一键复制日志文件路径
- 查看诊断日志
## 关于 Debug 模式
- `topon.debug=true` 只用于打开 SDK 调试日志
- 不会再因为 debug 模式自动弹出官方 DebugUI
- 官方 DebugUI 改为手动点击 `Open DebugUI`
## 场景到达测试方式
推荐验证两条链路:
1. 先点击 `Enter Rewarded Scene`,再点击 `Async Play Rewarded`
2. 不点击 `Enter Rewarded Scene`,直接点击 `Async Play Rewarded`
当前实现逻辑:
- 如果提前调用了 `EnterAdScenario`
- 播放时不会重复上报场景
- 如果没有提前调用
- 播放时不再自动补“场景到达”统计
## 建议测试流程
1. 打开场景并运行到真机
2. 点击 `Initialize ADManager`
3. 点击 `Open DebugUI`,确认 SDK Debug 面板可打开
4. 点击 `Enter Rewarded Scene`
5. 点击 `Async Play Rewarded`
6. 点击 `Enter Interstitial Scene`
7. 点击 `Async Play Interstitial`
8. 点击 `Copy All Logs`
9. 将日志直接粘贴给 Codex 分析
如果日志太长,不适合走剪贴板:
1. 点击 `Export Logs To File`
2. 点击 `Copy Log File Path`
3. 到真机的 `Application.persistentDataPath/IAAAdDebugLogs` 下取出日志文件
4. 将文件内容发给 Codex 分析
## 关于分辨率
当前 IMGUI 布局按 `1080 x 2340` 纵向手机做参考设计,并根据 `SafeArea` 自动缩放,适合作为真机调试面板。
## 关于签名
当前示例只负责在 Unity 内和 Android 调试时验证广告链路。
- 不需要官方提供签名
- 只有在你要正式导出 APK / AAB 时,才需要你自己的包名和签名配置
## 关于日志文件位置
- 运行时会自动创建一份 session log
- 目录:`Application.persistentDataPath/IAAAdDebugLogs`
- Android 上通常类似:
- `/storage/emulated/0/Android/data/<包名>/files/IAAAdDebugLogs`
- 当前测试包名是:
- `com.foldcc.starveg`

View File

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

View File

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

View File

@@ -0,0 +1,274 @@
%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: 9
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: 12
m_GIWorkflowMode: 1
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_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 512
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 256
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 1
m_PVRDenoiserTypeDirect: 1
m_PVRDenoiserTypeIndirect: 1
m_PVRDenoiserTypeAO: 1
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 1
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: 0}
--- !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 &320557560
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 320557563}
- component: {fileID: 320557562}
- component: {fileID: 320557561}
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 &320557561
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 320557560}
m_Enabled: 1
--- !u!20 &320557562
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 320557560}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, 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: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &320557563
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 320557560}
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 &924544598
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 924544600}
- component: {fileID: 924544599}
m_Layer: 0
m_Name: IAA Ad Debug Sample
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!114 &924544599
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 924544598}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 53b297b9bf0d4cfca2b77af0fd74c95c, type: 3}
m_Name:
m_EditorClassIdentifier:
adConfig: {fileID: 11400000, guid: 5acf750d60576674597ee314d6a39cf8, type: 2}
userId: debug_user_001
rewardedScenario: reward_debug
interstitialScenario: interstitial_debug
autoInitialize: 1
forcePortrait: 1
captureUnityLogs: 1
showVerboseState: 1
--- !u!4 &924544600
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 924544598}
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!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
m_Roots:
- {fileID: 320557563}
- {fileID: 924544600}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: f2af01c304b689445904b3b93d20ff83
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,166 @@
#if UNITY_EDITOR
using System;
using System.IO;
using System.Reflection;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
public static class IAAAdAndroidBuild
{
private const string OutputDirectory = "Build/Android";
private const string OutputApkPath = OutputDirectory + "/IAAAdDebugSample-starveg.apk";
private const string KeystorePath = "Build/Android/keys/starveg-test.keystore";
private const string KeystorePassword = "Foldcc123!";
private const string KeyAliasName = "starvegtest";
private const string PackageName = "com.foldcc.starveg";
[MenuItem("Topon/Build IAA Android Test APK")]
public static void BuildAndroidTestApk()
{
EnsureBuildFolders();
ConfigurePlayerSettings();
var resolverState = CaptureResolverState();
try
{
ApplyBatchFriendlyResolverSettings();
var scenes = new[] { "Assets/Samples/IAAAdDebugSample/Scenes/IAAAdDebugSample.unity" };
var options = new BuildPlayerOptions
{
scenes = scenes,
target = BuildTarget.Android,
locationPathName = OutputApkPath,
options = BuildOptions.None
};
var report = BuildPipeline.BuildPlayer(options);
if (report.summary.result != BuildResult.Succeeded)
{
throw new Exception(
$"Android APK build failed: {report.summary.result} ({report.summary.totalErrors} errors)");
}
Debug.Log($"[IAA Build] APK generated: {Path.GetFullPath(OutputApkPath)}");
}
finally
{
RestoreResolverState(resolverState);
}
}
public static void BuildAndroidTestApkInBatchMode()
{
BuildAndroidTestApk();
EditorApplication.Exit(0);
}
private static void ConfigurePlayerSettings()
{
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, PackageName);
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64;
PlayerSettings.Android.useCustomKeystore = true;
PlayerSettings.Android.keystoreName = Path.GetFullPath(KeystorePath);
PlayerSettings.Android.keystorePass = KeystorePassword;
PlayerSettings.Android.keyaliasName = KeyAliasName;
PlayerSettings.Android.keyaliasPass = KeystorePassword;
PlayerSettings.bundleVersion = string.IsNullOrWhiteSpace(PlayerSettings.bundleVersion)
? "1.0.0"
: PlayerSettings.bundleVersion;
PlayerSettings.Android.bundleVersionCode = Mathf.Max(1, PlayerSettings.Android.bundleVersionCode);
AssetDatabase.SaveAssets();
}
private static void EnsureBuildFolders()
{
Directory.CreateDirectory(Path.GetFullPath(OutputDirectory));
Directory.CreateDirectory(Path.GetFullPath(Path.GetDirectoryName(KeystorePath) ?? OutputDirectory));
}
private static ResolverState CaptureResolverState()
{
var settingsType = GetResolverSettingsType();
return new ResolverState
{
EnableAutoResolution = GetResolverBool(settingsType, "EnableAutoResolution"),
AutoResolveOnBuild = GetResolverBool(settingsType, "AutoResolveOnBuild"),
PromptBeforeAutoResolution = GetResolverBool(settingsType, "PromptBeforeAutoResolution")
};
}
private static void ApplyBatchFriendlyResolverSettings()
{
var settingsType = GetResolverSettingsType();
SetResolverBool(settingsType, "EnableAutoResolution", false);
SetResolverBool(settingsType, "AutoResolveOnBuild", false);
SetResolverBool(settingsType, "PromptBeforeAutoResolution", false);
}
private static void RestoreResolverState(ResolverState state)
{
var settingsType = GetResolverSettingsType();
SetResolverBool(settingsType, "EnableAutoResolution", state.EnableAutoResolution);
SetResolverBool(settingsType, "AutoResolveOnBuild", state.AutoResolveOnBuild);
SetResolverBool(settingsType, "PromptBeforeAutoResolution", state.PromptBeforeAutoResolution);
}
private static Type GetResolverSettingsType()
{
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var type = assembly.GetType("GooglePlayServices.SettingsDialog");
if (type != null)
{
return type;
}
}
return null;
}
private static bool GetResolverBool(Type settingsType, string propertyName)
{
if (settingsType == null)
{
return false;
}
var property = settingsType.GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (property == null)
{
return false;
}
return (bool)property.GetValue(null, null);
}
private static void SetResolverBool(Type settingsType, string propertyName, bool value)
{
if (settingsType == null)
{
return;
}
var property = settingsType.GetProperty(
propertyName,
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
if (property == null)
{
return;
}
property.SetValue(null, value, null);
}
private struct ResolverState
{
public bool EnableAutoResolution;
public bool AutoResolveOnBuild;
public bool PromptBeforeAutoResolution;
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97e92d5544ec4fc9adf7571f9f4ea241
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,159 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using Runtime.ADAggregator;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
public static class IAAAdDebugSampleCreator
{
private const string SampleRoot = "Assets/Samples/IAAAdDebugSample";
private const string SampleSceneDirectory = SampleRoot + "/Scenes";
private const string SampleConfigDirectory = SampleRoot + "/Configs";
private const string SampleScenePath = SampleSceneDirectory + "/IAAAdDebugSample.unity";
private const string SampleConfigPath = SampleConfigDirectory + "/IAAAdDebugSampleConfig.asset";
[MenuItem("Topon/Create IAA IMGUI Debug Sample")]
public static void CreateOrUpdateSample()
{
EnsureDirectory(SampleRoot);
EnsureDirectory(SampleSceneDirectory);
EnsureDirectory(SampleConfigDirectory);
var config = LoadOrCreateConfig();
CreateOrUpdateScene(config);
AppendSceneToBuildSettings(SampleScenePath);
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
EditorUtility.DisplayDialog(
"IAA Sample Created",
$"Scene: {SampleScenePath}\nConfig: {SampleConfigPath}",
"OK");
}
public static void CreateOrUpdateSampleInBatchMode()
{
CreateOrUpdateSample();
EditorApplication.Exit(0);
}
private static ADConfig LoadOrCreateConfig()
{
var config = AssetDatabase.LoadAssetAtPath<ADConfig>(SampleConfigPath);
if (config == null)
{
config = ScriptableObject.CreateInstance<ADConfig>();
AssetDatabase.CreateAsset(config, SampleConfigPath);
}
config.ConfigName = "IAA Ad Debug Sample";
config.Id = string.IsNullOrWhiteSpace(config.Id) ? "fill_taku_app_id" : config.Id;
config.Key = string.IsNullOrWhiteSpace(config.Key) ? "fill_taku_app_key" : config.Key;
config.Key2 = string.IsNullOrWhiteSpace(config.Key2) ? string.Empty : config.Key2;
config.BaseAwardAdKeyValue = config.BaseAwardAdKeyValue ?? new AdKeyValue();
config.BaseAwardAdKeyValue.key = "rewarded";
config.BaseAwardAdKeyValue.value = string.IsNullOrWhiteSpace(config.BaseAwardAdKeyValue.value)
? "fill_rewarded_placement"
: config.BaseAwardAdKeyValue.value;
config.BaseInteractionAdKeyValue = config.BaseInteractionAdKeyValue ?? new AdKeyValue();
config.BaseInteractionAdKeyValue.key = "interstitial";
config.BaseInteractionAdKeyValue.value = string.IsNullOrWhiteSpace(config.BaseInteractionAdKeyValue.value)
? "fill_interstitial_placement"
: config.BaseInteractionAdKeyValue.value;
config.BaseSplashAdKeyValue = config.BaseSplashAdKeyValue ?? new AdKeyValue();
config.BaseSplashAdKeyValue.key = "splash";
config.BaseSplashAdKeyValue.value = string.IsNullOrWhiteSpace(config.BaseSplashAdKeyValue.value)
? "optional_splash_placement"
: config.BaseSplashAdKeyValue.value;
config.CommonKeyValues ??= new List<AdKeyValue>();
SetOrAddCommonKey(config, "topon.rewarded_auto_load", "true");
SetOrAddCommonKey(config, "topon.rewarded_prewarm_on_init", "true");
SetOrAddCommonKey(config, "topon.rewarded_max_load_attempts", "3");
SetOrAddCommonKey(config, "topon.rewarded_load_retry_delay_ms", "1000");
SetOrAddCommonKey(config, "topon.interstitial_auto_load", "true");
SetOrAddCommonKey(config, "topon.interstitial_prewarm_on_init", "true");
SetOrAddCommonKey(config, "topon.interstitial_max_load_attempts", "2");
SetOrAddCommonKey(config, "topon.interstitial_load_retry_delay_ms", "500");
EditorUtility.SetDirty(config);
return config;
}
private static void CreateOrUpdateScene(ADConfig config)
{
var scene = EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
scene.name = "IAAAdDebugSample";
var root = new GameObject("IAA Ad Debug Sample");
var gui = root.AddComponent<IAAAdDebugSampleGui>();
gui.adConfig = config;
gui.userId = "debug_user_001";
gui.rewardedScenario = "reward_debug";
gui.interstitialScenario = "interstitial_debug";
gui.autoInitialize = true;
gui.forcePortrait = true;
gui.captureUnityLogs = true;
gui.showVerboseState = true;
EditorSceneManager.SaveScene(scene, SampleScenePath);
}
private static void AppendSceneToBuildSettings(string scenePath)
{
var scenes = new List<EditorBuildSettingsScene>(EditorBuildSettings.scenes);
var exists = false;
for (var i = 0; i < scenes.Count; i++)
{
if (scenes[i].path != scenePath)
{
continue;
}
scenes[i] = new EditorBuildSettingsScene(scenePath, true);
exists = true;
break;
}
if (!exists)
{
scenes.Add(new EditorBuildSettingsScene(scenePath, true));
}
EditorBuildSettings.scenes = scenes.ToArray();
}
private static void SetOrAddCommonKey(ADConfig config, string key, string value)
{
foreach (var item in config.CommonKeyValues)
{
if (item != null && item.key == key)
{
item.value = value;
return;
}
}
config.CommonKeyValues.Add(new AdKeyValue
{
key = key,
value = value
});
}
private static void EnsureDirectory(string assetPath)
{
if (AssetDatabase.IsValidFolder(assetPath))
{
return;
}
Directory.CreateDirectory(Path.GetFullPath(assetPath));
AssetDatabase.Refresh();
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c488fb4879e14f5cb366ce084ff2b748
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,78 @@
#if UNITY_EDITOR
using System;
using System.IO;
using UnityEditor;
using UnityEngine;
[InitializeOnLoad]
public static class IAABatchBuildBootstrap
{
private const string TaskFilePath = "Temp/iaa-batch-task.txt";
private static bool _subscribed;
private static bool _taskRunning;
static IAABatchBuildBootstrap()
{
if (!Application.isBatchMode)
{
return;
}
if (_subscribed)
{
return;
}
_subscribed = true;
EditorApplication.update += ExecutePendingTaskWhenReady;
}
private static void ExecutePendingTaskWhenReady()
{
if (!Application.isBatchMode || _taskRunning)
{
return;
}
var fullPath = Path.GetFullPath(TaskFilePath);
if (!File.Exists(fullPath))
{
return;
}
if (EditorApplication.isCompiling || EditorApplication.isUpdating)
{
return;
}
_taskRunning = true;
EditorApplication.update -= ExecutePendingTaskWhenReady;
var task = File.ReadAllText(fullPath).Trim();
File.Delete(fullPath);
try
{
switch (task)
{
case "create-sample":
IAAAdDebugSampleCreator.CreateOrUpdateSample();
break;
case "build-apk":
IAAAdAndroidBuild.BuildAndroidTestApk();
break;
default:
Debug.LogWarning($"[IAA Batch] Unknown task: {task}");
break;
}
EditorApplication.Exit(0);
}
catch (Exception exception)
{
Debug.LogException(exception);
EditorApplication.Exit(1);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 33e31dbdc20d47a78340d49f58d81ef9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -3,6 +3,7 @@
"rootNamespace": "",
"references": [
"GUID:8a3d1447e0a3bdf4fa07035516da8b62",
"GUID:3198a86b02613024e960e3d04a9638cd",
"GUID:483a01338fa974b4498cd71261d6e8b9",
"GUID:87bccae0237fd4a41ac446d6636f95e0"
],
@@ -17,4 +18,4 @@
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
}

View File

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

View File

@@ -0,0 +1,615 @@
using System;
using System.Collections.Generic;
using System.IO;
using AnyThinkAds.Api;
using Runtime.ADAggregator;
using UnityEngine;
using UnityEngine.EventSystems;
public sealed class IAAAdDebugSampleGui : MonoBehaviour
{
private const float ReferenceWidth = 1080f;
private const float ReferenceHeight = 2340f;
private const int MaxLogEntries = 120;
private const float StatusRefreshInterval = 1f;
public ADConfig adConfig;
public string userId = "debug_user_001";
public string rewardedScenario = "reward_debug";
public string interstitialScenario = "interstitial_debug";
public bool autoInitialize = true;
public bool forcePortrait = true;
public bool captureUnityLogs = true;
public bool showVerboseState = true;
public bool openDebugUiOnButton = true;
public bool autoWriteLogsToFile = true;
private readonly List<string> _logs = new List<string>(MaxLogEntries);
private Vector2 _pageScroll;
private Vector2 _logScroll;
private Vector2 _rewardedStatusScroll;
private Vector2 _interstitialStatusScroll;
private bool _initInvoked;
private bool _initCallbackReceived;
private bool _stylesInitialized;
private GUIStyle _titleStyle;
private GUIStyle _sectionStyle;
private GUIStyle _textStyle;
private GUIStyle _buttonStyle;
private GUIStyle _textFieldStyle;
private GUIStyle _logStyle;
private bool _rewardedReadyCache;
private bool _interstitialReadyCache;
private string _rewardedStatusCache = "Not refreshed yet.";
private string _interstitialStatusCache = "Not refreshed yet.";
private float _nextStatusRefreshTime;
private string _sessionLogFilePath;
private string _sessionLogDirectory;
private void Awake()
{
Application.targetFrameRate = 60;
Screen.sleepTimeout = SleepTimeout.NeverSleep;
if (forcePortrait)
{
Screen.orientation = ScreenOrientation.Portrait;
}
if (captureUnityLogs)
{
Application.logMessageReceived += OnLogMessageReceived;
}
ADManager.Instance.GLOBAL_ShowAwardVideoBefore += OnRewardedBefore;
ADManager.Instance.GLOBAL_ShowAwardVideoComplete += OnRewardedComplete;
InitializeLogFile();
AppendLog("IAA Ad Debug Sample is ready.");
}
private void Start()
{
if (autoInitialize)
{
TryInitialize();
}
RefreshStatusCaches(true);
}
private void OnDestroy()
{
if (captureUnityLogs)
{
Application.logMessageReceived -= OnLogMessageReceived;
}
ADManager.Instance.GLOBAL_ShowAwardVideoBefore -= OnRewardedBefore;
ADManager.Instance.GLOBAL_ShowAwardVideoComplete -= OnRewardedComplete;
}
private void OnGUI()
{
EnsureStyles();
var safeArea = Screen.safeArea;
var scaleX = safeArea.width / ReferenceWidth;
var scaleY = safeArea.height / ReferenceHeight;
var previousMatrix = GUI.matrix;
GUI.matrix = Matrix4x4.TRS(
new Vector3(safeArea.x, safeArea.y, 0f),
Quaternion.identity,
new Vector3(scaleX, scaleY, 1f));
GUILayout.BeginArea(new Rect(24f, 24f, ReferenceWidth - 48f, ReferenceHeight - 48f), GUI.skin.box);
_pageScroll = GUILayout.BeginScrollView(_pageScroll, false, true);
DrawHeader();
DrawSetupSection();
DrawStatusSection();
DrawControlSection();
DrawLogSection();
GUILayout.EndScrollView();
GUILayout.EndArea();
GUI.matrix = previousMatrix;
}
private void DrawHeader()
{
GUILayout.Label("IAA Ad Debug Sample", _titleStyle);
GUILayout.Label(
$"Screen: {Screen.width} x {Screen.height} | SafeArea: {Screen.safeArea.width:0} x {Screen.safeArea.height:0}",
_textStyle);
GUILayout.Label(
$"Reference Layout: {ReferenceWidth:0} x {ReferenceHeight:0} | Orientation: {Screen.orientation}",
_textStyle);
GUILayout.Space(10f);
}
private void DrawSetupSection()
{
GUILayout.Label("Setup", _sectionStyle);
GUILayout.Label(
$"ADConfig Asset: {(adConfig != null ? adConfig.name : "Missing")} | Init Invoked: {_initInvoked} | Init Callback: {_initCallbackReceived}",
_textStyle);
userId = DrawLabeledTextField("User ID", userId);
rewardedScenario = DrawLabeledTextField("Rewarded Scenario", rewardedScenario);
interstitialScenario = DrawLabeledTextField("Interstitial Scenario", interstitialScenario);
var rewardedPlacement = adConfig?.BaseAwardAdKeyValue?.value ?? string.Empty;
var interstitialPlacement = adConfig?.BaseInteractionAdKeyValue?.value ?? string.Empty;
GUILayout.Label($"Rewarded Placement: {DisplayValue(rewardedPlacement)}", _textStyle);
GUILayout.Label($"Interstitial Placement: {DisplayValue(interstitialPlacement)}", _textStyle);
GUILayout.Space(6f);
GUILayout.BeginHorizontal();
if (GUILayout.Button("Initialize ADManager", _buttonStyle, GUILayout.Height(78f)))
{
TryInitialize();
}
if (GUILayout.Button("Open DebugUI", _buttonStyle, GUILayout.Height(78f)))
{
if (EnsureInitialized("Open DebugUI"))
{
OpenDebugUi();
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10f);
}
private void DrawStatusSection()
{
GUILayout.Label("Runtime Status", _sectionStyle);
if (_initInvoked)
{
RefreshStatusCaches();
GUILayout.Label($"Rewarded Ready: {_rewardedReadyCache}", _textStyle);
GUILayout.Label($"Interstitial Ready: {_interstitialReadyCache}", _textStyle);
}
else
{
GUILayout.Label("Rewarded Ready: not initialized", _textStyle);
GUILayout.Label("Interstitial Ready: not initialized", _textStyle);
}
var options = ToponAdController.CurrentOptions;
if (options != null)
{
GUILayout.Label(
$"Rewarded Auto/Prewarm/Retry: {options.RewardedAutoLoad}/{options.RewardedPrewarmOnInit}/{options.RewardedMaxLoadAttempts}@{options.RewardedLoadRetryDelayMs}ms",
_textStyle);
GUILayout.Label(
$"Interstitial Auto/Prewarm/Retry: {options.InterstitialAutoLoad}/{options.InterstitialPrewarmOnInit}/{options.InterstitialMaxLoadAttempts}@{options.InterstitialLoadRetryDelayMs}ms",
_textStyle);
GUILayout.Label(
$"Local Strategy Asset: {DisplayValue(options.LocalStrategyAssetPath)}",
_textStyle);
}
if (showVerboseState && _initInvoked)
{
RefreshStatusCaches();
GUILayout.Space(6f);
if (GUILayout.Button("Refresh Status Snapshot", _buttonStyle, GUILayout.Height(64f)))
{
RefreshStatusCaches(true);
AppendLog("Manual status snapshot refreshed.");
}
GUILayout.Label("Rewarded Status", _sectionStyle);
_rewardedStatusScroll = GUILayout.BeginScrollView(_rewardedStatusScroll, GUILayout.Height(240f));
GUILayout.Label(_rewardedStatusCache, _logStyle);
GUILayout.EndScrollView();
GUILayout.Label("Interstitial Status", _sectionStyle);
_interstitialStatusScroll = GUILayout.BeginScrollView(_interstitialStatusScroll, GUILayout.Height(220f));
GUILayout.Label(_interstitialStatusCache, _logStyle);
GUILayout.EndScrollView();
}
GUILayout.Space(10f);
}
private void DrawControlSection()
{
GUILayout.Label("Ad Actions", _sectionStyle);
GUILayout.BeginHorizontal();
if (GUILayout.Button("Enter Rewarded Scene", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Enter Rewarded Scene"))
{
ADManager.Instance.EnterAdScenario(AD_Type.AwardVideo, rewardedScenario);
AppendLog($"Enter rewarded scene requested. scenario={rewardedScenario}");
}
}
if (GUILayout.Button("Enter Interstitial Scene", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Enter Interstitial Scene"))
{
ADManager.Instance.EnterAdScenario(AD_Type.Interaction, interstitialScenario);
AppendLog($"Enter interstitial scene requested. scenario={interstitialScenario}");
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Load Rewarded", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Load Rewarded"))
{
ADManager.Instance.LoadAD(AD_Type.AwardVideo);
AppendLog("Manual rewarded load requested.");
}
}
if (GUILayout.Button("Async Play Rewarded", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Play Rewarded"))
{
ADManager.Instance.AsyncPlayAD(AD_Type.AwardVideo, rewardedScenario, result =>
{
AppendLog($"Rewarded callback: {result}");
});
AppendLog($"AsyncPlayAD rewarded requested. scenario={rewardedScenario}");
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Load Interstitial", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Load Interstitial"))
{
ADManager.Instance.LoadAD(AD_Type.Interaction);
AppendLog("Manual interstitial load requested.");
}
}
if (GUILayout.Button("Async Play Interstitial", _buttonStyle, GUILayout.Height(84f)))
{
if (EnsureInitialized("Play Interstitial"))
{
ADManager.Instance.AsyncPlayAD(AD_Type.Interaction, interstitialScenario, result =>
{
AppendLog($"Interstitial callback: {result}");
});
AppendLog($"AsyncPlayAD interstitial requested. scenario={interstitialScenario}");
}
}
GUILayout.EndHorizontal();
GUILayout.Space(10f);
}
private void DrawLogSection()
{
GUILayout.Label("Diagnostics", _sectionStyle);
GUILayout.BeginHorizontal();
if (GUILayout.Button("Clear Logs", _buttonStyle, GUILayout.Height(70f)))
{
_logs.Clear();
AppendLog("Logs cleared.");
}
if (GUILayout.Button("Copy All Logs", _buttonStyle, GUILayout.Height(70f)))
{
GUIUtility.systemCopyBuffer = string.Join("\n", _logs);
AppendLog($"Copied {_logs.Count} log lines to clipboard.");
}
if (GUILayout.Button("Log Current Strategy", _buttonStyle, GUILayout.Height(70f)))
{
var options = ToponAdController.CurrentOptions;
if (options == null)
{
AppendLog("Current strategy unavailable: controller not initialized.");
}
else
{
AppendLog(
$"Strategy => RewardedAuto={options.RewardedAutoLoad}, RewardedPrewarm={options.RewardedPrewarmOnInit}, InterAuto={options.InterstitialAutoLoad}, InterPrewarm={options.InterstitialPrewarmOnInit}");
}
}
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
if (GUILayout.Button("Export Logs To File", _buttonStyle, GUILayout.Height(70f)))
{
ExportVisibleLogsToFile();
}
if (GUILayout.Button("Copy Log File Path", _buttonStyle, GUILayout.Height(70f)))
{
if (string.IsNullOrWhiteSpace(_sessionLogFilePath))
{
AppendLog("Log file path unavailable.");
}
else
{
GUIUtility.systemCopyBuffer = _sessionLogFilePath;
AppendLog($"Copied log file path: {_sessionLogFilePath}");
}
}
GUILayout.EndHorizontal();
GUILayout.Label($"Log File: {DisplayValue(_sessionLogFilePath)}", _textStyle);
_logScroll = GUILayout.BeginScrollView(_logScroll, GUILayout.Height(520f));
foreach (var line in _logs)
{
GUILayout.Label(line, _logStyle);
}
GUILayout.EndScrollView();
}
private string DrawLabeledTextField(string label, string value)
{
GUILayout.BeginHorizontal();
GUILayout.Label(label, _textStyle, GUILayout.Width(260f));
value = GUILayout.TextField(value ?? string.Empty, _textFieldStyle, GUILayout.Height(64f));
GUILayout.EndHorizontal();
return value;
}
private bool TryInitialize()
{
if (_initInvoked)
{
AppendLog("Initialize skipped: ADManager already initialized by sample.");
return true;
}
if (adConfig == null)
{
AppendLog("Initialize failed: sample ADConfig asset is missing.");
return false;
}
if (string.IsNullOrWhiteSpace(adConfig.Id) || string.IsNullOrWhiteSpace(adConfig.Key))
{
AppendLog("Initialize warning: Taku AppId/AppKey is empty. Please fill the sample ADConfig asset.");
}
_initInvoked = true;
var controller = new ToponAdController();
ADManager.Instance.Init(() =>
{
_initCallbackReceived = true;
AppendLog("ADManager.Init callback received.");
}, userId, adConfig, controller);
AppendLog($"ADManager.Init invoked with userId={userId}");
AppendLog($"Rewarded placement={adConfig.BaseAwardAdKeyValue?.value}, interstitial placement={adConfig.BaseInteractionAdKeyValue?.value}");
return true;
}
private bool EnsureInitialized(string actionName)
{
if (TryInitialize())
{
return true;
}
AppendLog($"{actionName} cancelled: initialization failed.");
return false;
}
private void RefreshStatusCaches(bool force = false)
{
if (!force && Time.unscaledTime < _nextStatusRefreshTime)
{
return;
}
_nextStatusRefreshTime = Time.unscaledTime + StatusRefreshInterval;
_rewardedReadyCache = _initInvoked && ADManager.Instance.IsRealy(AD_Type.AwardVideo);
_interstitialReadyCache = _initInvoked && ADManager.Instance.IsRealy(AD_Type.Interaction);
_rewardedStatusCache = GetRewardedStatusText();
_interstitialStatusCache = GetInterstitialStatusText();
}
private string GetRewardedStatusText()
{
var placement = adConfig?.BaseAwardAdKeyValue?.value;
if (string.IsNullOrWhiteSpace(placement))
{
return "No rewarded placement configured.";
}
var options = ToponAdController.CurrentOptions;
if (options?.RewardedAutoLoad ?? true)
{
return ATRewardedAutoVideo.Instance.checkAutoAdStatus(placement);
}
return ATRewardedVideo.Instance.checkAdStatus(placement);
}
private string GetInterstitialStatusText()
{
var placement = adConfig?.BaseInteractionAdKeyValue?.value;
if (string.IsNullOrWhiteSpace(placement))
{
return "No interstitial placement configured.";
}
var options = ToponAdController.CurrentOptions;
if (options?.InterstitialAutoLoad ?? true)
{
return ATInterstitialAutoAd.Instance.checkAutoAdStatus(placement);
}
return ATInterstitialAd.Instance.checkAdStatus(placement);
}
private void OnRewardedBefore(string placementId, string scenario)
{
AppendLog($"Rewarded before show => placement={placementId}, scenario={scenario}");
}
private void OnRewardedComplete(bool completed)
{
AppendLog($"Rewarded global complete => reward={completed}");
}
private void OnLogMessageReceived(string condition, string stackTrace, LogType type)
{
if (type != LogType.Error && type != LogType.Exception && type != LogType.Warning)
{
if (!condition.Contains("[Topon]") &&
!condition.Contains("ATSDK") &&
!condition.Contains("Rewarded") &&
!condition.Contains("Interstitial") &&
!condition.Contains("Taku"))
{
return;
}
}
AppendLog($"[{type}] {condition}");
}
private void OpenDebugUi()
{
var options = ToponAdController.CurrentOptions;
if (!openDebugUiOnButton)
{
AppendLog("DebugUI launch disabled by sample toggle.");
return;
}
if (options != null && !string.IsNullOrWhiteSpace(options.DebuggerKey))
{
ATSDKAPI.showDebuggerUI(options.DebuggerKey);
AppendLog($"Open DebugUI with debugger key={options.DebuggerKey}");
return;
}
ATSDKAPI.showDebuggerUI();
AppendLog("Open DebugUI with current SDK configuration.");
}
private void AppendLog(string message)
{
var line = $"{DateTime.Now:HH:mm:ss} {message}";
if (_logs.Count >= MaxLogEntries)
{
_logs.RemoveAt(0);
}
_logs.Add(line);
TryAppendLineToFile(line);
_logScroll.y = float.MaxValue;
}
private void InitializeLogFile()
{
try
{
_sessionLogDirectory = Path.Combine(Application.persistentDataPath, "IAAAdDebugLogs");
Directory.CreateDirectory(_sessionLogDirectory);
var fileName = $"iaa-ad-debug-{DateTime.Now:yyyyMMdd-HHmmss}.log";
_sessionLogFilePath = Path.Combine(_sessionLogDirectory, fileName);
File.WriteAllText(_sessionLogFilePath,
$"IAA Ad Debug Sample Log{Environment.NewLine}" +
$"Created: {DateTime.Now:yyyy-MM-dd HH:mm:ss}{Environment.NewLine}" +
$"persistentDataPath: {Application.persistentDataPath}{Environment.NewLine}" +
$"----------------------------------------{Environment.NewLine}");
}
catch (Exception exception)
{
_sessionLogFilePath = null;
Debug.LogWarning($"[IAA Sample] Failed to initialize log file: {exception.Message}");
}
}
private void TryAppendLineToFile(string line)
{
if (!autoWriteLogsToFile || string.IsNullOrWhiteSpace(_sessionLogFilePath))
{
return;
}
try
{
File.AppendAllText(_sessionLogFilePath, line + Environment.NewLine);
}
catch (Exception exception)
{
Debug.LogWarning($"[IAA Sample] Failed to append log file: {exception.Message}");
}
}
private void ExportVisibleLogsToFile()
{
try
{
if (string.IsNullOrWhiteSpace(_sessionLogDirectory))
{
InitializeLogFile();
}
var exportPath = Path.Combine(
_sessionLogDirectory ?? Application.persistentDataPath,
$"iaa-ad-debug-export-{DateTime.Now:yyyyMMdd-HHmmss}.log");
File.WriteAllLines(exportPath, _logs);
AppendLog($"Exported {_logs.Count} visible log lines to file: {exportPath}");
}
catch (Exception exception)
{
AppendLog($"Export log file failed: {exception.Message}");
}
}
private static string DisplayValue(string value)
{
return string.IsNullOrWhiteSpace(value) ? "<empty>" : value;
}
private void EnsureStyles()
{
if (_stylesInitialized)
{
return;
}
_stylesInitialized = true;
_titleStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 54,
fontStyle = FontStyle.Bold,
alignment = TextAnchor.MiddleCenter,
wordWrap = true
};
_sectionStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 34,
fontStyle = FontStyle.Bold,
wordWrap = true
};
_textStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 28,
wordWrap = true
};
_buttonStyle = new GUIStyle(GUI.skin.button)
{
fontSize = 28,
wordWrap = true
};
_textFieldStyle = new GUIStyle(GUI.skin.textField)
{
fontSize = 28
};
_logStyle = new GUIStyle(GUI.skin.label)
{
fontSize = 24,
wordWrap = true,
richText = false
};
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 53b297b9bf0d4cfca2b77af0fd74c95c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -7,12 +7,15 @@ using UnityEngine;
public class AwardVideoPlayer : ADPlayer , ATRewardedVideoListener
{
private ATRewardedVideo _atRewardedVideo;
private ATRewardedAutoVideo _atRewardedAutoVideo;
private Action<bool> _onVideoComplete;
private ADListenerAggregator _aggregator;
private bool _autoLoadRegistered;
public override void OnInit()
{
this._atRewardedVideo = ATRewardedVideo.Instance;
this._atRewardedAutoVideo = ATRewardedAutoVideo.Instance;
// this._atRewardedVideo.client.setListener(this); //由于新版本广告sdk弃用该方式通过以下方式重新桥接监听事件
this._aggregator = new ADListenerAggregator();
this._aggregator.BindAwardVideoListener(this._atRewardedVideo.client , this);
@@ -30,7 +33,15 @@ public class AwardVideoPlayer : ADPlayer , ATRewardedVideoListener
this.adListener.onClose = onClose;
this.adListener.onVideoComplete = this.OnVideoComplete;
var json = new Dictionary<string, string> { { AnyThinkAds.Api.ATConst.SCENARIO, this.AdScene } };
this._atRewardedVideo.showAd(this.Key , json);
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atRewardedAutoVideo.showAutoAd(this.Key, json);
}
else
{
this._atRewardedVideo.showAd(this.Key , json);
}
}
}
@@ -42,18 +53,54 @@ public class AwardVideoPlayer : ADPlayer , ATRewardedVideoListener
public override bool IsReadly()
{
return this.curState == 2;
if (UseAutoLoad())
{
if (this._atRewardedAutoVideo != null &&
this._atRewardedAutoVideo.autoLoadRewardedVideoReadyForPlacementID(this.Key))
{
this.curState = 2;
return true;
}
return this.curState == 2;
}
if (this.curState == 2)
{
return true;
}
if (this._atRewardedVideo != null && this._atRewardedVideo.hasAdReady(this.Key))
{
this.curState = 2;
return true;
}
return false;
}
public override void LoadAD()
{
if (curState == 0)
{
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atRewardedAutoVideo.setAutoLocalExtra(this.Key, BuildRewardedExtra());
this.curState = this._atRewardedAutoVideo.autoLoadRewardedVideoReadyForPlacementID(this.Key) ? 2 : 1;
return;
}
if (this._atRewardedVideo != null && this._atRewardedVideo.hasAdReady(this.Key))
{
this.curState = 2;
return;
}
{
Dictionary<string,string> jsonmap = new Dictionary<string,string>();
//ATConst.USERID_KEY必传用于标识每个用户;ATConst.USER_EXTRA_DATA为可选参数传入后将透传到开发者的服务器
jsonmap.Add(ATConst.USERID_KEY, ADManager.Instance.UserId);
jsonmap.Add(ATConst.USER_EXTRA_DATA, "user_extra_data");
jsonmap = BuildRewardedExtra();
curState = 1;
this._atRewardedVideo.loadVideoAd(this.Key, jsonmap);
}
@@ -130,5 +177,63 @@ public class AwardVideoPlayer : ADPlayer , ATRewardedVideoListener
}
#endregion
public override void OnPlayRequestStarted()
{
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atRewardedAutoVideo?.setAutoLocalExtra(this.Key, BuildRewardedExtra());
}
}
public override int MaxLoadAttempts => ToponAdController.CurrentOptions?.RewardedMaxLoadAttempts ?? base.MaxLoadAttempts;
public override float LoadRetryDelaySeconds =>
Math.Max(0f, (ToponAdController.CurrentOptions?.RewardedLoadRetryDelayMs ?? 750) / 1000f);
public override bool AutoPreloadOnInit => ToponAdController.CurrentOptions?.RewardedPrewarmOnInit ?? true;
private bool UseAutoLoad()
{
return ToponAdController.CurrentOptions?.RewardedAutoLoad ?? true;
}
private void EnsureAutoLoadRegistered()
{
if (_autoLoadRegistered)
{
return;
}
this._atRewardedAutoVideo?.addAutoLoadAdPlacementID(new[] { this.Key });
_autoLoadRegistered = true;
}
private Dictionary<string, string> BuildRewardedExtra()
{
return new Dictionary<string, string>
{
{ ATConst.USERID_KEY, ADManager.Instance.UserId },
{ ATConst.USER_EXTRA_DATA, "user_extra_data" }
};
}
public override void EnterAdScenario(string scenario)
{
if (string.IsNullOrWhiteSpace(scenario) || string.Equals(scenario, "__default__", StringComparison.Ordinal))
{
return;
}
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atRewardedAutoVideo?.entryAutoAdScenarioWithPlacementID(this.Key, scenario);
return;
}
this._atRewardedVideo?.entryScenarioWithPlacementID(this.Key, scenario);
}
}
}

View File

@@ -7,28 +7,83 @@ using UnityEngine;
public class InteractionPlayer : ADPlayer , ATInterstitialAdListener
{
private ATInterstitialAd _atInterstitialAd;
private ATInterstitialAutoAd _atInterstitialAutoAd;
private ADListenerAggregator _aggregator;
private bool _autoLoadRegistered;
public override void OnInit()
{
this._atInterstitialAd = ATInterstitialAd.Instance;
this._atInterstitialAutoAd = ATInterstitialAutoAd.Instance;
this._aggregator = new ADListenerAggregator();
this._aggregator.BindInterstitialAdListener(this._atInterstitialAd.client,this);
}
public override void ShowAD(Action onClose, Action<bool> onVideoComplete)
{
if (curState == 2)
if (this.IsReadly())
{
ATInterstitialAd.Instance.showInterstitialAd(this.Key);
this.adListener.onClose = onClose;
this.adListener.onVideoComplete = onVideoComplete;
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atInterstitialAutoAd.showAutoAd(this.Key, BuildInterstitialExtra());
}
else
{
ATInterstitialAd.Instance.showInterstitialAd(this.Key);
}
curState = 0;
}
}
public override bool IsReadly()
{
if (this.curState == 2)
{
return true;
}
if (UseAutoLoad())
{
if (this._atInterstitialAutoAd != null &&
this._atInterstitialAutoAd.autoLoadInterstitialAdReadyForPlacementID(this.Key))
{
this.curState = 2;
return true;
}
return false;
}
if (this._atInterstitialAd != null && this._atInterstitialAd.hasInterstitialAdReady(this.Key))
{
this.curState = 2;
return true;
}
return false;
}
public override void LoadAD()
{
if (curState == 0)
{
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atInterstitialAutoAd.setAutoLocalExtra(this.Key, BuildInterstitialExtra());
this.curState = this._atInterstitialAutoAd.autoLoadInterstitialAdReadyForPlacementID(this.Key) ? 2 : 1;
return;
}
if (this._atInterstitialAd != null && this._atInterstitialAd.hasInterstitialAdReady(this.Key))
{
this.curState = 2;
return;
}
{
//加载广告
Dictionary<string, object> jsonmap = new Dictionary<string, object>();
@@ -61,6 +116,8 @@ public class InteractionPlayer : ADPlayer , ATInterstitialAdListener
public void onInterstitialAdFailedToShow(string placementId)
{
this.curState = 0;
this.adListener.OnShowError();
}
public void onInterstitialAdClose(string placementId, ATCallbackInfo callbackInfo)
@@ -83,6 +140,8 @@ public class InteractionPlayer : ADPlayer , ATInterstitialAdListener
public void onInterstitialAdFailedToPlayVideo(string placementId, string code, string message)
{
this.curState = 0;
this.adListener.OnShowError();
}
public void startLoadingADSource(string placementId, ATCallbackInfo callbackInfo)
@@ -112,4 +171,61 @@ public class InteractionPlayer : ADPlayer , ATInterstitialAdListener
}
#endregion
}
public override void OnPlayRequestStarted()
{
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atInterstitialAutoAd?.setAutoLocalExtra(this.Key, BuildInterstitialExtra());
}
}
public override int MaxLoadAttempts => ToponAdController.CurrentOptions?.InterstitialMaxLoadAttempts ?? base.MaxLoadAttempts;
public override float LoadRetryDelaySeconds =>
Math.Max(0f, (ToponAdController.CurrentOptions?.InterstitialLoadRetryDelayMs ?? 500) / 1000f);
public override bool AutoPreloadOnInit => ToponAdController.CurrentOptions?.InterstitialPrewarmOnInit ?? true;
private bool UseAutoLoad()
{
return ToponAdController.CurrentOptions?.InterstitialAutoLoad ?? true;
}
private void EnsureAutoLoadRegistered()
{
if (_autoLoadRegistered)
{
return;
}
this._atInterstitialAutoAd?.addAutoLoadAdPlacementID(new[] { this.Key });
_autoLoadRegistered = true;
}
private Dictionary<string, string> BuildInterstitialExtra()
{
return new Dictionary<string, string>
{
{ ATConst.SCENARIO, this.AdScene ?? string.Empty }
};
}
public override void EnterAdScenario(string scenario)
{
if (string.IsNullOrWhiteSpace(scenario) || string.Equals(scenario, "__default__", StringComparison.Ordinal))
{
return;
}
if (UseAutoLoad())
{
EnsureAutoLoadRegistered();
this._atInterstitialAutoAd?.entryAutoAdScenarioWithPlacementID(this.Key, scenario);
return;
}
this._atInterstitialAd?.entryScenarioWithPlacementID(this.Key, scenario);
}
}

View File

@@ -7,6 +7,7 @@ public class ToponAdController : IAdController
{
public static string LastDetectedArea { get; private set; }
public static string LastAreaError { get; private set; }
public static ToponControllerOptions CurrentOptions { get; private set; }
private Action<bool> _maskAction;
private Action<string, string> _logEventAction;
@@ -20,14 +21,16 @@ public class ToponAdController : IAdController
_adConfig = adConfig;
_controllerOptions = ToponControllerOptions.Resolve(adConfig, args);
CurrentOptions = _controllerOptions;
ApplyPreInitOptions(_controllerOptions);
var isDebug = _controllerOptions.Debug ?? false;
ATSDKAPI.setLogDebug(isDebug);
ATSDKAPI.initSDK(adConfig.Id , adConfig.Key);
StartChinaMainlandSdkIfNeeded();
ApplyPostInitOptions(_controllerOptions);
if (isDebug)
if (_controllerOptions.AutoOpenDebuggerUI)
{
ShowAndroidTest ();
}
@@ -103,6 +106,11 @@ public class ToponAdController : IAdController
return;
}
if (!string.IsNullOrWhiteSpace(options.LocalStrategyAssetPath))
{
ATSDKAPI.setLocalStrategyAssetPath(options.LocalStrategyAssetPath);
}
if (options.InitCustomMap != null && options.InitCustomMap.Count > 0)
{
ATSDKAPI.initCustomMap(options.InitCustomMap);
@@ -163,6 +171,26 @@ public class ToponAdController : IAdController
}
}
private static void StartChinaMainlandSdkIfNeeded()
{
#if UNITY_ANDROID && !UNITY_EDITOR
try
{
if (!ATSDKAPI.isCnSDK())
{
return;
}
ATSDKAPI.start();
Debug.Log("[Topon] Called ATSDK.start() for China mainland SDK.");
}
catch (Exception exception)
{
Debug.LogWarning($"[Topon] Failed to call ATSDK.start(): {exception.Message}");
}
#endif
}
private sealed class ToponAreaListener : ATGetAreaListener
{
private readonly ToponAdController _controller;

View File

@@ -11,6 +11,7 @@ public sealed class ToponControllerOptions
public const string SubChannelKey = "topon.sub_channel";
public const string DebugKey = "topon.debug";
public const string DebuggerKeyKey = "topon.debugger_key";
public const string AutoOpenDebuggerUiKey = "topon.auto_open_debugger_ui";
public const string SDKAreaKey = "topon.sdk_area";
public const string LongitudeKey = "topon.longitude";
public const string LatitudeKey = "topon.latitude";
@@ -19,11 +20,21 @@ public sealed class ToponControllerOptions
public const string QueryAreaOnInitKey = "topon.query_area_on_init";
public const string InitCustomMapKey = "topon.custom_map";
public const string RewardedCustomDataKey = "topon.rewarded_custom_data";
public const string RewardedAutoLoadKey = "topon.rewarded_auto_load";
public const string RewardedPrewarmOnInitKey = "topon.rewarded_prewarm_on_init";
public const string RewardedMaxLoadAttemptsKey = "topon.rewarded_max_load_attempts";
public const string RewardedLoadRetryDelayMsKey = "topon.rewarded_load_retry_delay_ms";
public const string InterstitialAutoLoadKey = "topon.interstitial_auto_load";
public const string InterstitialPrewarmOnInitKey = "topon.interstitial_prewarm_on_init";
public const string InterstitialMaxLoadAttemptsKey = "topon.interstitial_max_load_attempts";
public const string InterstitialLoadRetryDelayMsKey = "topon.interstitial_load_retry_delay_ms";
public const string LocalStrategyAssetPathKey = "topon.local_strategy_asset_path";
public string Channel { get; set; }
public string SubChannel { get; set; }
public bool? Debug { get; set; }
public string DebuggerKey { get; set; }
public bool AutoOpenDebuggerUI { get; set; }
public int? SDKArea { get; set; }
public double? Longitude { get; set; }
public double? Latitude { get; set; }
@@ -34,6 +45,15 @@ public sealed class ToponControllerOptions
public Dictionary<string, string> RewardedCustomData { get; set; }
public Action<string> OnAreaReceived { get; set; }
public Action<string> OnAreaError { get; set; }
public bool RewardedAutoLoad { get; set; } = true;
public bool RewardedPrewarmOnInit { get; set; } = true;
public int RewardedMaxLoadAttempts { get; set; } = 3;
public int RewardedLoadRetryDelayMs { get; set; } = 1000;
public bool InterstitialAutoLoad { get; set; } = true;
public bool InterstitialPrewarmOnInit { get; set; } = true;
public int InterstitialMaxLoadAttempts { get; set; } = 2;
public int InterstitialLoadRetryDelayMs { get; set; } = 500;
public string LocalStrategyAssetPath { get; set; }
public static ToponControllerOptions Resolve(ADConfig adConfig, object[] args)
{
@@ -85,6 +105,7 @@ public sealed class ToponControllerOptions
SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel;
Debug = GetBool(map, DebugKey, "debug") ?? Debug;
DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey;
AutoOpenDebuggerUI = GetBool(map, AutoOpenDebuggerUiKey) ?? AutoOpenDebuggerUI;
SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea;
Longitude = GetDouble(map, LongitudeKey) ?? Longitude;
Latitude = GetDouble(map, LatitudeKey) ?? Latitude;
@@ -93,6 +114,15 @@ public sealed class ToponControllerOptions
QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit;
InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + "."));
RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + "."));
RewardedAutoLoad = GetBool(map, RewardedAutoLoadKey) ?? RewardedAutoLoad;
RewardedPrewarmOnInit = GetBool(map, RewardedPrewarmOnInitKey) ?? RewardedPrewarmOnInit;
RewardedMaxLoadAttempts = GetInt(map, RewardedMaxLoadAttemptsKey) ?? RewardedMaxLoadAttempts;
RewardedLoadRetryDelayMs = GetInt(map, RewardedLoadRetryDelayMsKey) ?? RewardedLoadRetryDelayMs;
InterstitialAutoLoad = GetBool(map, InterstitialAutoLoadKey) ?? InterstitialAutoLoad;
InterstitialPrewarmOnInit = GetBool(map, InterstitialPrewarmOnInitKey) ?? InterstitialPrewarmOnInit;
InterstitialMaxLoadAttempts = GetInt(map, InterstitialMaxLoadAttemptsKey) ?? InterstitialMaxLoadAttempts;
InterstitialLoadRetryDelayMs = GetInt(map, InterstitialLoadRetryDelayMsKey) ?? InterstitialLoadRetryDelayMs;
LocalStrategyAssetPath = GetString(map, LocalStrategyAssetPathKey) ?? LocalStrategyAssetPath;
}
private void ApplyLegacyArgs(object[] args)
@@ -124,6 +154,7 @@ public sealed class ToponControllerOptions
SubChannel = explicitOptions.SubChannel ?? SubChannel;
Debug = explicitOptions.Debug ?? Debug;
DebuggerKey = explicitOptions.DebuggerKey ?? DebuggerKey;
AutoOpenDebuggerUI = explicitOptions.AutoOpenDebuggerUI || AutoOpenDebuggerUI;
SDKArea = explicitOptions.SDKArea ?? SDKArea;
Longitude = explicitOptions.Longitude ?? Longitude;
Latitude = explicitOptions.Latitude ?? Latitude;
@@ -174,6 +205,7 @@ public sealed class ToponControllerOptions
SubChannel = GetString(map, SubChannelKey, "sub_channel") ?? SubChannel;
Debug = GetBool(map, DebugKey, "debug") ?? Debug;
DebuggerKey = GetString(map, DebuggerKeyKey) ?? DebuggerKey;
AutoOpenDebuggerUI = GetBool(map, AutoOpenDebuggerUiKey) ?? AutoOpenDebuggerUI;
SDKArea = GetInt(map, SDKAreaKey) ?? SDKArea;
Longitude = GetDouble(map, LongitudeKey) ?? Longitude;
Latitude = GetDouble(map, LatitudeKey) ?? Latitude;
@@ -182,6 +214,15 @@ public sealed class ToponControllerOptions
QueryAreaOnInit = GetBool(map, QueryAreaOnInitKey) ?? QueryAreaOnInit;
InitCustomMap = MergeMaps(InitCustomMap, GetPrefixedMap(map, InitCustomMapKey + "."));
RewardedCustomData = MergeMaps(RewardedCustomData, GetPrefixedMap(map, RewardedCustomDataKey + "."));
RewardedAutoLoad = GetBool(map, RewardedAutoLoadKey) ?? RewardedAutoLoad;
RewardedPrewarmOnInit = GetBool(map, RewardedPrewarmOnInitKey) ?? RewardedPrewarmOnInit;
RewardedMaxLoadAttempts = GetInt(map, RewardedMaxLoadAttemptsKey) ?? RewardedMaxLoadAttempts;
RewardedLoadRetryDelayMs = GetInt(map, RewardedLoadRetryDelayMsKey) ?? RewardedLoadRetryDelayMs;
InterstitialAutoLoad = GetBool(map, InterstitialAutoLoadKey) ?? InterstitialAutoLoad;
InterstitialPrewarmOnInit = GetBool(map, InterstitialPrewarmOnInitKey) ?? InterstitialPrewarmOnInit;
InterstitialMaxLoadAttempts = GetInt(map, InterstitialMaxLoadAttemptsKey) ?? InterstitialMaxLoadAttempts;
InterstitialLoadRetryDelayMs = GetInt(map, InterstitialLoadRetryDelayMsKey) ?? InterstitialLoadRetryDelayMs;
LocalStrategyAssetPath = GetString(map, LocalStrategyAssetPathKey) ?? LocalStrategyAssetPath;
}
private static string GetString(IDictionary<string, string> map, params string[] keys)

View File

@@ -2,7 +2,7 @@
"name": "com.commercialization.topon",
"displayName": "Commercialization.topon",
"description": "基于topon的广告sdk封装依赖基础商业化模块",
"version": "1.4.6",
"version": "1.4.7",
"unity": "2021.1",
"license": "MIT",
"repository": {
@@ -16,7 +16,7 @@
},
"dependencies":
{
"com.foldcc.cc-framework.commercialization" : "http://private.lightyears.ltd:18640/foldcc/CC-Framework.Commercialization.git#1.0.5"
"com.foldcc.cc-framework.commercialization" : "http://private.lightyears.ltd:18640/foldcc/CC-Framework.Commercialization.git#1.0.12"
},
"keywords": [
"Framework"

View File

@@ -0,0 +1,613 @@
# IAA广告架构重构实施指南
## 1. 目标
本文档的目标不是讨论“架构是否优雅”,而是以 **IAA 收益最大化** 为第一原则,对当前广告接入架构进行诊断、排序和重构规划。
核心目标:
1. 提高激励视频和插屏的首播成功率、Ready Rate、Show Rate、Reward Rate。
2. 将当前旧版 `load/show/listener` 心智升级为新版 Taku/TopOn 的 **生命周期 + 自动加载 + 场景统计 + 状态查询 + 策略化调度**
3. 保留业务层统一入口 `ADManager`,但重做中下层能力和策略层。
4. 把当前“联调环境”和“实际发布包依赖”统一起来,避免版本漂移导致的收益判断失真。
---
## 2. 当前架构快照
### 2.1 运行时主链路
```mermaid
flowchart LR
A["业务层\nADManager.AsyncPlayAD"] --> B["商业化抽象层\nADPlayer / AsyncAdPlayer"]
B --> C["平台实现层\nToponAdController / AwardVideoPlayer / InteractionPlayer"]
C --> D["Unity桥\nATSDKAPI / Rewarded / Interstitial"]
D --> E["原生桥\nSDKInitHelper / VideoHelper / VideoAutoAdHelper"]
E --> F["Taku/TopOn 原生 SDK\n微信 / 抖音 / 小程序 / 下载链路"]
```
### 2.2 当前仓库角色
- 抽象层本地源码:`Packages/CC-Framework.Commercialization`
- 当前平台实现层:`Assets/Topon_Adapter`
- 当前官方 Unity 插件:`Assets/AnyThinkPlugin`
- 当前 Android 额外插件与资源:`Assets/Plugins/Android`
### 2.3 当前已知版本状态
- 本地抽象层版本:`1.0.12`
- 文件:[Packages/CC-Framework.Commercialization/Assets/package.json](F:/UnityWork/Commercialization.topon/Packages/CC-Framework.Commercialization/Assets/package.json)
- 当前 Topon 包版本:`1.4.7`
- 文件:[Assets/package.json](F:/UnityWork/Commercialization.topon/Assets/package.json)
- 当前 Topon 包对抽象层的发布依赖是:`1.0.12`
- 文件同上
- 当前联调工程和发布依赖已对齐到同一抽象层版本,版本漂移问题已收敛。
- Android 接入侧需注意:
- 当前中国区 Gromore/CSJ 组合要求 `minSdkVersion >= 24`
---
## 3. 术语速查
### 3.1 Placement广告位
- 一个 placement 就是一个固定广告机会点的唯一 ID。
- 例如:
- 复活激励
- 双倍奖励激励
- 关卡结尾插屏
### 3.2 Scenario广告场景
- placement 是固定广告位scenario 是这次广告触发的业务上下文。
- 例如:
- `revive`
- `double_coin`
- `level_fail_exit`
- 它主要用于:
- 漏斗分析
- 场景收益对比
- 定位具体场景的展示问题
### 3.3 load / ready / show
- `load`:发起请求并缓存广告
- `ready`:当前已有可展示广告缓存
- `show`:真正展示广告
### 3.4 Auto Load自动加载
- SDK 自动维护某个 placement 的缓存和补量
- 更适合高频高价值激励位
### 3.5 Waterfall瀑布流
- 按优先级依次请求广告源
- 优先级和广告源配置主要在后台控制
### 3.6 Bidding竞价
- 广告源实时回传价格
- 平台再选择最优广告展示
### 3.7 eCPM
- 每千次展示收益
- 是重要指标,但不能脱离:
- Ready Rate
- Show Rate
- Fill Rate
- Reward Completion
单独看
### 3.8 Fill Rate填充率
- 发起请求后,最终拿到可展示广告的比例
### 3.9 Show Rate展示率
- 已到达广告场景或已请求后,最终真正展示成功的比例
### 3.10 Impression展示
- 广告真正展示给用户一次
- 收益通常基于展示结算,而不是基于请求结算
### 3.11 Report API
- 广告平台收益与展示数据接口
- 用于自动价格、收益归因、Waterfall 对账和优化
### 3.12 Local Extra / Custom Map
- `local extra`:请求级参数,通常带用户透传、业务信息
- `custom map`:初始化级参数,通常带渠道、用户分组、实验标记
### 3.13 为什么“预加载影响 eCPM”是旧命题
- 旧时代常说“不要太早 load会伤 eCPM”
- 现在更准确的说法是:
- 问题不在预加载本身
- 问题在于是否 **策略化预热**
- 真正影响收益的是:
- 预热时机不对
- placement 太多
- 长时间不展示
- 后台策略和客户端行为脱节
---
## 4. 官方推荐生命周期
### 4.1 推荐调用顺序
```mermaid
sequenceDiagram
participant App as App启动
participant Privacy as 隐私同意
participant SDK as Taku SDK
participant Policy as 客户端策略层
participant Game as 广告业务逻辑
App->>Privacy: 展示隐私协议
Privacy-->>App: 用户同意
App->>SDK: ATSDK.init(...)
App->>SDK: ATSDK.start()(中国区)
App->>Policy: 注册核心placement自动加载/预热
Game->>Policy: 到达广告场景 entryScenario
Policy->>SDK: ready/status 检查
alt 已ready
Policy->>SDK: show
else 未ready但在容错窗口
Policy->>SDK: 短等待 / 补偿重试
else 超时
Policy-->>Game: 播放失败
end
```
### 4.2 推荐自动调用的动作
这些动作建议由底层 provider 自动做掉,而不是依赖业务层自己记忆:
1. 中国区 `ATSDK.start()`
2. 核心激励位注册 auto load
3. 启动后预热高价值 placement
4. 核心 placement 的诊断日志与状态快照
5. `show start` 后安排下一轮预热
6. 首轮失败进入短重试窗口
### 4.3 业务层不应该感知的内容
以下内容应由策略层 / 平台层封装:
1. placement 是否自动加载
2. 何时预热
3. 是否可以等待更高价缓存
4. 冷启动是否容许重试
5. 微信/抖音/小程序/下载的具体差异
---
## 5.1 当前已接入的策略 Key第二阶段
目前已在 `ToponControllerOptions` 中接入的高收益策略 key
- `topon.rewarded_auto_load`
- 是否开启激励自动加载
- 当前建议默认:`true`
- `topon.rewarded_prewarm_on_init`
- 初始化后是否自动预热激励位
- 当前建议默认:`true`
- `topon.rewarded_max_load_attempts`
- 单次播放请求允许的最大加载尝试次数
- 当前建议默认:`3`
- `topon.rewarded_load_retry_delay_ms`
- 激励首次失败后的短补偿等待时间
- 当前建议默认:`1000`
- `topon.interstitial_auto_load`
- 是否开启插屏自动加载
- 当前建议默认:`true`
- `topon.interstitial_prewarm_on_init`
- 初始化后是否自动预热插屏位
- 当前建议默认:`true`
- `topon.interstitial_max_load_attempts`
- 单次插屏播放请求允许的最大加载尝试次数
- 当前建议默认:`2`
- `topon.interstitial_load_retry_delay_ms`
- 插屏首轮失败后的短补偿等待时间
- 当前建议默认:`500`
- `topon.local_strategy_asset_path`
- Android 本地策略资源路径
- `topon.auto_open_debugger_ui`
- 是否在初始化后自动打开官方 DebugUI
- 当前建议默认:`false`
适用方式:
- 通过 `ADConfig.CommonKeyValues`
- 或通过 `ToponControllerOptions`
- 或额外传入 `IDictionary`
推荐默认示例:
```text
topon.rewarded_auto_load=true
topon.rewarded_prewarm_on_init=true
topon.rewarded_max_load_attempts=3
topon.rewarded_load_retry_delay_ms=1000
topon.interstitial_auto_load=true
topon.interstitial_prewarm_on_init=true
topon.interstitial_max_load_attempts=2
topon.interstitial_load_retry_delay_ms=500
topon.auto_open_debugger_ui=false
```
调试说明:
- `topon.debug=true`
- 当前只用于开启 SDK 调试日志
- 不再默认自动弹出官方 DebugUI
- 官方 DebugUI 建议在真机样例或专用调试入口中手动打开
---
## 5. 官方最新 SDK 能力,与当前项目的差距
结合官方文档和当前插件/反编译结果,当前 Taku/TopOn 体系已经具备以下关键能力,但当前项目没有充分利用:
### 3.1 `ATSDK.start()`
- 官方中国区 SDK 初始化文档要求 `ATSDK.init(...)` 后建议调用 `ATSDK.start()`
- 反编译验证:`ATSDK.start()` 在当前 `anythink_core` 中真实存在,且是无参静态方法。
- 当前缺陷:
- `ToponAdController` 只调了 `ATSDKAPI.initSDK(...)`
- Unity 壳 `ATSDKAPI.cs` 没有暴露 `start`
### 3.2 自动加载能力
- 已有能力:
- `ATRewardedAutoVideo`
- `ATInterstitialAutoAd`
- 当前缺陷:
- 当前 `AwardVideoPlayer` / `InteractionPlayer` 仍是手工状态机模式
- 没有利用自动加载做高频 placement 的持续预热
### 3.3 状态查询能力
- 已有能力:
- `hasAdReady`
- `checkAdStatus`
- `getValidAdCaches`
- 当前缺陷:
- 未接入策略层
- 无法做“软等待”“缓存质量判断”“首轮失败补偿”
### 3.4 广告场景统计能力
- 官方推荐使用 `entryAdScenario` 做漏斗分析
- 当前缺陷:
- `adScene` 只在 `show` 阶段透传
- 没有将“用户到达广告机会点”单独建模
### 3.5 本地策略能力
- 官方原生 SDK 有 `ATSDK.setLocalStrategyAssetPath(...)`
- 当前缺陷:
- Unity 壳没暴露
- 客户端无法加载本地预置策略
### 3.6 流量分组 / 规则能力
- 当前已接的弱能力:
- `channel / sub_channel`
- `custom_map`
- `placement custom data`
- 当前缺陷:
- 只是透传,不是可运营的“收益策略层”
---
## 6. 现有问题,按收入影响从大到小排序
### P0. 首播请求容错太差,直接损失收入
表现:
- 激励视频第一次点击经常失败
- 冷启动期首轮加载不稳定
根因:
- `AsyncAdPlayer` 仍是“单次播放只给一次首轮 load 机会”
- 首次 `load` 比后续 `load` 多做了一层 `VideoHelper / ATRewardVideoAd` 初始化
- 冷启动期没有任何“延迟补偿 / 软等待 / 重试窗口”
影响:
- 直接损失首播展示
- 高价值激励点收益打折
- 拉低用户广告可接受度和视频完成率
### P1. 高价值 placement 没有自动加载Ready Rate 偏低
表现:
- 核心激励位依赖点击时加载
- 就绪完全由手工 load 时机决定
影响:
- 展示机会点到来时,经常没 ready
- 收入天花板偏低
### P2. 没有完整接入场景统计,无法做精细化收益调优
影响:
- 无法准确判断问题在“场景设计”还是“缓存策略”
- 客户端和后台漏斗对不上
### P3. 生命周期抽象过粗,不适配新版 SDK
表现:
- `init` 被调用 != 广告系统真正 ready
- 当前实现无法表达 `start`、本地策略、自动加载启动等生命周期
### P4. 插屏回调链本身存在缺陷
表现:
- `InteractionPlayer.ShowAD(...)` 没接 `onClose/onVideoComplete`
- `onInterstitialAdFailedToShow` 等回调是空实现
影响:
- 插屏统计、重试、展示完成事件不可靠
### P5. 版本漂移导致实验结果不可信
表现:
- 本地抽象层、联调工程、发布依赖三个版本不统一
影响:
- 本地验证表现无法可信映射到业务项目
---
## 7. 对“预加载影响 eCPM”的判断
这是一个 **旧时代常见结论,但今天已经不能直接这么说**
更准确的说法是:
- **无脑预加载本身不是收益最优策略**
- 但“预加载会直接伤 eCPM”这个说法过于粗暴已经过时
当前更合理的理解:
1. 预加载是 IAA 的基础能力,不做高价值位预热,收入上限一定低。
2. 问题不在“要不要预加载”,而在“何时预加载、预加载多少、多久不展示视为无效”。
3. 真正会伤收益的不是“有预加载”,而是:
- 预热过早
- 长时间不展示
- placement 太多导致缓存浪费
- 高价值场景没有拿到新鲜缓存
- 客户端策略和后台 Waterfall 配置脱节
最优结论:
- **高价值高频位**:必须预热,甚至自动加载
- **中频位**:场景前置预热
- **低频位**Just-in-time load + 短等待容错
- **插屏**:策略化预热,不建议全局常驻缓存所有位
---
## 8. 最优指南:面向 IAA 的广告策略
### 6.1 激励视频
推荐策略:
1. 核心激励位默认使用自动加载
2. 启动后预热
3. 用户进入关键广告场景时调用 `entryAdScenario`
4. 播放前先读 `ready/status`
5. 首次失败给短重试窗口,不要直接判死
6. `onRewardedVideoAdPlayStart` 后立即安排下一轮预热
### 6.2 插屏
推荐策略:
1. 重要插屏位可使用自动加载
2. 低频插屏采用场景前置预热
3. 插屏失败要完整上报 show fail 和 close 结果
4. 插屏不应该沿用激励的奖励回调模型
### 6.3 后台协同
推荐策略:
1. 开启报表 API
2. 配置自动价格
3. 做流量分组
4. 用 A/B 测试验证:
- 自动加载 vs 手工加载
- 启动预热 vs 场景预热
- 软等待 0/300/800ms
### 8.4 常见产品策略方式
#### 策略 A核心激励常驻预热
- 适用:
- 复活激励
- 双倍奖励激励
- 特点:
- 就绪率高
- 收入稳定
#### 策略 B场景前置预热
- 适用:
- 结算页激励
- 阶段性插屏
- 特点:
- 更省请求
- 更接近真实展示时机
#### 策略 C点击兜底加载 + 软等待
- 适用:
- 低频长尾位
- 特点:
- 节省资源
- 不适合核心高价值激励位
#### 策略 D自动加载 + 场景感知展示
- 适用:
- 高频核心激励
- 特点:
- 是当前推荐的主流 IAA 策略
- 平衡 ready rate 和展示体验
#### 策略 E更高价广告软等待
- 适用:
- 激励价值极高的场景
- 特点:
- 可能提高单次展示收益
- 风险:
- 等待过久伤转化
- 必须通过 A/B 测试验证
---
## 9. 推荐的新架构
```mermaid
flowchart LR
A["业务层\nADManager.AsyncPlayAD"] --> B["策略层\nPlacementPolicy / RevenuePolicy"]
B --> C["Provider Runtime\nRewardedRuntime / InterstitialRuntime"]
C --> D["Platform Adapter\nTopOn/Taku"]
D --> E["Unity SDK Bridge"]
E --> F["Native SDK"]
```
### 7.1 抽象层职责
- 保留 `ADManager` 作为统一入口
- 去掉“只懂 0/1/2 状态”的单薄模型
- 引入 placement 级策略
### 7.2 平台层职责
- 管理 SDK 生命周期
- 管理自动加载/手工加载
- 管理状态查询和场景统计
- 处理微信/抖音/小程序/下载相关链路
### 7.3 策略层职责
- placement 是否自动加载
- 预热时机
- soft wait 时长
- load fail 重试策略
- show 冷却和频控
---
## 10. 实施计划
### 阶段 1P0 止血
目标:解决首播失败和关键回调缺陷
实施项:
1. 将工程切换到本地 `CC-Framework.Commercialization`
2. 扩展 Unity 层,补 `ATSDK.start()`
3. `ToponAdController` 中接入 `start` 生命周期
4. 激励首播增加冷启动补偿和短重试窗口
5. 插屏回调链修正
### 阶段 2P1 收益增强
目标:提高 Ready Rate 和 Show Rate
实施项:
1. 核心激励位切自动加载
2. 插屏支持自动加载或策略化预热
3.`entryAdScenario`
4. 接状态查询、缓存查询
### 阶段 3P2 架构升级
目标:建立长期可运营体系
实施项:
1. 新增策略层
2. placement 级配置
3. 本地策略 / 后端策略下发
4. 数据埋点和 BI 打通
---
## 11. 第一阶段具体改动清单
### 9.1 必改
- `Packages/manifest.json`
- 切本地包依赖
- `Packages/CC-Framework.Commercialization/Assets/Runtime/ADAggregator/*`
- 升级 `AsyncAdPlayer`
- `Assets/Topon_Adapter/Runtime/Scripts/ToponAdController.cs`
-`start`
- `Assets/Topon_Adapter/Runtime/Scripts/AwardVideoPlayer.cs`
- 首播容错、场景统计
- `Assets/Topon_Adapter/Runtime/Scripts/InteractionPlayer.cs`
- 回调完整化
### 9.2 推荐
- 增加 `ToponSdkLifecycle``ToponStrategyConfig`
- 增加 `RewardedPlacementPolicy`
- 为激励和插屏补统一诊断日志
---
## 12. 产品 / 运营 / 后端建议
### 10.1 产品侧
- 明确每个激励位的触发场景和价值等级
- 区分“高价值激励位”和“长尾激励位”
- 插屏不要用“一刀切”频率
### 10.2 运营侧
- 配合场景统计做 placement 漏斗
- 按渠道分组优化 Waterfall
- 做自动价格和 A/B 实验
### 10.3 后端侧
- 下发 placement 策略
- 拉通报表 API
- 统一客户端和平台收益口径
- 对激励奖励做服务端幂等和风控
---
## 13. 一句话结论
当前最优方案不是继续修补旧版手工状态机,而是:
**保留 `ADManager` 统一入口,重做中下层为“策略层 + 平台能力层”,把新版 Taku/TopOn 的 `start / 自动加载 / 场景统计 / 状态查询 / 本地策略` 真正接进来。**

View File

@@ -40,6 +40,9 @@
- 包版本号以 `Assets/package.json` 为准。
- 业务项目引用的也是 `Assets/package.json` 对应 tag。
- 本地工程里的 `Packages/manifest.json` 只用于当前源码工程联调,不参与发布。
- 当前发布版本:
- `com.commercialization.topon``1.4.7`
- `com.foldcc.cc-framework.commercialization``1.0.12`
### 2.3 发布前检查
@@ -49,6 +52,7 @@
2. `Assets/package.json` 里的核心依赖版本与本地验证环境一致。
3. 所有本次升级需要发布的内容都在 `Assets` 下。
4. `Update Upm.bat` 执行前,工作区已整理干净。
5. Android 业务项目的 `minSdkVersion` 已不低于 `24`
## 3. 目录职责
@@ -207,6 +211,7 @@ EDM4U 依赖解析器,负责消费 `Dependencies.xml` 并拉取 Android/iOS
- `topon.sub_channel`
- `topon.debug`
- `topon.debugger_key`
- `topon.auto_open_debugger_ui`
- `topon.sdk_area`
- `topon.longitude`
- `topon.latitude`
@@ -215,9 +220,21 @@ EDM4U 依赖解析器,负责消费 `Dependencies.xml` 并拉取 Android/iOS
- `topon.query_area_on_init`
- `topon.custom_map.<name>`
- `topon.rewarded_custom_data.<name>`
- `topon.rewarded_auto_load`
- `topon.rewarded_prewarm_on_init`
- `topon.rewarded_max_load_attempts`
- `topon.rewarded_load_retry_delay_ms`
- `topon.interstitial_auto_load`
- `topon.interstitial_prewarm_on_init`
- `topon.interstitial_max_load_attempts`
- `topon.interstitial_load_retry_delay_ms`
- `topon.local_strategy_asset_path`
说明:
- `topon.debug=true` 现在只打开 SDK 调试日志。
- 官方 DebugUI 默认不再自动弹出,如需使用,手动调用或在样例面板点击 `Open DebugUI`
- `topon.exclude_bundle_ids`
- 用于全局排除指定广告主 bundle id
- `topon.rewarded_exclude_ad_source_ids`