956 lines
29 KiB
C#
956 lines
29 KiB
C#
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
|
||
|
||
/// <summary>
|
||
/// Author: Blastom
|
||
/// 通用工具类型代码
|
||
/// </summary>
|
||
public static class CommonUtility
|
||
{
|
||
/// <summary>
|
||
/// 客户端转到服务器位置精度的倍数
|
||
/// </summary>
|
||
public const float clientToServerPos = 10000f;
|
||
|
||
/// <summary>
|
||
/// 服务器转到客户端位置的精度倍数
|
||
/// </summary>
|
||
public const float serverToClientPos = 1f / clientToServerPos;
|
||
|
||
private const int _maxJumpRecursive = 10;
|
||
|
||
// 默认每次跳跃动画花费的时间
|
||
private const float _jumpAnimTime = 1f;
|
||
|
||
private static float _lastGetUnityTime;
|
||
private static int _lastGetTime;
|
||
|
||
/// <summary>
|
||
/// 数组Find方法
|
||
/// </summary>
|
||
public static T Find<T>(this T[] array, Predicate<T> condition)
|
||
{
|
||
var index = array.FindIndex(condition);
|
||
return index < 0 ? default(T) : array[index];
|
||
}
|
||
|
||
/// <summary>
|
||
/// 数组FindIndex方法
|
||
/// </summary>
|
||
public static int FindIndex<T>(this T[] array, Predicate<T> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 数组Contain方法
|
||
/// </summary>
|
||
public static bool Contain<T>(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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将用Vector2表示的方向,转换为服务器弧度值
|
||
/// </summary>
|
||
public static int DirToServerRadian(Vector2 axis)
|
||
{
|
||
return DirToServerRadian(axis.x, axis.y);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将用x和z表示的方向,转换为服务器弧度值
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将服务器弧度值转换为客户端旋转方向
|
||
/// </summary>
|
||
public static Quaternion ServerRadianToQuaternion(int dirRadian)
|
||
{
|
||
var radian = Mathf.PI * 0.5f - dirRadian * 0.01f;
|
||
return Quaternion.Euler(0f, radian * Mathf.Rad2Deg, 0f);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确保物体拥有组件T
|
||
/// </summary>
|
||
public static T EnsureComponent<T>(this GameObject gameObject) where T : Component
|
||
{
|
||
var component = gameObject.GetComponent<T>();
|
||
if (component == null)
|
||
component = gameObject.AddComponent<T>();
|
||
return component;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查物体是否拥有组件
|
||
/// </summary>
|
||
public static bool HasComponent<T>(this GameObject gameObject) where T : Component
|
||
{
|
||
var component = gameObject.GetComponent<T>();
|
||
return component != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查物体是否拥有组件
|
||
/// </summary>
|
||
public static bool HasComponent<T>(this Component item) where T : Component
|
||
{
|
||
return HasComponent<T>(item.gameObject);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除GameObject名称后面的(Clone)
|
||
/// </summary>
|
||
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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得平方数
|
||
/// </summary>
|
||
public static float ToSquare(this float source)
|
||
{
|
||
return source * source;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 整数转换为掩码数值
|
||
/// </summary>
|
||
public static int ToFlag(this int i)
|
||
{
|
||
return 1 << i;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 千分秒整数时间转化为浮点时间
|
||
/// </summary>
|
||
public static float ToClientTime(this int time)
|
||
{
|
||
return time * 0.001f;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查一个整数是否包含一个标志位
|
||
/// </summary>
|
||
public static bool ContainFlag(this int source, int flag)
|
||
{
|
||
return flag > 0 && (source & flag) == flag;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将一个整数位替换为新的数值
|
||
/// </summary>
|
||
public static int ReplaceFlag(this int source, int flag, bool isOn)
|
||
{
|
||
return isOn ? source | flag : source & ~flag;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 复制RectTransform参数
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 高精度输出Vector3数值的方法
|
||
/// </summary>
|
||
public static string ToPreciseString(this Vector3 source)
|
||
{
|
||
return string.Format("({0}, {1}, {2})", source.x, source.y, source.z);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 高精度输出Vector2数值的方法
|
||
/// </summary>
|
||
public static string ToPreciseString(this Vector2 source)
|
||
{
|
||
return string.Format("({0}, {1})", source.x, source.y);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 按照Root偏移一个本地Offset之后的世界位置
|
||
/// </summary>
|
||
/// <param name="root">原始坐标</param>
|
||
/// <param name="offset">偏移数值</param>
|
||
/// <returns>偏移后世界坐标</returns>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将一个数值校正到循环范围内
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置自己和子节点到某个layer
|
||
/// </summary>
|
||
public static void SetLayerRecursively(this GameObject gameObject, string layerName)
|
||
{
|
||
var layer = LayerMask.NameToLayer(layerName);
|
||
SetLayerRecursively(gameObject, layer);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置自己和子节点到某个layer
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 将物体转换为目标类型,转换失败输出错误和物体路径,用于代替项目中使用as转换屏蔽错误的情况。
|
||
/// </summary>
|
||
public static TTarget TryCastToType<TTarget>(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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 试图获得一个数据表,获得失败输出错误,用于代替项目中获得空数据后跳过的情况
|
||
/// </summary>
|
||
public static TTarget TryGetTable<TTarget>(int index, Func<int, TTarget> 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<Material>(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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 技能是否是一个以地面为目标的技能
|
||
/// </summary>
|
||
public static bool IsTargetGround(this Tab_SkillEx skillEx)
|
||
{
|
||
var selector = GetSkillSelector(skillEx);
|
||
if (selector == null)
|
||
return false;
|
||
return selector.RangCenter > 0 || selector.RangType > 2;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 技能是否是一个冲锋技能
|
||
/// </summary>
|
||
public static bool IsMoveSkill(this Tab_SkillEx skillEx)
|
||
{
|
||
return GetMoveSkillImpact(skillEx) != null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得技能的冲锋模块Impact
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 技能是否存在下一段。注:普攻不是这个逻辑。
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 通过SkillEx获得SkillSelector
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 配置Root以及其以下的GameObject的layer
|
||
/// </summary>
|
||
public static void SetLayersRecursively(Transform root, int layer)
|
||
{
|
||
var transforms = root.GetComponentsInChildren<Transform>(true);
|
||
for (var i = 0; i < transforms.Length; i++)
|
||
transforms[i].gameObject.layer = layer;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 防止值重复赋值引起的重绘
|
||
/// </summary>
|
||
/// <param name="text"></param>
|
||
/// <param name="val"></param>
|
||
/// <returns></returns>
|
||
public static Text EnsureVal(this Text text, string val)
|
||
{
|
||
if (!text.text.Equals(val))
|
||
text.text = val;
|
||
return text;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 支持增加StringComparison标签的Contain函数
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得当前的时间描述
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得当前时间的毫秒数值
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 服务器位置坐标转换为客户端位置坐标
|
||
/// </summary>
|
||
public static float ToClientPos(this int source)
|
||
{
|
||
return source * serverToClientPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 服务器位置坐标转换为客户端位置坐标
|
||
/// </summary>
|
||
public static Vector2 ToClientPos(this Vector2 source)
|
||
{
|
||
return source * serverToClientPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 服务器位置坐标转换为客户端位置坐标
|
||
/// </summary>
|
||
public static Vector3 ToClientPos(this Vector3 source)
|
||
{
|
||
return source * serverToClientPos;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 客户端坐标位置转换为服务器使用的整数坐标
|
||
/// </summary>
|
||
public static int ToServerPos(this float source)
|
||
{
|
||
return Mathf.FloorToInt(source * clientToServerPos);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查一个资源路径是否为项目资源路径
|
||
/// </summary>
|
||
public static bool IsCommonAssetPath(this string assetPath)
|
||
{
|
||
return !string.IsNullOrEmpty(assetPath) && assetPath.StartsWith(AssetConst.nonInternalHeader);
|
||
}
|
||
|
||
public static T GetMaxValue<T>(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<T>() where T : struct, IComparable
|
||
{
|
||
var values = (T[])Enum.GetValues(typeof(T));
|
||
return GetMaxValue(values);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算射线同水平面的交点
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从路径上获得一段距离后的点
|
||
/// </summary>
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 对一个数组进行冒泡排序
|
||
/// </summary>
|
||
public static void SortArray<T>(this T[] array, Comparison<T> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获得路径总长度
|
||
/// </summary>
|
||
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<T>(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<T, TV>(this List<TV> list, T id, ListGetMode mode) where TV : ListItemBase<T>, 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<T, TV>(this List<TV> list, T id) where TV : ListItemBase<T>, 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<string> _resourcePaths;
|
||
#endif
|
||
|
||
public static T LoadFromResources<T>(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<string>();
|
||
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<T>(_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<T>(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<GameObject> 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<GameObject> 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
|
||
}
|
||
|
||
/// <summary>
|
||
/// 列表用键值对,KeyValue是struct不易用于存在判断
|
||
/// </summary>
|
||
public class MyTuple<T1, T2>
|
||
{
|
||
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<T>
|
||
{
|
||
public T id;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 列表获取方法,用来让频繁访问的列表,避开匿名委托,又能用单个访问方法处理多种需求的工具
|
||
/// </summary>
|
||
public enum ListGetMode
|
||
{
|
||
get,
|
||
create,
|
||
remove
|
||
}
|
||
|
||
#endregion |