Fixed
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 1dfcdffb89f04cd468e4f8386b4f67e8, type: 3}
|
||||
m_Name: MapEnemyBalance
|
||||
m_EditorClassIdentifier:
|
||||
targetsPerWave: 5
|
||||
simulationStep: 0.1
|
||||
minimumSpawnDistanceFromBase: 8.35
|
||||
spawnPositionAttempts: 32
|
||||
movementSpeedMultiplier: 0.5
|
||||
attackDelayMultiplier: 1.5
|
||||
healthMultiplier: 1
|
||||
baseDamageMultiplier: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 662ffa97b173faf47a6eebcb4c6abf50
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -7,6 +7,8 @@ public static class MapRadarNetworkSetup
|
||||
{
|
||||
private const string LeverPrefabPath =
|
||||
"Assets/CONTENT/Game/Prefabs/Interaction/PhysicalLever.prefab";
|
||||
private const string EnemyBalancePath =
|
||||
"Assets/CONTENT/FEATURES/MapRadar/Configs/MapEnemyBalance.asset";
|
||||
|
||||
[MenuItem("FORT-BO/Map Radar/Configure Network Prototype")]
|
||||
public static void ConfigureNetworkPrototype()
|
||||
@@ -31,6 +33,7 @@ public static class MapRadarNetworkSetup
|
||||
GetOrAdd<NetworkObject>(mapController.gameObject);
|
||||
NetworkBaseHealth baseHealth = GetOrAdd<NetworkBaseHealth>(mapController.gameObject);
|
||||
MapControlBinder binder = GetOrAdd<MapControlBinder>(mapController.gameObject);
|
||||
MapEnemyBalanceConfig enemyBalance = GetOrCreateEnemyBalance();
|
||||
|
||||
Transform controlsRoot = GetOrCreateChild(
|
||||
mapController.transform.root,
|
||||
@@ -62,6 +65,7 @@ public static class MapRadarNetworkSetup
|
||||
SetObject(map, "roundGameLoop",
|
||||
Object.FindFirstObjectByType<RoundGameLoopController>());
|
||||
SetObject(map, "baseHealth", baseHealth);
|
||||
SetObject(map, "enemyBalance", enemyBalance);
|
||||
map.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
SerializedObject binding = new(binder);
|
||||
@@ -82,10 +86,24 @@ public static class MapRadarNetworkSetup
|
||||
|
||||
Debug.Log(
|
||||
"[Map] Network prototype configured: X/Y wheels, fire lever, " +
|
||||
"scan button and base health are connected.",
|
||||
"scan button, enemy balance and base health are connected.",
|
||||
mapController);
|
||||
}
|
||||
|
||||
private static MapEnemyBalanceConfig GetOrCreateEnemyBalance()
|
||||
{
|
||||
MapEnemyBalanceConfig config =
|
||||
AssetDatabase.LoadAssetAtPath<MapEnemyBalanceConfig>(EnemyBalancePath);
|
||||
if (config != null)
|
||||
return config;
|
||||
|
||||
config = ScriptableObject.CreateInstance<MapEnemyBalanceConfig>();
|
||||
AssetDatabase.CreateAsset(config, EnemyBalancePath);
|
||||
EditorUtility.SetDirty(config);
|
||||
AssetDatabase.SaveAssets();
|
||||
return config;
|
||||
}
|
||||
|
||||
private static NetworkPhysicalWheelInteractable CreateWheel(
|
||||
Transform parent,
|
||||
string name,
|
||||
|
||||
@@ -9,12 +9,35 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
[SerializeField] private NetworkPhysicalLeverInteractable fireLever;
|
||||
[SerializeField] private NetworkButtonInteractable scanButton;
|
||||
|
||||
private bool controlsBound;
|
||||
private bool initialValuesApplied;
|
||||
|
||||
private void Reset() => mapController = GetComponent<MapController>();
|
||||
|
||||
private void OnEnable()
|
||||
private void OnEnable() => TryBindControls();
|
||||
|
||||
private void OnDisable() => UnbindControls();
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TryBindControls();
|
||||
|
||||
if (!controlsBound || initialValuesApplied ||
|
||||
horizontalWheel == null || verticalWheel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
initialValuesApplied = true;
|
||||
HandleHorizontalChanged(horizontalWheel.NormalizedValue);
|
||||
HandleVerticalChanged(verticalWheel.NormalizedValue);
|
||||
}
|
||||
|
||||
private void TryBindControls()
|
||||
{
|
||||
if (controlsBound || !AreNetworkObjectsReady())
|
||||
return;
|
||||
|
||||
if (horizontalWheel != null)
|
||||
horizontalWheel.ValueChanged += HandleHorizontalChanged;
|
||||
if (verticalWheel != null)
|
||||
@@ -23,10 +46,15 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
fireLever.StateChanged += HandleFireChanged;
|
||||
if (scanButton != null)
|
||||
scanButton.Pressed += HandleScanPressed;
|
||||
|
||||
controlsBound = true;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
private void UnbindControls()
|
||||
{
|
||||
if (!controlsBound)
|
||||
return;
|
||||
|
||||
if (horizontalWheel != null)
|
||||
horizontalWheel.ValueChanged -= HandleHorizontalChanged;
|
||||
if (verticalWheel != null)
|
||||
@@ -35,20 +63,18 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
fireLever.StateChanged -= HandleFireChanged;
|
||||
if (scanButton != null)
|
||||
scanButton.Pressed -= HandleScanPressed;
|
||||
|
||||
controlsBound = false;
|
||||
initialValuesApplied = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
private bool AreNetworkObjectsReady()
|
||||
{
|
||||
if (initialValuesApplied || mapController == null || !mapController.IsSpawned ||
|
||||
horizontalWheel == null || verticalWheel == null ||
|
||||
!horizontalWheel.IsSpawned || !verticalWheel.IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
initialValuesApplied = true;
|
||||
HandleHorizontalChanged(horizontalWheel.NormalizedValue);
|
||||
HandleVerticalChanged(verticalWheel.NormalizedValue);
|
||||
return mapController != null && mapController.IsSpawned &&
|
||||
(horizontalWheel == null || horizontalWheel.IsSpawned) &&
|
||||
(verticalWheel == null || verticalWheel.IsSpawned) &&
|
||||
(fireLever == null || fireLever.IsSpawned) &&
|
||||
(scanButton == null || scanButton.IsSpawned);
|
||||
}
|
||||
|
||||
private void HandleHorizontalChanged(float value) =>
|
||||
|
||||
@@ -14,11 +14,11 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private MapPlayer mapPlayer;
|
||||
[SerializeField] private RoundGameLoopController roundGameLoop;
|
||||
[SerializeField] private NetworkBaseHealth baseHealth;
|
||||
[SerializeField] private MapEnemyBalanceConfig enemyBalance;
|
||||
|
||||
[Header("Target System")]
|
||||
[SerializeField] private MapTargetController targetController;
|
||||
[SerializeField] private Transform targetParent;
|
||||
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
|
||||
|
||||
[Header("Radar System")]
|
||||
[SerializeField] private MapRadarController radarController;
|
||||
@@ -30,11 +30,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private Transform relayY;
|
||||
[SerializeField] private Transform crosshairVisual;
|
||||
|
||||
[Header("Pseudo Shot")]
|
||||
[Header("Shot Rules")]
|
||||
[SerializeField, Min(0.01f)] private float hitRadius = 0.65f;
|
||||
[SerializeField, Min(1)] private int shotDamage = 1;
|
||||
[SerializeField] private MissileType pseudoMissileType = MissileType.Standard;
|
||||
[SerializeField] private bool ignoreMissileTypeForPrototype = true;
|
||||
[FormerlySerializedAs("ignoreMissileTypeForPrototype")]
|
||||
[SerializeField] private bool ignoreMissileTypeRequirements = true;
|
||||
|
||||
[Header("Network Events")]
|
||||
[SerializeField] private UnityEvent<int> waveStarted;
|
||||
@@ -42,6 +43,9 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private UnityEvent allTargetsResolved;
|
||||
[SerializeField] private UnityEvent allTargetsDestroyed;
|
||||
|
||||
[Header("Shot Presentation")]
|
||||
[SerializeField] private MapImpactMarkerView impactMarkerView;
|
||||
|
||||
private readonly NetworkVariable<Vector2> crosshairCoordinates = new(
|
||||
new Vector2(5f, 5f), NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
@@ -77,6 +81,10 @@ public sealed class MapController : NetworkBehaviour
|
||||
public int CurrentWave => currentWave.Value;
|
||||
public int ActiveTargetCount => activeTargets.Count;
|
||||
public bool WaveIsActive => waveIsActive;
|
||||
public MapEnemyBalanceConfig EnemyBalance => enemyBalance;
|
||||
public float HitRadius => hitRadius;
|
||||
public int ShotDamage => shotDamage;
|
||||
public bool IgnoreMissileTypeRequirements => ignoreMissileTypeRequirements;
|
||||
public double RadarCooldownRemaining => IsSpawned && NetworkManager != null
|
||||
? Math.Max(0d, radarReadyAt.Value - NetworkManager.ServerTime.Time)
|
||||
: 0d;
|
||||
@@ -90,7 +98,6 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
simulationStep = Mathf.Max(0.02f, simulationStep);
|
||||
hitRadius = Mathf.Max(0.01f, hitRadius);
|
||||
shotDamage = Mathf.Max(1, shotDamage);
|
||||
}
|
||||
@@ -143,11 +150,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
if (!IsServer || !IsSpawned || !waveIsActive)
|
||||
return;
|
||||
|
||||
float step = enemyBalance != null ? enemyBalance.SimulationStep : 0.1f;
|
||||
simulationAccumulator += Time.deltaTime;
|
||||
while (simulationAccumulator >= simulationStep)
|
||||
while (simulationAccumulator >= step)
|
||||
{
|
||||
simulationAccumulator -= simulationStep;
|
||||
SimulateTargets(simulationStep);
|
||||
simulationAccumulator -= step;
|
||||
SimulateTargets(step);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +184,20 @@ public sealed class MapController : NetworkBehaviour
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (IsServer)
|
||||
ResolvePseudoShotOnServer();
|
||||
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||
else
|
||||
RequestPseudoFireRpc();
|
||||
}
|
||||
|
||||
public bool RequestShot(MissileType missileType)
|
||||
{
|
||||
if (!IsSpawned || !IsServer || missileType == MissileType.None)
|
||||
return false;
|
||||
|
||||
ResolveShotOnServer(missileType, ignoreMissileTypeRequirements);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RequestRadarScan()
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -224,7 +241,8 @@ public sealed class MapController : NetworkBehaviour
|
||||
private void SetCrosshairCoordinatesRpc(Vector2 coordinates) =>
|
||||
SetCrosshairCoordinatesOnServer(coordinates);
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestPseudoFireRpc() => ResolvePseudoShotOnServer();
|
||||
private void RequestPseudoFireRpc() =>
|
||||
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestRadarScanRpc() => ScanRadarOnServer();
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
@@ -253,10 +271,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private bool StartNextWaveOnServer()
|
||||
{
|
||||
if (!IsServer || targetController == null ||
|
||||
if (!IsServer || targetController == null || enemyBalance == null ||
|
||||
!targetController.HasValidConfiguration())
|
||||
{
|
||||
Debug.LogWarning("[Map] Cannot start target wave: map or target configs are incomplete.", this);
|
||||
Debug.LogWarning(
|
||||
"[Map] Cannot start target wave: map, enemy balance or target configs are incomplete.",
|
||||
this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -268,19 +288,18 @@ public sealed class MapController : NetworkBehaviour
|
||||
waveIsActive = true;
|
||||
simulationAccumulator = 0f;
|
||||
|
||||
for (int index = 0; index < targetController.TargetsPerWave; index++)
|
||||
for (int index = 0; index < enemyBalance.TargetsPerWave; index++)
|
||||
{
|
||||
int configIndex = targetController.GetRandomValidConfigIndex();
|
||||
TargetConfig config = targetController.GetConfig(configIndex);
|
||||
if (config == null)
|
||||
continue;
|
||||
|
||||
Vector2 coordinates = new(
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
|
||||
Vector2 coordinates = CreateSpawnCoordinates();
|
||||
activeTargets.Add(new MapTargetNetworkState(
|
||||
nextTargetId++, configIndex, coordinates,
|
||||
config.Health, config.TimeBeforeAttack));
|
||||
enemyBalance.GetHealth(config),
|
||||
enemyBalance.GetAttackDelay(config)));
|
||||
initialTargetCount++;
|
||||
}
|
||||
|
||||
@@ -303,11 +322,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
}
|
||||
|
||||
bool isAirTarget = config.TargetPrefab is AirTarget;
|
||||
if (isAirTarget && mapPlayer != null && config.MovementSpeed > 0f)
|
||||
float movementSpeed = enemyBalance.GetMovementSpeed(config);
|
||||
if (isAirTarget && mapPlayer != null && movementSpeed > 0f)
|
||||
{
|
||||
Vector2 destination = mapPlayer.MapCoordinates;
|
||||
state.Coordinates = Vector2.MoveTowards(
|
||||
state.Coordinates, destination, config.MovementSpeed * deltaTime);
|
||||
state.Coordinates, destination, movementSpeed * deltaTime);
|
||||
if (Vector2.Distance(state.Coordinates, destination) <= config.StoppingDistance)
|
||||
{
|
||||
ResolveEscapedTarget(index, state, config);
|
||||
@@ -333,7 +353,9 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private void ResolveEscapedTarget(int index, MapTargetNetworkState target, TargetConfig config)
|
||||
{
|
||||
int damage = config != null ? config.BaseDamage : 1;
|
||||
int damage = enemyBalance != null
|
||||
? enemyBalance.GetBaseDamage(config)
|
||||
: config != null ? config.BaseDamage : 1;
|
||||
escapedTargetCount++;
|
||||
activeTargets.RemoveAt(index);
|
||||
if (baseHealth != null)
|
||||
@@ -341,11 +363,15 @@ public sealed class MapController : NetworkBehaviour
|
||||
Debug.Log($"[Map] Target {target.TargetId} reached the base. Base damage: {damage}.", this);
|
||||
}
|
||||
|
||||
private void ResolvePseudoShotOnServer()
|
||||
private void ResolveShotOnServer(MissileType missileType, bool ignoreMissileType)
|
||||
{
|
||||
if (!IsServer || !waveIsActive)
|
||||
{
|
||||
PublishShotResult(false, -1, -1f);
|
||||
PublishShotResult(
|
||||
false, -1, crosshairCoordinates.Value, -1f, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] MISS with {missileType}: there is no active target wave.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,36 +391,67 @@ public sealed class MapController : NetworkBehaviour
|
||||
{
|
||||
MapTargetNetworkState target = activeTargets[nearestIndex];
|
||||
TargetConfig config = targetController.GetConfig(target.ConfigIndex);
|
||||
bool missileMatches = ignoreMissileTypeForPrototype || config == null ||
|
||||
bool missileMatches = ignoreMissileType || config == null ||
|
||||
config.RequiredMissileType == MissileType.None ||
|
||||
config.RequiredMissileType == pseudoMissileType;
|
||||
if (missileMatches)
|
||||
config.RequiredMissileType == missileType;
|
||||
if (!missileMatches)
|
||||
{
|
||||
target.Health -= shotDamage;
|
||||
if (target.Health <= 0)
|
||||
activeTargets.RemoveAt(nearestIndex);
|
||||
else
|
||||
activeTargets[nearestIndex] = target;
|
||||
|
||||
PublishShotResult(true, target.TargetId, nearestDistance);
|
||||
Debug.Log($"[Map] HIT target {target.TargetId} at {nearestDistance:0.00} map units.", this);
|
||||
TryCompleteWaveOnServer();
|
||||
PublishShotResult(
|
||||
false, target.TargetId, crosshairCoordinates.Value,
|
||||
nearestDistance, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] MISS target {target.TargetId}: {missileType} does not " +
|
||||
$"match required {config.RequiredMissileType}. Distance " +
|
||||
$"{nearestDistance:0.00} is inside hit radius {hitRadius:0.00}.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
target.Health -= shotDamage;
|
||||
if (target.Health <= 0)
|
||||
activeTargets.RemoveAt(nearestIndex);
|
||||
else
|
||||
activeTargets[nearestIndex] = target;
|
||||
|
||||
PublishShotResult(
|
||||
true, target.TargetId, target.Coordinates,
|
||||
nearestDistance, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] HIT target {target.TargetId} with {missileType} " +
|
||||
$"at {nearestDistance:0.00} map units. Damage: {shotDamage}.",
|
||||
this);
|
||||
TryCompleteWaveOnServer();
|
||||
return;
|
||||
}
|
||||
|
||||
int missedTargetId = nearestIndex >= 0 ? activeTargets[nearestIndex].TargetId : -1;
|
||||
PublishShotResult(false, missedTargetId, nearestDistance);
|
||||
float printedDistance = float.IsPositiveInfinity(nearestDistance) ? -1f : nearestDistance;
|
||||
Debug.Log($"[Map] MISS at {crosshairCoordinates.Value}. Nearest distance: {printedDistance:0.00}.", this);
|
||||
PublishShotResult(
|
||||
false, missedTargetId, crosshairCoordinates.Value,
|
||||
nearestDistance, missileType);
|
||||
if (nearestIndex < 0)
|
||||
{
|
||||
Debug.Log($"[Map] MISS with {missileType}: no active targets.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log(
|
||||
$"[Map] MISS target {activeTargets[nearestIndex].TargetId}: distance " +
|
||||
$"{nearestDistance:0.00} exceeds hit radius {hitRadius:0.00}.",
|
||||
this);
|
||||
}
|
||||
|
||||
private void PublishShotResult(bool hit, int targetId, float distance)
|
||||
private void PublishShotResult(
|
||||
bool hit,
|
||||
int targetId,
|
||||
Vector2 coordinates,
|
||||
float distance,
|
||||
MissileType missileType)
|
||||
{
|
||||
lastShotResult.Value = new MapShotResultState(
|
||||
lastShotResult.Value.Sequence + 1, hit, targetId,
|
||||
crosshairCoordinates.Value,
|
||||
float.IsPositiveInfinity(distance) ? -1f : distance);
|
||||
coordinates,
|
||||
float.IsPositiveInfinity(distance) ? -1f : distance,
|
||||
missileType);
|
||||
}
|
||||
|
||||
private void ScanRadarOnServer()
|
||||
@@ -428,11 +485,39 @@ public sealed class MapController : NetworkBehaviour
|
||||
$"escaped: {escapedTargetCount}.", this);
|
||||
}
|
||||
|
||||
private Vector2 CreateSpawnCoordinates()
|
||||
{
|
||||
Vector2 baseCoordinates = mapPlayer != null
|
||||
? mapPlayer.MapCoordinates
|
||||
: new Vector2(5f, 5f);
|
||||
Vector2 farthestCandidate = Vector2.zero;
|
||||
float farthestDistance = -1f;
|
||||
|
||||
for (int attempt = 0; attempt < enemyBalance.SpawnPositionAttempts; attempt++)
|
||||
{
|
||||
Vector2 candidate = new(
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
|
||||
float distance = Vector2.Distance(candidate, baseCoordinates);
|
||||
if (distance >= enemyBalance.MinimumSpawnDistanceFromBase)
|
||||
return candidate;
|
||||
|
||||
if (distance > farthestDistance)
|
||||
{
|
||||
farthestDistance = distance;
|
||||
farthestCandidate = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return farthestCandidate;
|
||||
}
|
||||
|
||||
private void ConfigurePresentation()
|
||||
{
|
||||
mapPlayer?.Configure(coordinateSystem);
|
||||
targetController?.Configure(coordinateSystem, targetParent, mapPlayer);
|
||||
radarController?.Configure(coordinateSystem, radarMarkPrefab);
|
||||
impactMarkerView?.Configure(coordinateSystem);
|
||||
crosshair?.Configure(
|
||||
coordinateSystem, relayX, relayY, crosshairVisual, targetController);
|
||||
}
|
||||
@@ -498,6 +583,7 @@ public sealed class MapController : NetworkBehaviour
|
||||
return;
|
||||
ShotResolved?.Invoke(current);
|
||||
shotResolved?.Invoke(current.Hit);
|
||||
impactMarkerView?.Show(current);
|
||||
}
|
||||
|
||||
private void HandleResolvedSequenceChanged(uint previous, uint current)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "MapEnemyBalance",
|
||||
menuName = "Map Radar/Enemy Balance Config")]
|
||||
public sealed class MapEnemyBalanceConfig : ScriptableObject
|
||||
{
|
||||
[Header("Wave")]
|
||||
[SerializeField, Min(0)] private int targetsPerWave = 5;
|
||||
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
|
||||
|
||||
[Header("Spawn")]
|
||||
[SerializeField, Range(0f, MapCoordinateSystem.CoordinateMaximum)]
|
||||
private float minimumSpawnDistanceFromBase = 5f;
|
||||
[SerializeField, Range(1, 128)] private int spawnPositionAttempts = 32;
|
||||
|
||||
[Header("Global Balance Multipliers")]
|
||||
[SerializeField, Min(0f)] private float movementSpeedMultiplier = 0.5f;
|
||||
[SerializeField, Min(0f)] private float attackDelayMultiplier = 1.5f;
|
||||
[SerializeField, Min(0.01f)] private float healthMultiplier = 1f;
|
||||
[SerializeField, Min(0f)] private float baseDamageMultiplier = 1f;
|
||||
|
||||
public int TargetsPerWave => Mathf.Max(0, targetsPerWave);
|
||||
public float SimulationStep => Mathf.Max(0.02f, simulationStep);
|
||||
public float MinimumSpawnDistanceFromBase => Mathf.Clamp(
|
||||
minimumSpawnDistanceFromBase,
|
||||
0f,
|
||||
MapCoordinateSystem.CoordinateMaximum);
|
||||
public int SpawnPositionAttempts => Mathf.Clamp(spawnPositionAttempts, 1, 128);
|
||||
public float MovementSpeedMultiplier => Mathf.Max(0f, movementSpeedMultiplier);
|
||||
public float AttackDelayMultiplier => Mathf.Max(0f, attackDelayMultiplier);
|
||||
public float HealthMultiplier => Mathf.Max(0.01f, healthMultiplier);
|
||||
public float BaseDamageMultiplier => Mathf.Max(0f, baseDamageMultiplier);
|
||||
|
||||
public float GetMovementSpeed(TargetConfig target) =>
|
||||
target != null ? target.MovementSpeed * MovementSpeedMultiplier : 0f;
|
||||
|
||||
public float GetAttackDelay(TargetConfig target) =>
|
||||
target != null ? target.TimeBeforeAttack * AttackDelayMultiplier : 0f;
|
||||
|
||||
public int GetHealth(TargetConfig target) => target != null
|
||||
? Mathf.Max(1, Mathf.RoundToInt(target.Health * HealthMultiplier))
|
||||
: 1;
|
||||
|
||||
public int GetBaseDamage(TargetConfig target) => target != null
|
||||
? Mathf.Max(0, Mathf.RoundToInt(target.BaseDamage * BaseDamageMultiplier))
|
||||
: 1;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
targetsPerWave = Mathf.Max(0, targetsPerWave);
|
||||
simulationStep = Mathf.Max(0.02f, simulationStep);
|
||||
minimumSpawnDistanceFromBase = Mathf.Clamp(
|
||||
minimumSpawnDistanceFromBase,
|
||||
0f,
|
||||
MapCoordinateSystem.CoordinateMaximum);
|
||||
spawnPositionAttempts = Mathf.Clamp(spawnPositionAttempts, 1, 128);
|
||||
movementSpeedMultiplier = Mathf.Max(0f, movementSpeedMultiplier);
|
||||
attackDelayMultiplier = Mathf.Max(0f, attackDelayMultiplier);
|
||||
healthMultiplier = Mathf.Max(0.01f, healthMultiplier);
|
||||
baseDamageMultiplier = Mathf.Max(0f, baseDamageMultiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dfcdffb89f04cd468e4f8386b4f67e8
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class MapImpactMarkerView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private MapCoordinateSystem coordinateSystem;
|
||||
[SerializeField, Min(0.01f)] private float markerLength = 0.35f;
|
||||
[SerializeField, Min(0.005f)] private float markerThickness = 0.035f;
|
||||
[SerializeField, Min(0f)] private float surfaceOffset = 0.025f;
|
||||
[SerializeField, Min(0.1f)] private float lifetime = 5f;
|
||||
[SerializeField] private Color hitColor = new(1f, 0.1f, 0.05f, 1f);
|
||||
[SerializeField] private Color missColor = new(1f, 0.75f, 0f, 1f);
|
||||
[SerializeField] private bool showMisses = true;
|
||||
[SerializeField] private UnityEvent hitMarked;
|
||||
[SerializeField] private UnityEvent missMarked;
|
||||
|
||||
public void Configure(MapCoordinateSystem coordinates) => coordinateSystem = coordinates;
|
||||
|
||||
public void Show(MapShotResultState result)
|
||||
{
|
||||
if (coordinateSystem == null || (!result.Hit && !showMisses))
|
||||
return;
|
||||
|
||||
CreateMarker(result.Coordinates, result.Hit ? hitColor : missColor);
|
||||
if (result.Hit)
|
||||
hitMarked?.Invoke();
|
||||
else
|
||||
missMarked?.Invoke();
|
||||
}
|
||||
|
||||
private void CreateMarker(Vector2 coordinates, Color color)
|
||||
{
|
||||
Vector3 horizontal = coordinateSystem.BottomRight.position -
|
||||
coordinateSystem.BottomLeft.position;
|
||||
Vector3 vertical = coordinateSystem.TopLeft.position -
|
||||
coordinateSystem.BottomLeft.position;
|
||||
Vector3 normal = Vector3.Cross(horizontal, vertical).normalized;
|
||||
if (normal.sqrMagnitude < 0.5f)
|
||||
normal = transform.forward;
|
||||
|
||||
GameObject marker = new($"Impact {coordinates.x:0.0}, {coordinates.y:0.0}");
|
||||
marker.transform.SetParent(transform, true);
|
||||
marker.transform.SetPositionAndRotation(
|
||||
coordinateSystem.CoordinatesToWorldPosition(coordinates) + normal * surfaceOffset,
|
||||
Quaternion.LookRotation(normal, vertical.normalized));
|
||||
|
||||
CreateBar(marker.transform, 45f, color);
|
||||
CreateBar(marker.transform, -45f, color);
|
||||
StartCoroutine(DestroyAfterLifetime(marker));
|
||||
}
|
||||
|
||||
private void CreateBar(Transform parent, float angle, Color color)
|
||||
{
|
||||
GameObject bar = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
bar.name = "Marker Bar";
|
||||
bar.transform.SetParent(parent, false);
|
||||
bar.transform.localRotation = Quaternion.Euler(0f, 0f, angle);
|
||||
bar.transform.localScale = new Vector3(
|
||||
markerLength, markerThickness, markerThickness);
|
||||
Collider collider = bar.GetComponent<Collider>();
|
||||
if (collider != null)
|
||||
Destroy(collider);
|
||||
Renderer renderer = bar.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
renderer.material.color = color;
|
||||
}
|
||||
|
||||
private IEnumerator DestroyAfterLifetime(GameObject marker)
|
||||
{
|
||||
yield return new WaitForSeconds(lifetime);
|
||||
if (marker != null)
|
||||
Destroy(marker);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b3852b847aa89b48b9d38476e56250e
|
||||
@@ -5,8 +5,7 @@ using UnityEngine;
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class MapTargetController : MonoBehaviour
|
||||
{
|
||||
[Header("Wave Settings")]
|
||||
[SerializeField, Min(0)] private int targetsPerWave = 5;
|
||||
[Header("Target Types")]
|
||||
[SerializeField] private TargetConfig[] randomTargetConfigs = Array.Empty<TargetConfig>();
|
||||
[SerializeField] private bool showLiveTargetVisuals;
|
||||
|
||||
@@ -24,11 +23,9 @@ public sealed class MapTargetController : MonoBehaviour
|
||||
public IReadOnlyList<MapTarget> Targets => currentWaveTargets;
|
||||
public IReadOnlyList<TargetConfig> TargetConfigs => randomTargetConfigs;
|
||||
public int CurrentWave => currentWave;
|
||||
public int TargetsPerWave => targetsPerWave;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
targetsPerWave = Mathf.Max(0, targetsPerWave);
|
||||
currentWave = Mathf.Max(0, currentWave);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,19 +51,22 @@ public struct MapShotResultState : INetworkSerializable,
|
||||
public int TargetId;
|
||||
public Vector2 Coordinates;
|
||||
public float Distance;
|
||||
public MissileType MissileType;
|
||||
|
||||
public MapShotResultState(
|
||||
uint sequence,
|
||||
bool hit,
|
||||
int targetId,
|
||||
Vector2 coordinates,
|
||||
float distance)
|
||||
float distance,
|
||||
MissileType missileType)
|
||||
{
|
||||
Sequence = sequence;
|
||||
Hit = hit;
|
||||
TargetId = targetId;
|
||||
Coordinates = coordinates;
|
||||
Distance = distance;
|
||||
MissileType = missileType;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||
@@ -74,10 +77,11 @@ public struct MapShotResultState : INetworkSerializable,
|
||||
serializer.SerializeValue(ref TargetId);
|
||||
serializer.SerializeValue(ref Coordinates);
|
||||
serializer.SerializeValue(ref Distance);
|
||||
serializer.SerializeValue(ref MissileType);
|
||||
}
|
||||
|
||||
public bool Equals(MapShotResultState other) =>
|
||||
Sequence == other.Sequence && Hit == other.Hit &&
|
||||
TargetId == other.TargetId && Coordinates == other.Coordinates &&
|
||||
Distance.Equals(other.Distance);
|
||||
Distance.Equals(other.Distance) && MissileType == other.MissileType;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user