Commit c141e353 authored by gaorui's avatar gaorui
Browse files

Merge branch 'feat-dev3' into 'main'

Feat dev3

See merge request unity-cross/UnityDemo!1
parents 2ce8d1c7 b31b4ba9
fileFormatVersion: 2
guid: d4f9c53afafed4c1d86aaefb24e1d773
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:
apply plugin: 'com.android.library'
**APPLY_PLUGINS**
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
**DEPS**}
android {
ndkPath "**NDKPATH**"
namespace "com.unity3d.player"
compileSdkVersion **APIVERSION**
buildToolsVersion '**BUILDTOOLS**'
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
defaultConfig {
minSdkVersion **MINSDKVERSION**
targetSdkVersion **TARGETSDKVERSION**
ndk {
abiFilters **ABIFILTERS**
}
versionCode **VERSIONCODE**
versionName '**VERSIONNAME**'
consumerProguardFiles 'proguard-unity.txt'**USER_PROGUARD**
**DEFAULT_CONFIG_SETUP**
}
lintOptions {
abortOnError false
}
aaptOptions {
noCompress = **BUILTIN_NOCOMPRESS** + unityStreamingAssets.tokenize(', ')
ignoreAssetsPattern = "!.svn:!.git:!.ds_store:!*.scc:!CVS:!thumbs.db:!picasa.ini:!*~"
}**PACKAGING_OPTIONS**
}
**IL_CPP_BUILD_SETUP**
**SOURCE_BUILD_SETUP**
**EXTERNAL_SOURCES**
fileFormatVersion: 2
guid: 097939e4939b448a4adddc6e6ad3e7d9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 999ad5b1efbd045d5a2eae05d7750b13
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
pluginManagement {
repositories {
**ARTIFACTORYREPOSITORY**
// 默认的仓库
maven {
url 'https://sdk.wdyxgames.com/nexus/repository/sdk-public/'
}
gradlePluginPortal()
google()
mavenCentral()
}
}
include ':launcher', ':unityLibrary'
**INCLUDES**
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
**ARTIFACTORYREPOSITORY**
// 默认的仓库
maven {
url 'https://sdk.wdyxgames.com/nexus/repository/sdk-public/'
}
google()
mavenCentral()
flatDir {
dirs "${project(':unityLibrary').projectDir}/libs"
}
}
}
fileFormatVersion: 2
guid: 95b8e847504c448a3a54779856323607
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 758ccc9bdb13143e48b0b6110a60e8ce
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
#if UNITY_IOS
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;
using System;
public static class IOSPostBuildProcessor
{
// 全局配置变量
private static readonly string BUNDLE_IDENTIFIER = "com.hoolai.saintseiya";
private static readonly string ACCESS_SDK_SCHEME_SUFFIX = "223333";
private static readonly string TRACKING_USAGE_DESCRIPTION = "想访问您的设备标识符(IDFA)用于广告追踪";
private static readonly string PHOTO_LIBRARY_USAGE_DESCRIPTION = "将要保存截图到相册";
private static readonly string PHOTO_LIBRARY_ADD_USAGE_DESCRIPTION = "将要访问您的相册";
[PostProcessBuild]
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
{
if (target != BuildTarget.iOS) return;
Debug.Log("🚀 [IOSPostBuildProcessor] 开始自动配置 iOS 工程...");
string projPath = PBXProject.GetPBXProjectPath(pathToBuiltProject);
PBXProject proj = new PBXProject();
proj.ReadFromFile(projPath);
#if UNITY_2019_3_OR_NEWER
string mainTarget = proj.GetUnityMainTargetGuid();
string frameworkTarget = proj.GetUnityFrameworkTargetGuid();
#else
string mainTarget = proj.TargetGuidByName("Unity-iPhone");
string frameworkTarget = mainTarget;
#endif
// ✅ 设置 Bundle ID
SetBundleIdentifier(proj, mainTarget, BUNDLE_IDENTIFIER);
// ✅ 添加系统 Framework
string[] frameworks = {
"StoreKit.framework", "WebKit.framework", "Network.framework",
"SystemConfiguration.framework", "AppTrackingTransparency.framework",
"AdSupport.framework", "CoreTelephony.framework", "iAd.framework", "AdServices.framework"
};
foreach (var fw in frameworks)
proj.AddFrameworkToProject(frameworkTarget, fw, false);
// ✅ 添加 .tbd 库
string[] tbds = { "libz.tbd", "libc++.tbd", "libresolv.tbd" };
foreach (var tbd in tbds)
proj.AddFrameworkToProject(frameworkTarget, tbd, false);
// ✅ 链接参数
proj.AddBuildProperty(frameworkTarget, "OTHER_LDFLAGS", "-ObjC");
// ✅ 修改 Info.plist
UpdatePlist(pathToBuiltProject);
// ✅ 替换 Unity 生成文件
ReplaceUnityGeneratedFiles(pathToBuiltProject);
// ✅ 修正引用路径
FixFileReference(proj, frameworkTarget, pathToBuiltProject);
// ✅ 保存工程
proj.WriteToFile(projPath);
Debug.Log("🎉 [IOSPostBuildProcessor] 自动化配置完成!");
}
private static void SetBundleIdentifier(PBXProject proj, string targetGuid, string bundleId)
{
if (string.IsNullOrEmpty(bundleId)){
Debug.LogWarning("⚠️ 未设置 Bundle Identifier,保持默认值");
return;
}
proj.SetBuildProperty(targetGuid, "PRODUCT_BUNDLE_IDENTIFIER", bundleId);
Debug.Log($"📦 Bundle Identifier 已设置为:{bundleId}");
}
private static void UpdatePlist(string pathToBuiltProject)
{
string plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
if (!File.Exists(plistPath)){
Debug.LogWarning("⚠️ 未找到 Info.plist,跳过修改");
return;
}
PlistDocument plist = new PlistDocument();
plist.ReadFromFile(plistPath);
PlistElementDict root = plist.root;
string gameName = PlayerSettings.productName;
root.SetString("NSUserTrackingUsageDescription", $"{gameName} {TRACKING_USAGE_DESCRIPTION}");
root.SetString("NSPhotoLibraryUsageDescription", $"{gameName} {PHOTO_LIBRARY_USAGE_DESCRIPTION}");
root.SetString("NSPhotoLibraryAddUsageDescription", $"{gameName} {PHOTO_LIBRARY_ADD_USAGE_DESCRIPTION}");
root.SetBoolean("NSAppTransportSecurity:NSAllowsArbitraryLoads", true);
// ✅ 添加 URL Types
PlistElementArray urlTypes;
if (root.values.ContainsKey("CFBundleURLTypes"))
urlTypes = root["CFBundleURLTypes"].AsArray();
else
urlTypes = root.CreateArray("CFBundleURLTypes");
PlistElementDict newUrlType = urlTypes.AddDict();
newUrlType.SetString("CFBundleTypeRole", "Editor");
PlistElementArray urlSchemes = newUrlType.CreateArray("CFBundleURLSchemes");
urlSchemes.AddString("accesssdk" + ACCESS_SDK_SCHEME_SUFFIX);
plist.WriteToFile(plistPath);
Debug.Log("📄 Info.plist 已更新");
}
private static void ReplaceUnityGeneratedFiles(string pathToBuiltProject)
{
Debug.Log("🛠 开始替换 Unity 导出文件...");
string srcDir = "Assets/Plugins/iOS";
string classesDir = Path.Combine(pathToBuiltProject, "Classes");
string librariesDir = Path.Combine(pathToBuiltProject, "Libraries");
string[] filesToReplace =
{
"UnityAppController.h",
"UnityAppController.mm"
};
foreach (var file in filesToReplace){
string srcPath = Path.Combine(srcDir, file);
if (!File.Exists(srcPath)){
Debug.LogWarning($"⚠️ 未找到源文件:{srcPath}");
continue;
}
// 删除旧文件
TryDelete(Path.Combine(classesDir, file));
TryDelete(Path.Combine(librariesDir, file));
// 拷贝新文件到 Classes
Directory.CreateDirectory(classesDir);
string dstPath = Path.Combine(classesDir, file);
File.Copy(srcPath, dstPath, true);
Debug.Log($"✅ 已替换文件:{file}");
}
Debug.Log("🔁 文件替换完成");
}
private static void TryDelete(string filePath)
{
if (File.Exists(filePath)){
File.Delete(filePath);
Debug.Log($"🗑️ 已删除旧文件:{filePath}");
}
}
/// <summary>
/// 修复 PBX 工程文件中对 UnityAppController 的错误引用
/// </summary>
private static void FixFileReference(PBXProject proj, string targetGuid, string pathToBuiltProject)
{
string newPathH = "Classes/UnityAppController.h";
string newPathMM = "Classes/UnityAppController.mm";
// 删除旧的 .h 文件引用
var oldPathH = "Libraries/Plugins/Ios/UnityAppController.h";
var oldGUID_H = proj.FindFileGuidByProjectPath(oldPathH);
if (!string.IsNullOrEmpty(oldGUID_H)){
proj.RemoveFileFromBuild(targetGuid, oldGUID_H);
proj.RemoveFile(oldGUID_H);
Debug.Log("🧹 已清除旧的 UnityAppController.h 引用");
}
// 删除旧的 .mm 文件引用
var oldPathMM = "Libraries/Plugins/Ios/UnityAppController.mm";
var oldGUID_MM = proj.FindFileGuidByProjectPath(oldPathMM);
if (!string.IsNullOrEmpty(oldGUID_MM)){
proj.RemoveFileFromBuild(targetGuid, oldGUID_MM);
proj.RemoveFile(oldGUID_MM);
Debug.Log("🧹 已清除旧的 UnityAppController.mm 引用");
}
// 添加新的引用
proj.AddFileToBuild(targetGuid, proj.AddFile(newPathH, newPathH));
proj.AddFileToBuild(targetGuid, proj.AddFile(newPathMM, newPathMM));
Debug.Log("✅ 已添加新的 UnityAppController 引用");
}
}
#endif
\ No newline at end of file
fileFormatVersion: 2
guid: ba2977f3852c448f0917f461a52c5146
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: f7754ce8d7bd44444989f8a6d4d03f71
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
#pragma once
#import <QuartzCore/CADisplayLink.h>
#include "RenderPluginDelegate.h"
#import <platform_core_sdk/platform_core_sdk.h>
@class UnityView;
@class UnityViewControllerBase;
@class DisplayConnection;
__attribute__ ((visibility("default")))
@interface UnityAppController : AccessCoreAppDelegate //NSObject<UIApplicationDelegate>
{
UnityView* _unityView;
CADisplayLink* _displayLink;
UIWindow* _window;
UIView* _rootView;
UIViewController* _rootController;
UIViewController* _snapshotViewController;
DisplayConnection* _mainDisplay;
// CODE ARCHEOLOGY: we were caching view controllers, both autorotation one and per-fixed-orientation ones
// CODE ARCHEOLOGY: we stopped doing this as the performance impact is negligible,
// CODE ARCHEOLOGY: yet it introduces corner cases and in general lots of code
#if UNITY_SUPPORT_ROTATION
UIInterfaceOrientation _curOrientation;
#endif
id<RenderPluginDelegate> _renderDelegate;
}
// override it to add your render plugin delegate
- (void)shouldAttachRenderDelegate;
// this one is called at the very end of didFinishLaunchingWithOptions:
// after views have been created but before initing engine itself
// override it to register plugins, tweak UI etc
- (void)preStartUnity;
// this one is called at at the very end of didFinishLaunchingWithOptions:
// it will start showing unity view and rendering unity content
- (void)startUnity:(UIApplication*)application;
// this is a part of UIApplicationDelegate protocol starting with ios5
// setter will be generated empty
@property (retain, nonatomic) UIWindow* window;
@property (readonly, copy, nonatomic) UnityView* unityView;
@property (readonly, copy, nonatomic) CADisplayLink* unityDisplayLink;
@property (readonly, copy, nonatomic) UIView* rootView;
@property (readonly, copy, nonatomic) UIViewController* rootViewController;
@property (readonly, copy, nonatomic) DisplayConnection* mainDisplay;
#if UNITY_SUPPORT_ROTATION
@property (readonly, nonatomic) UIInterfaceOrientation interfaceOrientation;
#endif
@property (nonatomic, retain) id renderDelegate;
@property (nonatomic, copy) void (^quitHandler)(void);
@end
// accessing app controller
#ifdef __cplusplus
extern "C" {
#endif
extern UnityAppController* _UnityAppController;
extern UnityAppController* GetAppController(void);
#ifdef __cplusplus
} // extern "C"
#endif
// Put this into mm file with your subclass implementation
// pass subclass name to define
#define IMPL_APP_CONTROLLER_SUBCLASS(ClassName) \
@interface ClassName(OverrideAppDelegate) \
{ \
} \
+(void)load; \
@end \
@implementation ClassName(OverrideAppDelegate) \
+(void)load \
{ \
extern const char* AppControllerClassName; \
AppControllerClassName = #ClassName; \
} \
@end \
// plugins
#define APP_CONTROLLER_RENDER_PLUGIN_METHOD(method) \
do { \
id<RenderPluginDelegate> delegate = GetAppController().renderDelegate; \
if([delegate respondsToSelector:@selector(method)]) \
[delegate method]; \
} while(0)
#define APP_CONTROLLER_RENDER_PLUGIN_METHOD_ARG(method, arg) \
do { \
id<RenderPluginDelegate> delegate = GetAppController().renderDelegate; \
if([delegate respondsToSelector:@selector(method:)]) \
[delegate method:arg]; \
} while(0)
// these are simple wrappers about ios api, added for convenience
void AppController_SendNotification(NSString* name);
void AppController_SendNotificationWithArg(NSString* name, id arg);
void AppController_SendUnityViewControllerNotification(NSString* name);
// in the case when apple adds new api that has easy fallback path for old ios
// we will add new api methods at runtime on older ios, so we can switch to new api universally
// in that case we still need actual declaration: we will do it here as it is the most convenient place
// history:
// [CADisplayLink preferredFramesPerSecond], [UIScreen maximumFramesPerSecond], [UIView safeAreaInsets]
// were removed after we started to enforce xcode9 (sdk 11)
fileFormatVersion: 2
guid: 7c52c6bae4f6d42c296f0de9df536ef5
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:
#import "UnityAppController.h"
#import "UnityAppController+ViewHandling.h"
#import "UnityAppController+Rendering.h"
#import "iPhone_Sensors.h"
#import <CoreGraphics/CoreGraphics.h>
#import <QuartzCore/QuartzCore.h>
#import <QuartzCore/CADisplayLink.h>
#import <Availability.h>
#import <AVFoundation/AVFoundation.h>
#include <mach/mach_time.h>
// MSAA_DEFAULT_SAMPLE_COUNT was removed
// ENABLE_INTERNAL_PROFILER and related defines were moved to iPhone_Profiler.h
// kFPS define for removed: you can use Application.targetFrameRate (30 fps by default)
// DisplayLink is the only run loop mode now - all others were removed
#include "CrashReporter.h"
#include "UI/OrientationSupport.h"
#include "UI/UnityView.h"
#include "UI/Keyboard.h"
#include "UI/UnityViewControllerBase.h"
#include "Unity/InternalProfiler.h"
#include "Unity/DisplayManager.h"
#include "Unity/ObjCRuntime.h"
#include "PluginBase/AppDelegateListener.h"
#include <assert.h>
#include <stdbool.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/sysctl.h>
#import "UnityBridge.h"
// we assume that app delegate is never changed and we can cache it, instead of re-query UIApplication every time
UnityAppController* _UnityAppController = nil;
UnityAppController* GetAppController()
{
return _UnityAppController;
}
// we keep old bools around to support "old" code that might have used them
bool _ios81orNewer = false, _ios82orNewer = false, _ios83orNewer = false, _ios90orNewer = false, _ios91orNewer = false;
bool _ios100orNewer = false, _ios101orNewer = false, _ios102orNewer = false, _ios103orNewer = false;
bool _ios110orNewer = false, _ios111orNewer = false, _ios112orNewer = false;
bool _ios130orNewer = false, _ios140orNewer = false, _ios150orNewer = false, _ios160orNewer = false;
// minimal Unity initialization done, enough to do calls to provide data like URL launch
bool _unityEngineLoaded = false;
// was core of Unity loaded (non-graphics part prior to loading first scene)
bool _unityEngineInitialized = false;
// was unity rendering already inited: we should not touch rendering while this is false
bool _renderingInited = false;
// was unity inited: we should not touch unity api while this is false
bool _unityAppReady = false;
// see if there's a need to do internal player pause/resume handling
//
// Typically the trampoline code should manage this internally, but
// there are use cases, videoplayer, plugin code, etc where the player
// is paused before the internal handling comes relevant. Avoid
// overriding externally managed player pause/resume handling by
// caching the state
bool _wasPausedExternal = false;
// should we skip present on next draw: used in corner cases (like rotation) to fill both draw-buffers with some content
bool _skipPresent = false;
// was app "resigned active": some operations do not make sense while app is in background
bool _didResignActive = false;
#if UNITY_SUPPORT_ROTATION
// Required to enable specific orientation for some presentation controllers: see supportedInterfaceOrientationsForWindow below for details
NSInteger _forceInterfaceOrientationMask = 0;
#endif
@implementation UnityAppController
@synthesize unityView = _unityView;
@synthesize unityDisplayLink = _displayLink;
@synthesize rootView = _rootView;
@synthesize rootViewController = _rootController;
@synthesize mainDisplay = _mainDisplay;
@synthesize renderDelegate = _renderDelegate;
@synthesize quitHandler = _quitHandler;
#if UNITY_SUPPORT_ROTATION
@synthesize interfaceOrientation = _curOrientation;
#endif
- (id)init
{
if ((self = _UnityAppController = [super init]))
{
// due to clang issues with generating warning for overriding deprecated methods
// we will simply assert if deprecated methods are present
// NB: methods table is initied at load (before this call), so it is ok to check for override
NSAssert(![self respondsToSelector: @selector(createUnityViewImpl)],
@"createUnityViewImpl is deprecated and will not be called. Override createUnityView"
);
NSAssert(![self respondsToSelector: @selector(createViewHierarchyImpl)],
@"createViewHierarchyImpl is deprecated and will not be called. Override willStartWithViewController"
);
NSAssert(![self respondsToSelector: @selector(createViewHierarchy)],
@"createViewHierarchy is deprecated and will not be implemented. Use createUI"
);
}
return self;
}
- (void)setWindow:(id)object {}
- (UIWindow*)window { return _window; }
- (void)shouldAttachRenderDelegate {}
- (void)preStartUnity {}
- (void)startUnity:(UIApplication*)application
{
NSAssert(_unityAppReady == NO, @"[UnityAppController startUnity:] called after Unity has been initialized");
UnityInitApplicationGraphics();
#if !PLATFORM_VISIONOS
// we make sure that first level gets correct display list and orientation
[[DisplayManager Instance] updateDisplayListCacheInUnity];
#endif
UnityLoadApplication();
Profiler_InitProfiler();
[self showGameUI];
[self createDisplayLink];
UnitySetPlayerFocus(1);
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
// If Unity audio is disabled, we set the category to ambient to make sure we don't mute other app's audio. We set the audio session
// to active so we can get outputVolume callbacks. If Unity audio is enabled, FMOD should have already handled all of this AVAudioSession init.
if (!UnityIsAudioManagerAvailableAndEnabled())
{
[audioSession setCategory: AVAudioSessionCategoryAmbient error: nil];
[audioSession setActive: YES error: nil];
}
[audioSession addObserver: self forKeyPath: @"outputVolume" options: 0 context: nil];
UnityUpdateMuteState([audioSession outputVolume] < 0.01f ? 1 : 0);
#if UNITY_REPLAY_KIT_AVAILABLE
void InitUnityReplayKit(); // Classes/Unity/UnityReplayKit.mm
InitUnityReplayKit();
#endif
}
extern "C" void UnityDestroyDisplayLink()
{
[GetAppController() destroyDisplayLink];
}
extern "C" void UnityRequestUnload()
{
_unityAppReady = false;
[[NSNotificationCenter defaultCenter] postNotificationName: kUnityDidUnload object: nil];
}
extern "C" void UnityRequestQuit()
{
_didResignActive = true;
if (GetAppController().quitHandler)
GetAppController().quitHandler();
else
exit(0);
}
extern void SensorsCleanup();
extern "C" void UnityCleanupTrampoline()
{
// Prevent multiple cleanups
if (_UnityAppController == nil)
return;
// Unity view and viewController will not necessary be destroyed right after this function execution.
// We need to ensure that these objects will not receive any callbacks from system during that time.
_UnityAppController.window.rootViewController = nil;
[_UnityAppController.unityView removeFromSuperview];
[KeyboardDelegate Destroy];
SensorsCleanup();
Profiler_UninitProfiler();
[DisplayManager Destroy];
UnityDestroyDisplayLink();
_UnityAppController = nil;
}
#if UNITY_SUPPORT_ROTATION
- (NSUInteger)application:(UIApplication*)application supportedInterfaceOrientationsForWindow:(UIWindow*)window
{
// No rootViewController is set because we are switching from one view controller to another, all orientations should be enabled
if ([window rootViewController] == nil)
return UIInterfaceOrientationMaskAll;
// During splash screen show phase no forced orientations should be allowed.
// This will prevent unwanted rotation while splash screen is on and application is not yet ready to present (Ex. Fogbugz cases: 1190428, 1269547).
if (!_unityAppReady)
return [_rootController supportedInterfaceOrientations];
// Some presentation controllers (e.g. UIImagePickerController) require portrait orientation and will throw exception if it is not supported.
// At the same time enabling all orientations by returning UIInterfaceOrientationMaskAll might cause unwanted orientation change
// (e.g. when using UIActivityViewController to "share to" another application, iOS will use supportedInterfaceOrientations to possibly reorient).
// So to avoid exception we are returning combination of constraints for root view controller and orientation requested by iOS.
// _forceInterfaceOrientationMask is updated in willChangeStatusBarOrientation, which is called if some presentation controller insists on orientation change.
return [[window rootViewController] supportedInterfaceOrientations] | _forceInterfaceOrientationMask;
}
- (void)application:(UIApplication*)application willChangeStatusBarOrientation:(UIInterfaceOrientation)newStatusBarOrientation duration:(NSTimeInterval)duration
{
// Setting orientation mask which is requested by iOS: see supportedInterfaceOrientationsForWindow above for details
_forceInterfaceOrientationMask = 1 << newStatusBarOrientation;
}
#endif
#if UNITY_USES_REMOTE_NOTIFICATIONS
- (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
AppController_SendNotificationWithArg(kUnityDidRegisterForRemoteNotificationsWithDeviceToken, deviceToken);
}
#if !PLATFORM_TVOS
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler
{
AppController_SendNotificationWithArg(kUnityDidReceiveRemoteNotification, userInfo);
if (handler)
{
handler(UIBackgroundFetchResultNoData);
}
}
#endif
- (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
AppController_SendNotificationWithArg(kUnityDidFailToRegisterForRemoteNotificationsWithError, error);
// alas people do not check remote notification error through api (which is clunky, i agree) so log here to have at least some visibility
::printf("\nFailed to register for remote notifications:\n%s\n\n", [[error localizedDescription] UTF8String]);
}
#endif
// UIApplicationOpenURLOptionsKey was added only in ios10 sdk, while we still support ios9 sdk
- (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options
{
id sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey], annotation = options[UIApplicationOpenURLOptionsAnnotationKey];
NSMutableDictionary<NSString*, id>* notifData = [NSMutableDictionary dictionaryWithCapacity: 3];
if (url)
{
notifData[@"url"] = url;
UnitySetAbsoluteURL(url.absoluteString.UTF8String);
}
if (sourceApplication) notifData[@"sourceApplication"] = sourceApplication;
if (annotation) notifData[@"annotation"] = annotation;
AppController_SendNotificationWithArg(kUnityOnOpenURL, notifData);
return [super application:app openURL:url options:options];
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring> > * _Nullable restorableObjects))restorationHandler
{
NSURL* url = userActivity.webpageURL;
if (url)
UnitySetAbsoluteURL(url.absoluteString.UTF8String);
return YES;
}
- (BOOL)application:(UIApplication*)application willFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
AppController_SendNotificationWithArg(kUnityWillFinishLaunchingWithOptions, launchOptions);
NSURL* url = [self extractURLFromLaunchOptions: launchOptions];
if (url != nil)
{
[self initUnityApplicationNoGraphics];
UnitySetAbsoluteURL(url.absoluteString.UTF8String);
}
return YES;
}
// Helper method to extract URL from launch options
- (NSURL*)extractURLFromLaunchOptions:(NSDictionary*)launchOptions
{
// Check for the direct launch URL
NSURL* url = launchOptions[UIApplicationLaunchOptionsURLKey];
if (url != nil)
{
return url;
}
// Check for the user activity dictionary and URL from user activity
NSUserActivity* userActivity = launchOptions[UIApplicationLaunchOptionsUserActivityDictionaryKey][@"UIApplicationLaunchOptionsUserActivityKey"];
if (userActivity != nil && [userActivity.activityType isEqualToString: NSUserActivityTypeBrowsingWeb])
{
url = userActivity.webpageURL;
if (url != nil)
{
return url;
}
}
return nil;
}
- (UIWindowScene*)pickStartupWindowScene:(NSSet<UIScene*>*)scenes API_AVAILABLE(ios(13.0), tvos(13.0))
{
// if we have scene with UISceneActivationStateForegroundActive - pick it
// otherwise UISceneActivationStateForegroundInactive will work
// it will be the scene going into active state
// if there were no active/inactive scenes (only background) we should allow background scene
// this might happen in some cases with native plugins doing "things"
UIWindowScene *foregroundScene = nil, *backgroundScene = nil;
for (UIScene* scene in scenes)
{
if (![scene isKindOfClass: [UIWindowScene class]])
continue;
UIWindowScene* windowScene = (UIWindowScene*)scene;
if (scene.activationState == UISceneActivationStateForegroundActive)
return windowScene;
if (scene.activationState == UISceneActivationStateForegroundInactive)
foregroundScene = windowScene;
else if (scene.activationState == UISceneActivationStateBackground)
backgroundScene = windowScene;
}
return foregroundScene ? foregroundScene : backgroundScene;
}
@synthesize window;
- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
::printf("-> applicationDidFinishLaunching()\n");
[UnityBridge initSdk];
// send notfications
#if !PLATFORM_TVOS && !PLATFORM_VISIONOS
if ([UIDevice currentDevice].generatesDeviceOrientationNotifications == NO)
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
#endif
// if application is in background, don't initialize Unity
// this happens if app uses location fence, notifications with content/actions, ...
// initUnityWithApplication: initializes rendering, possibly loads scene and calls Start(), none meant for background
if (UIApplication.sharedApplication.applicationState == UIApplicationStateBackground)
return YES;
[self initUnityWithApplication: application];
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)initUnityApplicationNoGraphics
{
if (_unityEngineLoaded)
return;
_unityEngineLoaded = true;
UnityInitApplicationNoGraphics(UnityDataBundleDir());
}
- (void)initUnityWithApplication:(UIApplication*)application
{
if (_unityEngineInitialized)
return;
_unityEngineInitialized = true;
// basic unity init
[self initUnityApplicationNoGraphics];
[self selectRenderingAPI];
[UnityRenderingView InitializeForAPI: self.renderingAPI];
#if !PLATFORM_VISIONOS
if (@available(iOS 13, tvOS 13, *))
_window = [[UIWindow alloc] initWithWindowScene: [self pickStartupWindowScene: application.connectedScenes]];
else
_window = [[UIWindow alloc] initWithFrame: [UIScreen mainScreen].bounds];
#else
_window = [[UIWindow alloc] init];
#endif
_unityView = [self createUnityView];
[DisplayManager Initialize];
_mainDisplay = [DisplayManager Instance].mainDisplay;
[_mainDisplay createWithWindow: _window andView: _unityView];
[self createUI];
[self preStartUnity];
// if you wont use keyboard you may comment it out at save some memory
[KeyboardDelegate Initialize];
#if UNITY_DEVELOPER_BUILD
// Causes a black screen after splash screen, but would deadlock if waiting for manged debugger otherwise
[self performSelector: @selector(startUnity:) withObject: application afterDelay: 0];
#else
[self startUnity: application];
#endif
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context
{
if ([keyPath isEqual: @"outputVolume"])
{
UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
}
}
- (void)applicationDidEnterBackground:(UIApplication*)application
{
::printf("-> applicationDidEnterBackground()\n");
}
- (void)applicationWillEnterForeground:(UIApplication*)application
{
::printf("-> applicationWillEnterForeground()\n");
// applicationWillEnterForeground: might sometimes arrive *before* actually initing unity (e.g. locking on startup)
if (_unityAppReady)
{
// if we were showing video before going to background - the view size may be changed while we are in background
[GetAppController().unityView recreateRenderingSurfaceIfNeeded];
}
}
- (void)applicationDidBecomeActive:(UIApplication*)application
{
::printf("-> applicationDidBecomeActive()\n");
[self removeSnapshotViewController];
if (_unityAppReady)
{
if (UnityIsPaused() && _wasPausedExternal == false)
{
UnityWillResume();
UnityPause(0);
}
if (_wasPausedExternal)
{
if (UnityIsFullScreenPlaying())
TryResumeFullScreenVideo();
}
// need to do this with delay because FMOD restarts audio in AVAudioSessionInterruptionNotification handler
[self performSelector: @selector(updateUnityAudioOutput) withObject: nil afterDelay: 0.1];
UnitySetPlayerFocus(1);
}
else
{
[self initUnityWithApplication: application];
}
_didResignActive = false;
}
- (void)updateUnityAudioOutput
{
UnityUpdateMuteState([[AVAudioSession sharedInstance] outputVolume] < 0.01f ? 1 : 0);
}
- (void)addSnapshotViewController
{
if (!_didResignActive || self->_snapshotViewController)
{
return;
}
UIView* snapshotView = [self createSnapshotView];
if (snapshotView != nil)
{
UIViewController* snapshotViewController = [AllocUnityViewController() init];
snapshotViewController.modalPresentationStyle = UIModalPresentationFullScreen;
snapshotViewController.view = snapshotView;
[self->_rootController presentViewController: snapshotViewController animated: false completion: nil];
self->_snapshotViewController = snapshotViewController;
}
}
- (void)removeSnapshotViewController
{
// do this on the main queue async so that if we try to create one
// and remove in the same frame, this always happens after in the same queue
dispatch_async(dispatch_get_main_queue(), ^{
if (self->_snapshotViewController)
{
// we've got a view on top of the snapshot view (3rd party plugin/social media login etc).
if (self->_snapshotViewController.presentedViewController)
{
[self performSelector: @selector(removeSnapshotViewController) withObject: nil afterDelay: 0.05];
return;
}
[self->_snapshotViewController dismissViewControllerAnimated: NO completion: nil];
self->_snapshotViewController = nil;
// Make sure that the keyboard input field regains focus after the application becomes active.
[[KeyboardDelegate Instance] becomeFirstResponder];
}
});
}
- (void)applicationWillResignActive:(UIApplication*)application
{
::printf("-> applicationWillResignActive()\n");
if (_unityAppReady)
{
UnitySetPlayerFocus(0);
// signal unity that the frame rendering have ended
// as we will not get the callback from the display link current frame
UnityDisplayLinkCallback(0);
_wasPausedExternal = UnityIsPaused();
if (_wasPausedExternal == false)
{
// Pause Unity only if we don't need special background processing
// otherwise batched player loop can be called to run user scripts.
if (!UnityGetUseCustomAppBackgroundBehavior())
{
#if UNITY_SNAPSHOT_VIEW_ON_APPLICATION_PAUSE
// Force player to do one more frame, so scripts get a chance to render custom screen for minimized app in task manager.
// NB: UnityWillPause will schedule OnApplicationPause message, which will be sent normally inside repaint (unity player loop)
// NB: We will actually pause after the loop (when calling UnityPause).
UnityWillPause();
[self repaint];
UnityWaitForFrame();
[self addSnapshotViewController];
#endif
UnityPause(1);
}
}
}
_didResignActive = true;
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication*)application
{
UnityLowMemory();
}
- (void)applicationWillTerminate:(UIApplication*)application
{
::printf("-> applicationWillTerminate()\n");
// Only clean up if Unity has finished initializing, else the clean up process will crash,
// this happens if the app is force closed immediately after opening it.
if (_unityAppReady)
{
// make sure that we are in a "unity cannot be touched" state
// if there was some complex UI shown when terminating, we can get extra UI calls from iOS after applicationWillTerminate:
// and we want to make sure we never do anything touching unity runtime at this point
_unityAppReady = _renderingInited = _unityEngineInitialized = false;
_didResignActive = true;
UnityCleanup();
UnityCleanupTrampoline();
}
}
- (void)application:(UIApplication*)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)())completionHandler
{
NSDictionary* arg = @{identifier: completionHandler};
AppController_SendNotificationWithArg(kUnityHandleEventsForBackgroundURLSession, arg);
}
@end
void AppController_SendNotification(NSString* name)
{
[[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController()];
}
void AppController_SendNotificationWithArg(NSString* name, id arg)
{
[[NSNotificationCenter defaultCenter] postNotificationName: name object: GetAppController() userInfo: arg];
}
void AppController_SendUnityViewControllerNotification(NSString* name)
{
[[NSNotificationCenter defaultCenter] postNotificationName: name object: UnityGetGLViewController()];
}
extern "C" UIWindow* UnityGetMainWindow() { return GetAppController().mainDisplay.window; }
extern "C" UIViewController* UnityGetGLViewController() { return GetAppController().rootViewController; }
extern "C" UnityView* UnityGetUnityView() { return GetAppController().unityView; }
extern "C" UIView* UnityGetGLView() { return UnityGetUnityView(); }
extern "C" ScreenOrientation UnityCurrentOrientation() { return GetAppController().unityView.contentOrientation; }
bool LogToNSLogHandler(LogType logType, const char* log, va_list list)
{
NSLogv([NSString stringWithUTF8String: log], list);
return true;
}
static void AddNewAPIImplIfNeeded();
// From https://stackoverflow.com/questions/4744826/detecting-if-ios-app-is-run-in-debugger
static bool isDebuggerAttachedToConsole(void)
// Returns true if the current process is being debugged (either
// running under the debugger or has a debugger attached post facto).
{
int junk;
int mib[4];
struct kinfo_proc info;
size_t size;
// Initialize the flags so that, if sysctl fails for some bizarre
// reason, we get a predictable result.
info.kp_proc.p_flag = 0;
// Initialize mib, which tells sysctl the info we want, in this case
// we're looking for information about a specific process ID.
mib[0] = CTL_KERN;
mib[1] = KERN_PROC;
mib[2] = KERN_PROC_PID;
mib[3] = getpid();
// Call sysctl.
size = sizeof(info);
junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0);
assert(junk == 0);
// We're being debugged if the P_TRACED flag is set.
// But if we are starting app on device (and make debugger wait and attach after start)
// it will NOT connect stout (only stderr, used by nslog)
// Hence we also check that stoud is rerouted
return ((info.kp_proc.p_flag & P_TRACED) != 0) && isatty(STDOUT_FILENO);
}
void UnityInitTrampoline()
{
InitCrashHandling();
NSString* version = [[UIDevice currentDevice] systemVersion];
#define CHECK_VER(s) [version compare: s options: NSNumericSearch] != NSOrderedAscending
_ios81orNewer = CHECK_VER(@"8.1"); _ios82orNewer = CHECK_VER(@"8.2"); _ios83orNewer = CHECK_VER(@"8.3");
_ios90orNewer = CHECK_VER(@"9.0"); _ios91orNewer = CHECK_VER(@"9.1");
_ios100orNewer = CHECK_VER(@"10.0"); _ios101orNewer = CHECK_VER(@"10.1"); _ios102orNewer = CHECK_VER(@"10.2"); _ios103orNewer = CHECK_VER(@"10.3");
_ios110orNewer = CHECK_VER(@"11.0"); _ios111orNewer = CHECK_VER(@"11.1"); _ios112orNewer = CHECK_VER(@"11.2");
_ios130orNewer = CHECK_VER(@"13.0"); _ios140orNewer = CHECK_VER(@"14.0"); _ios150orNewer = CHECK_VER(@"15.0");
_ios160orNewer = CHECK_VER(@"16.0");
#undef CHECK_VER
AddNewAPIImplIfNeeded();
#if !TARGET_IPHONE_SIMULATOR
// Use NSLog logging if a debugger is not attached, otherwise we write to stdout.
if (!isDebuggerAttachedToConsole())
UnitySetLogEntryHandler(LogToNSLogHandler);
#endif
}
extern "C" bool UnityiOS81orNewer() { return _ios81orNewer; }
extern "C" bool UnityiOS82orNewer() { return _ios82orNewer; }
extern "C" bool UnityiOS90orNewer() { return _ios90orNewer; }
extern "C" bool UnityiOS91orNewer() { return _ios91orNewer; }
extern "C" bool UnityiOS100orNewer() { return _ios100orNewer; }
extern "C" bool UnityiOS101orNewer() { return _ios101orNewer; }
extern "C" bool UnityiOS102orNewer() { return _ios102orNewer; }
extern "C" bool UnityiOS103orNewer() { return _ios103orNewer; }
extern "C" bool UnityiOS110orNewer() { return _ios110orNewer; }
extern "C" bool UnityiOS111orNewer() { return _ios111orNewer; }
extern "C" bool UnityiOS112orNewer() { return _ios112orNewer; }
extern "C" bool UnityiOS130orNewer() { return _ios130orNewer; }
extern "C" bool UnityiOS140orNewer() { return _ios140orNewer; }
extern "C" bool UnityiOS150orNewer() { return _ios150orNewer; }
extern "C" bool UnityiOS160orNewer() { return _ios160orNewer; }
// sometimes apple adds new api with obvious fallback on older ios.
// in that case we simply add these functions ourselves to simplify code
static void AddNewAPIImplIfNeeded()
{
#if !PLATFORM_VISIONOS
if (![[UIScreen class] instancesRespondToSelector: @selector(maximumFramesPerSecond)])
{
IMP UIScreen_MaximumFramesPerSecond_IMP = imp_implementationWithBlock(^NSInteger(id _self) {
return 60;
});
class_replaceMethod([UIScreen class], @selector(maximumFramesPerSecond), UIScreen_MaximumFramesPerSecond_IMP, UIScreen_maximumFramesPerSecond_Enc);
}
if (![[UIView class] instancesRespondToSelector: @selector(safeAreaInsets)])
{
IMP UIView_SafeAreaInsets_IMP = imp_implementationWithBlock(^UIEdgeInsets(id _self) {
return UIEdgeInsetsZero;
});
class_replaceMethod([UIView class], @selector(safeAreaInsets), UIView_SafeAreaInsets_IMP, UIView_safeAreaInsets_Enc);
}
#endif
}
fileFormatVersion: 2
guid: 12d3c24cb8df145abb64f72b45206b99
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:
#import <Foundation/Foundation.h>
//#ifdef __cplusplus
//extern "C" {
//#endif
//
//// 调用 SDK 登录
//void _LoginSdk(void);
//
//// 调用 SDK 登出
//void _LogoutSdk(void);
//
//// 查询商品信息(原先的 _QueryGoodsInfo)
//void _QueryGoodsInfo(void);
//
//// 上报完整用户信息
//void _ReportUserInfoFull(int eventType, const char* jsonInfo);
//
//// 调用 SDK 原生支付方法
//void _PayWithInfo(const char* jsonInfo);
//
//// 调用 SDK 分享功能
//void _StartShare(int shareType,
// const char* title,
// const char* imagePath,
// const char* url);
//
//// 参与活动
//void _AccessParticipate(int activityType, const char* activityData);
//
//// 打开账号中心
//void _OpenAccountCenter(void);
//
//// 打开客服中心
//void _OpenService(void);
//
//#ifdef __cplusplus
//}
//#endif
@interface UnityBridge : NSObject
// 请使用此进行SDK初始化
+ (void)initSdk;
@end
fileFormatVersion: 2
guid: fbcc30c9db7014f8f8e136ddfd11516e
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:
#import <Foundation/Foundation.h>
#import "UnityBridge.h"
#import <platform_core_sdk/platform_core_sdk.h>
@interface SDKDelegate : AccessCoreAppDelegate <HLSystemDelegate, HLPaymentDelegate, HLAccountDelegate, HLShareCallback>
@end
extern "C" {
// JSON工具函数
NSString* jsonStringWithDictionary(NSDictionary* dict) {
if (!dict) return @"{}";
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:&error];
if (jsonData) {
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
return @"{}";
}
void UnitySendMessage(const char*, const char*, const char*);
}
@implementation SDKDelegate
#pragma mark - HLSystemDelegate
- (void)initSuccess:(AccessInitResult *)initResult {
NSLog(@"🍎 SDK初始化成功");
NSDictionary *dict = @{
@"channel": initResult.channel ?: @"",
@"gameId": @(initResult.gameId),
@"channelId": @(initResult.channelId)
};
NSString *json = jsonStringWithDictionary(dict);
UnitySendMessage("SdkManager", "OnSdkInit", [json UTF8String]);
}
- (void)initFailed {
NSLog(@"🍎 SDK初始化失败");
UnitySendMessage("SdkManager", "OnSdkInit", "failed");
}
#pragma mark - HLPaymentDelegate
- (void)paySuccess {
NSLog(@"🍎 支付成功");
UnitySendMessage("SdkManager", "OnSdkPay", "pay_success");
}
- (void)payError:(NSError *)error {
NSLog(@"🍎 支付失败: %@", error.localizedDescription);
NSString *errorMsg = [NSString stringWithFormat:@"pay_failed:%@", error.localizedDescription];
UnitySendMessage("SdkManager", "OnSdkPay", [errorMsg UTF8String]);
}
- (void)payGoodsList:(NSArray<AccessGoodsInfo *>*)info {
NSLog(@"🍎 获取商品列表成功,数量: %lu", (unsigned long)info.count);
NSMutableArray *array = [NSMutableArray array];
for (AccessGoodsInfo *item in info) {
NSDictionary *dict = @{
@"region": item.region ?: @"",
@"currency": item.currency ?: @"",
@"symbol": item.symbol ?: @"",
@"itemId": item.itemId ?: @"",
@"itemName": item.itemName ?: @"",
@"itemCount": item.itemCount ?: @"",
@"itemPrice": item.itemPrice ?: @"",
@"showTag": item.showTag ?: @""
};
[array addObject:dict];
}
// 包一层,方便 Unity JsonUtility 解析
NSDictionary *wrapper = @{@"array": array};
NSString *json = jsonStringWithDictionary(wrapper);
UnitySendMessage("SdkManager", "OnPayGoodsList", [json UTF8String]);
}
- (void)payGpodsListFail {
NSLog(@"🍎 获取商品列表失败");
UnitySendMessage("SdkManager", "OnGoodsList", "get_goods_failed");
}
#pragma mark - HLAccountDelegate
- (void)loginSuccess:(AccessLoginInfo *)accountInfo {
NSLog(@"🍎 登录成功: %lld", accountInfo.uid);
NSDictionary *dict = @{
@"uid": @(accountInfo.uid),
@"accessToken": accountInfo.accessToken ?: @"",
@"channel": accountInfo.channel ?: @"",
// @"channelUid": accountInfo.channelUid ?: @"",
@"serverArea": accountInfo.serverArea ?: @"",
@"extendInfo": accountInfo.extendInfo ?: @{}
};
NSString *json = jsonStringWithDictionary(dict);
UnitySendMessage("SdkManager", "OnLoginSuccess", [json UTF8String]);
}
- (void)refreshUser:(AccessLoginInfo *)refreshAccountInfo {
NSLog(@"🍎 用户信息刷新: %lld", refreshAccountInfo.uid);
NSDictionary *dict = @{
@"uid": @(refreshAccountInfo.uid),
@"accessToken": refreshAccountInfo.accessToken ?: @"",
@"channel": refreshAccountInfo.channel ?: @"",
// @"channelUid": refreshAccountInfo.channelUid ?: @"",
@"serverArea": refreshAccountInfo.serverArea ?: @"",
@"extendInfo": refreshAccountInfo.extendInfo ?: @{}
};
NSString *json = jsonStringWithDictionary(dict);
UnitySendMessage("SdkManager", "OnUserRefresh", [json UTF8String]);
}
- (void)loginFailure {
NSLog(@"🍎 登录失败");
UnitySendMessage("SdkManager", "OnSdkLogin", "login_failed");
}
- (void)logout {
NSLog(@"🍎 用户登出");
UnitySendMessage("SdkManager", "OnSdkLogout", "logout_success");
}
#pragma mark - HLShareCallback
- (void)shareSucceeded:(BOOL)result {
NSLog(@"🍎 分享成功: %@", result ? @"YES" : @"NO");
UnitySendMessage("SdkManager", "OnSdkShare", "share_success");
}
- (void)shareFailed:(BOOL)result {
NSLog(@"🍎 分享失败: %@", result ? @"YES" : @"NO");
UnitySendMessage("SdkManager", "OnSdkShare", "share_failed");
}
@end
// 全局委托实例
static SDKDelegate *sdkDelegate = nil;
@implementation UnityBridge
+ (void)initSdk {
if (!sdkDelegate) {
sdkDelegate = [[SDKDelegate alloc] init];
}
// 获取SDK单例并设置委托
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
sdk.systemDelegate = sdkDelegate;
sdk.paymentDelegate = sdkDelegate;
sdk.accountDelegate = sdkDelegate;
sdk.shareDelegate = sdkDelegate;
}
@end
// C函数实现
extern "C" {
void _LoginSdk() {
NSLog(@"🍎 调用SDK登录");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
[sdk login];
}
void _LogoutSdk() {
NSLog(@"🍎 调用SDK登出");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
[sdk logout];
}
void _QueryGoodsInfo() {
NSLog(@"🍎 查询商品信息");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
[sdk queryGoodsInfo];
}
//上报用户信息至SDK
void _ReportUserInfoFull(int eventType, const char* jsonInfo) {
NSLog(@"🍎 上报完整用户信息(JSON方式)");
if (!jsonInfo) {
NSLog(@"🍎 JSON数据为空");
return;
}
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
AccessPlayerInfo *playerInfo = [[AccessPlayerInfo alloc] init];
NSString *jsonStr = [NSString stringWithUTF8String:jsonInfo];
NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error || ![jsonDict isKindOfClass:[NSDictionary class]]) {
NSLog(@"🍎 解析JSON失败: %@", error.localizedDescription);
return;
}
NSString* (^safeString)(id) = ^(id obj) {
if (obj == nil || [obj isKindOfClass:[NSNull class]]) {
return @"";
}
if ([obj isKindOfClass:[NSString class]]) {
return (NSString*)obj;
}
if ([obj isKindOfClass:[NSNumber class]]) {
return [(NSNumber*)obj stringValue];
}
return [obj description] ?: @"";
};
playerInfo.role_id = safeString(jsonDict[@"roleId"]);
playerInfo.role_name = safeString(jsonDict[@"roleName"]);
playerInfo.role_level = safeString(jsonDict[@"roleLevel"]);
playerInfo.server_id = safeString(jsonDict[@"serverId"]);
playerInfo.server_name = safeString(jsonDict[@"serverName"]);
playerInfo.zone_id = safeString(jsonDict[@"zoneId"]);
playerInfo.zone_name = safeString(jsonDict[@"zoneName"]);
playerInfo.party_name = safeString(jsonDict[@"partyName"]);
playerInfo.vip = safeString(jsonDict[@"vip"]);
playerInfo.balance = safeString(jsonDict[@"balance"]);
playerInfo.phylum = safeString(jsonDict[@"phylum"]);
playerInfo.classfield = safeString(jsonDict[@"classField"]);
playerInfo.extene_action = safeString(jsonDict[@"extendAction"]);
id extraObj = jsonDict[@"extra"];
if (extraObj == nil || [extraObj isKindOfClass:[NSNull class]]) {
playerInfo.extra = @"";
} else if ([extraObj isKindOfClass:[NSDictionary class]]) {
NSDictionary *dict = (NSDictionary *)extraObj;
NSMutableArray *keyValuePairs = [NSMutableArray array];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
[keyValuePairs addObject:[NSString stringWithFormat:@"%@:%@", key, value]];
}];
playerInfo.extra = [keyValuePairs componentsJoinedByString:@";"];
}
[sdk report:(EventType)eventType data:playerInfo];
}
void _StartPay(const char* jsonInfo) {
NSLog(@"🍎 开始支付(JSON方式)");
if (!jsonInfo) return;
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
AccessBeginPayInfo *payInfo = [[AccessBeginPayInfo alloc] init];
NSString *jsonStr = [NSString stringWithUTF8String:jsonInfo];
NSData *jsonData = [jsonStr dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (error) return;
// 直接映射字段
payInfo.productIdentifier = jsonDict[@"ProductId"] ? [NSString stringWithFormat:@"%@", jsonDict[@"ProductId"]] : @"";
payInfo.price = jsonDict[@"Price"] ? [jsonDict[@"Price"] intValue] : 0;
payInfo.currencyLocale = jsonDict[@"Currency"] ? [NSString stringWithFormat:@"%@", jsonDict[@"Currency"]] : @"";
payInfo.notifyUrl = jsonDict[@"NotifyUrl"] ? [NSString stringWithFormat:@"%@", jsonDict[@"NotifyUrl"]] : @"";
payInfo.gameInfo = jsonDict[@"GameInfo"] ? [NSString stringWithFormat:@"%@", jsonDict[@"GameInfo"]] : @"";
NSLog(@"🍎 支付信息: productId=%@, price=%d", payInfo.productIdentifier, payInfo.price);
[sdk pay:payInfo];
}
void _StartShare(int shareType, const char* title, const char* imagePath, const char* url) {
NSLog(@"🍎 调用SDK分享");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
AccessShareInfo *shareInfo = [[AccessShareInfo alloc] init];
shareInfo.title = [NSString stringWithUTF8String:title];
shareInfo.imagePath = [NSString stringWithUTF8String:imagePath];
shareInfo.linkPath = [NSString stringWithUTF8String:url];
[sdk shareType:(THREE_SHARE_TYPE)shareType shareObject:shareInfo];
}
void _AccessParticipate(int activityType, const char* activityData) {
NSLog(@"🍎 参与活动");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
AccessActivityDataInfo *activityDataInfo = [[AccessActivityDataInfo alloc] init];
// 根据实际需求设置activityDataInfo的属性
[sdk accessParticipate:(ACTIVITY_TYPE)activityType ActivityDataType:activityDataInfo];
}
void _OpenAccountCenter() {
NSLog(@"🍎 打开账号中心");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
[sdk openAccountCenter];
}
void _OpenService() {
NSLog(@"🍎 打开客服中心");
AccessCoreSdk *sdk = [AccessCoreSdk shareAccessCoreSdkManage];
[sdk openService];
}
}
fileFormatVersion: 2
guid: 8a2bed7a3301d4300827ecb96648cd28
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 0
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
- first:
iPhone: iOS
second:
enabled: 1
settings:
AddToEmbeddedBinaries: false
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ce22c0e2bf8fa483983d1db4cb055020
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment