using RK_Runtime.RK_Space_UI; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; public class VC_SubScrollViewCheck : RK_UIMonoViewComponent, IBeginDragHandler, IDragHandler, IEndDragHandler, IScrollHandler, IInitializePotentialDragHandler, ICanvasElement, ILayoutGroup { public ScrollRect ParentScrollRect; private bool m_IsDrag = false; private Vector2 m_PrePos; public float minWidth; public void CalculateLayoutInputHorizontal() { ParentScrollRect.CalculateLayoutInputHorizontal(); } public void CalculateLayoutInputVertical() { ParentScrollRect.CalculateLayoutInputVertical(); } public void GraphicUpdateComplete() { ParentScrollRect.GraphicUpdateComplete(); } public bool IsDestroyed() { return ParentScrollRect.IsDestroyed(); } public void LayoutComplete() { ParentScrollRect.LayoutComplete(); } public void OnBeginDrag(PointerEventData eventData) { m_IsDrag = true; m_PrePos = eventData.position; PassEvent(eventData, ExecuteEvents.beginDragHandler); } public void OnDrag(PointerEventData eventData) { bool parDir = ParentScrollRect.vertical; var offset = eventData.position - m_PrePos; var x = Mathf.Abs(offset.x); var y = Mathf.Abs(offset.y); if (parDir) { if (y > x) { PassEvent(eventData, ExecuteEvents.dragHandler); } } else { if (y < x) { PassEvent(eventData, ExecuteEvents.dragHandler); } } m_PrePos = eventData.position; } public void OnEndDrag(PointerEventData eventData) { m_IsDrag = false; PassEvent(eventData, ExecuteEvents.endDragHandler); } public void OnInitializePotentialDrag(PointerEventData eventData) { PassEvent(eventData, ExecuteEvents.initializePotentialDrag); } public void OnScroll(PointerEventData eventData) { PassEvent(eventData, ExecuteEvents.scrollHandler); } //把事件透下去 public void PassEvent<T>(PointerEventData data, ExecuteEvents.EventFunction<T> function) where T : IEventSystemHandler { //List<RaycastResult> results = new List<RaycastResult>(); //EventSystem.current.RaycastAll(data, results); //GameObject current = data.pointerCurrentRaycast.gameObject; //for (int i = 0; i < results.Count; i++) //{ // if (current != results[i].gameObject && GetComponent<VC_SubScrollViewCheck>() == null) // { // ExecuteEvents.Execute(ParentScrollRect.gameObject, data, function); // //RaycastAll后ugui会自己排序,如果你只想响应透下去的最近的一个响应,这里ExecuteEvents.Execute后直接break就行。 // break; // } //} ExecuteEvents.Execute(ParentScrollRect.gameObject, data, function); } public void Rebuild(CanvasUpdate executing) { ParentScrollRect.Rebuild(executing); } public void SetLayoutHorizontal() { ParentScrollRect.SetLayoutHorizontal(); } public void SetLayoutVertical() { ParentScrollRect.SetLayoutVertical(); } }
build.py
import json import os import tail import subprocess import sys import _thread import time import re import send2trash import xcode_pre_build AppName = "Cascading_Stars" xcode_build_type = "Release" def tail_log(log_file): print("into tail file = %s" % log_file) while True: if os.path.exists(log_file): print("begin tail file = %s" % log_file) break t = tail.tail(log_file, print_log) def print_log(txt): print(txt) def build_android_app(ide_path, log_path, project_path, params): time1 = time.time() if os.path.exists(log_path): os.remove(log_path) _thread.start_new_thread(tail_log, (log_path, )) if params['aot'] == "1": cmd = ide_path + " -quit" + " -batchmode" + " -logfile " + log_path + " -projectPath " + project_path + " -executeMethod MenuItemTools.HybridClrAotBuildAndroid" process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_path) process.wait() cmd = ide_path + " -quit" + " -batchmode" + " -logfile " + log_path + " -projectPath " + project_path + " -executeMethod MenuItemTools.PackageApk" process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_path) process.wait() stdout,stderr = process.communicate() print(stdout.decode()) if stderr: print(stderr.decode()) time2 = time.time() print("build android time = " + str(time2-time1)) def export_xcode(ide_path, log_path, project_path): print("export unity to xcode") if os.path.exists(log_path): os.remove(log_path) cmd = ide_path + " -quit" + " -batchmode" + " -logfile " + log_path + " -projectPath " + project_path + " -executeMethod MenuItemTools.PackageIOS" _thread.start_new_thread(tail_log, (log_path, )) process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_path) process.wait() stdout,stderr = process.communicate() print(stdout.decode()) if stderr: print(stderr.decode()) def xcode_to_app(xcode_path, params): name = None if str(params['debug']) == "0": name = "Release" else: name = "Debug" file_name = AppName + "-" + name + "-" + params['appver'] export_path = os.path.join(xcode_path, "..", "..", "exportipa") archive_path = xcode_path + "/archive/export.xcarchive" process = subprocess.Popen('cd %s;xcodebuild clean -configuration %s' % (xcode_path, xcode_build_type), shell=True) process.wait() #process = subprocess.Popen('cd %s;pod update' % (xcode_path), shell=True) #process.wait() process = subprocess.Popen( 'cd %s;xcodebuild archive -workspace Unity-iPhone.xcworkspace -scheme Unity-iPhone -configuration %s -archivePath %s -destination "generic/platform=iOS" || exit' % ( xcode_path, xcode_build_type, archive_path), shell=True) process.wait() process = subprocess.Popen('mkdir %s' % export_path, shell=True) process.wait() plist_path = os.path.join(py_path, "exportOptions.plist") process = subprocess.Popen('xcodebuild -exportArchive -archivePath %s -exportOptionsPlist %s -exportPath %s/%s' % ( archive_path, plist_path, export_path, file_name), shell=True) process.wait() def build_ios_app(ide_path, log_path, project_path, xcode_path, params): time1 = time.time() if params['aot'] == "1": cmd = ide_path + " -quit" + " -batchmode" + " -logfile " + log_path + " -projectPath " + project_path + " -executeMethod MenuItemTools.HybridClrAotBuildIOS" process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=project_path) process.wait() export_xcode(ide_path, log_path, project_path) time.sleep(2) #i2cppbuild_path = os.path.join(project_path, "HybridCLRData/iOSBuild") #process = subprocess.Popen('cd %s;bash ./build_libil2cpp.sh' %(i2cppbuild_path) , shell=True) #process.wait() xcode_pre_build.pre_build(xcode_path, project_path) xcode_to_app(xcode_path, params) time2 = time.time() print("build ios time = " + str(time2-time1)) def clear_server_data(project_path, params): data_path = None if params['ios'] == "1": data_path = os.path.join(project_path, "ServerData", "iOS") else: data_path = os.path.join(project_path, "ServerData", "Android") if os.path.exists(data_path): send2trash.send2trash(data_path) def change_version_code(project_path, params): appver = '"AppVer" : "' + params["appver"] + '"' resver = '"ResVer" : "' + params["resver"] + '"' version_path = os.path.join(project_path, "Assets", "Resources", "Version", "Version.bytes") stream = open(version_path, 'r') content = stream.read() stream.close() pattern = re.compile(r'"AppVer" : "([\d\.]*)"') content = re.sub(pattern, appver, content) pattern = re.compile(r'"ResVer" : "([\d\.]*)"') content = re.sub(pattern, resver, content) stream = open(version_path, 'w') stream.write(content) stream.close() def change_debug_code(project_path, params): debug = None channel = None if str(params['debug']) == "0": debug = '''Debug = false''' channel = '''{ "Head" : "Official", "Version" : "http://update.whales-entertainment.com:9850/Update/Official/", "Res" : "http://update.whales-entertainment.com:9850/Update/Official/", "NeedAccount": false }''' else: debug = '''Debug = true''' channel = '''{ "Head": "Test", "Version": "http://88lsj.com:9850/Update/Test/", "Res": "http://88lsj.com:9850/Update/Test/", "NeedAccount": false }''' config_path = os.path.join(project_path, "Assets", "Frame", "Runtime", "Config", "RK_FrameConfig.cs") stream = open(config_path, 'r') content = stream.read() stream.close() content = content.replace('''Debug = false''', debug) content = content.replace('''Debug = true''', debug) stream = open(config_path, 'w') stream.write(content) stream.close() config_path = os.path.join(project_path, "Assets", "StreamingAssets", "ChannelInfo.bytes") #stream = open(config_path, 'r') #content = stream.read() #stream.close() #content = content.replace('''Official''', name) #content = content.replace('''Test''', name) stream = open(config_path, 'w') stream.write(channel) stream.close() def delete_striaotdlltemp(project_path): #这鬼东西 删不掉一直报错 aotdll_temp_path = os.path.join(project_path, "HybridCLRData/StrippedAOTDllsTempProj") if os.path.exists(aotdll_temp_path): send2trash.send2trash(aotdll_temp_path) def set_proxy(): process = subprocess.Popen('export.UTF-8', shell=True) process.wait() process = subprocess.Popen('export LANGUAGE=en_US.UTF-8', shell=True) process.wait() process = subprocess.Popen('export LC_ALL=en_US.UTF-8', shell=True) process.wait() process = subprocess.Popen('export http_proxy=http://127.0.0.1:7890', shell=True) process.wait() process = subprocess.Popen('export https_proxy=http://127.0.0.1:7890', shell=True) process.wait() process = subprocess.Popen('git config --global http.proxy http://127.0.0.1:7890', shell=True) process.wait() process = subprocess.Popen('git config --global http.proxy https://127.0.0.1:7890', shell=True) process.wait() def unset_proxy(): process = subprocess.Popen('git config --global --unset http.proxy', shell=True) process.wait() process = subprocess.Popen('git config --global --unset https.proxy', shell=True) process.wait() if __name__ == "__main__": default_params = '''{"ios":"1", "aot":"0" ,"appver":"1.0.0", "resver":"1000", "debug":"1"}''' params = json.loads(default_params) for arg in sys.argv: par = arg.split("=") if(len(par)>1): params[par[0]] = par[1] ide_path = "/Applications/Unity/Hub/Editor/2021.3.44f1c1/Unity.app/Contents/MacOS/Unity" py_path = os.path.split(os.path.realpath(__file__))[0] project_path = os.path.join(py_path, "../") log_path = os.path.join(py_path, "package.log") delete_striaotdlltemp(project_path) clear_server_data(project_path, params) change_version_code(project_path, params) change_debug_code(project_path, params) name = None if str(params['debug']) == "0": name = "Release" else: name = "Debug" #set_proxy() if str(params['ios']) == "0": build_android_app(ide_path, log_path, project_path,params) else: xcode_path = os.path.join(project_path, 'Build', 'iPhone', AppName + "-" + params['appver'] + "-" + name) build_ios_app(ide_path, log_path, project_path, xcode_path, params) #unset_proxy()
xcode_pre_build.py
import os import shutil import subprocess def copy_il2cpplib(xcode_path, project_path): origin_file = os.path.join(project_path, "HybridCLRData", "iOSBuild", "build", "libil2cpp.a") target_file = os.path.join(xcode_path, "Libraries", "libil2cpp.a") shutil.copy(origin_file, target_file) print("copy src:" + origin_file + " to:" + target_file) def change_plist(xcode_path): info_plist_path = os.path.join(xcode_path, 'Info.plist') stream = open(info_plist_path, 'r') plist = stream.read() stream.close() plist = plist.replace('<string>fb-messenger-api</string>', '<string>fb</string><string>fb-messenger-share-api</string><string>fb-messenger-api</string>', 1) stream = open(info_plist_path, 'w') stream.write(plist) stream.close() deleteInfoPlist(info_plist_path,'NSAppTransportSecurity:NSAllowsArbitraryLoadsInWebContent') def pre_build(xcode_path, project_path): #copy_il2cpplib(xcode_path, project_path) change_plist(xcode_path) @staticmethod def deleteInfoPlist(infoPlistPath,key): (stauts, output) = subprocess.getstatusoutput( '/usr/libexec/PlistBuddy -c \"Delete :{}\" {} '.format(key, infoPlistPath)) if stauts != 0: if output.find('Does Not Exist') == -1: print('删除Plist失败 key=%s,[%s]' % (key, infoPlistPath))
-executeMethod MenuItemTools.PackageApk -executeMethod MenuItemTools.PackageIOS Unity中提供这两个静态方法打包
using UnityEditor; using UnityEngine; public class MenuItemTools { [MenuItem("Package/Build/Android", false,1)] public static void PackageApk() { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { Package.PackageApk(); } else { EditorUserBuildSettings.activeBuildTargetChanged = delegate () { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { Package.PackageApk(); } }; EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); } } [MenuItem("Package/Build/Android-AAB", false, 1)] public static void PackageAAB() { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { Package.PackageAAB(); } else { EditorUserBuildSettings.activeBuildTargetChanged = delegate () { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { Package.PackageAAB(); } }; EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android); } } [MenuItem("Package/Build/Export to Xcode", false, 3)] public static void PackageIOS() { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { Package.PackageIos(); } else { EditorUserBuildSettings.activeBuildTargetChanged = delegate () { if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { Package.PackageIos(); } }; EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.iOS, BuildTarget.iOS); } } }
Package.cs
using System.Collections.Generic; using System.IO; using UnityEditor; using HybridCLR.Editor.Commands; using UnityEngine; using UnityEditor.Build.Content; #if RK_GoogleAds using GoogleMobileAds.Editor; #endif public class Package { public static void BuildSceneStream() { UnityEngine.Object[] scenes = Selection.GetFiltered(typeof(SceneAsset), SelectionMode.Assets); foreach (UnityEngine.Object o in scenes) { string path = AssetDatabase.GetAssetPath(o); if (!path.Contains(".unity")) continue; string sceneName = o.name + ".scene"; BuildPipeline.BuildPlayer(new string[] { path }, "Res/Scenes/" + sceneName, BuildTarget.Android, BuildOptions.BuildAdditionalStreamedScenes); } } public static void PackageApk() { PreparePackage(); EditorUserBuildSettings.buildAppBundle = false; BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/Android/{GetAppFileName()}.apk", BuildTarget.Android, BuildOptions.CompressWithLz4HC); } public static void PackageAAB() { PreparePackage(); EditorUserBuildSettings.buildAppBundle = true; BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/Android/{GetAppFileName()}.aab", BuildTarget.Android, BuildOptions.CompressWithLz4HC); } public static void PackageStandaloneWindows64() { PreparePackage(); BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/StandaloneWindows64/{AotConfig.ProductName}-{GetAppVer()}/{AotConfig.ProductName}.exe", BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4HC); } public static void PackageIos() { PreparePackage(); string module = null; if (RK_FrameConfig.IsDebug) { module = "Debug"; } else { module = "Release"; } BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/iPhone/{AotConfig.ProductName}-{GetAppVer()}-{module}", BuildTarget.iOS, BuildOptions.CompressWithLz4HC); } public static void PackageOSX() { PreparePackage(); BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/Osx/{AotConfig.ProductName}-{GetAppVer()}/{AotConfig.ProductName}", BuildTarget.StandaloneOSX, BuildOptions.CompressWithLz4HC); } private static string GetAppFileName() { string name = AotConfig.ProductName; #if RK_GameDebug name += "-Debug"; #else name += "-Release"; #endif #if RK_CSJ name += "-CSJ"; #endif #if RK_TapTap name += "-TapTap"; #endif #if RK_GoogleAds name += "-Admod"; #endif #if RK_TopOnAds name += "-Topon"; #endif if (AotConfig.IsSkipAd) { name += "-SkipAd"; } else { if (AotConfig.IsTestAd) { name += "-TestAd"; } } if (AotConfig.NoPay) { name += "-NoPay"; } name += "-" + AotConfig.DefaultLanguage; name += "-" + GetAppVer(); return name; } public static void PreparePackage() { Debug.Log("PreparePackage"); //ChangeDebugSymbol(); //ChangeReviewSymbol(); //PlayerSettings.bundleVersion = GetAppVer(); //PlayerSettings.Android.bundleVersionCode = GetIntAppVer(); if (AotConfig.DefaultLanguage == "Chinese") { PlayerSettings.productName = AotConfig.SettingProductName_CN; } else { PlayerSettings.productName = AotConfig.SettingProductName_EN; } PlayerSettings.iOS.buildNumber = "0"; if(!AotConfig.DisableHybridCLR) PrebuildCommand.GenerateAll(); PackageRes(); AssetDatabase.Refresh(); } public static void ChangeDebugSymbol() { SymbolMenuItemTools.ChangeDebugSymbol(RK_FrameConfig.IsDebug); } public static void PackageRes() { ClearServerData(); //#if UNITY_EDITOR_WIN // ProtobufTool.AllProto2CS(); //#endif AotHotUpdateDll.BuildAndCopyABAOTHotUpdateDlls(); AASUtility.CleanPlayerContent(); PackageAtlas.PackAtlas(); AssetDatabase.Refresh(); AddressableTool.MarkAssets(); AddressableTool.BuildPlayerContent(); } public static void HybridClrAotBuild() { string path = Path.Combine(Application.dataPath, "../Build/AotTemp"); if (Directory.Exists(path)) { Directory.Delete(path, true); } BuildTarget target = EditorUserBuildSettings.activeBuildTarget; CompileDllCommand.CompileDll(target); Il2CppDefGeneratorCommand.GenerateIl2CppDef(); LinkGeneratorCommand.GenerateLinkXml(target); BuildPipeline.BuildPlayer(GetBuildScenes(), $"Build/AotTemp/{AotConfig.ProductName}-{GetAppVer()}", target, BuildOptions.CompressWithLz4HC); } public static void ClearServerData() { string path = Path.Combine(Application.dataPath, "../","ServerData"); if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows) { path = Path.Combine(path, "StandaloneWindows"); } else if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.StandaloneWindows64) { path = Path.Combine(path, "StandaloneWindows64"); } else if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.Android) { path = Path.Combine(path, "Android"); } else if (EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS) { path = Path.Combine(path, "iOS"); } Debug.Log(path); if (Directory.Exists(path)) { Directory.Delete(path, true); } } private static string[] GetBuildScenes() { List<EditorBuildSettingsScene> editorBuildSettingsScenes = new List<EditorBuildSettingsScene>(); foreach (EditorBuildSettingsScene e in EditorBuildSettings.scenes) { string scenePath = e.path; if (!string.IsNullOrEmpty(scenePath)) editorBuildSettingsScenes.Add(new EditorBuildSettingsScene(scenePath, true)); } EditorBuildSettings.scenes = editorBuildSettingsScenes.ToArray(); List<string> names = new List<string>(); foreach (EditorBuildSettingsScene e in EditorBuildSettings.scenes) { if (e != null && e.enabled) { names.Add(e.path); } } return names.ToArray(); } private static string GetAppVer() { var json = Resources.Load<TextAsset>(BootMainConfig.LocalVersionPath).text; var local_version = JsonUtility.FromJson<VersionInfo>(json); return local_version.AppVer; } }
初始化UnityServer
using Firebase.Extensions; using RK_Runtime; using System; using System.Collections; using System.Collections.Generic; using Unity.Services.Core; using UnityEngine; public class UnityServiceTool : RK_Singleton<UnityServiceTool> { public bool IsInit = false; const string k_Environment = "production"; public override void StartUp() { base.StartUp(); Initialize(OnSuccess, OnError); } void Initialize(Action onSuccess, Action<string> onError) { try { var options = new InitializationOptions(); options.SetOption("Environment", k_Environment); UnityServices.InitializeAsync(options).ContinueWithOnMainThread(task => onSuccess()); } catch (Exception exception) { onError(exception.Message); } } void OnSuccess() { IsInit = true; var text = "Congratulations!\nUnity Gaming Services has been successfully initialized."; RK.Log.Log(text); MG_IAPurchaseManager.Instance.Initialize(0, false); } void OnError(string message) { var text = $"Unity Gaming Services failed to initialize with error: {message}."; RK.Log.LogError(text); } }
支付管理类
using cfg.Tpl; using RK_Runtime; using RK_Runtime.RK_Space_Http; using System.Collections.Generic; using System.Text; using UnityEngine; using UnityEngine.Purchasing; using UnityEngine.Purchasing.Extension; using SimpleJSON; using System; using System.Threading.Tasks; using Newtonsoft.Json; using RK_Runtime.RK_Space_Event; using RK_Runtime.Utility; //using UnityEngine.Purchasing.Security; //using DataTower.Core; public class MG_IAPurchaseManager : RK_MonoSingleton<MG_IAPurchaseManager>,IDetailedStoreListener { private IStoreController m_StoreController; // Unity 采购系统 private IExtensionProvider m_StoreExtensionProvider; // 商店特定的采购子系统. private Product m_PurchasedProduct; private int m_InitReTryTime = 0; private bool m_IsIniting = false; private string m_PurchasedProductID = string.Empty; private Dictionary<string,bool> m_OrderMap = new Dictionary<string,bool>(); class PayCheckReceiveJsonData { public int Code = 0; public string Data; } private void Start() { RK.Event.Add(OnEventApplicationPause); } [RK_EventHandle(EventDefine.EventApplicationPause)] private static void OnEventApplicationPause(object data) { bool pause = RK_ValueRef<bool>.GetValue(data); if (pause) { //PL_CommonLoadingPanel.HideLoading("PurchaseProduct"); } } public async Task Initialize(int trytime = 0, bool isLoading = true) { if (IsInitialized()) { return; } RK.Log.LogWarning("IAPurchaseManager initializing."); var orderStr = PlayerPrefs.GetString(PlayerPrefsKey.OrderInfoKey, ""); if (!string.IsNullOrEmpty(orderStr)) { var dic = JsonConvert.DeserializeObject<Dictionary<string,bool>>(orderStr); if (dic != null) { m_OrderMap = dic; } } m_IsIniting = true; m_InitReTryTime = trytime; if(isLoading) //PL_CommonLoadingPanel.ShowLoading("PayInitialize", delayTime: 0, when: () => { return !m_IsIniting; }); while (!UnityServiceTool.Instance.IsInit)//等待服务初始化 { await Task.Yield(); } var module = StandardPurchasingModule.Instance(); module.useFakeStoreUIMode = FakeStoreUIMode.StandardUser; module.useFakeStoreAlways = false; var builder = ConfigurationBuilder.Instance(module); for (int i = 0; i < SH_TableData.Instance.Datas.Goods.DataList.Count; i++) { if (!string.IsNullOrEmpty(SH_TableData.Instance.Datas.Goods.DataList[i].ProductID)) { builder.AddProduct(SH_TableData.Instance.Datas.Goods.DataList[i].ProductID, ProductType.Consumable); } } //StringBuilder stringBuilder = new StringBuilder(); //foreach (var item in builder.products) //{ // stringBuilder.AppendLine(item.id); //} //RK.Log.LogWarning(stringBuilder.ToString()); if (Application.platform == RuntimePlatform.Android) { var config = builder.Configure<IGooglePlayConfiguration>(); config.SetObfuscatedAccountId("UID:" + SH_UserData.Instance.LoginID); } UnityPurchasing.Initialize(this, builder); } public bool IsInitialized() { return m_StoreController != null && m_StoreExtensionProvider != null; } public void PurchaseProduct(string productId) { RK.Log.Log("Purchase:" + productId); //if (AotConfig.NoPay) //{ // SH_ShopData.Instance.BuySuccess(productId); // return; //} if (IsInitialized()) { if (m_PurchasedProduct != null) { //有订单正在购买中 //UT_CommonUtil.ShowErrorToast("TitleBuyConfirm"); CheckOrder(m_PurchasedProduct);//再去验证下 return; } Product product = m_StoreController.products.WithID(productId); if (product != null && product.availableToPurchase) { //PL_CommonLoadingPanel.ShowLoading("PurchaseProduct", delayTime: 0); BaseAdManager.Instance.SkipOpenAdTime++; m_PurchasedProduct = product; m_StoreController.InitiatePurchase(productId); } else { RK.Log.LogError(string.Format("Purchasing product asychronously: '{0}'", product.definition.id)); } } else { m_PurchasedProductID = productId; if (m_IsIniting) { m_InitReTryTime = 2; //PL_CommonLoadingPanel.ShowLoading("PayInitialize", delayTime: 0, when: () => { return !m_IsIniting; }); return; } Initialize(2); } } public void RestorePurchases() { if (!IsInitialized()) { Initialize(); return; } // If we are running on an Apple device ... if (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.OSXPlayer) { // ... begin restoring purchases Debug.Log("RestorePurchases started ..."); // Fetch the Apple store-specific subsystem. var apple = m_StoreExtensionProvider.GetExtension<IAppleExtensions>(); // Begin the asynchronous process of restoring purchases. Expect a confirmation response in // the Action<bool> below, and ProcessPurchase if there are previously purchased products to restore. apple.RestoreTransactions((result, error) => { // The first phase of restoration. If no more responses are received on ProcessPurchase then // no purchases are available to be restored. if (result) { Debug.Log("RestorePurchases continuing: " + result + ". If no further messages, no purchases available to restore."); } else { RK.Log.LogWarning(error); } }); } // Otherwise ... else { // We are not running on an Apple device. No work is necessary to restore purchases. Debug.Log("RestorePurchases FAIL. Not supported on this platform. Current = " + Application.platform); } } /* * 验证 */ public void CheckOrder(Product product) { if (!product.hasReceipt) { m_PurchasedProduct = null; return; } RK.Log.Log("Success : " + product.ToString()); RK.Log.Log("Success receipt : " + product.receipt); List<string> produces = new List<string>(); List<string> orders = new List<string>(); bool validPurchase = true; // 假设对没有收据验证的平台有效。 bool hasProduct = false; // 假设对没有收据验证的平台有效。 //#if !UNITY_EDITOR // var validator = new CrossPlatformValidator(GooglePlayTangle.Data(), // AppleTangle.Data(), Application.identifier); // try { // //在 Google Play 上,结果中仅有一个商品 ID。 // //在 Apple 商店中,收据包含多个商品。 // var validateResult = validator.Validate(product.receipt); // //为便于参考,我们将收据列出 // Debug.Log("Receipt is valid. Contents:"); // //validateResult. // foreach (IPurchaseReceipt productReceipt in validateResult) { // produces.Add(productReceipt.productID); // orders.Add(productReceipt.transactionID); // } // } catch (IAPSecurityException) { // Debug.Log("Invalid receipt, not unlocking content"); // validPurchase = false; // } //#endif if (validPurchase) { List<string> lists = new List<string>(); for (int i = 0; i < produces.Count; i++) { if (!m_OrderMap.TryGetValue(orders[i], out var flat)) { SH_ShopData.Instance.BuySuccess(produces[i]); lists.Add(orders[i]); } } RK.Event.Call(EventDefine.EventUpdateUserInfo); m_StoreController.ConfirmPendingPurchase(product); m_PurchasedProduct = null; for (int i = 0; i < lists.Count; i++) { m_OrderMap.Add(lists[i], true); } PlayerPrefs.SetString(PlayerPrefsKey.OrderInfoKey, JsonConvert.SerializeObject(m_OrderMap)); PlayerPrefs.Save(); SH_UserData.Instance.ReqSaveUserInfo(()=>{ }); return; } else { m_PurchasedProduct = null; SH_ShopData.Instance.BuyFail(product.definition.id); } } public float GetPrice(string id) { return 0; } public void Release() { m_PurchasedProduct = null; m_StoreController = null; m_StoreExtensionProvider = null; m_PurchasedProductID = null; m_InitReTryTime = 0; m_IsIniting = false; } //----------------------------------IDetailedStoreListener---------------------------- public void OnInitialized(IStoreController controller, IExtensionProvider extensions) { m_IsIniting = false; m_InitReTryTime = 0; RK.Log.Log($"IAPurchaseManager Init Success"); m_StoreController = controller; m_StoreExtensionProvider = extensions; if (Application.platform == RuntimePlatform.IPhonePlayer) { extensions.GetExtension<IAppleExtensions>().SetApplicationUsername("特定的格式-UUID 映射uid"); } if (!string.IsNullOrEmpty(m_PurchasedProductID)) { PurchaseProduct(m_PurchasedProductID); m_PurchasedProductID = null; } } public void OnInitializeFailed(InitializationFailureReason error, string message) { if (m_InitReTryTime > 0) { RK.Log.Log($"IAPurchaseManager Init : {error} {message} retry "); m_InitReTryTime--; Initialize(m_InitReTryTime); } else { m_IsIniting = false; RK.Log.LogError($"IAPurchaseManager Init : {error} {message} "); if (!string.IsNullOrEmpty(m_PurchasedProductID)) { //弹出提示 //UT_CommonUtil.ShowErrorToast(RK_I18NLanguageUtil.GetI18nString("PaymentFailed")); } } } public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs purchaseEvent) { //让服务端去确认 m_PurchasedProduct = purchaseEvent.purchasedProduct; CheckOrder(m_PurchasedProduct); return PurchaseProcessingResult.Pending; } public void OnPurchaseFailed(Product product, PurchaseFailureDescription failureDescription) { m_PurchasedProduct = null; RK.Log.Log(failureDescription.reason.ToString() + ":" + failureDescription.message); //CommonUtil.ShowToast(failureDescription.message); switch (failureDescription.reason) { case PurchaseFailureReason.UserCancelled: //UT_CommonUtil.ShowToast(RK_I18NLanguageUtil.GetI18nString("ButtonCancle")); break; default: UT_CommonUtil.ShowErrorToast(failureDescription.reason.ToString()); break; } // Dictionary<string, string> param = new Dictionary<string, string>(); // param.Add("platform", Application.platform == RuntimePlatform.IPhonePlayer ? "ios" : "android"); // param.Add("productId", failureDescription.productId); // param.Add("transactionID", product.transactionID); // param.Add("appleOriginalTransactionID", product.appleOriginalTransactionID); // param.Add("reason", failureDescription.reason.ToString()); // param.Add("message", failureDescription.message); // // string json = JsonConvert.SerializeObject(param); SH_ShopData.Instance.BuyFail(product.definition.id); } public void OnInitializeFailed(InitializationFailureReason error) { throw new System.NotImplementedException(); } public void OnPurchaseFailed(Product product, PurchaseFailureReason failureReason) { m_PurchasedProduct = null; RK.Log.Log(failureReason.ToString()); //CommonUtil.ShowToast(failureDescription.message); switch (failureReason) { case PurchaseFailureReason.UserCancelled: //UT_CommonUtil.ShowToast(RK_I18NLanguageUtil.GetI18nString("ButtonCancle")); break; default: UT_CommonUtil.ShowErrorToast(failureReason.ToString()); break; } // Dictionary<string, string> param = new Dictionary<string, string>(); // param.Add("platform", Application.platform == RuntimePlatform.IPhonePlayer ? "ios" : "android"); // param.Add("productId", failureDescription.productId); // param.Add("transactionID", product.transactionID); // param.Add("appleOriginalTransactionID", product.appleOriginalTransactionID); // param.Add("reason", failureDescription.reason.ToString()); // param.Add("message", failureDescription.message); // // string json = JsonConvert.SerializeObject(param); SH_ShopData.Instance.BuyFail(product.definition.id); } }
薯塔、bugly、Firebase、In App Purchasing
关闭 HybridClr 关闭热更
webgl的addressable的bundle是url来的 都用下载的方式
m2repository的声明放在Assets/目录/m2repository,这样下载的库文件路径和自动生成的AndroidResolverDependencies.xml里配置的引用路径才对
可以在unity中设置图形API如下:
就可以复现android手机上出现的情况,经排查顶点uv等数据输出,法线uv会随着旋转视角改变时而变动,经尝试是模型太大了,缩放后正常了
unity-jar-resolver android
cocoapods ios
打包失败一般是依赖库下载失败 脚本打包失败,可以打开Unity,尝试resolusion(android),ios直接xcode export,然后命令行声明代理,再pod update or pod intsall
带CanvasUI是不在剪裁的目标之内的,所以需要一个中介自身是剪裁目标的,然后获取到这些带Canvas的UI通知它们也做相应处理。
Proxy的脚本为:
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; [DisallowMultipleComponent] public class RectMask2DClipProxy : MaskableGraphic { private List<MaskableGraphic> _maskableList; protected RectMask2DClipProxy() { useLegacyMeshGeneration = false; } protected override void OnPopulateMesh(VertexHelper toFill) { toFill.Clear(); } protected override void Awake() { base.Awake(); _maskableList = new List<MaskableGraphic>(); } public override void Cull(Rect clipRect, bool validRect) { base.Cull(clipRect, validRect); GetComponentsInChildren(false, _maskableList); if (null != _maskableList) { for (int i = 0; i < _maskableList.Count; i++) { var maskable = _maskableList[i]; if (maskable != null && maskable != this) { maskable.Cull(clipRect, validRect); } } } } public override void SetClipRect(Rect clipRect, bool validRect) { base.SetClipRect(clipRect, validRect); GetComponentsInChildren(false, _maskableList); if (null != _maskableList) { for (int i = 0; i < _maskableList.Count; i++) { var maskable = _maskableList[i]; if (maskable != null && maskable != this) { maskable.SetClipRect(clipRect, validRect); } } } } }
如果元素不是动态变化的,就缓存起来。
inline float SoftUnityGet2DClipping (in float2 position, in float4 clipRect) { float2 xy = (position.xy-clipRect.xy)/float2(_ClipSoftX,_ClipSoftY)*step(clipRect.xy, position.xy); float2 zw = (clipRect.zw-position.xy)/float2(_ClipSoftX,_ClipSoftY)*step(position.xy,clipRect.zw); float2 factor = clamp(0, zw, xy); return saturate(min(factor.x,factor.y)); } c.a *= SoftUnityGet2DClipping(i.wpos.xy, float4(_MinX, _MinY, _MaxX, _MaxY));