347 lines
12 KiB
C#
347 lines
12 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using Cielonos.MainGame.Characters;
|
||
using Cielonos.MainGame.Inventory;
|
||
using Sirenix.OdinInspector;
|
||
using SLSFramework.LeanPoolAssistance;
|
||
using SLSFramework.WwiseAssistance;
|
||
using SLSUtilities.FunctionalAnimation;
|
||
using UnityEngine;
|
||
|
||
namespace Cielonos.MainGame
|
||
{
|
||
public abstract partial class AttackAreaBase : PooledObject
|
||
{
|
||
[Title("References")]
|
||
public CharacterBase creator;
|
||
public bool isGeneratedByPlayer => creator == MainGameManager.Instance.player;
|
||
public ItemBase itemSource;
|
||
public List<Fraction> targetFractions;
|
||
public Transform topParent;
|
||
public AudioContainer audioContainer;
|
||
public Collider areaCollider;
|
||
public Dictionary<string, GameObject> functionalParts;
|
||
|
||
[Title("Status")]
|
||
public bool isEnabling;
|
||
public bool canTriggerHitEvent = true;
|
||
public Action updateAction;
|
||
|
||
[Title("Submodules")]
|
||
[HideInEditorMode] public AttackSubmodule attackSm;
|
||
[HideInEditorMode] public TimeSubmodule timeSm;
|
||
[HideInEditorMode] public HitSubmodule hitSm;
|
||
[HideInEditorMode] public MoveSubmoduleBase moveSm;
|
||
[HideInEditorMode] public RaycastSubmodule raycastSm;
|
||
[HideInEditorMode] public ForceSubmodule forceSm;
|
||
[HideInEditorMode] public ReactionSubmodule reactionSm;
|
||
|
||
#if UNITY_EDITOR
|
||
private void Reset()
|
||
{
|
||
isAutoDespawn = false;
|
||
}
|
||
#endif
|
||
|
||
public T Initialize<T>(CharacterBase creator, params Fraction[] targetFractions) where T : AttackAreaBase
|
||
{
|
||
return Initialize<T>(creator, null, targetFractions);
|
||
}
|
||
|
||
public T Initialize<T>(CharacterBase creator, ItemBase itemSource, params Fraction[] targetFractions) where T : AttackAreaBase
|
||
{
|
||
this.isEnabling = true;
|
||
this.creator = creator;
|
||
this.itemSource = itemSource;
|
||
this.targetFractions = targetFractions.ToList();
|
||
this.topParent = transform;
|
||
this.canTriggerHitEvent = true;
|
||
|
||
areaCollider = GetComponent<Collider>();
|
||
if (areaCollider != null)
|
||
{
|
||
//areaCollider.excludeLayers = LayerMask.GetMask("AttackAreaVFX", "DecoVFX", "Ignore Raycast");
|
||
}
|
||
|
||
audioContainer = GetComponent<AudioContainer>();
|
||
if (audioContainer != null)
|
||
{
|
||
audioContainer.soundEventDictionary = new Dictionary<string, AK.Wwise.Event>();
|
||
}
|
||
|
||
while (topParent.parent != null &&
|
||
//topParent.parent != creator.flexibleCenterPoint &&
|
||
//topParent.parent != creator.staticCenterPoint &&
|
||
topParent.parent != creator.transform)
|
||
{
|
||
topParent = topParent.parent;
|
||
}
|
||
|
||
foreach (TrailRenderer trail in GetComponentsInChildren<TrailRenderer>())
|
||
{
|
||
trail.Clear();
|
||
}
|
||
|
||
this.SetReactionSubmodule<T>();
|
||
|
||
return this as T;
|
||
}
|
||
|
||
protected virtual void Update()
|
||
{
|
||
raycastSm?.Update();
|
||
updateAction?.Invoke();
|
||
hitSm?.Update();
|
||
timeSm?.Update();
|
||
moveSm?.Update();
|
||
}
|
||
}
|
||
|
||
public partial class AttackAreaBase
|
||
{
|
||
#region AttackSubmodule
|
||
public T SetAttackSubmodule<T>(AttackUnit attackUnit, GameObject hitVFX = null) where T : AttackAreaBase
|
||
{
|
||
attackSm = new AttackSubmodule(this, attackUnit.GetAttackValue(), hitVFX == null ? attackUnit.GetHitVFX() : hitVFX);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region TimeSubmodule
|
||
public T SetTimeSubmodule<T>(float lifeTime) where T : AttackAreaBase
|
||
{
|
||
timeSm = new TimeSubmodule(this, lifeTime);
|
||
return this as T;
|
||
}
|
||
|
||
public T SetTimeSubmodule<T>(float lifeTime, float enableTime, float enableDelay,
|
||
Action enableAction = null, Action timeOutAction = null) where T : AttackAreaBase
|
||
{
|
||
timeSm = new TimeSubmodule(this, lifeTime, enableTime, enableDelay, enableAction, timeOutAction);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region HitSubmodule
|
||
public T SetHitSubmodule<T>() where T : AttackAreaBase
|
||
{
|
||
hitSm = new HitSubmodule(this);
|
||
return this as T;
|
||
}
|
||
|
||
public T SetHitSubmodule<T>(float hitInterval, int hitCount) where T : AttackAreaBase
|
||
{
|
||
hitSm = new HitSubmodule(this, hitInterval, hitCount);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region LinearDirectionMoveModule
|
||
public T SetLinearDirectionMoveModule<T>(Vector3 direction, float speed,
|
||
float acceleration = 0, bool overrideRotation = true, float timeScaleCoefficient = 1) where T : AttackAreaBase
|
||
{
|
||
moveSm = new LinearDirectionMoveSubmodule(this, direction, speed, acceleration, overrideRotation, timeScaleCoefficient);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region TraceMoveModule
|
||
|
||
public T SetTraceMoveModule<T>(CharacterBase target, float moveSpeed, float moveAcceleration,
|
||
float angularSpeed, float angularAcceleration, Vector3 initialDirection) where T : AttackAreaBase
|
||
{
|
||
moveSm = new TraceMoveSubmodule(this, target, moveSpeed, moveAcceleration, angularSpeed, angularAcceleration, initialDirection);
|
||
return this as T;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region RaycastSubmodule
|
||
/// <summary>
|
||
/// 设置射线检测子模块
|
||
/// </summary>
|
||
/// <param name="direction">射线方向</param>
|
||
/// <param name="rayRadius">球形射线半径,若小于等于0则为直线射线</param>
|
||
/// <param name="rayLength">射线长度,若小于0则为动态长度(与移动速度相等)</param>
|
||
public T SetRaycastSubmodule<T>(Vector3 direction = default, float rayRadius = -1f, float rayLength = -1f) where T : AttackAreaBase
|
||
{
|
||
raycastSm = new RaycastSubmodule(this, direction, rayLength, rayRadius);
|
||
raycastSm.Update();
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region ForceSubmodule
|
||
public T SetForceSubmodule<T>(float dynamicForce) where T : AttackAreaBase
|
||
{
|
||
forceSm = new ForceSubmodule(this, dynamicForce);
|
||
return this as T;
|
||
}
|
||
|
||
public T SetForceSubmodule<T>(Vector3 customForce) where T : AttackAreaBase
|
||
{
|
||
forceSm = new ForceSubmodule(this, customForce);
|
||
return this as T;
|
||
}
|
||
|
||
public T SetForceSubmodule<T>(float strengthXZ, bool isRepulsion, float strengthY = 0) where T : AttackAreaBase
|
||
{
|
||
forceSm = new ForceSubmodule(this, strengthXZ, isRepulsion, strengthY);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
|
||
#region ReactionSubmodule
|
||
public T SetReactionSubmodule<T>() where T : AttackAreaBase
|
||
{
|
||
reactionSm = new ReactionSubmodule(this);
|
||
return this as T;
|
||
}
|
||
|
||
public T SetReactionSubmodule<T>(bool canBeBlocked, bool hasPerfectBlock, bool canBreakBlock,
|
||
bool canBeDodged, bool hasPerfectDodge, bool canBreakDodge) where T : AttackAreaBase
|
||
{
|
||
reactionSm = new ReactionSubmodule(this);
|
||
reactionSm.SetBlock(canBeBlocked, hasPerfectBlock, canBreakBlock);
|
||
reactionSm.SetDodge(canBeDodged, hasPerfectDodge, canBreakDodge);
|
||
return this as T;
|
||
}
|
||
#endregion
|
||
}
|
||
|
||
public partial class AttackAreaBase
|
||
{
|
||
public virtual void HitCharacter(Collider characterCollider, Vector3 hitPosition)
|
||
{
|
||
|
||
}
|
||
|
||
public virtual void HitEnvironment(Collider other, Vector3 hitPosition)
|
||
{
|
||
|
||
}
|
||
|
||
protected virtual void HitOnTarget(Collider hitCollider, Vector3 hitPosition)
|
||
{
|
||
CharacterBase target = hitCollider.GetComponentInParent<CharacterBase>();
|
||
|
||
if (target == null) return;
|
||
|
||
bool isBlocked = reactionSm?.CheckBlock(target, hitPosition) ?? false;
|
||
bool isDodged = reactionSm?.CheckDodge(target) ?? false;
|
||
bool isMissed = false; // reactionModule?.SetMiss(creator.attributeModule.currentAttributes.GetValue("AttackMissProbability", 0)) ?? false;
|
||
bool isEvaded = false; // reactionModule?.SetEvasion(player.attributeModule.currentAttributes.GetValue("EvasionProbability", 0)) ?? false;
|
||
bool isDead = false;
|
||
|
||
if (!isBlocked && !isDodged && !isMissed && !isEvaded)
|
||
{
|
||
//受击事件
|
||
InvokeHitEvents(target, hitPosition);
|
||
|
||
//最终伤害结算
|
||
float finalDamage = CalculateFinalDamage(target, out isDead);
|
||
if (finalDamage > 0)
|
||
{
|
||
//player.eventController.GetHurtEvents.InvokeAllEvents(this, finalDamage);
|
||
GenerateDamageNumber(finalDamage, hitPosition);
|
||
}
|
||
|
||
//特效
|
||
GenerateHitEffect(target, hitCollider, hitPosition);
|
||
|
||
//音效
|
||
PlaySoundFX(hitPosition);
|
||
|
||
//计算力的方向
|
||
//CalculateAdditionalForceDirection(player);
|
||
|
||
//打断,击退,受击动画
|
||
//float disruptionBreakLevel = attackModule.modifiedAttackValue.baseDisruptionBreakLevel;
|
||
//SetReaction(player, disruptionBreakLevel);
|
||
|
||
target.GetHit(attackSm.modifiedAttackValue.breakthroughType);
|
||
}
|
||
|
||
//应用额外力
|
||
if (!isDead && !isDodged && !isMissed && !isEvaded)
|
||
{
|
||
forceSm?.ApplyForce(target);
|
||
}
|
||
}
|
||
}
|
||
|
||
public partial class AttackAreaBase
|
||
{
|
||
protected virtual void InvokeHitEvents(CharacterBase target, Vector3 hitPosition)
|
||
{
|
||
hitSm.InvokeAllHitEvents(target, hitPosition);
|
||
if (target is Player player)
|
||
{
|
||
//player.eventController.GetHitEvents.InvokeAllEvents(this);
|
||
}
|
||
/*else if (target is EnemyController enemy)
|
||
{
|
||
//enemy.enemyEventController.GetHitEvents.InvokeAllEvents(this);
|
||
}*/
|
||
}
|
||
|
||
protected virtual float CalculateFinalDamage(CharacterBase target, out bool isDead)
|
||
{
|
||
if (attackSm == null)
|
||
{
|
||
isDead = false;
|
||
return 0;
|
||
}
|
||
|
||
float finalDamage = target.DealAttack(attackSm.modifiedAttackValue, out isDead);
|
||
return finalDamage;
|
||
}
|
||
|
||
protected virtual void GenerateDamageNumber(float finalDamage, Vector3 hitPosition)
|
||
{
|
||
bool isCritical = attackSm.modifiedAttackValue.isCritical;
|
||
attackSm.SpawnDamageNumber(hitPosition, isCritical, finalDamage);
|
||
}
|
||
|
||
protected virtual GameObject GenerateHitEffect(CharacterBase target, Collider hitCollider, Vector3 hitPosition)
|
||
{
|
||
GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition);
|
||
attackSm.modifyHitEffectAction?.Invoke(hitEffect, target);
|
||
return hitEffect;
|
||
}
|
||
|
||
protected virtual GameObject GenerateHitEffect(Vector3 hitPosition)
|
||
{
|
||
GameObject hitEffect = attackSm.SpawnHitVFX(hitPosition);
|
||
attackSm.modifyHitEffectAction?.Invoke(hitEffect, null);
|
||
return hitEffect;
|
||
}
|
||
|
||
protected virtual void PlaySoundFX(Vector3 hitPosition, string soundKey = "")
|
||
{
|
||
if (string.IsNullOrEmpty(soundKey))
|
||
{
|
||
if (hitSm.isAutoPlayHitSound)
|
||
{
|
||
hitSm.hitSoundList.ForEach(hitSound => audioContainer.PlaySoundFX(hitSound, hitPosition));
|
||
}
|
||
}
|
||
else
|
||
{
|
||
audioContainer.PlaySoundFX(soundKey, hitPosition);
|
||
}
|
||
}
|
||
}
|
||
|
||
public class AttackAreaSubmoduleBase : SubmoduleBase<AttackAreaBase>
|
||
{
|
||
protected AttackAreaBase attackArea => owner;
|
||
public bool isEnabling;
|
||
|
||
public AttackAreaSubmoduleBase(AttackAreaBase owner) : base(owner)
|
||
{
|
||
isEnabling = true;
|
||
}
|
||
}
|
||
} |