2.5d test
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
using UnityEngine;
|
||||
using BumpCombat.Combat;
|
||||
using BumpCombat.Core;
|
||||
using BumpCombat.Player;
|
||||
|
||||
namespace BumpCombat.Defense
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class DefenseSummonUnit : MonoBehaviour
|
||||
public sealed class DefenseSummonUnit : CharacterModel
|
||||
{
|
||||
private Sprite[] idleFrames;
|
||||
private Sprite[] attackFrames;
|
||||
@@ -21,19 +24,46 @@ namespace BumpCombat.Defense
|
||||
private Vector2 anchorLocalPosition;
|
||||
private SpriteRenderer spriteRenderer;
|
||||
private float attackCooldown;
|
||||
private CircleCollider2D bodyCollider;
|
||||
private Rigidbody2D body;
|
||||
private static readonly RaycastHit2D[] MovementHits = new RaycastHit2D[16];
|
||||
private float currentHealth;
|
||||
private bool dead;
|
||||
private bool returningToAnchor;
|
||||
private DefenseRouteAgent pendingAttackTarget;
|
||||
private bool attackDamageApplied;
|
||||
public int SlotIndex { get; private set; }
|
||||
public override float MoveSpeed => controller != null ? controller.Tuning.SummonUnitMoveSpeed : 0f;
|
||||
public override float MaxHealth => owner != null ? owner.SummonUnitMaximumHealth : 1f;
|
||||
public bool IsDead => dead;
|
||||
public float CurrentHealth => currentHealth;
|
||||
public DefenseTower Owner => owner;
|
||||
public Collider2D BodyCollider => bodyCollider;
|
||||
public bool IsOperational => isActiveAndEnabled && !dead && owner != null && owner.IsOperational;
|
||||
|
||||
public void Configure(
|
||||
DefenseTower tower,
|
||||
DefensePrototypeController prototypeController,
|
||||
Vector2 localAnchor)
|
||||
Vector2 localAnchor,
|
||||
int slotIndex)
|
||||
{
|
||||
IsMinion = true;
|
||||
owner = tower;
|
||||
controller = prototypeController;
|
||||
SlotIndex = slotIndex;
|
||||
anchorLocalPosition = localAnchor;
|
||||
bodyCollider = gameObject.AddComponent<CircleCollider2D>();
|
||||
bodyCollider.radius = owner.SummonUnitCollisionRadius;
|
||||
body = gameObject.AddComponent<Rigidbody2D>();
|
||||
body.bodyType = RigidbodyType2D.Kinematic;
|
||||
body.gravityScale = 0f;
|
||||
body.freezeRotation = true;
|
||||
spriteRenderer = gameObject.AddComponent<SpriteRenderer>();
|
||||
spriteRenderer.color = Color.white;
|
||||
spriteRenderer.sortingOrder = 11;
|
||||
IgnorePlayerCollisionPairs();
|
||||
RefreshArt();
|
||||
currentHealth = MaxHealth;
|
||||
}
|
||||
|
||||
public void RefreshArt()
|
||||
@@ -64,54 +94,195 @@ namespace BumpCombat.Defense
|
||||
transform.localScale = new Vector3(scale, scale, 1f);
|
||||
}
|
||||
|
||||
private void Update()
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (owner == null || !owner.isActiveAndEnabled
|
||||
|| controller == null || !IsOperational) return;
|
||||
|
||||
UpdateSpriteAnimation();
|
||||
if (owner == null
|
||||
|| !owner.isActiveAndEnabled
|
||||
|| controller == null
|
||||
|| !controller.CanOperateTowers)
|
||||
attackCooldown = Mathf.Max(0f, attackCooldown - Time.deltaTime);
|
||||
Vector2 anchor = owner.transform.TransformPoint(anchorLocalPosition);
|
||||
bool isBasicSlime = owner.Tier < 4;
|
||||
bool isScout = owner.Tier == 4
|
||||
&& owner.Branch == DefenseTowerBranch.Second;
|
||||
|
||||
if (playingAttack)
|
||||
{
|
||||
if (!attackDamageApplied
|
||||
&& (pendingAttackTarget == null
|
||||
|| pendingAttackTarget != owner.CommandTarget
|
||||
|| pendingAttackTarget.HasArrived
|
||||
|| pendingAttackTarget.Enemy == null
|
||||
|| pendingAttackTarget.Enemy.IsDead))
|
||||
{
|
||||
playingAttack = false;
|
||||
attackDamageApplied = true;
|
||||
pendingAttackTarget = null;
|
||||
if (isBasicSlime) returningToAnchor = true;
|
||||
spriteFrame = 0;
|
||||
spriteFrameTime = 0f;
|
||||
if (idleFrames != null && idleFrames.Length > 0)
|
||||
spriteRenderer.sprite = idleFrames[0];
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 contactPosition = body.position;
|
||||
if (isBasicSlime && spriteFrame >= controller.Tuning.SummonBodySlamImpactFrame)
|
||||
{
|
||||
if (pendingAttackTarget != null && !pendingAttackTarget.HasArrived
|
||||
&& pendingAttackTarget.Enemy != null && !pendingAttackTarget.Enemy.IsDead)
|
||||
{
|
||||
contactPosition = MoveBody(Vector2.MoveTowards(body.position,
|
||||
pendingAttackTarget.transform.position,
|
||||
MoveSpeed * controller.Tuning.SummonBodySlamSpeedMultiplier
|
||||
* Time.fixedDeltaTime));
|
||||
}
|
||||
}
|
||||
|
||||
if (!attackDamageApplied
|
||||
&& spriteFrame >= controller.Tuning.SummonBodySlamImpactFrame
|
||||
&& pendingAttackTarget != null && !pendingAttackTarget.HasArrived
|
||||
&& pendingAttackTarget.Enemy != null && !pendingAttackTarget.Enemy.IsDead
|
||||
&& (isScout || !isBasicSlime
|
||||
|| IsAtContactDistance(pendingAttackTarget, contactPosition)))
|
||||
{
|
||||
bool hit = owner.TryAttack(pendingAttackTarget);
|
||||
attackDamageApplied = true;
|
||||
if (hit) owner.ShowSummonAttackEffect(body.position, pendingAttackTarget);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
attackCooldown = Mathf.Max(0f, attackCooldown - Time.deltaTime);
|
||||
Vector2 anchor = owner.transform.TransformPoint(anchorLocalPosition);
|
||||
DefenseRouteAgent target = controller.FindNearestTarget(
|
||||
anchor,
|
||||
controller.Tuning.SummonAreaRadius,
|
||||
DefenseTowerKind.Summon);
|
||||
if (target == null)
|
||||
if (returningToAnchor)
|
||||
{
|
||||
Vector2 previous = transform.position;
|
||||
transform.position = Vector2.MoveTowards(
|
||||
previous, anchor, controller.Tuning.SummonUnitMoveSpeed * Time.deltaTime);
|
||||
isWalking = ((Vector2)transform.position - previous).sqrMagnitude > 0.000001f;
|
||||
Vector2 previous = body.position;
|
||||
Vector2 next = MoveBody(Vector2.MoveTowards(previous, anchor,
|
||||
MoveSpeed * Time.fixedDeltaTime));
|
||||
isWalking = (next - previous).sqrMagnitude > 0.000001f;
|
||||
if ((next - anchor).sqrMagnitude < 0.01f
|
||||
|| (next - previous).sqrMagnitude < 0.000001f)
|
||||
{
|
||||
returningToAnchor = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
DefenseRouteAgent target = owner.CommandTarget;
|
||||
if (target == null || target.HasArrived || target.Enemy == null || target.Enemy.IsDead)
|
||||
{
|
||||
Vector2 previous = body.position;
|
||||
Vector2 next = MoveBody(Vector2.MoveTowards(previous, anchor,
|
||||
MoveSpeed * Time.fixedDeltaTime));
|
||||
isWalking = (next - previous).sqrMagnitude > 0.000001f;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector2 targetPosition = target.transform.position;
|
||||
spriteRenderer.flipX = targetPosition.x < transform.position.x;
|
||||
Vector2 previousPosition = transform.position;
|
||||
transform.position = Vector2.MoveTowards(
|
||||
previousPosition, targetPosition,
|
||||
controller.Tuning.SummonUnitMoveSpeed * Time.deltaTime);
|
||||
isWalking = ((Vector2)transform.position - previousPosition).sqrMagnitude > 0.000001f;
|
||||
float attackRange = controller.Tuning.SummonAttackRange
|
||||
* (owner.Tier == 4 && owner.Branch == DefenseTowerBranch.Second ? 2.5f : 1f);
|
||||
if (attackCooldown <= 0f
|
||||
&& (targetPosition - (Vector2)transform.position).sqrMagnitude
|
||||
<= attackRange * attackRange
|
||||
&& owner.TryAttack(target))
|
||||
* (isScout ? 2.5f : 1f);
|
||||
Vector2 previousPosition = body.position;
|
||||
Vector2 toTarget = targetPosition - previousPosition;
|
||||
Vector2 stopPosition = targetPosition - toTarget.normalized * attackRange * 0.8f;
|
||||
Vector2 nextPosition = previousPosition;
|
||||
if (toTarget.magnitude > attackRange * 0.8f)
|
||||
{
|
||||
nextPosition = MoveBody(Vector2.MoveTowards(previousPosition,
|
||||
stopPosition, MoveSpeed * Time.fixedDeltaTime));
|
||||
}
|
||||
isWalking = (nextPosition - previousPosition).sqrMagnitude > 0.000001f;
|
||||
if (attackCooldown <= 0f
|
||||
&& (targetPosition - nextPosition).sqrMagnitude <= attackRange * attackRange)
|
||||
{
|
||||
pendingAttackTarget = target;
|
||||
attackDamageApplied = false;
|
||||
returningToAnchor = isBasicSlime;
|
||||
PlayAttackSprite();
|
||||
owner.ShowSummonAttackEffect(transform.position, targetPosition);
|
||||
attackCooldown = controller.Tuning.SummonAttackInterval
|
||||
/ (1f + 0.15f * (owner.Tier - 1))
|
||||
* controller.GetTowerAttackIntervalMultiplier(owner);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsAtContactDistance(DefenseRouteAgent target, Vector2 summonCenter)
|
||||
{
|
||||
Collider2D targetCollider = target.Enemy.GetComponent<Collider2D>();
|
||||
if (targetCollider == null)
|
||||
{
|
||||
return Vector2.Distance(summonCenter, target.transform.position)
|
||||
<= controller.Tuning.SummonBodySlamContactMargin;
|
||||
}
|
||||
|
||||
Vector2 targetSurface = targetCollider.ClosestPoint(summonCenter);
|
||||
float bodyRadius = bodyCollider.bounds.extents.x;
|
||||
float surfaceGap = Vector2.Distance(summonCenter, targetSurface) - bodyRadius;
|
||||
return surfaceGap <= controller.Tuning.SummonBodySlamContactMargin;
|
||||
}
|
||||
|
||||
private void IgnorePlayerCollisionPairs()
|
||||
{
|
||||
if (!IsMinion) return;
|
||||
PlayerController player = FindAnyObjectByType<PlayerController>();
|
||||
CharacterModel playerModel = player != null
|
||||
? player.GetComponent<CharacterModel>() : null;
|
||||
if (player == null || playerModel == null || playerModel.IsMinion) return;
|
||||
|
||||
Collider2D[] playerColliders = player.GetComponentsInChildren<Collider2D>(true);
|
||||
for (int i = 0; i < playerColliders.Length; i++)
|
||||
{
|
||||
Collider2D playerCollider = playerColliders[i];
|
||||
if (playerCollider != null && playerCollider != bodyCollider)
|
||||
{
|
||||
Physics2D.IgnoreCollision(bodyCollider, playerCollider, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
private Vector2 MoveBody(Vector2 destination)
|
||||
{
|
||||
Vector2 delta = destination - body.position;
|
||||
float distance = delta.magnitude;
|
||||
if (distance <= 0.0001f) return body.position;
|
||||
Vector2 direction = delta / distance;
|
||||
ContactFilter2D filter = ContactFilter2D.noFilter;
|
||||
filter.useTriggers = false;
|
||||
int hitCount = bodyCollider.Cast(direction, filter, MovementHits, distance);
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
Collider2D hit = MovementHits[i].collider;
|
||||
if (hit == null || hit.transform == transform) continue;
|
||||
if (IsMinion && hit.GetComponentInParent<PlayerController>() != null) continue;
|
||||
if (MovementHits[i].distance <= 0.001f
|
||||
&& Vector2.Dot(direction, body.position - (Vector2)hit.bounds.center) >= 0f) continue;
|
||||
distance = Mathf.Min(distance, Mathf.Max(0f, MovementHits[i].distance - 0.01f));
|
||||
}
|
||||
Vector2 next = body.position + direction * distance;
|
||||
body.MovePosition(next);
|
||||
return next;
|
||||
}
|
||||
public void RetireForReplacement()
|
||||
{
|
||||
if (bodyCollider != null) bodyCollider.enabled = false;
|
||||
if (spriteRenderer != null) spriteRenderer.enabled = false;
|
||||
if (body != null) body.simulated = false;
|
||||
enabled = false;
|
||||
owner = null;
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
public bool TryTakeDamage(float damage, Vector2 knockbackDirection)
|
||||
{
|
||||
if (!IsOperational || damage <= 0f) return false;
|
||||
float applied = DamageCalculator.FinalizeDamage(CalculateIncomingDamage(damage));
|
||||
if (applied <= 0f) return false;
|
||||
currentHealth = Mathf.Max(0f, currentHealth - applied);
|
||||
if (currentHealth <= 0f)
|
||||
{
|
||||
dead = true;
|
||||
bodyCollider.enabled = false;
|
||||
spriteRenderer.enabled = false;
|
||||
owner.NotifySummonUnitDied(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private void PlayAttackSprite()
|
||||
{
|
||||
if (attackFrames == null || attackFrames.Length == 0)
|
||||
|
||||
Reference in New Issue
Block a user