m_TitleSpine.AnimationState.ClearTrack(0); m_Track = m_TitleSpine.AnimationState.SetAnimation(0, m_AniName, false); m_TitleSpine.Update(0); m_TitleSpine.LateUpdate();
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);
}
}
}
}
}如果元素不是动态变化的,就缓存起来。