using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Text; using AssetManagement; using Games.GlobeDefine; using GCGame.Table; using Module.Log; using UnityEngine; using UnityEngine.AI; using UnityEngine.UI; using Object = UnityEngine.Object; #if UNITY_EDITOR using UnityEditor; #endif /// /// Author: Blastom /// 通用工具类型代码 /// public static class CommonUtility { /// /// 客户端转到服务器位置精度的倍数 /// public const float clientToServerPos = 10000f; /// /// 服务器转到客户端位置的精度倍数 /// public const float serverToClientPos = 1f / clientToServerPos; private const int _maxJumpRecursive = 10; // 默认每次跳跃动画花费的时间 private const float _jumpAnimTime = 1f; private static float _lastGetUnityTime; private static int _lastGetTime; /// /// 数组Find方法 /// public static T Find(this T[] array, Predicate condition) { var index = array.FindIndex(condition); return index < 0 ? default(T) : array[index]; } /// /// 数组FindIndex方法 /// public static int FindIndex(this T[] array, Predicate condition) { var index = -1; for (var i = 0; i < array.Length; i++) if (condition(array[i])) { index = i; break; } return index; } public static string RemoveExtension(this string assetPath) { var i = assetPath.LastIndexOf('.'); if (i > 0) assetPath = assetPath.Remove(i); return assetPath; } /// /// 数组Contain方法 /// public static bool Contain(this T[] array, T item) { var result = false; for (var i = 0; i < array.Length; i++) if (array[i].Equals(item)) { result = true; break; } return result; } public static string GetHierarchyName(this Transform transform) { var builder = new StringBuilder(); builder.Append(transform.gameObject.name); transform = transform.parent; while (transform) { builder.Insert(0, transform.gameObject.name + "/"); transform = transform.parent; } return builder.ToString(); } public static void ToCopyClip(string text) { var textEditor = new TextEditor { text = text }; textEditor.SelectAll(); textEditor.Copy(); } /// /// 将用Vector2表示的方向,转换为服务器弧度值 /// public static int DirToServerRadian(Vector2 axis) { return DirToServerRadian(axis.x, axis.y); } /// /// 将用x和z表示的方向,转换为服务器弧度值 /// public static int DirToServerRadian(float x, float z) { var dirRadian = Mathf.Atan2(z, x); if (dirRadian < 0) dirRadian += 2 * Mathf.PI; return Mathf.FloorToInt(dirRadian * 100f); } /// /// 将服务器弧度值转换为客户端旋转方向 /// public static Quaternion ServerRadianToQuaternion(int dirRadian) { var radian = Mathf.PI * 0.5f - dirRadian * 0.01f; return Quaternion.Euler(0f, radian * Mathf.Rad2Deg, 0f); } /// /// 确保物体拥有组件T /// public static T EnsureComponent(this GameObject gameObject) where T : Component { var component = gameObject.GetComponent(); if (component == null) component = gameObject.AddComponent(); return component; } /// /// 检查物体是否拥有组件 /// public static bool HasComponent(this GameObject gameObject) where T : Component { var component = gameObject.GetComponent(); return component != null; } /// /// 检查物体是否拥有组件 /// public static bool HasComponent(this Component item) where T : Component { return HasComponent(item.gameObject); } /// /// 删除GameObject名称后面的(Clone) /// public static void TrimCloneInName(this GameObject gameObject) { const string clone = "(Clone)"; var name = gameObject.name; if (name.EndsWith(clone)) { name = name.Remove(name.Length - clone.Length); gameObject.name = name; } } /// /// 获得平方数 /// public static float ToSquare(this float source) { return source * source; } /// /// 整数转换为掩码数值 /// public static int ToFlag(this int i) { return 1 << i; } /// /// 千分秒整数时间转化为浮点时间 /// public static float ToClientTime(this int time) { return time * 0.001f; } /// /// 检查一个整数是否包含一个标志位 /// public static bool ContainFlag(this int source, int flag) { return flag > 0 && (source & flag) == flag; } /// /// 将一个整数位替换为新的数值 /// public static int ReplaceFlag(this int source, int flag, bool isOn) { return isOn ? source | flag : source & ~flag; } /// /// 复制RectTransform参数 /// public static void CopyTo(this RectTransform source, RectTransform target) { target.pivot = source.pivot; target.anchorMin = source.anchorMin; target.anchorMax = source.anchorMax; target.anchoredPosition = source.anchoredPosition; target.sizeDelta = source.sizeDelta; target.SetParent(source.parent, false); } /// /// 高精度输出Vector3数值的方法 /// public static string ToPreciseString(this Vector3 source) { return string.Format("({0}, {1}, {2})", source.x, source.y, source.z); } /// /// 高精度输出Vector2数值的方法 /// public static string ToPreciseString(this Vector2 source) { return string.Format("({0}, {1})", source.x, source.y); } /// /// 按照Root偏移一个本地Offset之后的世界位置 /// /// 原始坐标 /// 偏移数值 /// 偏移后世界坐标 public static Vector3 RootOffsetPosition(Transform root, Vector3 offset) { return root.localToWorldMatrix.MultiplyPoint3x4(offset); } public static AnimationCurve GetAnimationCurveById(int shakeId) { AnimationCurve result = null; if (shakeId > 0) { var keyFrameList = TableManager.GetAnimationCurveByID(shakeId); if (keyFrameList != null && keyFrameList.Count > 0) { result = new AnimationCurve(); for (var i = 0; i < keyFrameList.Count; i++) { var data = keyFrameList[i]; var keyFrame = new Keyframe(data.Time, data.Value, data.InTangent, data.OutTangent) { tangentMode = data.TangentMode }; result.AddKey(keyFrame); } result.preWrapMode = (WrapMode)keyFrameList[0].PreWrapMode; result.postWrapMode = (WrapMode)keyFrameList[0].PostWrapMode; } } #if UNITY_EDITOR if (result == null) LogModule.ErrorLog(string.Format("无法获得id为{0}的动画曲线!", shakeId)); #endif return result; } /// /// 将一个数值校正到循环范围内 /// public static float LoopFloat(float source, float minValue, float maxValue) { var cycle = maxValue - minValue; var multiplier = Mathf.Floor((source - minValue) / cycle); return source - multiplier * cycle; } /// /// 设置自己和子节点到某个layer /// public static void SetLayerRecursively(this GameObject gameObject, string layerName) { var layer = LayerMask.NameToLayer(layerName); SetLayerRecursively(gameObject, layer); } /// /// 设置自己和子节点到某个layer /// public static void SetLayerRecursively(this GameObject gameObject, int layer) { SetLayerRecursively(gameObject.transform, layer); } private static void SetLayerRecursively(Transform transform, int layer) { transform.gameObject.layer = layer; foreach (Transform child in transform) SetLayersRecursively(child, layer); } public static Transform FindIgnoreCase(this Transform root, string name) { Transform result = null; foreach (Transform child in root) if (string.Equals(child.gameObject.name, name, StringComparison.CurrentCultureIgnoreCase)) { result = child; break; } return result; } /// /// 将物体转换为目标类型,转换失败输出错误和物体路径,用于代替项目中使用as转换屏蔽错误的情况。 /// public static TTarget TryCastToType(this MonoBehaviour source) where TTarget : MonoBehaviour { var target = source as TTarget; if (target == null) LogModule.ErrorLog(string.Format("无法将组件{0}转换为{1}, 所在路径{2}", source.GetType(), typeof(TTarget), source.transform.GetHierarchyName())); return target; } /// /// 试图获得一个数据表,获得失败输出错误,用于代替项目中获得空数据后跳过的情况 /// public static TTarget TryGetTable(int index, Func func) where TTarget : class { var target = func(index); if (target == null) LogModule.ErrorLog(string.Format("无法获得id = {0},类型 = {1}的数据表!", index, typeof(TTarget))); return target; } // 临时使用的Resources.Load接口 - 后期考虑改为同步读取预加载的AssetBundle public static Material LoadSharedMaterial(string name) { return LoadFromResources(name); } public static bool IsCharacterShader(this Shader shader) { //return shader.name.StartsWith(_characterShaderHeading, StringComparison.CurrentCultureIgnoreCase); // 2019.04.08 过滤粒子 // 之前有不过滤Shader名的情况,无法确认具体需要哪一些组合 // 注:particle 有particle/ 和 particles/ 等多种组合,搞正则表达式Loose End会更多 return !shader.name.Contains("particle", StringComparison.CurrentCultureIgnoreCase); } /// /// 技能是否是一个以地面为目标的技能 /// public static bool IsTargetGround(this Tab_SkillEx skillEx) { var selector = GetSkillSelector(skillEx); if (selector == null) return false; return selector.RangCenter > 0 || selector.RangType > 2; } /// /// 技能是否是一个冲锋技能 /// public static bool IsMoveSkill(this Tab_SkillEx skillEx) { return GetMoveSkillImpact(skillEx) != null; } /// /// 获得技能的冲锋模块Impact /// public static Tab_Impact GetMoveSkillImpact(this Tab_SkillEx skillEx) { Tab_Impact result = null; var impactCount = skillEx.getImpactIdCount(); for (var i = 0; i < impactCount; i++) { var impactId = skillEx.GetImpactIdbyIndex(i); if (impactId >= 0) { var impact = TableManager.GetImpactByID(impactId); if (GlobeVar.moveSkillLogicIds.Contain(impact.LogicID)) { result = impact; break; } } } return result; } /// /// 技能是否存在下一段。注:普攻不是这个逻辑。 /// public static bool HasNextCombo(this Tab_SkillEx skillEx) { //var hasNext = false; //if (skillEx.NextStageSkillId >= 0) //{ // var nextSkill = GameManager.gameManager.PlayerDataPool.GetOwnSkillInfo(skillEx.NextStageSkillId); // if (nextSkill != null) // hasNext = nextSkill.IsCooldownFinish(); //} //return hasNext; return skillEx.NextStageSkillId >= 0 && GameManager.gameManager.PlayerDataPool.GetOwnSkillInfo(skillEx.NextStageSkillId) != null; } /// /// 通过SkillEx获得SkillSelector /// public static Tab_SkillTargetSelector GetSkillSelector(this Tab_SkillEx skillEx) { Tab_SkillTargetSelector result = null; var selectorId = skillEx.GetSelectorIdbyIndex(0); if (selectorId > -1) result = TryGetTable(selectorId, a => TableManager.GetSkillTargetSelectorByID(a)); return result; } /// /// 配置Root以及其以下的GameObject的layer /// public static void SetLayersRecursively(Transform root, int layer) { var transforms = root.GetComponentsInChildren(true); for (var i = 0; i < transforms.Length; i++) transforms[i].gameObject.layer = layer; } /// /// 防止值重复赋值引起的重绘 /// /// /// /// public static Text EnsureVal(this Text text, string val) { if (!text.text.Equals(val)) text.text = val; return text; } /// /// 支持增加StringComparison标签的Contain函数 /// public static bool Contains(this string source, string target, StringComparison comparison) { return !string.IsNullOrEmpty(source) && !string.IsNullOrEmpty(target) && source.IndexOf(target, comparison) >= 0; } public static Vector3 InsertY(this Vector2 source, float y = 0f) { return new Vector3(source.x, y, source.y); } public static Vector2 RemoveY(this Vector3 source) { return new Vector2(source.x, source.z); } /// /// 获得当前的时间描述 /// public static string GetCurrentTimeString(bool showInterval = true) { var time = GetCurrentTimeInMilliseconds(); var result = time.ToString(); if (showInterval) result = string.Format("({0} - {1})", result, time - _lastGetTime); _lastGetTime = time; return result; } public static string GetCurrentUnityTimeString(bool showInterval = true) { var result = showInterval ? string.Format("({0} - {1})", Time.unscaledTime, Time.unscaledTime - _lastGetUnityTime) : Time.unscaledTime.ToString(CultureInfo.InvariantCulture); _lastGetUnityTime = Time.unscaledTime; return result; } /// /// 获得当前时间的毫秒数值 /// private static int GetCurrentTimeInMilliseconds() { return (int)(DateTime.Now - DateTime.Today).TotalMilliseconds; } public static Vector3 ParseVector3(string source) { var result = Vector3.zero; var segments = source.Split(';'); result.x = float.Parse(segments[0]); result.y = float.Parse(segments[1]); result.z = float.Parse(segments[2]); return result; } /// /// 服务器位置坐标转换为客户端位置坐标 /// public static float ToClientPos(this int source) { return source * serverToClientPos; } /// /// 服务器位置坐标转换为客户端位置坐标 /// public static Vector2 ToClientPos(this Vector2 source) { return source * serverToClientPos; } /// /// 服务器位置坐标转换为客户端位置坐标 /// public static Vector3 ToClientPos(this Vector3 source) { return source * serverToClientPos; } /// /// 客户端坐标位置转换为服务器使用的整数坐标 /// public static int ToServerPos(this float source) { return Mathf.FloorToInt(source * clientToServerPos); } /// /// 检查一个资源路径是否为项目资源路径 /// public static bool IsCommonAssetPath(this string assetPath) { return !string.IsNullOrEmpty(assetPath) && assetPath.StartsWith(AssetConst.nonInternalHeader); } public static T GetMaxValue(params T[] values) where T : IComparable { if (values.Length > 0) { var result = values[0]; for (var i = 1; i < values.Length; i++) if (result.CompareTo(values[i]) < 0) result = values[i]; return result; } return default(T); } public static bool IsAutoCombatCopyScene(int fubenId) { var fubenData = TableManager.GetFubenByID(fubenId); return IsAutoCombatCopyScene(fubenData); } public static bool IsAutoCombatCopyScene(Tab_Fuben fubenData) { return FubenAutoType(fubenData) == Games.GlobeDefine.FubenAutoType.auto; } public static int FubenAutoType() { var fubenId = GlobeVar.INVALID_ID; var cache = GameManager.gameManager.PlayerDataPool.EnterSceneCache; if (cache != null) fubenId = cache.EnterCopySceneID; if (fubenId <= GlobeVar.INVALID_ID) fubenId = GameManager.gameManager.RunningScene; return FubenAutoType(fubenId); } public static int FubenAutoType(int fubenId) { var fubenData = TableManager.GetFubenByID(fubenId); return FubenAutoType(fubenData); } public static int FubenAutoType(Tab_Fuben fubenData) { return fubenData == null ? 0 : fubenData.AutoType; } public static bool IsCopyScene(this Tab_SceneClass sceneClassData) { return sceneClassData.SceneID != (int)GameDefine_Globe.SCENE_DEFINE.SCENE_GUILD && sceneClassData.Type == (int)GameDefine_Globe.SCENE_TYPE.SCENETYPE_COPYSCENE; } public static Tab_Jump GetFinialJumpData(this Tab_Jump jumpData) { var firstId = jumpData.JumpId; for (var i = 0; i < _maxJumpRecursive; i++) if (jumpData.NextJumpId > GlobeVar.INVALID_ID) { var temp = TableManager.GetJumpByID(jumpData.NextJumpId); if (temp == null) { LogModule.ErrorLog(string.Format("无法Jump{0}的NextJump{1}", jumpData.JumpId, jumpData.NextJumpId)); break; } jumpData = temp; } else { break; } if (jumpData.NextJumpId > -1) LogModule.ErrorLog(string.Format("跳跃点{0}迭代超过{1}次,检查是否配表问题,或者提高代码中迭代上限!", firstId, _maxJumpRecursive)); return jumpData; } public static Tab_Jump GetFinialJumpData(this Tab_Jump jumpData, out float jumpTime) { jumpTime = 0f; var firstId = jumpData.JumpId; for (var i = 0; i < _maxJumpRecursive; i++) { jumpTime += jumpData.Duration.ToClientTime(); if (jumpData.NextJumpId > GlobeVar.INVALID_ID) { var temp = TableManager.GetJumpByID(jumpData.NextJumpId); if (temp == null) { LogModule.ErrorLog(string.Format("无法Jump{0}的NextJump{1}", jumpData.JumpId, jumpData.NextJumpId)); break; } jumpData = temp; } else { break; } } if (jumpData.NextJumpId > -1) LogModule.ErrorLog(string.Format("跳跃点{0}迭代超过{1}次,检查是否配表问题,或者提高代码中迭代上限!", firstId, _maxJumpRecursive)); // 如果不是瞬间传送,则增加最后一段的落地动画时间 if (jumpTime > 0f) jumpTime += _jumpAnimTime; return jumpData; } public static Tab_SkillEx GetSkillMaxLevelByBaseId(int baseId) { Tab_SkillEx result = null; var maxLevel = int.MinValue; var skillExDict = TableManager.GetSkillEx(); foreach (var value in skillExDict.Values) { var skillEx = value; if (skillEx.BaseId == baseId && skillEx.Level > maxLevel) { result = skillEx; maxLevel = skillEx.Level; } } return result; } // 注:C#不支持对泛型限制为枚举 public static T GetMaxEnum() where T : struct, IComparable { var values = (T[])Enum.GetValues(typeof(T)); return GetMaxValue(values); } /// /// 计算射线同水平面的交点 /// public static Vector3 RayToHorizontalPlane(Ray ray, float y) { if (ray.direction.y == 0f) { LogModule.ErrorLog("水平射线无法同水平面相交!"); return ray.origin; } var a = (y - ray.origin.y) / ray.direction.y; return ray.origin + ray.direction * a; } /// /// 从路径上获得一段距离后的点 /// public static Vector3 GetPointOnPath(this NavMeshPath path, Vector3 startPos, float distance) { var corners = path.corners; if (corners.Length > 1) for (var i = 1; i < corners.Length; i++) { var length = Vector3.Distance(corners[i], startPos); if (length >= distance) { startPos = Vector3.Lerp(startPos, corners[i], distance / length); break; } startPos = corners[i]; distance -= length; } return startPos; } /// /// 对一个数组进行冒泡排序 /// public static void SortArray(this T[] array, Comparison comparison) { for (var i = 0; i < array.Length; i++) for (var j = i + 1; j < array.Length; j++) if (comparison(array[i], array[j]) > 0) { var temp = array[i]; array[i] = array[j]; array[j] = temp; } } /// /// 获得路径总长度 /// public static float GetTotalDistance(this NavMeshPath path) { var length = 0f; var corners = path.corners; if (corners.Length > 1) for (var i = 1; i < corners.Length; i++) length += Vector3.Distance(corners[i], corners[i - 1]); return length; } public static T Max(params T[] items) where T : IComparable { if (items.Length > 0) { var result = items[0]; for (var i = 1; i < items.Length; i++) if (items[i].CompareTo(result) > 0) result = items[i]; return result; } return default(T); } public static TV GetItem(this List list, T id, ListGetMode mode) where TV : ListItemBase, new() { TV result = null; for (var i = 0; i < list.Count; i++) if (list[i].id.Equals(id)) { result = list[i]; if (mode == ListGetMode.remove) list.RemoveAt(i); break; } if (mode == ListGetMode.create && result == null) { result = new TV { id = id }; list.Add(result); } return result; } public static int GetIndex(this List list, T id) where TV : ListItemBase, new() { var result = -1; for (var i = 0; i < list.Count; i++) if (list[i].id.Equals(id)) { result = i; break; } return result; } #region 编辑器测试加载方式 #if UNITY_EDITOR private static List _resourcePaths; #endif public static T LoadFromResources(string name) where T : Object { T result = null; name = Path.GetFileNameWithoutExtension(name); #if UNITY_EDITOR if (AssetUpdateManager.useResources) { if (_resourcePaths == null) { var assetPaths = AssetDatabase.GetAllAssetPaths(); _resourcePaths = new List(); for (var i = 0; i < assetPaths.Length; i++) { var assetPath = assetPaths[i]; if (assetPath.StartsWith("Assets/Project/GameRes")) { var extension = Path.GetExtension(assetPath); if (!string.IsNullOrEmpty(extension)) _resourcePaths.Add(assetPath); } } } var assetHeader = ObjectTypeToGameResPath(typeof(T)); for (var i = 0; i < _resourcePaths.Count; i++) if (_resourcePaths[i].StartsWith(assetHeader) && Path.GetFileNameWithoutExtension(_resourcePaths[i]) == name) { result = AssetDatabase.LoadAssetAtPath(_resourcePaths[i]); if (result != null) break; } return result; } #endif var bundleName = LoadAssetBundle.FixBundleName(LoadAssetBundle.BUNDLE_PATH_GAMERES); if (AssetManager.instance == null) { throw new Exception("AssetManager.instance is null"); } return AssetManager.instance.LoadAssetSync(bundleName, name); } #if UNITY_EDITOR public static string ObjectTypeToGameResPath(Type itemType) { var typeName = itemType.ToString(); var dotIndex = typeName.LastIndexOf('.'); if (dotIndex >= 0) typeName = typeName.Substring(dotIndex + 1); return "Assets/Project/GameRes".Open(typeName); } #endif // public static void LoadGameObjectInstance(string bundleName, string assetName, LoadBundleAssetCallback callBack, bool archive) // { //#if UNITY_EDITOR // if (AssetBundleManager.SimulateAssetBundleInEditor) // { // var result = EditorTestUtility.LoadGameObject(assetName); // callBack(assetName, result, null); // return; // } //#endif // LoadAssetBundle.Instance.LoadGameObject(bundleName, assetName, callBack, null, archive); // } public static void LoadAssetInstance(string bundleName, string assetName, LoadBundleAssetCallback callBack, bool archive) { #if UNITY_EDITOR if (AssetUpdateManager.useResources) { var result = EditorTestUtility.LoadGameObject(assetName); callBack(assetName, result, null); return; } #endif // 使用LoadUI接口,统一处理所有类型的加载;LoadGameObject会将bundleName额外加上assetName LoadAssetBundle.Instance.LoadUI(bundleName, assetName, callBack, null, archive); } #endregion } /// /// 列表用键值对,KeyValue是struct不易用于存在判断 /// public class MyTuple { public T1 first; public T2 second; public MyTuple() { } public MyTuple(T1 first, T2 second) { this.first = first; this.second = second; } } #region 安全列表系统 // 不想再重复处理列表匹配、添加、删除之类的事情了,新的列表元素都继承匹配方法,直接用扩展统一处理 public abstract class ListItemBase { public T id; } /// /// 列表获取方法,用来让频繁访问的列表,避开匿名委托,又能用单个访问方法处理多种需求的工具 /// public enum ListGetMode { get, create, remove } #endregion