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来的 都用下载的方式,修改BeginOperation
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Threading.Tasks;
using UnityEngine.AddressableAssets;
using UnityEngine.Networking;
using UnityEngine.ResourceManagement.AsyncOperations;
using UnityEngine.ResourceManagement.Exceptions;
using UnityEngine.ResourceManagement.ResourceLocations;
using UnityEngine.ResourceManagement.Util;
using UnityEngine.Serialization;
namespace UnityEngine.ResourceManagement.ResourceProviders
{
#region ================> extent from addressable WebRequestQueue.cs
internal class WebRequestQueueOperation
{
public UnityWebRequestAsyncOperation Result;
public Action<UnityWebRequestAsyncOperation> OnComplete;
public bool IsDone
{
get { return Result != null; }
}
internal UnityWebRequest m_WebRequest;
public WebRequestQueueOperation(UnityWebRequest request)
{
m_WebRequest = request;
}
internal void Complete(UnityWebRequestAsyncOperation asyncOp)
{
Result = asyncOp;
OnComplete?.Invoke(Result);
}
}
internal static class WebRequestQueue
{
internal static int s_MaxRequest = 500;
internal static Queue<WebRequestQueueOperation> s_QueuedOperations = new Queue<WebRequestQueueOperation>();
internal static List<UnityWebRequestAsyncOperation> s_ActiveRequests = new List<UnityWebRequestAsyncOperation>();
public static void SetMaxConcurrentRequests(int maxRequests)
{
if (maxRequests < 1)
throw new ArgumentException("MaxRequests must be 1 or greater.", "maxRequests");
s_MaxRequest = maxRequests;
}
public static WebRequestQueueOperation QueueRequest(UnityWebRequest request)
{
WebRequestQueueOperation queueOperation = new WebRequestQueueOperation(request);
if (s_ActiveRequests.Count < s_MaxRequest)
{
var webRequestAsyncOp = request.SendWebRequest();
s_ActiveRequests.Add(webRequestAsyncOp);
if (webRequestAsyncOp.isDone)
OnWebAsyncOpComplete(webRequestAsyncOp);
else
webRequestAsyncOp.completed += OnWebAsyncOpComplete;
queueOperation.Complete(webRequestAsyncOp);
}
else
s_QueuedOperations.Enqueue(queueOperation);
return queueOperation;
}
private static void OnWebAsyncOpComplete(AsyncOperation operation)
{
s_ActiveRequests.Remove((operation as UnityWebRequestAsyncOperation));
if (s_QueuedOperations.Count > 0)
{
var nextQueuedOperation = s_QueuedOperations.Dequeue();
var webRequestAsyncOp = nextQueuedOperation.m_WebRequest.SendWebRequest();
webRequestAsyncOp.completed += OnWebAsyncOpComplete;
s_ActiveRequests.Add(webRequestAsyncOp);
nextQueuedOperation.Complete(webRequestAsyncOp);
}
}
}
#endregion
/// <summary>
/// Contains cache information to be used by the AssetBundleProvider
/// </summary>
[Serializable]
public class MyAssetBundleRequestOptions : ILocationSizeData
{
[FormerlySerializedAs("m_hash")]
[SerializeField]
string m_Hash = "";
/// <summary>
/// Hash value of the asset bundle.
/// </summary>
public string Hash { get { return m_Hash; } set { m_Hash = value; } }
[FormerlySerializedAs("m_crc")]
[SerializeField]
uint m_Crc;
/// <summary>
/// CRC value of the bundle.
/// </summary>
public uint Crc { get { return m_Crc; } set { m_Crc = value; } }
[FormerlySerializedAs("m_timeout")]
[SerializeField]
int m_Timeout;
/// <summary>
/// Sets UnityWebRequest to attempt to abort after the number of seconds in timeout have passed.
/// </summary>
public int Timeout { get { return m_Timeout; } set { m_Timeout = value; } }
[FormerlySerializedAs("m_chunkedTransfer")]
[SerializeField]
bool m_ChunkedTransfer;
/// <summary>
/// Indicates whether the UnityWebRequest system should employ the HTTP/1.1 chunked-transfer encoding method.
/// </summary>
public bool ChunkedTransfer { get { return m_ChunkedTransfer; } set { m_ChunkedTransfer = value; } }
[FormerlySerializedAs("m_redirectLimit")]
[SerializeField]
int m_RedirectLimit = -1;
/// <summary>
/// Indicates the number of redirects which this UnityWebRequest will follow before halting with a “Redirect Limit Exceeded” system error.
/// </summary>
public int RedirectLimit { get { return m_RedirectLimit; } set { m_RedirectLimit = value; } }
[FormerlySerializedAs("m_retryCount")]
[SerializeField]
int m_RetryCount;
/// <summary>
/// Indicates the number of times the request will be retried.
/// </summary>
public int RetryCount { get { return m_RetryCount; } set { m_RetryCount = value; } }
[SerializeField]
string m_BundleName = null;
/// <summary>
/// The name of the original bundle. This does not contain the appended hash.
/// </summary>
public string BundleName { get { return m_BundleName; } set { m_BundleName = value; } }
[SerializeField]
long m_BundleSize;
/// <summary>
/// The size of the bundle, in bytes.
/// </summary>
public long BundleSize { get { return m_BundleSize; } set { m_BundleSize = value; } }
[SerializeField]
bool m_UseCrcForCachedBundles;
/// <summary>
/// If false, the CRC will not be used when loading bundles from the cache.
/// </summary>
public bool UseCrcForCachedBundle { get { return m_UseCrcForCachedBundles; } set { m_UseCrcForCachedBundles = value; } }
[SerializeField]
bool m_UseUWRForLocalBundles;
/// <summary>
/// If true, UnityWebRequest will be used even if the bundle is stored locally.
/// </summary>
public bool UseUnityWebRequestForLocalBundles { get { return m_UseUWRForLocalBundles; } set { m_UseUWRForLocalBundles = value; } }
[SerializeField]
bool m_ClearOtherCachedVersionsWhenLoaded;
/// <summary>
/// If false, the CRC will not be used when loading bundles from the cache.
/// </summary>
public bool ClearOtherCachedVersionsWhenLoaded { get { return m_ClearOtherCachedVersionsWhenLoaded; } set { m_ClearOtherCachedVersionsWhenLoaded = value; } }
/// <summary>
/// Computes the amount of data needed to be downloaded for this bundle.
/// </summary>
/// <param name="location">The location of the bundle.</param>
/// <param name="resourceManager">The object that contains all the resource locations.</param>
/// <returns>The size in bytes of the bundle that is needed to be downloaded. If the local cache contains the bundle or it is a local bundle, 0 will be returned.</returns>
public virtual long ComputeSize(IResourceLocation location, ResourceManager resourceManager)
{
var id = resourceManager == null ? location.InternalId : resourceManager.TransformInternalId(location);
if (!ResourceManagerConfig.IsPathRemote(id))
return 0;
var locHash = Hash128.Parse(Hash);
#if ENABLE_CACHING
if (locHash.isValid) //If we have a hash, ensure that our desired version is cached.
{
if (Caching.IsVersionCached(new CachedAssetBundle(BundleName, locHash)))
return 0;
return BundleSize;
}
#endif //ENABLE_CACHING
return BundleSize;
}
}
class CustomAssetBundleResource : IAssetBundleResource
{
private static string KEY = AESTool.GenAssetBundleSecretKey();
AssetBundle m_AssetBundle;
DownloadHandler m_downloadHandler;
AsyncOperation m_RequestOperation;
WebRequestQueueOperation m_WebRequestQueueOperation;
internal ProvideHandle m_ProvideHandle;
internal AssetBundleRequestOptions m_Options;
int m_Retries;
long m_BytesToDownload;
long m_DownloadedBytes;
bool m_Completed = false;
internal UnityWebRequest CreateWebRequest(IResourceLocation loc)
{
var url = m_ProvideHandle.ResourceManager.TransformInternalId(loc);
return CreateWebRequest(url);
}
internal UnityWebRequest CreateWebRequest(string url)
{
UnityWebRequest webRequest = null;
webRequest = new UnityWebRequest(url);
DownloadHandlerBuffer dH = new DownloadHandlerBuffer();
webRequest.downloadHandler = dH;
if (webRequest == null)
return webRequest;
if (m_Options != null)
{
if (m_Options.Timeout > 0)
webRequest.timeout = m_Options.Timeout;
if (m_Options.RedirectLimit > 0)
webRequest.redirectLimit = m_Options.RedirectLimit;
#if !UNITY_2019_3_OR_NEWER
webRequest.chunkedTransfer = m_Options.ChunkedTransfer;
#endif
}
if (m_ProvideHandle.ResourceManager.CertificateHandlerInstance != null)
{
webRequest.certificateHandler = m_ProvideHandle.ResourceManager.CertificateHandlerInstance;
webRequest.disposeCertificateHandlerOnDispose = false;
}
return webRequest;
}
float PercentComplete() { return m_RequestOperation != null ? m_RequestOperation.progress : 0.0f; }
DownloadStatus GetDownloadStatus()
{
if (m_Options == null)
return default;
var status = new DownloadStatus() { TotalBytes = m_BytesToDownload, IsDone = PercentComplete() >= 1f };
if (m_BytesToDownload > 0)
{
if (m_WebRequestQueueOperation != null && string.IsNullOrEmpty(m_WebRequestQueueOperation.m_WebRequest.error))
m_DownloadedBytes = (long)(m_WebRequestQueueOperation.m_WebRequest.downloadedBytes);
else if (m_RequestOperation != null && m_RequestOperation is UnityWebRequestAsyncOperation operation && string.IsNullOrEmpty(operation.webRequest.error))
m_DownloadedBytes = (long)operation.webRequest.downloadedBytes;
}
status.DownloadedBytes = m_DownloadedBytes;
return status;
}
/// <summary>
/// Get the asset bundle object managed by this resource. This call may force the bundle to load if not already loaded.
/// </summary>
/// <returns>The asset bundle.</returns>
public AssetBundle GetAssetBundle()
{
if (m_AssetBundle == null)
{
if (m_downloadHandler != null)
{
m_AssetBundle = AssetBundle.LoadFromMemory(m_downloadHandler.data);
m_downloadHandler.Dispose();
m_downloadHandler = null;
}
else if (m_RequestOperation is AssetBundleCreateRequest)
{
m_AssetBundle = (m_RequestOperation as AssetBundleCreateRequest).assetBundle;
}
}
return m_AssetBundle;
}
internal void Start(ProvideHandle provideHandle)
{
m_Retries = 0;
m_AssetBundle = null;
m_downloadHandler = null;
m_RequestOperation = null;
m_ProvideHandle = provideHandle;
m_Options = m_ProvideHandle.Location.Data as AssetBundleRequestOptions;
if (m_Options != null)
m_BytesToDownload = m_Options.ComputeSize(m_ProvideHandle.Location, m_ProvideHandle.ResourceManager);
m_ProvideHandle.SetProgressCallback(PercentComplete);
m_ProvideHandle.SetDownloadProgressCallbacks(GetDownloadStatus);
m_ProvideHandle.SetWaitForCompletionCallback(WaitForCompletionHandler);
BeginOperation();
}
private bool WaitForCompletionHandler()
{
if (m_RequestOperation == null)
return false;
//We don't want to wait for request op to complete if it's a LoadFromFileAsync. Only UWR will complete in a tight loop like this.
if (!(m_RequestOperation is AssetBundleCreateRequest))
while (!m_RequestOperation.isDone) { }
var assetBundle = GetAssetBundle();
if (!m_Completed && assetBundle != null)
{
m_ProvideHandle.Complete(this, m_AssetBundle != null, null);
m_Completed = true;
}
return m_Completed;
}
void AddCallbackInvokeIfDone(AsyncOperation operation, Action<AsyncOperation> callback)
{
if (operation.isDone)
callback(operation);
else
operation.completed += callback;
}
private void BeginOperation()
{
string path = m_ProvideHandle.Location.InternalId;
var url = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location);
string bundleName = Path.GetFileName(url);
// if a path starts with jar:file, it is an android embeded resource. The resource is a local file but cannot be accessed by
// FileStream(called in LoadWithDataProc) directly
// Need to use webrequest's async call to get the content.
if (Application.platform == RuntimePlatform.WeixinMiniGamePlayer || Application.platform == RuntimePlatform.WebGLPlayer)
{
string streamPath = UnityEngine.AddressableAssets.Addressables.RuntimePath + "/" + PlatformMappingService.GetPlatformPathSubFolder() + "/" + bundleName;
Debug.Log("LoadZero:" + streamPath);
var req = CreateWebRequest(streamPath);
req.disposeDownloadHandlerOnDispose = false;
m_WebRequestQueueOperation = WebRequestQueue.QueueRequest(req);
if (m_WebRequestQueueOperation.IsDone)
{
m_RequestOperation = m_WebRequestQueueOperation.Result;
m_RequestOperation.completed += WebRequestOperationCompleted;
}
else
{
m_WebRequestQueueOperation.OnComplete += asyncOp =>
{
m_RequestOperation = asyncOp;
m_RequestOperation.completed += WebRequestOperationCompleted;
};
}
}
else if (BundleMgr.Instance.CheckIsBuildInBundle(bundleName))//本地资源
{
string streamPath = UnityEngine.AddressableAssets.Addressables.RuntimePath + "/" + PlatformMappingService.GetPlatformPathSubFolder() + "/" + bundleName;
//#if UNITY_IPHONE && !UNITY_EDITOR
// streamPath ="file://" + streamPath;
//#elif UNITY_STANDALONE_WIN || UNITY_EDITOR
// streamPath = "file://" + streamPath;
//#endif
// Debug.Log("LoadOne:" + streamPath);
// var crc = m_Options == null ? 0 : m_Options.Crc;
// var req = CreateWebRequest(streamPath);
// req.disposeDownloadHandlerOnDispose = false;
// m_WebRequestQueueOperation = WebRequestQueue.QueueRequest(req);
// if (m_WebRequestQueueOperation.IsDone)
// {
// m_RequestOperation = m_WebRequestQueueOperation.Result;
// m_RequestOperation.completed += StreamWebRequestOperationCompleted;
// }
// else
// {
// m_WebRequestQueueOperation.OnComplete += asyncOp =>
// {
// m_RequestOperation = asyncOp;
// m_RequestOperation.completed += StreamWebRequestOperationCompleted;
// };
// }
Debug.Log("LoadOne:" + streamPath);
CompleteBundleLoad(AssetBundle.LoadFromFile(streamPath));
}
else if (BundleMgr.Instance.IsBundleCache(bundleName))
{
string cachePath = Path.Combine(BundleMgr.Instance.GetBundleCachePath(), bundleName);
Debug.Log("LoadTwo:" + cachePath);
var crc = m_Options == null ? 0 : m_Options.Crc;
//AssetBundle.LoadFromMemoryAsync(AESTool.DecryptBytes(File.ReadAllBytes(cachePath), AESTool.GenAssetBundleSecretKey()));
//m_RequestOperation.completed += LocalRequestOperationCompleted;
CompleteBundleLoad(AssetBundle.LoadFromFile(cachePath));
}
else if (ResourceManagerConfig.ShouldPathUseWebRequest(path))
{
Debug.Log("DownloadThree:" + url);
var req = CreateWebRequest(m_ProvideHandle.Location);
req.disposeDownloadHandlerOnDispose = false;
m_WebRequestQueueOperation = WebRequestQueue.QueueRequest(req);
if (m_WebRequestQueueOperation.IsDone)
{
m_RequestOperation = m_WebRequestQueueOperation.Result;
m_RequestOperation.completed += WebRequestOperationCompleted;
}
else
{
m_WebRequestQueueOperation.OnComplete += asyncOp =>
{
m_RequestOperation = asyncOp;
m_RequestOperation.completed += WebRequestOperationCompleted;
};
}
}
else
{
m_RequestOperation = null;
m_ProvideHandle.Complete<CustomAssetBundleResource>(null, false, new Exception(string.Format("Invalid path in AssetBundleProvider: '{0}'.", path)));
}
}
private void LocalRequestOperationCompleted(AsyncOperation op)
{
CompleteBundleLoad((op as AssetBundleCreateRequest).assetBundle);
}
private void CompleteBundleLoad(AssetBundle bundle)
{
m_AssetBundle = bundle;
m_ProvideHandle.Complete(this, m_AssetBundle != null, null);
m_Completed = true;
}
private void StreamWebRequestOperationCompleted(AsyncOperation op)
{
UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation;
var webReq = remoteReq.webRequest;
if (!UnityWebRequestUtilities.RequestHasErrors(webReq, out UnityWebRequestResult uwrResult))
{
m_downloadHandler = webReq.downloadHandler;
//CompleteBundleLoad(AssetBundle.LoadFromMemory(AESTool.DecryptBytes(m_downloadHandler.data, KEY)));
//CompleteBundleLoad(AssetBundle.LoadFromMemory(m_downloadHandler.data));
if (!m_Completed)
{
m_ProvideHandle.Complete(this, true, null);
m_Completed = true;
}
}
webReq.Dispose();
}
private void WebRequestOperationCompleted(AsyncOperation op)
{
UnityWebRequestAsyncOperation remoteReq = op as UnityWebRequestAsyncOperation;
var webReq = remoteReq.webRequest;
if (!UnityWebRequestUtilities.RequestHasErrors(webReq, out UnityWebRequestResult uwrResult))
{
m_downloadHandler = webReq.downloadHandler;
string path = m_ProvideHandle.ResourceManager.TransformInternalId(m_ProvideHandle.Location);
string bundleName = Path.GetFileName(path);
BundleMgr.Instance.CacheBundle(bundleName, m_downloadHandler.data);
if (!m_Completed)
{
m_ProvideHandle.Complete(this, true, null);
m_Completed = true;
}
}
else
{
m_downloadHandler = webReq.downloadHandler;
m_downloadHandler.Dispose();
m_downloadHandler = null;
string message = string.Format("Web request {0} failed with error '{1}', retrying ({2}/{3})...", webReq.url, webReq.error, m_Retries, m_Options.RetryCount);
if (m_Retries < m_Options.RetryCount)
{
Debug.LogFormat(message);
BeginOperation();
m_Retries++;
}
else
{
var exception = new Exception(string.Format(
"RemoteAssetBundleProvider unable to load from url {0}, result='{1}'.", webReq.url,
webReq.error));
m_ProvideHandle.Complete<CustomAssetBundleResource>(null, false, exception);
}
}
webReq.Dispose();
}
/// <summary>
/// Unloads all resources associated with this asset bundle.
/// </summary>
public void Unload()
{
if (m_AssetBundle != null)
{
m_AssetBundle.Unload(true);
m_AssetBundle = null;
}
if (m_downloadHandler != null)
{
m_downloadHandler.Dispose();
m_downloadHandler = null;
}
m_RequestOperation = null;
}
}
/// <summary>
/// IResourceProvider for asset bundles. Loads bundles via UnityWebRequestAssetBundle API if the internalId starts with "http". If not, it will load the bundle via AssetBundle.LoadFromFileAsync.
/// </summary>
[DisplayName("My AssetBundle Provider")]
public class CustomAssetBundleProvider : ResourceProviderBase
{
/// <inheritdoc/>
public override void Provide(ProvideHandle providerInterface)
{
new CustomAssetBundleResource().Start(providerInterface);
}
/// <inheritdoc/>
public override Type GetDefaultType(IResourceLocation location)
{
return typeof(IAssetBundleResource);
}
/// <summary>
/// Releases the asset bundle via AssetBundle.Unload(true).
/// </summary>
/// <param name="location">The location of the asset to release</param>
/// <param name="asset">The asset in question</param>
public override void Release(IResourceLocation location, object asset)
{
if (location == null)
throw new ArgumentNullException("location");
if (asset == null)
{
Debug.LogWarningFormat("Releasing null asset bundle from location {0}. This is an indication that the bundle failed to load.", location);
return;
}
var bundle = asset as CustomAssetBundleResource;
if (bundle != null)
{
bundle.Unload();
return;
}
}
}
}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);
}
}
}
}
}如果元素不是动态变化的,就缓存起来。