400 lines
16 KiB
C#
400 lines
16 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace BumpCombat.Enemies
|
|
{
|
|
public enum EnemyKind
|
|
{
|
|
Slime = 0,
|
|
Skeleton = 1,
|
|
Lancer = 2,
|
|
GreatswordSkeleton = 3,
|
|
Warlock = 4,
|
|
Bat = 5,
|
|
Necrofire = 6,
|
|
SkeletonArcher = 7,
|
|
ArmoredSkeleton = 8,
|
|
Werewolf = 9,
|
|
Werebear = 10,
|
|
NecroGolem = 11,
|
|
Necromancer = 12,
|
|
}
|
|
|
|
public enum EnemyRole
|
|
{
|
|
Normal,
|
|
Crowd,
|
|
}
|
|
|
|
public enum EnemyAttackShape
|
|
{
|
|
Body,
|
|
Box,
|
|
Cone,
|
|
TargetCircle,
|
|
}
|
|
|
|
public enum EnemyAttackDirectionMode
|
|
{
|
|
LockedDirection,
|
|
HorizontalRoot,
|
|
}
|
|
|
|
public enum EnemyAttackGeometryScaleMode
|
|
{
|
|
WorldUnits,
|
|
RootScale,
|
|
}
|
|
|
|
[Serializable]
|
|
public struct EnemyAttackContactPhase
|
|
{
|
|
[SerializeField, Range(0f, 1f)] private float startNormalized;
|
|
[SerializeField, Range(0f, 1f)] private float endNormalized;
|
|
|
|
public float StartNormalized => Mathf.Clamp01(startNormalized);
|
|
public float EndNormalized => Mathf.Clamp01(Mathf.Max(
|
|
startNormalized,
|
|
endNormalized));
|
|
|
|
public static EnemyAttackContactPhase Create(float start, float end)
|
|
{
|
|
return new EnemyAttackContactPhase
|
|
{
|
|
startNormalized = Mathf.Clamp01(start),
|
|
endNormalized = Mathf.Clamp01(end),
|
|
};
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public struct EnemyAttackPattern
|
|
{
|
|
[SerializeField] private string animationState;
|
|
[SerializeField] private EnemyAttackShape attackShape;
|
|
[SerializeField] private EnemyAttackDirectionMode directionMode;
|
|
[SerializeField] private EnemyAttackGeometryScaleMode geometryScaleMode;
|
|
[SerializeField, Min(0f)] private float warningDuration;
|
|
[SerializeField, Min(0f)] private float activeDuration;
|
|
[SerializeField, Min(0f)] private float recoveryDuration;
|
|
[SerializeField, Min(0f)] private float attackRange;
|
|
[SerializeField, Min(0f)] private float attackLength;
|
|
[SerializeField, Min(0f)] private float attackWidth;
|
|
[SerializeField, Min(0f)] private float attackRadius;
|
|
[SerializeField] private Vector2 attackOriginOffset;
|
|
[SerializeField, Min(0f)] private float animationLeadTime;
|
|
[SerializeField, Min(0f)] private float damageMultiplier;
|
|
[SerializeField] private EnemyAttackContactPhase[] contactPhases;
|
|
|
|
public string AnimationState => animationState;
|
|
public EnemyAttackShape AttackShape => attackShape;
|
|
public EnemyAttackDirectionMode DirectionMode => directionMode;
|
|
public EnemyAttackGeometryScaleMode GeometryScaleMode => geometryScaleMode;
|
|
public float WarningDuration => Mathf.Max(0f, warningDuration);
|
|
public float ActiveDuration => Mathf.Max(0f, activeDuration);
|
|
public float RecoveryDuration => Mathf.Max(0f, recoveryDuration);
|
|
public float AttackRange => Mathf.Max(0f, attackRange);
|
|
public float AttackLength => Mathf.Max(0f, attackLength);
|
|
public float AttackWidth => Mathf.Max(0f, attackWidth);
|
|
public float AttackRadius => Mathf.Max(0f, attackRadius);
|
|
public Vector2 AttackOriginOffset => attackOriginOffset;
|
|
public float AnimationLeadTime => Mathf.Max(0f, animationLeadTime);
|
|
public float DamageMultiplier => damageMultiplier <= 0f ? 1f : damageMultiplier;
|
|
public int ContactPhaseCount => contactPhases?.Length ?? 0;
|
|
|
|
public EnemyAttackContactPhase GetContactPhase(int index)
|
|
{
|
|
if (contactPhases == null || contactPhases.Length == 0)
|
|
{
|
|
return EnemyAttackContactPhase.Create(0f, 1f);
|
|
}
|
|
|
|
return contactPhases[Mathf.Clamp(index, 0, contactPhases.Length - 1)];
|
|
}
|
|
|
|
public bool IsContactWindowActive(
|
|
float activeElapsed,
|
|
float effectiveActiveDuration)
|
|
{
|
|
if (effectiveActiveDuration <= 0.0001f
|
|
|| activeElapsed < 0f
|
|
|| activeElapsed >= effectiveActiveDuration)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (contactPhases == null || contactPhases.Length == 0)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
float normalized = effectiveActiveDuration <= 0.0001f
|
|
? 1f
|
|
: Mathf.Clamp01(activeElapsed / effectiveActiveDuration);
|
|
for (int i = 0; i < contactPhases.Length; i++)
|
|
{
|
|
EnemyAttackContactPhase phase = contactPhases[i];
|
|
if (normalized >= phase.StartNormalized
|
|
&& normalized < phase.EndNormalized)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public static EnemyAttackPattern Create(
|
|
string state,
|
|
EnemyAttackShape shape,
|
|
float warning,
|
|
float active,
|
|
float recovery,
|
|
float range,
|
|
float length,
|
|
float width,
|
|
float radius,
|
|
float damage = 1f,
|
|
float animationLead = 0f)
|
|
{
|
|
return new EnemyAttackPattern
|
|
{
|
|
animationState = state,
|
|
attackShape = shape,
|
|
directionMode = EnemyAttackDirectionMode.LockedDirection,
|
|
geometryScaleMode = EnemyAttackGeometryScaleMode.WorldUnits,
|
|
warningDuration = warning,
|
|
activeDuration = active,
|
|
recoveryDuration = recovery,
|
|
attackRange = range,
|
|
attackLength = length,
|
|
attackWidth = width,
|
|
attackRadius = radius,
|
|
attackOriginOffset = Vector2.zero,
|
|
animationLeadTime = animationLead,
|
|
damageMultiplier = damage,
|
|
contactPhases = null,
|
|
};
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
public struct RunEventEnemyTuning
|
|
{
|
|
[SerializeField] private EnemyKind prefabKind;
|
|
[SerializeField, Min(1f)] private float healthMultiplier;
|
|
[SerializeField, Min(0)] private int experienceValue;
|
|
[SerializeField, Min(1f)] private float scaleMultiplier;
|
|
[SerializeField] private Color tint;
|
|
[SerializeField, Min(0f)] private float moveSpeedMultiplier;
|
|
[SerializeField, Min(0f)] private float attackDamageMultiplier;
|
|
[SerializeField, Min(0f)] private float warningDurationMultiplier;
|
|
[SerializeField, Min(0f)] private float activeDurationMultiplier;
|
|
[SerializeField, Min(0f)] private float recoveryDurationMultiplier;
|
|
[SerializeField, Min(0f)] private float attackRangeMultiplier;
|
|
[SerializeField, Min(0f)] private float attackLengthMultiplier;
|
|
[SerializeField, Min(0f)] private float attackWidthMultiplier;
|
|
[SerializeField, Min(0f)] private float attackRadiusMultiplier;
|
|
[SerializeField, Min(1)] private int attacksPerSequence;
|
|
[SerializeField, Min(0f)] private float repeatWarningDurationMultiplier;
|
|
[SerializeField, Min(0f)] private float betweenAttacksRecoveryMultiplier;
|
|
[SerializeField, Min(1)] private int backHitsToInterrupt;
|
|
[SerializeField, Range(0f, 1f)] private float knockbackMultiplier;
|
|
[SerializeField] private bool canBeLaunched;
|
|
|
|
public EnemyKind PrefabKind => prefabKind;
|
|
public float HealthMultiplier => Mathf.Max(1f, healthMultiplier);
|
|
public int ExperienceValue => Mathf.Max(0, experienceValue);
|
|
public float ScaleMultiplier => Mathf.Max(1f, scaleMultiplier);
|
|
public Color Tint => tint;
|
|
public float MoveSpeedMultiplier => Mathf.Max(0f, moveSpeedMultiplier);
|
|
public float AttackDamageMultiplier => Mathf.Max(0f, attackDamageMultiplier);
|
|
public float WarningDurationMultiplier => Mathf.Max(0f, warningDurationMultiplier);
|
|
public float ActiveDurationMultiplier => Mathf.Max(0f, activeDurationMultiplier);
|
|
public float RecoveryDurationMultiplier => Mathf.Max(0f, recoveryDurationMultiplier);
|
|
public float AttackRangeMultiplier => Mathf.Max(0f, attackRangeMultiplier);
|
|
public float AttackLengthMultiplier => Mathf.Max(0f, attackLengthMultiplier);
|
|
public float AttackWidthMultiplier => Mathf.Max(0f, attackWidthMultiplier);
|
|
public float AttackRadiusMultiplier => Mathf.Max(0f, attackRadiusMultiplier);
|
|
public int AttacksPerSequence => Mathf.Max(1, attacksPerSequence);
|
|
public float RepeatWarningDurationMultiplier =>
|
|
Mathf.Max(0f, repeatWarningDurationMultiplier);
|
|
public float BetweenAttacksRecoveryMultiplier =>
|
|
Mathf.Max(0f, betweenAttacksRecoveryMultiplier);
|
|
public int BackHitsToInterrupt => Mathf.Max(1, backHitsToInterrupt);
|
|
public float KnockbackMultiplier => Mathf.Clamp01(knockbackMultiplier);
|
|
public bool CanBeLaunched => canBeLaunched;
|
|
|
|
public static RunEventEnemyTuning Create(
|
|
EnemyKind kind,
|
|
float health,
|
|
int experience,
|
|
float scale,
|
|
Color color,
|
|
float moveSpeed,
|
|
float attackDamage,
|
|
float warningDuration,
|
|
float activeDuration,
|
|
float recoveryDuration,
|
|
float attackRange,
|
|
float attackLength,
|
|
float attackWidth,
|
|
float attackRadius,
|
|
int attackCount,
|
|
float repeatWarning,
|
|
float betweenAttacksRecovery,
|
|
int staggerHits,
|
|
float knockback,
|
|
bool launchable)
|
|
{
|
|
return new RunEventEnemyTuning
|
|
{
|
|
prefabKind = kind,
|
|
healthMultiplier = health,
|
|
experienceValue = experience,
|
|
scaleMultiplier = scale,
|
|
tint = color,
|
|
moveSpeedMultiplier = moveSpeed,
|
|
attackDamageMultiplier = attackDamage,
|
|
warningDurationMultiplier = warningDuration,
|
|
activeDurationMultiplier = activeDuration,
|
|
recoveryDurationMultiplier = recoveryDuration,
|
|
attackRangeMultiplier = attackRange,
|
|
attackLengthMultiplier = attackLength,
|
|
attackWidthMultiplier = attackWidth,
|
|
attackRadiusMultiplier = attackRadius,
|
|
attacksPerSequence = attackCount,
|
|
repeatWarningDurationMultiplier = repeatWarning,
|
|
betweenAttacksRecoveryMultiplier = betweenAttacksRecovery,
|
|
backHitsToInterrupt = staggerHits,
|
|
knockbackMultiplier = knockback,
|
|
canBeLaunched = launchable,
|
|
};
|
|
}
|
|
}
|
|
|
|
[CreateAssetMenu(menuName = "BumpCombat/Enemy Definition")]
|
|
public sealed class EnemyDefinition : ScriptableObject
|
|
{
|
|
[SerializeField] private EnemyKind kind;
|
|
[SerializeField] private EnemyRole role;
|
|
[SerializeField] private EnemyAttackShape attackShape;
|
|
[SerializeField, Min(1f)] private float maxHealth;
|
|
[SerializeField, Min(0f)] private float moveSpeed;
|
|
[SerializeField, Min(0f)] private float attackDamage;
|
|
[SerializeField, Min(0f)] private float warningDuration;
|
|
[SerializeField, Min(0f)] private float activeDuration;
|
|
[SerializeField, Min(0f)] private float recoveryDuration;
|
|
[SerializeField, Min(0f)] private float attackRange;
|
|
[SerializeField, Min(0f)] private float preferredRange;
|
|
[SerializeField, Min(0f)] private float attackLength;
|
|
[SerializeField, Min(0f)] private float attackWidth;
|
|
[SerializeField, Min(0f)] private float attackRadius;
|
|
[SerializeField, Min(0f)] private float attackAnimationLeadTime;
|
|
[SerializeField, Min(0f)] private float projectileSpeed;
|
|
[SerializeField, Min(0f)] private float projectileRadius;
|
|
[SerializeField, Min(0)] private int experienceValue;
|
|
[SerializeField] private EnemyAttackPattern[] attackPatterns;
|
|
[SerializeField, Min(0f)] private float separationRadius = 0.55f;
|
|
[SerializeField, Min(0f)] private float separationStrength = 0.6f;
|
|
|
|
public EnemyKind Kind => kind;
|
|
public EnemyRole Role => role;
|
|
public EnemyAttackShape AttackShape => attackShape;
|
|
public float MaxHealth => maxHealth;
|
|
public float MoveSpeed => moveSpeed;
|
|
public float AttackDamage => attackDamage;
|
|
public float WarningDuration => warningDuration;
|
|
public float ActiveDuration => activeDuration;
|
|
public float RecoveryDuration => recoveryDuration;
|
|
public float AttackRange => attackRange;
|
|
public float PreferredRange => preferredRange;
|
|
public float AttackLength => attackLength;
|
|
public float AttackWidth => attackWidth;
|
|
public float AttackRadius => attackRadius;
|
|
public float AttackAnimationLeadTime => Mathf.Max(0f, attackAnimationLeadTime);
|
|
public float ProjectileSpeed => Mathf.Max(0f, projectileSpeed);
|
|
public float ProjectileRadius => Mathf.Max(0f, projectileRadius);
|
|
public int ExperienceValue => experienceValue;
|
|
public float SeparationRadius => Mathf.Max(0f, separationRadius);
|
|
public float SeparationStrength => Mathf.Max(0f, separationStrength);
|
|
public int AttackPatternCount => attackPatterns?.Length ?? 0;
|
|
public bool IsRanged => kind == EnemyKind.Warlock
|
|
|| kind == EnemyKind.Necrofire
|
|
|| kind == EnemyKind.SkeletonArcher
|
|
|| kind == EnemyKind.Necromancer;
|
|
public bool IsCrowd => role == EnemyRole.Crowd;
|
|
public bool HasContactDamage => kind == EnemyKind.Slime
|
|
|| kind == EnemyKind.Bat;
|
|
|
|
public EnemyAttackPattern GetAttackPattern(int index)
|
|
{
|
|
if (attackPatterns == null || attackPatterns.Length == 0)
|
|
{
|
|
return EnemyAttackPattern.Create(
|
|
"Attack",
|
|
attackShape,
|
|
warningDuration,
|
|
activeDuration,
|
|
recoveryDuration,
|
|
attackRange,
|
|
attackLength,
|
|
attackWidth,
|
|
attackRadius,
|
|
1f,
|
|
attackAnimationLeadTime);
|
|
}
|
|
|
|
return attackPatterns[Mathf.Clamp(index, 0, attackPatterns.Length - 1)];
|
|
}
|
|
|
|
public EnemyAttackPattern[] AttackPatterns => attackPatterns;
|
|
|
|
#if UNITY_EDITOR
|
|
public void Configure(
|
|
EnemyKind enemyKind,
|
|
EnemyAttackShape shape,
|
|
float health,
|
|
float speed,
|
|
float damage,
|
|
float warning,
|
|
float active,
|
|
float recovery,
|
|
float range,
|
|
float preferred,
|
|
float length,
|
|
float width,
|
|
float radius,
|
|
int experience,
|
|
float animationLeadTime = 0f)
|
|
{
|
|
kind = enemyKind;
|
|
attackShape = shape;
|
|
maxHealth = health;
|
|
moveSpeed = speed;
|
|
attackDamage = damage;
|
|
warningDuration = warning;
|
|
activeDuration = active;
|
|
recoveryDuration = recovery;
|
|
attackRange = range;
|
|
preferredRange = preferred;
|
|
attackLength = length;
|
|
attackWidth = width;
|
|
attackRadius = radius;
|
|
attackAnimationLeadTime = animationLeadTime;
|
|
experienceValue = experience;
|
|
}
|
|
|
|
public void ConfigureRole(EnemyRole enemyRole)
|
|
{
|
|
role = enemyRole;
|
|
}
|
|
|
|
public void ConfigureAttackPatterns(EnemyAttackPattern[] patterns)
|
|
{
|
|
attackPatterns = patterns;
|
|
}
|
|
#endif
|
|
}
|
|
}
|