修复旋转问题 + 防御塔范围可视化
- BasicBearingComp 和 ShooterBullet 的旋转由之前的 3D 方式调整为只旋转 Z 轴 - DefenseTowerController 通过 LineRenderer 添加了攻击范围可视化
This commit is contained in:
@@ -9,7 +9,6 @@ namespace Components
|
||||
[SerializeField] [Min(0.1f)] private float _attackRange = 5f;
|
||||
[SerializeField] [Min(0.1f)] private float _aimToleranceAngle = 2f;
|
||||
[SerializeField] private Transform _rotateRoot;
|
||||
[SerializeField] private bool _yawOnly = true;
|
||||
|
||||
public float RotateSpeed => _rotateSpeed;
|
||||
public float AttackRange => _attackRange;
|
||||
@@ -47,20 +46,19 @@ namespace Components
|
||||
}
|
||||
|
||||
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
|
||||
Vector3 toTarget = target.position - rotateRoot.position;
|
||||
if (_yawOnly)
|
||||
{
|
||||
toTarget.y = 0f;
|
||||
}
|
||||
Vector2 toTarget = (Vector2)(target.position - rotateRoot.position);
|
||||
|
||||
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Quaternion targetRotation = Quaternion.LookRotation(toTarget.normalized, Vector3.up);
|
||||
float rotateAngle = _rotateSpeed * Mathf.Max(0f, deltaTime);
|
||||
rotateRoot.rotation = Quaternion.RotateTowards(rotateRoot.rotation, targetRotation, rotateAngle);
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, toTarget);
|
||||
float currentZAngle = rotateRoot.eulerAngles.z;
|
||||
float maxStep = _rotateSpeed * Mathf.Max(0f, deltaTime);
|
||||
float angleDelta = Mathf.DeltaAngle(currentZAngle, targetZAngle);
|
||||
float nextZAngle = currentZAngle + Mathf.Clamp(angleDelta, -maxStep, maxStep);
|
||||
rotateRoot.rotation = Quaternion.Euler(0f, 0f, nextZAngle);
|
||||
return IsTargetAligned(target);
|
||||
}
|
||||
|
||||
@@ -72,18 +70,16 @@ namespace Components
|
||||
}
|
||||
|
||||
Transform rotateRoot = _rotateRoot != null ? _rotateRoot : transform;
|
||||
Vector3 toTarget = target.position - rotateRoot.position;
|
||||
if (_yawOnly)
|
||||
{
|
||||
toTarget.y = 0f;
|
||||
}
|
||||
Vector2 toTarget = (Vector2)(target.position - rotateRoot.position);
|
||||
|
||||
if (toTarget.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
float angle = Vector3.Angle(rotateRoot.forward, toTarget.normalized);
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, toTarget);
|
||||
float currentZAngle = rotateRoot.eulerAngles.z;
|
||||
float angle = Mathf.Abs(Mathf.DeltaAngle(currentZAngle, targetZAngle));
|
||||
return angle <= _aimToleranceAngle;
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,24 @@ namespace Components
|
||||
[DisallowMultipleComponent]
|
||||
public class DefenseTowerController : MonoBehaviour
|
||||
{
|
||||
private const string AttackRangeIndicatorObjectName = "AttackRangeIndicator";
|
||||
private static Material s_AttackRangeSharedMaterial;
|
||||
|
||||
[SerializeField] private ShooterMuzzleComp _muzzleComp;
|
||||
[SerializeField] private BasicBearingComp _bearingComp;
|
||||
[SerializeField] private BasicBaseComp _baseComp;
|
||||
[SerializeField] private Transform _scanOrigin;
|
||||
[SerializeField] [Min(0.02f)] private float _retargetInterval = 0.1f;
|
||||
[SerializeField] private bool _autoUpdate = true;
|
||||
[SerializeField] private LineRenderer _attackRangeRenderer;
|
||||
[SerializeField] [Min(12)] private int _attackRangeSegments = 64;
|
||||
[SerializeField] [Min(0.005f)] private float _attackRangeLineWidth = 0.08f;
|
||||
[SerializeField] private Color _attackRangeColor = new Color(0.1f, 1f, 0.4f, 0.8f);
|
||||
[SerializeField] private float _attackRangeZOffset = -0.01f;
|
||||
|
||||
private Transform _currentTarget;
|
||||
private float _retargetTimer;
|
||||
private float _attackRange;
|
||||
|
||||
public Transform CurrentTarget => _currentTarget;
|
||||
|
||||
@@ -45,12 +54,15 @@ namespace Components
|
||||
_muzzleComp?.OnInit(stats.AttackDamage, stats.AttackMethodType);
|
||||
_bearingComp?.OnInit(stats.RotateSpeed, stats.AttackRange);
|
||||
_baseComp?.OnInit(stats.AttackSpeed, stats.AttackPropertyType);
|
||||
SetAttackRange(stats.AttackRange);
|
||||
SetAttackRangeVisible(false);
|
||||
_currentTarget = null;
|
||||
_retargetTimer = 0f;
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
SetAttackRangeVisible(false);
|
||||
_currentTarget = null;
|
||||
_retargetTimer = 0f;
|
||||
_muzzleComp?.OnReset();
|
||||
@@ -58,6 +70,22 @@ namespace Components
|
||||
_baseComp?.OnReset();
|
||||
}
|
||||
|
||||
public void SetAttackRangeVisible(bool visible)
|
||||
{
|
||||
EnsureAttackRangeRenderer();
|
||||
if (_attackRangeRenderer != null)
|
||||
{
|
||||
_attackRangeRenderer.enabled = visible;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetAttackRange(float range)
|
||||
{
|
||||
_attackRange = Mathf.Max(0.05f, range);
|
||||
EnsureAttackRangeRenderer();
|
||||
RebuildAttackRangeGeometry();
|
||||
}
|
||||
|
||||
public void SetAutoUpdate(bool autoUpdate)
|
||||
{
|
||||
_autoUpdate = autoUpdate;
|
||||
@@ -153,6 +181,8 @@ namespace Components
|
||||
{
|
||||
_baseComp = GetComponent<BasicBaseComp>();
|
||||
}
|
||||
|
||||
EnsureAttackRangeRenderer();
|
||||
}
|
||||
|
||||
private bool HasCoreComponents()
|
||||
@@ -164,5 +194,72 @@ namespace Components
|
||||
{
|
||||
return target != null && target.gameObject.activeInHierarchy;
|
||||
}
|
||||
|
||||
private void EnsureAttackRangeRenderer()
|
||||
{
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
Transform indicatorTransform = transform.Find(AttackRangeIndicatorObjectName);
|
||||
if (indicatorTransform == null)
|
||||
{
|
||||
GameObject indicatorObject = new GameObject(AttackRangeIndicatorObjectName);
|
||||
indicatorTransform = indicatorObject.transform;
|
||||
indicatorTransform.SetParent(transform, false);
|
||||
}
|
||||
|
||||
_attackRangeRenderer = indicatorTransform.GetComponent<LineRenderer>();
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
_attackRangeRenderer = indicatorTransform.gameObject.AddComponent<LineRenderer>();
|
||||
}
|
||||
}
|
||||
|
||||
_attackRangeRenderer.useWorldSpace = false;
|
||||
_attackRangeRenderer.loop = true;
|
||||
_attackRangeRenderer.positionCount = Mathf.Max(12, _attackRangeSegments);
|
||||
_attackRangeRenderer.widthMultiplier = _attackRangeLineWidth;
|
||||
_attackRangeRenderer.startColor = _attackRangeColor;
|
||||
_attackRangeRenderer.endColor = _attackRangeColor;
|
||||
_attackRangeRenderer.numCapVertices = 4;
|
||||
_attackRangeRenderer.numCornerVertices = 4;
|
||||
_attackRangeRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
|
||||
_attackRangeRenderer.receiveShadows = false;
|
||||
_attackRangeRenderer.allowOcclusionWhenDynamic = false;
|
||||
_attackRangeRenderer.enabled = false;
|
||||
|
||||
if (s_AttackRangeSharedMaterial == null)
|
||||
{
|
||||
Shader lineShader = Shader.Find("Sprites/Default");
|
||||
if (lineShader != null)
|
||||
{
|
||||
s_AttackRangeSharedMaterial = new Material(lineShader);
|
||||
}
|
||||
}
|
||||
|
||||
if (s_AttackRangeSharedMaterial != null)
|
||||
{
|
||||
_attackRangeRenderer.sharedMaterial = s_AttackRangeSharedMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
private void RebuildAttackRangeGeometry()
|
||||
{
|
||||
if (_attackRangeRenderer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int segmentCount = Mathf.Max(12, _attackRangeSegments);
|
||||
_attackRangeRenderer.positionCount = segmentCount;
|
||||
|
||||
float stepAngle = Mathf.PI * 2f / segmentCount;
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
float angle = stepAngle * i;
|
||||
float x = Mathf.Cos(angle) * _attackRange;
|
||||
float y = Mathf.Sin(angle) * _attackRange;
|
||||
_attackRangeRenderer.SetPosition(i, new Vector3(x, y, _attackRangeZOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,12 @@ namespace Components
|
||||
_lifetime = 0f;
|
||||
_despawnRequested = false;
|
||||
_isRunning = _target != null;
|
||||
if (_isRunning)
|
||||
{
|
||||
Vector2 initialDirection = (Vector2)(_target.position - transform.position);
|
||||
RotateToDirection(initialDirection);
|
||||
}
|
||||
|
||||
if (!_isRunning)
|
||||
{
|
||||
_despawnRequested = true;
|
||||
@@ -63,7 +69,8 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 toTarget = _target.position - transform.position;
|
||||
Vector3 currentPosition = transform.position;
|
||||
Vector2 toTarget = (Vector2)(_target.position - currentPosition);
|
||||
float hitDistanceSquared = _hitDistance * _hitDistance;
|
||||
if (toTarget.sqrMagnitude <= hitDistanceSquared)
|
||||
{
|
||||
@@ -71,17 +78,18 @@ namespace Components
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 forward = toTarget.normalized;
|
||||
Vector2 direction = toTarget.normalized;
|
||||
RotateToDirection(direction);
|
||||
float moveDistance = _speed * Mathf.Max(0f, deltaTime);
|
||||
if (moveDistance * moveDistance >= toTarget.sqrMagnitude)
|
||||
{
|
||||
transform.position = _target.position;
|
||||
Vector3 targetPosition = _target.position;
|
||||
transform.position = new Vector3(targetPosition.x, targetPosition.y, currentPosition.z);
|
||||
HitTarget();
|
||||
return;
|
||||
}
|
||||
|
||||
transform.position += forward * moveDistance;
|
||||
transform.forward = forward;
|
||||
transform.position += (Vector3)(direction * moveDistance);
|
||||
}
|
||||
|
||||
public bool TryConsumeDespawnRequest()
|
||||
@@ -105,9 +113,9 @@ namespace Components
|
||||
}
|
||||
|
||||
MonoBehaviour[] components = _target.GetComponentsInParent<MonoBehaviour>();
|
||||
for (int i = 0; i < components.Length; i++)
|
||||
foreach (var mono in components)
|
||||
{
|
||||
if (components[i] is IDamageReceiver damageReceiver)
|
||||
if (mono is IDamageReceiver damageReceiver)
|
||||
{
|
||||
damageReceiver.TakeDamage(_damage, _attackPropertyType);
|
||||
break;
|
||||
@@ -117,5 +125,16 @@ namespace Components
|
||||
_isRunning = false;
|
||||
_despawnRequested = true;
|
||||
}
|
||||
|
||||
private void RotateToDirection(Vector2 direction)
|
||||
{
|
||||
if (direction.sqrMagnitude <= Mathf.Epsilon)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float targetZAngle = Vector2.SignedAngle(Vector2.up, direction);
|
||||
transform.rotation = Quaternion.Euler(0f, 0f, targetZAngle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Components
|
||||
[DisallowMultipleComponent]
|
||||
public class ShooterMuzzleComp : MonoBehaviour
|
||||
{
|
||||
[SerializeField] [Min(1f)] private int _attackDamage = 10;
|
||||
[SerializeField] [Min(1f)] private int _attackDamage = 100;
|
||||
[SerializeField] private AttackMethodType _attackMethodType = AttackMethodType.NormalBullet;
|
||||
[SerializeField] [Min(1)] private int _bulletTypeId = 501;
|
||||
[SerializeField] private Transform _muzzlePoint;
|
||||
@@ -54,7 +54,6 @@ namespace Components
|
||||
bulletEntityId,
|
||||
_bulletTypeId,
|
||||
spawnPoint.position,
|
||||
spawnPoint.rotation,
|
||||
target,
|
||||
_attackDamage,
|
||||
_bulletSpeed,
|
||||
|
||||
Reference in New Issue
Block a user