2.5d test

This commit is contained in:
2026-09-27 21:08:05 +09:00
parent 84dd12414a
commit abd2da2057
201 changed files with 24297 additions and 295 deletions
@@ -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)