Initial commit: Tiny Tackle Heroes Unity project
This commit is contained in:
@@ -0,0 +1,399 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user