Fixed
This commit is contained in:
@@ -510,7 +510,28 @@ MonoBehaviour:
|
|||||||
- Assets/JMO Assets/WarFX/Demo/Assets/WFX_Demo_M4.fbx
|
- Assets/JMO Assets/WarFX/Demo/Assets/WFX_Demo_M4.fbx
|
||||||
- Assets/JMO Assets/WarFX/Mesh/CirclePlane12v.FBX
|
- Assets/JMO Assets/WarFX/Mesh/CirclePlane12v.FBX
|
||||||
- Assets/JMO Assets/WarFX/Mesh/Plane1x1.FBX
|
- Assets/JMO Assets/WarFX/Mesh/Plane1x1.FBX
|
||||||
uvOverlapAssetList: 00000000ffffffff010100000101000001010000000000000000000000000000000000000000000001010000000000000000000001010000000000000000000000000000000000000000000000000000010100000000000000000000000000000101000000000000010100000000000000000000000000000101000000000000010100000000000001010000000000000000000001010000010100000000000000000000010100000000000000000000010100000101000000000000000000000000000000000000000000000000000000000000000000000101000000000000010100000101000000000000000000000000000000000000000000000000000000000000
|
- Assets/UnityTechnologies/EffectExamples/Misc Effects/Animations/@EllenIdle.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/Railing.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/SideWall.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Misc Effects/Models/Ellen.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/Stairs.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/AreaLine.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Magic Effects/Models/IceLance.FBX
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/FloorLong.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/SmallLight.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/Step.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/WaterEffects/Models/DropSplash.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Misc Effects/Models/Candle.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Magic Effects/Models/IceShard.FBX
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/EndWall.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Magic Effects/Models/RockDebris.FBX
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Magic Effects/Models/RockSpike.FBX
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/CornerWall.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Misc Effects/Models/Flakes.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Misc Effects/Models/FireFly.fbx
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Smoke & Steam Effects/Models/RockDebris.FBX
|
||||||
|
- Assets/UnityTechnologies/EffectExamples/Shared/Environment/Models/LongOverhangWall.fbx
|
||||||
|
uvOverlapAssetList: 00000000ffffffff010100000101000001010000000000000000000000000000000000000000000001010000000000000000000001010000000000000000000000000000000000000000000000000000010100000000000000000000000000000101000000000000010100000000000000000000000000000101000000000000010100000000000001010000000000000000000001010000010100000000000000000000010100000000000000000000010100000101000000000000000000000000000000000000000000000000000000000000000000000101000000000000010100000101000000000000000000000000000000000000000000000000000000000000000000000101000001010000000000000000000000000000010100000000000001010000000000000000000001010000010100000101000000000000000000000101000001010000010100000000000001010000
|
||||||
xatlasWarningShown: 0
|
xatlasWarningShown: 0
|
||||||
foundCompatibleSetup: 1
|
foundCompatibleSetup: 1
|
||||||
gpuName_2: NVIDIA GeForce RTX 5080
|
gpuName_2: NVIDIA GeForce RTX 5080
|
||||||
|
|||||||
@@ -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 =
|
private const string LeverPrefabPath =
|
||||||
"Assets/CONTENT/Game/Prefabs/Interaction/PhysicalLever.prefab";
|
"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")]
|
[MenuItem("FORT-BO/Map Radar/Configure Network Prototype")]
|
||||||
public static void ConfigureNetworkPrototype()
|
public static void ConfigureNetworkPrototype()
|
||||||
@@ -31,6 +33,7 @@ public static class MapRadarNetworkSetup
|
|||||||
GetOrAdd<NetworkObject>(mapController.gameObject);
|
GetOrAdd<NetworkObject>(mapController.gameObject);
|
||||||
NetworkBaseHealth baseHealth = GetOrAdd<NetworkBaseHealth>(mapController.gameObject);
|
NetworkBaseHealth baseHealth = GetOrAdd<NetworkBaseHealth>(mapController.gameObject);
|
||||||
MapControlBinder binder = GetOrAdd<MapControlBinder>(mapController.gameObject);
|
MapControlBinder binder = GetOrAdd<MapControlBinder>(mapController.gameObject);
|
||||||
|
MapEnemyBalanceConfig enemyBalance = GetOrCreateEnemyBalance();
|
||||||
|
|
||||||
Transform controlsRoot = GetOrCreateChild(
|
Transform controlsRoot = GetOrCreateChild(
|
||||||
mapController.transform.root,
|
mapController.transform.root,
|
||||||
@@ -62,6 +65,7 @@ public static class MapRadarNetworkSetup
|
|||||||
SetObject(map, "roundGameLoop",
|
SetObject(map, "roundGameLoop",
|
||||||
Object.FindFirstObjectByType<RoundGameLoopController>());
|
Object.FindFirstObjectByType<RoundGameLoopController>());
|
||||||
SetObject(map, "baseHealth", baseHealth);
|
SetObject(map, "baseHealth", baseHealth);
|
||||||
|
SetObject(map, "enemyBalance", enemyBalance);
|
||||||
map.ApplyModifiedPropertiesWithoutUndo();
|
map.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
SerializedObject binding = new(binder);
|
SerializedObject binding = new(binder);
|
||||||
@@ -82,10 +86,24 @@ public static class MapRadarNetworkSetup
|
|||||||
|
|
||||||
Debug.Log(
|
Debug.Log(
|
||||||
"[Map] Network prototype configured: X/Y wheels, fire lever, " +
|
"[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);
|
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(
|
private static NetworkPhysicalWheelInteractable CreateWheel(
|
||||||
Transform parent,
|
Transform parent,
|
||||||
string name,
|
string name,
|
||||||
|
|||||||
@@ -9,12 +9,35 @@ public sealed class MapControlBinder : MonoBehaviour
|
|||||||
[SerializeField] private NetworkPhysicalLeverInteractable fireLever;
|
[SerializeField] private NetworkPhysicalLeverInteractable fireLever;
|
||||||
[SerializeField] private NetworkButtonInteractable scanButton;
|
[SerializeField] private NetworkButtonInteractable scanButton;
|
||||||
|
|
||||||
|
private bool controlsBound;
|
||||||
private bool initialValuesApplied;
|
private bool initialValuesApplied;
|
||||||
|
|
||||||
private void Reset() => mapController = GetComponent<MapController>();
|
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)
|
if (horizontalWheel != null)
|
||||||
horizontalWheel.ValueChanged += HandleHorizontalChanged;
|
horizontalWheel.ValueChanged += HandleHorizontalChanged;
|
||||||
if (verticalWheel != null)
|
if (verticalWheel != null)
|
||||||
@@ -23,10 +46,15 @@ public sealed class MapControlBinder : MonoBehaviour
|
|||||||
fireLever.StateChanged += HandleFireChanged;
|
fireLever.StateChanged += HandleFireChanged;
|
||||||
if (scanButton != null)
|
if (scanButton != null)
|
||||||
scanButton.Pressed += HandleScanPressed;
|
scanButton.Pressed += HandleScanPressed;
|
||||||
|
|
||||||
|
controlsBound = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnDisable()
|
private void UnbindControls()
|
||||||
{
|
{
|
||||||
|
if (!controlsBound)
|
||||||
|
return;
|
||||||
|
|
||||||
if (horizontalWheel != null)
|
if (horizontalWheel != null)
|
||||||
horizontalWheel.ValueChanged -= HandleHorizontalChanged;
|
horizontalWheel.ValueChanged -= HandleHorizontalChanged;
|
||||||
if (verticalWheel != null)
|
if (verticalWheel != null)
|
||||||
@@ -35,20 +63,18 @@ public sealed class MapControlBinder : MonoBehaviour
|
|||||||
fireLever.StateChanged -= HandleFireChanged;
|
fireLever.StateChanged -= HandleFireChanged;
|
||||||
if (scanButton != null)
|
if (scanButton != null)
|
||||||
scanButton.Pressed -= HandleScanPressed;
|
scanButton.Pressed -= HandleScanPressed;
|
||||||
|
|
||||||
|
controlsBound = false;
|
||||||
|
initialValuesApplied = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Update()
|
private bool AreNetworkObjectsReady()
|
||||||
{
|
{
|
||||||
if (initialValuesApplied || mapController == null || !mapController.IsSpawned ||
|
return mapController != null && mapController.IsSpawned &&
|
||||||
horizontalWheel == null || verticalWheel == null ||
|
(horizontalWheel == null || horizontalWheel.IsSpawned) &&
|
||||||
!horizontalWheel.IsSpawned || !verticalWheel.IsSpawned)
|
(verticalWheel == null || verticalWheel.IsSpawned) &&
|
||||||
{
|
(fireLever == null || fireLever.IsSpawned) &&
|
||||||
return;
|
(scanButton == null || scanButton.IsSpawned);
|
||||||
}
|
|
||||||
|
|
||||||
initialValuesApplied = true;
|
|
||||||
HandleHorizontalChanged(horizontalWheel.NormalizedValue);
|
|
||||||
HandleVerticalChanged(verticalWheel.NormalizedValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleHorizontalChanged(float value) =>
|
private void HandleHorizontalChanged(float value) =>
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
[SerializeField] private MapPlayer mapPlayer;
|
[SerializeField] private MapPlayer mapPlayer;
|
||||||
[SerializeField] private RoundGameLoopController roundGameLoop;
|
[SerializeField] private RoundGameLoopController roundGameLoop;
|
||||||
[SerializeField] private NetworkBaseHealth baseHealth;
|
[SerializeField] private NetworkBaseHealth baseHealth;
|
||||||
|
[SerializeField] private MapEnemyBalanceConfig enemyBalance;
|
||||||
|
|
||||||
[Header("Target System")]
|
[Header("Target System")]
|
||||||
[SerializeField] private MapTargetController targetController;
|
[SerializeField] private MapTargetController targetController;
|
||||||
[SerializeField] private Transform targetParent;
|
[SerializeField] private Transform targetParent;
|
||||||
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
|
|
||||||
|
|
||||||
[Header("Radar System")]
|
[Header("Radar System")]
|
||||||
[SerializeField] private MapRadarController radarController;
|
[SerializeField] private MapRadarController radarController;
|
||||||
@@ -30,11 +30,12 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
[SerializeField] private Transform relayY;
|
[SerializeField] private Transform relayY;
|
||||||
[SerializeField] private Transform crosshairVisual;
|
[SerializeField] private Transform crosshairVisual;
|
||||||
|
|
||||||
[Header("Pseudo Shot")]
|
[Header("Shot Rules")]
|
||||||
[SerializeField, Min(0.01f)] private float hitRadius = 0.65f;
|
[SerializeField, Min(0.01f)] private float hitRadius = 0.65f;
|
||||||
[SerializeField, Min(1)] private int shotDamage = 1;
|
[SerializeField, Min(1)] private int shotDamage = 1;
|
||||||
[SerializeField] private MissileType pseudoMissileType = MissileType.Standard;
|
[SerializeField] private MissileType pseudoMissileType = MissileType.Standard;
|
||||||
[SerializeField] private bool ignoreMissileTypeForPrototype = true;
|
[FormerlySerializedAs("ignoreMissileTypeForPrototype")]
|
||||||
|
[SerializeField] private bool ignoreMissileTypeRequirements = true;
|
||||||
|
|
||||||
[Header("Network Events")]
|
[Header("Network Events")]
|
||||||
[SerializeField] private UnityEvent<int> waveStarted;
|
[SerializeField] private UnityEvent<int> waveStarted;
|
||||||
@@ -42,6 +43,9 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
[SerializeField] private UnityEvent allTargetsResolved;
|
[SerializeField] private UnityEvent allTargetsResolved;
|
||||||
[SerializeField] private UnityEvent allTargetsDestroyed;
|
[SerializeField] private UnityEvent allTargetsDestroyed;
|
||||||
|
|
||||||
|
[Header("Shot Presentation")]
|
||||||
|
[SerializeField] private MapImpactMarkerView impactMarkerView;
|
||||||
|
|
||||||
private readonly NetworkVariable<Vector2> crosshairCoordinates = new(
|
private readonly NetworkVariable<Vector2> crosshairCoordinates = new(
|
||||||
new Vector2(5f, 5f), NetworkVariableReadPermission.Everyone,
|
new Vector2(5f, 5f), NetworkVariableReadPermission.Everyone,
|
||||||
NetworkVariableWritePermission.Server);
|
NetworkVariableWritePermission.Server);
|
||||||
@@ -77,6 +81,10 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
public int CurrentWave => currentWave.Value;
|
public int CurrentWave => currentWave.Value;
|
||||||
public int ActiveTargetCount => activeTargets.Count;
|
public int ActiveTargetCount => activeTargets.Count;
|
||||||
public bool WaveIsActive => waveIsActive;
|
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
|
public double RadarCooldownRemaining => IsSpawned && NetworkManager != null
|
||||||
? Math.Max(0d, radarReadyAt.Value - NetworkManager.ServerTime.Time)
|
? Math.Max(0d, radarReadyAt.Value - NetworkManager.ServerTime.Time)
|
||||||
: 0d;
|
: 0d;
|
||||||
@@ -90,7 +98,6 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
simulationStep = Mathf.Max(0.02f, simulationStep);
|
|
||||||
hitRadius = Mathf.Max(0.01f, hitRadius);
|
hitRadius = Mathf.Max(0.01f, hitRadius);
|
||||||
shotDamage = Mathf.Max(1, shotDamage);
|
shotDamage = Mathf.Max(1, shotDamage);
|
||||||
}
|
}
|
||||||
@@ -143,11 +150,12 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
if (!IsServer || !IsSpawned || !waveIsActive)
|
if (!IsServer || !IsSpawned || !waveIsActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
float step = enemyBalance != null ? enemyBalance.SimulationStep : 0.1f;
|
||||||
simulationAccumulator += Time.deltaTime;
|
simulationAccumulator += Time.deltaTime;
|
||||||
while (simulationAccumulator >= simulationStep)
|
while (simulationAccumulator >= step)
|
||||||
{
|
{
|
||||||
simulationAccumulator -= simulationStep;
|
simulationAccumulator -= step;
|
||||||
SimulateTargets(simulationStep);
|
SimulateTargets(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,11 +184,20 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
if (!IsSpawned)
|
if (!IsSpawned)
|
||||||
return;
|
return;
|
||||||
if (IsServer)
|
if (IsServer)
|
||||||
ResolvePseudoShotOnServer();
|
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||||
else
|
else
|
||||||
RequestPseudoFireRpc();
|
RequestPseudoFireRpc();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool RequestShot(MissileType missileType)
|
||||||
|
{
|
||||||
|
if (!IsSpawned || !IsServer || missileType == MissileType.None)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ResolveShotOnServer(missileType, ignoreMissileTypeRequirements);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public void RequestRadarScan()
|
public void RequestRadarScan()
|
||||||
{
|
{
|
||||||
if (!IsSpawned)
|
if (!IsSpawned)
|
||||||
@@ -224,7 +241,8 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
private void SetCrosshairCoordinatesRpc(Vector2 coordinates) =>
|
private void SetCrosshairCoordinatesRpc(Vector2 coordinates) =>
|
||||||
SetCrosshairCoordinatesOnServer(coordinates);
|
SetCrosshairCoordinatesOnServer(coordinates);
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
private void RequestPseudoFireRpc() => ResolvePseudoShotOnServer();
|
private void RequestPseudoFireRpc() =>
|
||||||
|
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
private void RequestRadarScanRpc() => ScanRadarOnServer();
|
private void RequestRadarScanRpc() => ScanRadarOnServer();
|
||||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
@@ -253,10 +271,12 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
|
|
||||||
private bool StartNextWaveOnServer()
|
private bool StartNextWaveOnServer()
|
||||||
{
|
{
|
||||||
if (!IsServer || targetController == null ||
|
if (!IsServer || targetController == null || enemyBalance == null ||
|
||||||
!targetController.HasValidConfiguration())
|
!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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,19 +288,18 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
waveIsActive = true;
|
waveIsActive = true;
|
||||||
simulationAccumulator = 0f;
|
simulationAccumulator = 0f;
|
||||||
|
|
||||||
for (int index = 0; index < targetController.TargetsPerWave; index++)
|
for (int index = 0; index < enemyBalance.TargetsPerWave; index++)
|
||||||
{
|
{
|
||||||
int configIndex = targetController.GetRandomValidConfigIndex();
|
int configIndex = targetController.GetRandomValidConfigIndex();
|
||||||
TargetConfig config = targetController.GetConfig(configIndex);
|
TargetConfig config = targetController.GetConfig(configIndex);
|
||||||
if (config == null)
|
if (config == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
Vector2 coordinates = new(
|
Vector2 coordinates = CreateSpawnCoordinates();
|
||||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
|
|
||||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
|
|
||||||
activeTargets.Add(new MapTargetNetworkState(
|
activeTargets.Add(new MapTargetNetworkState(
|
||||||
nextTargetId++, configIndex, coordinates,
|
nextTargetId++, configIndex, coordinates,
|
||||||
config.Health, config.TimeBeforeAttack));
|
enemyBalance.GetHealth(config),
|
||||||
|
enemyBalance.GetAttackDelay(config)));
|
||||||
initialTargetCount++;
|
initialTargetCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,11 +322,12 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isAirTarget = config.TargetPrefab is AirTarget;
|
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;
|
Vector2 destination = mapPlayer.MapCoordinates;
|
||||||
state.Coordinates = Vector2.MoveTowards(
|
state.Coordinates = Vector2.MoveTowards(
|
||||||
state.Coordinates, destination, config.MovementSpeed * deltaTime);
|
state.Coordinates, destination, movementSpeed * deltaTime);
|
||||||
if (Vector2.Distance(state.Coordinates, destination) <= config.StoppingDistance)
|
if (Vector2.Distance(state.Coordinates, destination) <= config.StoppingDistance)
|
||||||
{
|
{
|
||||||
ResolveEscapedTarget(index, state, config);
|
ResolveEscapedTarget(index, state, config);
|
||||||
@@ -333,7 +353,9 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
|
|
||||||
private void ResolveEscapedTarget(int index, MapTargetNetworkState target, TargetConfig config)
|
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++;
|
escapedTargetCount++;
|
||||||
activeTargets.RemoveAt(index);
|
activeTargets.RemoveAt(index);
|
||||||
if (baseHealth != null)
|
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);
|
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)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,36 +391,67 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
{
|
{
|
||||||
MapTargetNetworkState target = activeTargets[nearestIndex];
|
MapTargetNetworkState target = activeTargets[nearestIndex];
|
||||||
TargetConfig config = targetController.GetConfig(target.ConfigIndex);
|
TargetConfig config = targetController.GetConfig(target.ConfigIndex);
|
||||||
bool missileMatches = ignoreMissileTypeForPrototype || config == null ||
|
bool missileMatches = ignoreMissileType || config == null ||
|
||||||
config.RequiredMissileType == MissileType.None ||
|
config.RequiredMissileType == MissileType.None ||
|
||||||
config.RequiredMissileType == pseudoMissileType;
|
config.RequiredMissileType == missileType;
|
||||||
if (missileMatches)
|
if (!missileMatches)
|
||||||
{
|
{
|
||||||
target.Health -= shotDamage;
|
PublishShotResult(
|
||||||
if (target.Health <= 0)
|
false, target.TargetId, crosshairCoordinates.Value,
|
||||||
activeTargets.RemoveAt(nearestIndex);
|
nearestDistance, missileType);
|
||||||
else
|
Debug.Log(
|
||||||
activeTargets[nearestIndex] = target;
|
$"[Map] MISS target {target.TargetId}: {missileType} does not " +
|
||||||
|
$"match required {config.RequiredMissileType}. Distance " +
|
||||||
PublishShotResult(true, target.TargetId, nearestDistance);
|
$"{nearestDistance:0.00} is inside hit radius {hitRadius:0.00}.",
|
||||||
Debug.Log($"[Map] HIT target {target.TargetId} at {nearestDistance:0.00} map units.", this);
|
this);
|
||||||
TryCompleteWaveOnServer();
|
|
||||||
return;
|
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;
|
int missedTargetId = nearestIndex >= 0 ? activeTargets[nearestIndex].TargetId : -1;
|
||||||
PublishShotResult(false, missedTargetId, nearestDistance);
|
PublishShotResult(
|
||||||
float printedDistance = float.IsPositiveInfinity(nearestDistance) ? -1f : nearestDistance;
|
false, missedTargetId, crosshairCoordinates.Value,
|
||||||
Debug.Log($"[Map] MISS at {crosshairCoordinates.Value}. Nearest distance: {printedDistance:0.00}.", this);
|
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 = new MapShotResultState(
|
||||||
lastShotResult.Value.Sequence + 1, hit, targetId,
|
lastShotResult.Value.Sequence + 1, hit, targetId,
|
||||||
crosshairCoordinates.Value,
|
coordinates,
|
||||||
float.IsPositiveInfinity(distance) ? -1f : distance);
|
float.IsPositiveInfinity(distance) ? -1f : distance,
|
||||||
|
missileType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ScanRadarOnServer()
|
private void ScanRadarOnServer()
|
||||||
@@ -428,11 +485,39 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
$"escaped: {escapedTargetCount}.", this);
|
$"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()
|
private void ConfigurePresentation()
|
||||||
{
|
{
|
||||||
mapPlayer?.Configure(coordinateSystem);
|
mapPlayer?.Configure(coordinateSystem);
|
||||||
targetController?.Configure(coordinateSystem, targetParent, mapPlayer);
|
targetController?.Configure(coordinateSystem, targetParent, mapPlayer);
|
||||||
radarController?.Configure(coordinateSystem, radarMarkPrefab);
|
radarController?.Configure(coordinateSystem, radarMarkPrefab);
|
||||||
|
impactMarkerView?.Configure(coordinateSystem);
|
||||||
crosshair?.Configure(
|
crosshair?.Configure(
|
||||||
coordinateSystem, relayX, relayY, crosshairVisual, targetController);
|
coordinateSystem, relayX, relayY, crosshairVisual, targetController);
|
||||||
}
|
}
|
||||||
@@ -498,6 +583,7 @@ public sealed class MapController : NetworkBehaviour
|
|||||||
return;
|
return;
|
||||||
ShotResolved?.Invoke(current);
|
ShotResolved?.Invoke(current);
|
||||||
shotResolved?.Invoke(current.Hit);
|
shotResolved?.Invoke(current.Hit);
|
||||||
|
impactMarkerView?.Show(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleResolvedSequenceChanged(uint previous, uint 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]
|
[DisallowMultipleComponent]
|
||||||
public sealed class MapTargetController : MonoBehaviour
|
public sealed class MapTargetController : MonoBehaviour
|
||||||
{
|
{
|
||||||
[Header("Wave Settings")]
|
[Header("Target Types")]
|
||||||
[SerializeField, Min(0)] private int targetsPerWave = 5;
|
|
||||||
[SerializeField] private TargetConfig[] randomTargetConfigs = Array.Empty<TargetConfig>();
|
[SerializeField] private TargetConfig[] randomTargetConfigs = Array.Empty<TargetConfig>();
|
||||||
[SerializeField] private bool showLiveTargetVisuals;
|
[SerializeField] private bool showLiveTargetVisuals;
|
||||||
|
|
||||||
@@ -24,11 +23,9 @@ public sealed class MapTargetController : MonoBehaviour
|
|||||||
public IReadOnlyList<MapTarget> Targets => currentWaveTargets;
|
public IReadOnlyList<MapTarget> Targets => currentWaveTargets;
|
||||||
public IReadOnlyList<TargetConfig> TargetConfigs => randomTargetConfigs;
|
public IReadOnlyList<TargetConfig> TargetConfigs => randomTargetConfigs;
|
||||||
public int CurrentWave => currentWave;
|
public int CurrentWave => currentWave;
|
||||||
public int TargetsPerWave => targetsPerWave;
|
|
||||||
|
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
targetsPerWave = Mathf.Max(0, targetsPerWave);
|
|
||||||
currentWave = Mathf.Max(0, currentWave);
|
currentWave = Mathf.Max(0, currentWave);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,19 +51,22 @@ public struct MapShotResultState : INetworkSerializable,
|
|||||||
public int TargetId;
|
public int TargetId;
|
||||||
public Vector2 Coordinates;
|
public Vector2 Coordinates;
|
||||||
public float Distance;
|
public float Distance;
|
||||||
|
public MissileType MissileType;
|
||||||
|
|
||||||
public MapShotResultState(
|
public MapShotResultState(
|
||||||
uint sequence,
|
uint sequence,
|
||||||
bool hit,
|
bool hit,
|
||||||
int targetId,
|
int targetId,
|
||||||
Vector2 coordinates,
|
Vector2 coordinates,
|
||||||
float distance)
|
float distance,
|
||||||
|
MissileType missileType)
|
||||||
{
|
{
|
||||||
Sequence = sequence;
|
Sequence = sequence;
|
||||||
Hit = hit;
|
Hit = hit;
|
||||||
TargetId = targetId;
|
TargetId = targetId;
|
||||||
Coordinates = coordinates;
|
Coordinates = coordinates;
|
||||||
Distance = distance;
|
Distance = distance;
|
||||||
|
MissileType = missileType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||||
@@ -74,10 +77,11 @@ public struct MapShotResultState : INetworkSerializable,
|
|||||||
serializer.SerializeValue(ref TargetId);
|
serializer.SerializeValue(ref TargetId);
|
||||||
serializer.SerializeValue(ref Coordinates);
|
serializer.SerializeValue(ref Coordinates);
|
||||||
serializer.SerializeValue(ref Distance);
|
serializer.SerializeValue(ref Distance);
|
||||||
|
serializer.SerializeValue(ref MissileType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(MapShotResultState other) =>
|
public bool Equals(MapShotResultState other) =>
|
||||||
Sequence == other.Sequence && Hit == other.Hit &&
|
Sequence == other.Sequence && Hit == other.Hit &&
|
||||||
TargetId == other.TargetId && Coordinates == other.Coordinates &&
|
TargetId == other.TargetId && Coordinates == other.Coordinates &&
|
||||||
Distance.Equals(other.Distance);
|
Distance.Equals(other.Distance) && MissileType == other.MissileType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ GameObject:
|
|||||||
- component: {fileID: 6442729922619842682}
|
- component: {fileID: 6442729922619842682}
|
||||||
- component: {fileID: 5032171862004131710}
|
- component: {fileID: 5032171862004131710}
|
||||||
- component: {fileID: 5463195673691487104}
|
- component: {fileID: 5463195673691487104}
|
||||||
|
- component: {fileID: 5685992952666159115}
|
||||||
|
- component: {fileID: 5909870405075214492}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: NetworkPlayer
|
m_Name: NetworkPlayer
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -205,6 +207,7 @@ MonoBehaviour:
|
|||||||
sprintSpeed: 7
|
sprintSpeed: 7
|
||||||
jumpHeight: 1.4
|
jumpHeight: 1.4
|
||||||
gravity: 20
|
gravity: 20
|
||||||
|
impulseDamping: 9
|
||||||
--- !u!114 &3719056139727702052
|
--- !u!114 &3719056139727702052
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -219,6 +222,7 @@ MonoBehaviour:
|
|||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
verticalLookLimit: 89
|
verticalLookLimit: 89
|
||||||
|
remotePitchFollowSpeed: 20
|
||||||
--- !u!114 &2519868927162778852
|
--- !u!114 &2519868927162778852
|
||||||
MonoBehaviour:
|
MonoBehaviour:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
@@ -305,6 +309,31 @@ MonoBehaviour:
|
|||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
ShowTopMostFoldoutHeaderGroup: 1
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
--- !u!114 &5685992952666159115
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2724913065371725993}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: b12d3c124e93b5f4f9c06c7e870d4068, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
--- !u!114 &5909870405075214492
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2724913065371725993}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 6a7ddedc58d3467449dfbe83af1c9c51, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
--- !u!1 &4726019310047661441
|
--- !u!1 &4726019310047661441
|
||||||
GameObject:
|
GameObject:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2b80031cb8a86414c9b054ed5a280465
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
%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: e069cd58c327cc94e9c2598c9e4c449f, type: 3}
|
||||||
|
m_Name: CannonConfig
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
minimumDelay: 0
|
||||||
|
maximumDelay: 30
|
||||||
|
defaultDelay: 5
|
||||||
|
adjustmentStep: 1
|
||||||
|
acceptDefectiveRockets: 1
|
||||||
|
acceptCriticallyDamagedRockets: 0
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23aa810e05d4cbc4da4bb729f5e54688
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
%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: 96e16f6f88696ea4c8b884f3e7cef9f7, type: 3}
|
||||||
|
m_Name: CannonShockwave
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
zones:
|
||||||
|
- Radius: 8
|
||||||
|
PlayerImpulse: 12
|
||||||
|
RigidbodyImpulse: 18
|
||||||
|
UpwardBias: 0.35
|
||||||
|
CameraShakeAmplitude: 0.22
|
||||||
|
CameraShakeDuration: 0.9
|
||||||
|
CameraShakeFrequency: 28
|
||||||
|
- Radius: 18
|
||||||
|
PlayerImpulse: 5
|
||||||
|
RigidbodyImpulse: 8
|
||||||
|
UpwardBias: 0.25
|
||||||
|
CameraShakeAmplitude: 0.12
|
||||||
|
CameraShakeDuration: 0.75
|
||||||
|
CameraShakeFrequency: 24
|
||||||
|
- Radius: 35
|
||||||
|
PlayerImpulse: 0
|
||||||
|
RigidbodyImpulse: 1.5
|
||||||
|
UpwardBias: 0.1
|
||||||
|
CameraShakeAmplitude: 0.055
|
||||||
|
CameraShakeDuration: 0.55
|
||||||
|
CameraShakeFrequency: 20
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9155e8ad462c3484ba4275bcb64babe2
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
%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: 96e16f6f88696ea4c8b884f3e7cef9f7, type: 3}
|
||||||
|
m_Name: RocketImpactShockwave
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
zones:
|
||||||
|
- Radius: 4
|
||||||
|
PlayerImpulse: 8
|
||||||
|
RigidbodyImpulse: 12
|
||||||
|
UpwardBias: 0.4
|
||||||
|
CameraShakeAmplitude: 0.18
|
||||||
|
CameraShakeDuration: 0.7
|
||||||
|
CameraShakeFrequency: 27
|
||||||
|
- Radius: 8
|
||||||
|
PlayerImpulse: 3
|
||||||
|
RigidbodyImpulse: 5
|
||||||
|
UpwardBias: 0.3
|
||||||
|
CameraShakeAmplitude: 0.09
|
||||||
|
CameraShakeDuration: 0.55
|
||||||
|
CameraShakeFrequency: 23
|
||||||
|
- Radius: 14
|
||||||
|
PlayerImpulse: 0
|
||||||
|
RigidbodyImpulse: 0.5
|
||||||
|
UpwardBias: 0.1
|
||||||
|
CameraShakeAmplitude: 0.04
|
||||||
|
CameraShakeDuration: 0.4
|
||||||
|
CameraShakeFrequency: 18
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5afb0716a3dcecf469e0fe2128d5b98b
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -16,4 +16,5 @@ MonoBehaviour:
|
|||||||
displayName: Full-Rocket-Standart
|
displayName: Full-Rocket-Standart
|
||||||
worldPrefab: {fileID: 7456648717704822871, guid: 746f04d9ba6c87d4bae8a09287c5d090, type: 3}
|
worldPrefab: {fileID: 7456648717704822871, guid: 746f04d9ba6c87d4bae8a09287c5d090, type: 3}
|
||||||
rocketTypeId: standard
|
rocketTypeId: standard
|
||||||
|
missileType: 1
|
||||||
criticalImpactSpeed: 7
|
criticalImpactSpeed: 7
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ GameObject:
|
|||||||
- component: {fileID: 4269639932524941515}
|
- component: {fileID: 4269639932524941515}
|
||||||
- component: {fileID: 6493104616022895181}
|
- component: {fileID: 6493104616022895181}
|
||||||
- component: {fileID: 3635736001397410476}
|
- component: {fileID: 3635736001397410476}
|
||||||
|
- component: {fileID: 633656625536386338}
|
||||||
|
- component: {fileID: 4161244424843525666}
|
||||||
m_Layer: 0
|
m_Layer: 0
|
||||||
m_Name: Ready-Rocket-Standart
|
m_Name: Ready-Rocket-Standart
|
||||||
m_TagString: Untagged
|
m_TagString: Untagged
|
||||||
@@ -52,7 +54,7 @@ MonoBehaviour:
|
|||||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||||
m_Name:
|
m_Name:
|
||||||
m_EditorClassIdentifier:
|
m_EditorClassIdentifier:
|
||||||
GlobalObjectIdHash: 3332465624
|
GlobalObjectIdHash: 246746440
|
||||||
InScenePlacedSourceGlobalObjectIdHash: 0
|
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||||
DeferredDespawnTick: 0
|
DeferredDespawnTick: 0
|
||||||
Ownership: 1
|
Ownership: 1
|
||||||
@@ -261,6 +263,44 @@ MonoBehaviour:
|
|||||||
onCriticalImpactSpeed:
|
onCriticalImpactSpeed:
|
||||||
m_PersistentCalls:
|
m_PersistentCalls:
|
||||||
m_Calls: []
|
m_Calls: []
|
||||||
|
--- !u!114 &633656625536386338
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2376631388558761094}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: 43f56ec799f9aec4ca1bf372c88592cd, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
profile: {fileID: 11400000, guid: 5afb0716a3dcecf469e0fe2128d5b98b, type: 2}
|
||||||
|
origin: {fileID: 1632953841127884940}
|
||||||
|
physicsLayers:
|
||||||
|
serializedVersion: 2
|
||||||
|
m_Bits: 4294967295
|
||||||
|
shockwaveEmitted:
|
||||||
|
m_PersistentCalls:
|
||||||
|
m_Calls: []
|
||||||
|
--- !u!114 &4161244424843525666
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 2376631388558761094}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: ac11f143457835f46a1a2defaa624354, type: 3}
|
||||||
|
m_Name:
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
ShowTopMostFoldoutHeaderGroup: 1
|
||||||
|
rocket: {fileID: 6493104616022895181}
|
||||||
|
shockwave: {fileID: 633656625536386338}
|
||||||
|
despawnAfterExplosion: 1
|
||||||
|
despawnDelay: 0.15
|
||||||
--- !u!1001 &5717474834163769460
|
--- !u!1001 &5717474834163769460
|
||||||
PrefabInstance:
|
PrefabInstance:
|
||||||
m_ObjectHideFlags: 0
|
m_ObjectHideFlags: 0
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2dab561baa00bfb46b838b978cb45129
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[CreateAssetMenu(
|
||||||
|
fileName = "CannonConfig",
|
||||||
|
menuName = "FORT-BO/Cannon/Cannon Config")]
|
||||||
|
public sealed class CannonConfig : ScriptableObject
|
||||||
|
{
|
||||||
|
[SerializeField, Min(0f)] private float minimumDelay = 0f;
|
||||||
|
[SerializeField, Min(0.1f)] private float maximumDelay = 60f;
|
||||||
|
[SerializeField, Min(0f)] private float defaultDelay = 5f;
|
||||||
|
[SerializeField, Min(0.1f)] private float adjustmentStep = 1f;
|
||||||
|
[SerializeField] private bool acceptDefectiveRockets = true;
|
||||||
|
[SerializeField] private bool acceptCriticallyDamagedRockets;
|
||||||
|
|
||||||
|
public float MinimumDelay => minimumDelay;
|
||||||
|
public float MaximumDelay => maximumDelay;
|
||||||
|
public float DefaultDelay => ClampDelay(defaultDelay);
|
||||||
|
public float AdjustmentStep => adjustmentStep;
|
||||||
|
public bool AcceptDefectiveRockets => acceptDefectiveRockets;
|
||||||
|
public bool AcceptCriticallyDamagedRockets => acceptCriticallyDamagedRockets;
|
||||||
|
|
||||||
|
public float ClampDelay(float value) =>
|
||||||
|
Mathf.Clamp(value, minimumDelay, Mathf.Max(minimumDelay, maximumDelay));
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
minimumDelay = Mathf.Max(0f, minimumDelay);
|
||||||
|
maximumDelay = Mathf.Max(minimumDelay, maximumDelay);
|
||||||
|
defaultDelay = ClampDelay(defaultDelay);
|
||||||
|
adjustmentStep = Mathf.Max(0.1f, adjustmentStep);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e069cd58c327cc94e9c2598c9e4c449f
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class CannonControlBinder : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkCannonController cannon;
|
||||||
|
[SerializeField] private NetworkPhysicalLeverInteractable fireLever;
|
||||||
|
[SerializeField] private NetworkButtonInteractable decreaseTimerButton;
|
||||||
|
[SerializeField] private NetworkButtonInteractable increaseTimerButton;
|
||||||
|
|
||||||
|
private bool bound;
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!bound)
|
||||||
|
TryBind();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable() => Unbind();
|
||||||
|
|
||||||
|
private void TryBind()
|
||||||
|
{
|
||||||
|
if (cannon == null || !cannon.IsSpawned || !cannon.IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (fireLever != null)
|
||||||
|
fireLever.StateChanged += HandleFireLever;
|
||||||
|
if (decreaseTimerButton != null)
|
||||||
|
decreaseTimerButton.Pressed += HandleDecreaseTimer;
|
||||||
|
if (increaseTimerButton != null)
|
||||||
|
increaseTimerButton.Pressed += HandleIncreaseTimer;
|
||||||
|
bound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unbind()
|
||||||
|
{
|
||||||
|
if (!bound)
|
||||||
|
return;
|
||||||
|
if (fireLever != null)
|
||||||
|
fireLever.StateChanged -= HandleFireLever;
|
||||||
|
if (decreaseTimerButton != null)
|
||||||
|
decreaseTimerButton.Pressed -= HandleDecreaseTimer;
|
||||||
|
if (increaseTimerButton != null)
|
||||||
|
increaseTimerButton.Pressed -= HandleIncreaseTimer;
|
||||||
|
bound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleFireLever(bool isActivated) => cannon.SetFireRequested(isActivated);
|
||||||
|
private void HandleDecreaseTimer() => cannon.RequestDecreaseShotDelay();
|
||||||
|
private void HandleIncreaseTimer() => cannon.RequestIncreaseShotDelay();
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 24a208cb322bf904bb12481f5fc58a85
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[RequireComponent(typeof(Collider))]
|
||||||
|
public sealed class CannonRocketSlot : MonoBehaviour, IInteractable
|
||||||
|
{
|
||||||
|
[SerializeField] private Transform snapPoint;
|
||||||
|
|
||||||
|
private NetworkCannonController cannon;
|
||||||
|
private Collider triggerCollider;
|
||||||
|
|
||||||
|
public Transform SnapPoint => snapPoint != null ? snapPoint : transform;
|
||||||
|
public Vector3 InteractionPosition => SnapPoint.position;
|
||||||
|
public string InteractionPrompt => cannon != null
|
||||||
|
? cannon.LoadedRocket != null
|
||||||
|
? $"Take {cannon.LoadedRocket.RocketTypeId} rocket"
|
||||||
|
: "Rocket slot"
|
||||||
|
: "Rocket slot";
|
||||||
|
public bool IsInteractionAvailable => cannon != null && cannon.CanTakeRocket;
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
snapPoint = transform;
|
||||||
|
ResolveCollider();
|
||||||
|
triggerCollider.isTrigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake() => ResolveCollider();
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
ResolveCollider();
|
||||||
|
if (triggerCollider != null)
|
||||||
|
triggerCollider.isTrigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Configure(NetworkCannonController owner) => cannon = owner;
|
||||||
|
|
||||||
|
public void RequestInteraction()
|
||||||
|
{
|
||||||
|
if (IsInteractionAvailable)
|
||||||
|
cannon.RequestTakeRocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTriggerEnter(Collider other) => TryInsert(other);
|
||||||
|
private void OnTriggerStay(Collider other) => TryInsert(other);
|
||||||
|
|
||||||
|
private void TryInsert(Collider other)
|
||||||
|
{
|
||||||
|
if (cannon == null || !cannon.IsServer || !cannon.IsSpawned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkRocketItem rocket = other.GetComponentInParent<NetworkRocketItem>();
|
||||||
|
if (rocket != null)
|
||||||
|
cannon.TryInsertRocket(rocket);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OverlapsAny(Collider[] itemColliders, float clearance)
|
||||||
|
{
|
||||||
|
ResolveCollider();
|
||||||
|
if (triggerCollider == null || itemColliders == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Bounds bounds = triggerCollider.bounds;
|
||||||
|
bounds.Expand(Mathf.Max(0f, clearance) * 2f);
|
||||||
|
foreach (Collider itemCollider in itemColliders)
|
||||||
|
{
|
||||||
|
if (itemCollider != null && itemCollider.enabled &&
|
||||||
|
bounds.Intersects(itemCollider.bounds))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveCollider()
|
||||||
|
{
|
||||||
|
if (triggerCollider == null)
|
||||||
|
triggerCollider = GetComponent<Collider>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7ad0f04c740f9b343b2ed4f083c407a6
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class CannonTimerView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkCannonController controller;
|
||||||
|
[SerializeField] private TMP_Text timerText;
|
||||||
|
[SerializeField] private string emptyText = "INSERT ROCKET";
|
||||||
|
[SerializeField] private string loadedFormat = "DELAY {0:0}s";
|
||||||
|
[SerializeField] private string countdownFormat = "FIRE IN {0:0.0}s";
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (controller != null)
|
||||||
|
controller.Changed += Refresh;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (controller != null)
|
||||||
|
controller.Changed -= Refresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (controller != null && controller.State == CannonState.Countdown)
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Refresh()
|
||||||
|
{
|
||||||
|
if (timerText == null || controller == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
timerText.text = controller.State switch
|
||||||
|
{
|
||||||
|
CannonState.Empty => emptyText,
|
||||||
|
CannonState.Loaded => string.Format(loadedFormat, controller.SelectedDelay),
|
||||||
|
CannonState.Countdown => string.Format(
|
||||||
|
countdownFormat, controller.RemainingSeconds),
|
||||||
|
_ => emptyText
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 451ab5135cfc56f489740516a23e7834
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class CannonView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkCannonController controller;
|
||||||
|
[SerializeField] private Transform recoilVisual;
|
||||||
|
[SerializeField] private Vector3 localRecoilOffset = new(0f, 0f, -0.75f);
|
||||||
|
[SerializeField, Min(0.01f)] private float recoilOutDuration = 0.08f;
|
||||||
|
[SerializeField, Min(0.01f)] private float recoilReturnDuration = 0.45f;
|
||||||
|
[SerializeField] private UnityEvent shotFired;
|
||||||
|
[SerializeField] private UnityEvent<MissileType> missileFired;
|
||||||
|
|
||||||
|
private Vector3 baseLocalPosition;
|
||||||
|
private Coroutine recoilRoutine;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (recoilVisual != null)
|
||||||
|
baseLocalPosition = recoilVisual.localPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnEnable()
|
||||||
|
{
|
||||||
|
if (controller != null)
|
||||||
|
controller.ShotFired += HandleShotFired;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (controller != null)
|
||||||
|
controller.ShotFired -= HandleShotFired;
|
||||||
|
if (recoilRoutine != null)
|
||||||
|
StopCoroutine(recoilRoutine);
|
||||||
|
if (recoilVisual != null)
|
||||||
|
recoilVisual.localPosition = baseLocalPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleShotFired(CannonShotState shot)
|
||||||
|
{
|
||||||
|
if (recoilRoutine != null)
|
||||||
|
StopCoroutine(recoilRoutine);
|
||||||
|
recoilRoutine = StartCoroutine(PlayRecoil());
|
||||||
|
shotFired?.Invoke();
|
||||||
|
missileFired?.Invoke(shot.MissileType);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator PlayRecoil()
|
||||||
|
{
|
||||||
|
if (recoilVisual == null)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
Vector3 recoilPosition = baseLocalPosition + localRecoilOffset;
|
||||||
|
yield return Move(recoilVisual.localPosition, recoilPosition, recoilOutDuration);
|
||||||
|
yield return Move(recoilVisual.localPosition, baseLocalPosition, recoilReturnDuration);
|
||||||
|
recoilRoutine = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator Move(Vector3 from, Vector3 to, float duration)
|
||||||
|
{
|
||||||
|
float elapsed = 0f;
|
||||||
|
while (elapsed < duration)
|
||||||
|
{
|
||||||
|
elapsed += Time.deltaTime;
|
||||||
|
float t = Mathf.Clamp01(elapsed / duration);
|
||||||
|
recoilVisual.localPosition = Vector3.LerpUnclamped(
|
||||||
|
from, to, 1f - Mathf.Pow(1f - t, 3f));
|
||||||
|
yield return null;
|
||||||
|
}
|
||||||
|
recoilVisual.localPosition = to;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Recoil")]
|
||||||
|
private void PreviewRecoil()
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
HandleShotFired(new CannonShotState(0, MissileType.Standard));
|
||||||
|
else if (recoilVisual != null)
|
||||||
|
recoilVisual.localPosition = baseLocalPosition + localRecoilOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Reset")]
|
||||||
|
private void ResetPreview()
|
||||||
|
{
|
||||||
|
if (recoilVisual != null)
|
||||||
|
recoilVisual.localPosition = baseLocalPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f174c161f1871f44bba1b7e09ce03e61
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4c3f0760c12468543bab2cf3f99294f6
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
using TMPro;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public static class CannonPrototypeSetup
|
||||||
|
{
|
||||||
|
private const string DataFolder = "Assets/CONTENT/Game/Data/Cannon";
|
||||||
|
private const string CannonConfigPath = DataFolder + "/CannonConfig.asset";
|
||||||
|
private const string CannonShockwavePath = DataFolder + "/CannonShockwave.asset";
|
||||||
|
private const string RocketShockwavePath = DataFolder + "/RocketImpactShockwave.asset";
|
||||||
|
private const string PlayerPrefabPath =
|
||||||
|
"Assets/CONTENT/Friendslop/Prefabs/NetworkPlayer.prefab";
|
||||||
|
private const string RocketPrefabPath =
|
||||||
|
"Assets/CONTENT/Game/Prefabs/Items/Ready-Rocket-Standart.prefab";
|
||||||
|
private const string StandardRocketDefinitionPath =
|
||||||
|
"Assets/CONTENT/Game/Data/Items/Rocket-Ready/Ready-Rocket-Standart.asset";
|
||||||
|
private const string LeverPrefabPath =
|
||||||
|
"Assets/CONTENT/Game/Prefabs/Interaction/PhysicalLever.prefab";
|
||||||
|
|
||||||
|
[MenuItem("FORT-BO/Cannon/Configure Network Prototype")]
|
||||||
|
public static void ConfigureNetworkPrototype()
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
{
|
||||||
|
Debug.LogWarning("Stop Play Mode before configuring the cannon prototype.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureDataFolder();
|
||||||
|
CannonConfig config = GetOrCreateCannonConfig();
|
||||||
|
ShockwaveProfile cannonShockwave = GetOrCreateProfile(
|
||||||
|
CannonShockwavePath,
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 8f,
|
||||||
|
PlayerImpulse = 12f,
|
||||||
|
RigidbodyImpulse = 18f,
|
||||||
|
UpwardBias = 0.35f,
|
||||||
|
CameraShakeAmplitude = 0.22f,
|
||||||
|
CameraShakeDuration = 0.9f,
|
||||||
|
CameraShakeFrequency = 28f
|
||||||
|
},
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 18f,
|
||||||
|
PlayerImpulse = 5f,
|
||||||
|
RigidbodyImpulse = 8f,
|
||||||
|
UpwardBias = 0.25f,
|
||||||
|
CameraShakeAmplitude = 0.12f,
|
||||||
|
CameraShakeDuration = 0.75f,
|
||||||
|
CameraShakeFrequency = 24f
|
||||||
|
},
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 35f,
|
||||||
|
PlayerImpulse = 0f,
|
||||||
|
RigidbodyImpulse = 1.5f,
|
||||||
|
UpwardBias = 0.1f,
|
||||||
|
CameraShakeAmplitude = 0.055f,
|
||||||
|
CameraShakeDuration = 0.55f,
|
||||||
|
CameraShakeFrequency = 20f
|
||||||
|
});
|
||||||
|
ShockwaveProfile rocketShockwave = GetOrCreateProfile(
|
||||||
|
RocketShockwavePath,
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 4f,
|
||||||
|
PlayerImpulse = 8f,
|
||||||
|
RigidbodyImpulse = 12f,
|
||||||
|
UpwardBias = 0.4f,
|
||||||
|
CameraShakeAmplitude = 0.18f,
|
||||||
|
CameraShakeDuration = 0.7f,
|
||||||
|
CameraShakeFrequency = 27f
|
||||||
|
},
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 8f,
|
||||||
|
PlayerImpulse = 3f,
|
||||||
|
RigidbodyImpulse = 5f,
|
||||||
|
UpwardBias = 0.3f,
|
||||||
|
CameraShakeAmplitude = 0.09f,
|
||||||
|
CameraShakeDuration = 0.55f,
|
||||||
|
CameraShakeFrequency = 23f
|
||||||
|
},
|
||||||
|
new ShockwaveZone
|
||||||
|
{
|
||||||
|
Radius = 14f,
|
||||||
|
PlayerImpulse = 0f,
|
||||||
|
RigidbodyImpulse = 0.5f,
|
||||||
|
UpwardBias = 0.1f,
|
||||||
|
CameraShakeAmplitude = 0.04f,
|
||||||
|
CameraShakeDuration = 0.4f,
|
||||||
|
CameraShakeFrequency = 18f
|
||||||
|
});
|
||||||
|
|
||||||
|
ConfigurePlayerPrefab();
|
||||||
|
ConfigureRocketPrefab(rocketShockwave);
|
||||||
|
ConfigureStandardRocketDefinition();
|
||||||
|
NetworkCannonController cannon = ConfigureCannonScene(config, cannonShockwave);
|
||||||
|
ConfigureMapMarker();
|
||||||
|
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
EditorSceneManager.MarkSceneDirty(cannon.gameObject.scene);
|
||||||
|
EditorSceneManager.SaveOpenScenes();
|
||||||
|
Selection.activeObject = cannon.gameObject;
|
||||||
|
Debug.Log(
|
||||||
|
"[Cannon] Network prototype configured. Insert a ready rocket, " +
|
||||||
|
"set delay with -/+ buttons and pull the fire lever.", cannon);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CannonConfig GetOrCreateCannonConfig()
|
||||||
|
{
|
||||||
|
CannonConfig config = AssetDatabase.LoadAssetAtPath<CannonConfig>(CannonConfigPath);
|
||||||
|
if (config == null)
|
||||||
|
{
|
||||||
|
config = ScriptableObject.CreateInstance<CannonConfig>();
|
||||||
|
AssetDatabase.CreateAsset(config, CannonConfigPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializedObject serialized = new(config);
|
||||||
|
SetFloat(serialized, "minimumDelay", 0f);
|
||||||
|
SetFloat(serialized, "maximumDelay", 30f);
|
||||||
|
SetFloat(serialized, "defaultDelay", 5f);
|
||||||
|
SetFloat(serialized, "adjustmentStep", 1f);
|
||||||
|
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
EditorUtility.SetDirty(config);
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ShockwaveProfile GetOrCreateProfile(
|
||||||
|
string path,
|
||||||
|
params ShockwaveZone[] zones)
|
||||||
|
{
|
||||||
|
ShockwaveProfile profile = AssetDatabase.LoadAssetAtPath<ShockwaveProfile>(path);
|
||||||
|
if (profile == null)
|
||||||
|
{
|
||||||
|
profile = ScriptableObject.CreateInstance<ShockwaveProfile>();
|
||||||
|
AssetDatabase.CreateAsset(profile, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
SerializedObject serialized = new(profile);
|
||||||
|
SerializedProperty array = serialized.FindProperty("zones");
|
||||||
|
array.arraySize = zones.Length;
|
||||||
|
for (int index = 0; index < zones.Length; index++)
|
||||||
|
{
|
||||||
|
SerializedProperty element = array.GetArrayElementAtIndex(index);
|
||||||
|
ShockwaveZone zone = zones[index];
|
||||||
|
element.FindPropertyRelative("Radius").floatValue = zone.Radius;
|
||||||
|
element.FindPropertyRelative("PlayerImpulse").floatValue = zone.PlayerImpulse;
|
||||||
|
element.FindPropertyRelative("RigidbodyImpulse").floatValue = zone.RigidbodyImpulse;
|
||||||
|
element.FindPropertyRelative("UpwardBias").floatValue = zone.UpwardBias;
|
||||||
|
element.FindPropertyRelative("CameraShakeAmplitude").floatValue =
|
||||||
|
zone.CameraShakeAmplitude;
|
||||||
|
element.FindPropertyRelative("CameraShakeDuration").floatValue =
|
||||||
|
zone.CameraShakeDuration;
|
||||||
|
element.FindPropertyRelative("CameraShakeFrequency").floatValue =
|
||||||
|
zone.CameraShakeFrequency;
|
||||||
|
}
|
||||||
|
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
EditorUtility.SetDirty(profile);
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigurePlayerPrefab()
|
||||||
|
{
|
||||||
|
GameObject root = PrefabUtility.LoadPrefabContents(PlayerPrefabPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GetOrAdd<PlayerCameraShakeController>(root);
|
||||||
|
GetOrAdd<PlayerImpactController>(root);
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, PlayerPrefabPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrefabUtility.UnloadPrefabContents(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureRocketPrefab(ShockwaveProfile rocketShockwave)
|
||||||
|
{
|
||||||
|
GameObject root = PrefabUtility.LoadPrefabContents(RocketPrefabPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
NetworkRocketItem rocket = root.GetComponent<NetworkRocketItem>();
|
||||||
|
NetworkShockwaveEmitter emitter = GetOrAdd<NetworkShockwaveEmitter>(root);
|
||||||
|
RocketImpactExplosion explosion = GetOrAdd<RocketImpactExplosion>(root);
|
||||||
|
|
||||||
|
SerializedObject serializedEmitter = new(emitter);
|
||||||
|
SetObject(serializedEmitter, "profile", rocketShockwave);
|
||||||
|
SetObject(serializedEmitter, "origin", root.transform);
|
||||||
|
serializedEmitter.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
SerializedObject serializedExplosion = new(explosion);
|
||||||
|
SetObject(serializedExplosion, "rocket", rocket);
|
||||||
|
SetObject(serializedExplosion, "shockwave", emitter);
|
||||||
|
serializedExplosion.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
PrefabUtility.SaveAsPrefabAsset(root, RocketPrefabPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
PrefabUtility.UnloadPrefabContents(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureStandardRocketDefinition()
|
||||||
|
{
|
||||||
|
RocketItemDefinition definition =
|
||||||
|
AssetDatabase.LoadAssetAtPath<RocketItemDefinition>(StandardRocketDefinitionPath);
|
||||||
|
if (definition == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SerializedObject serialized = new(definition);
|
||||||
|
serialized.FindProperty("missileType").enumValueIndex = (int)MissileType.Standard;
|
||||||
|
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
EditorUtility.SetDirty(definition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkCannonController ConfigureCannonScene(
|
||||||
|
CannonConfig config,
|
||||||
|
ShockwaveProfile cannonShockwave)
|
||||||
|
{
|
||||||
|
GameObject root = GameObject.Find("Cannon Prototype");
|
||||||
|
bool created = root == null;
|
||||||
|
if (created)
|
||||||
|
{
|
||||||
|
root = new GameObject("Cannon Prototype");
|
||||||
|
root.transform.SetPositionAndRotation(
|
||||||
|
new Vector3(132f, 2.55f, 24.3f),
|
||||||
|
Quaternion.Euler(0f, 90f, 0f));
|
||||||
|
Undo.RegisterCreatedObjectUndo(root, "Create cannon prototype");
|
||||||
|
}
|
||||||
|
|
||||||
|
GetOrAdd<NetworkObject>(root);
|
||||||
|
NetworkCannonController controller = GetOrAdd<NetworkCannonController>(root);
|
||||||
|
NetworkShockwaveEmitter emitter = GetOrAdd<NetworkShockwaveEmitter>(root);
|
||||||
|
CannonControlBinder binder = GetOrAdd<CannonControlBinder>(root);
|
||||||
|
CannonView view = GetOrAdd<CannonView>(root);
|
||||||
|
|
||||||
|
Transform baseVisual = GetOrCreatePrimitive(
|
||||||
|
root.transform, "Cannon Base", PrimitiveType.Cube);
|
||||||
|
baseVisual.localPosition = new Vector3(0f, 0.75f, 0f);
|
||||||
|
baseVisual.localRotation = Quaternion.identity;
|
||||||
|
baseVisual.localScale = new Vector3(2.2f, 1.5f, 2.2f);
|
||||||
|
|
||||||
|
Transform recoil = GetOrCreateChild(root.transform, "Barrel Recoil");
|
||||||
|
recoil.localPosition = new Vector3(0f, 2.1f, 0f);
|
||||||
|
recoil.localRotation = Quaternion.identity;
|
||||||
|
recoil.localScale = Vector3.one;
|
||||||
|
Transform barrel = GetOrCreatePrimitive(recoil, "Barrel", PrimitiveType.Cube);
|
||||||
|
barrel.localPosition = new Vector3(0f, 0f, 2.1f);
|
||||||
|
barrel.localRotation = Quaternion.identity;
|
||||||
|
barrel.localScale = new Vector3(0.8f, 0.8f, 4.2f);
|
||||||
|
Transform muzzle = GetOrCreateChild(recoil, "Muzzle");
|
||||||
|
muzzle.localPosition = new Vector3(0f, 0f, 4.25f);
|
||||||
|
|
||||||
|
Transform slotObject = GetOrCreateChild(root.transform, "Rocket Slot");
|
||||||
|
slotObject.localPosition = new Vector3(-2.2f, 1.25f, 0f);
|
||||||
|
slotObject.localRotation = Quaternion.Euler(0f, 90f, 0f);
|
||||||
|
slotObject.localScale = Vector3.one;
|
||||||
|
BoxCollider slotCollider = GetOrAdd<BoxCollider>(slotObject.gameObject);
|
||||||
|
slotCollider.isTrigger = true;
|
||||||
|
slotCollider.size = new Vector3(1.4f, 1.4f, 3.2f);
|
||||||
|
CannonRocketSlot slot = GetOrAdd<CannonRocketSlot>(slotObject.gameObject);
|
||||||
|
Transform snapPoint = GetOrCreateChild(slotObject, "Rocket Snap Point");
|
||||||
|
snapPoint.localPosition = Vector3.zero;
|
||||||
|
snapPoint.localRotation = Quaternion.identity;
|
||||||
|
|
||||||
|
Transform controls = GetOrCreateChild(root.transform, "Controls");
|
||||||
|
controls.localPosition = Vector3.zero;
|
||||||
|
controls.localRotation = Quaternion.identity;
|
||||||
|
controls.localScale = Vector3.one;
|
||||||
|
NetworkPhysicalLeverInteractable lever = CreateLever(controls);
|
||||||
|
NetworkButtonInteractable minus = CreateButton(
|
||||||
|
controls, "Timer Minus", new Vector3(1.8f, 1.4f, -1.4f), "Timer -");
|
||||||
|
NetworkButtonInteractable plus = CreateButton(
|
||||||
|
controls, "Timer Plus", new Vector3(3.1f, 1.4f, -1.4f), "Timer +");
|
||||||
|
TMP_Text timerText = CreateTimerText(controls);
|
||||||
|
|
||||||
|
SerializedObject serializedEmitter = new(emitter);
|
||||||
|
SetObject(serializedEmitter, "profile", cannonShockwave);
|
||||||
|
SetObject(serializedEmitter, "origin", muzzle);
|
||||||
|
serializedEmitter.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
SerializedObject serializedController = new(controller);
|
||||||
|
SetObject(serializedController, "config", config);
|
||||||
|
SetObject(serializedController, "rocketSlot", slot);
|
||||||
|
SetObject(serializedController, "rocketSnapPoint", snapPoint);
|
||||||
|
SetObject(serializedController, "mapController",
|
||||||
|
Object.FindFirstObjectByType<MapController>(FindObjectsInactive.Include));
|
||||||
|
SetObject(serializedController, "shotShockwave", emitter);
|
||||||
|
serializedController.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
SerializedObject serializedSlot = new(slot);
|
||||||
|
SetObject(serializedSlot, "snapPoint", snapPoint);
|
||||||
|
serializedSlot.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
SerializedObject serializedBinder = new(binder);
|
||||||
|
SetObject(serializedBinder, "cannon", controller);
|
||||||
|
SetObject(serializedBinder, "fireLever", lever);
|
||||||
|
SetObject(serializedBinder, "decreaseTimerButton", minus);
|
||||||
|
SetObject(serializedBinder, "increaseTimerButton", plus);
|
||||||
|
serializedBinder.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
SerializedObject serializedView = new(view);
|
||||||
|
SetObject(serializedView, "controller", controller);
|
||||||
|
SetObject(serializedView, "recoilVisual", recoil);
|
||||||
|
serializedView.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
CannonTimerView timerView = GetOrAdd<CannonTimerView>(timerText.gameObject);
|
||||||
|
SerializedObject serializedTimer = new(timerView);
|
||||||
|
SetObject(serializedTimer, "controller", controller);
|
||||||
|
SetObject(serializedTimer, "timerText", timerText);
|
||||||
|
serializedTimer.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
|
||||||
|
slot.Configure(controller);
|
||||||
|
EditorUtility.SetDirty(root);
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkPhysicalLeverInteractable CreateLever(Transform parent)
|
||||||
|
{
|
||||||
|
Transform existing = parent.Find("Fire Lever");
|
||||||
|
GameObject root;
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
root = existing.gameObject;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameObject prefab = AssetDatabase.LoadAssetAtPath<GameObject>(LeverPrefabPath);
|
||||||
|
root = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
|
||||||
|
root.name = "Fire Lever";
|
||||||
|
}
|
||||||
|
|
||||||
|
root.transform.localPosition = new Vector3(4.5f, 1.25f, -1.2f);
|
||||||
|
root.transform.localRotation = Quaternion.Euler(0f, -90f, 0f);
|
||||||
|
root.transform.localScale = Vector3.one * 0.65f;
|
||||||
|
NetworkPhysicalLeverInteractable lever =
|
||||||
|
root.GetComponent<NetworkPhysicalLeverInteractable>();
|
||||||
|
SerializedObject serialized = new(lever);
|
||||||
|
SetString(serialized, "interactionLabel", "Fire cannon");
|
||||||
|
SetBool(serialized, "initialPositionIsMaximum", false);
|
||||||
|
SetBool(serialized, "returnToInitialPositionAfterSwitch", true);
|
||||||
|
SetFloat(serialized, "automaticReturnDelay", 0.2f);
|
||||||
|
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
return lever;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static NetworkButtonInteractable CreateButton(
|
||||||
|
Transform parent,
|
||||||
|
string name,
|
||||||
|
Vector3 localPosition,
|
||||||
|
string label)
|
||||||
|
{
|
||||||
|
Transform root = GetOrCreateChild(parent, name);
|
||||||
|
root.localPosition = localPosition;
|
||||||
|
root.localRotation = Quaternion.identity;
|
||||||
|
root.localScale = Vector3.one;
|
||||||
|
GetOrAdd<NetworkObject>(root.gameObject);
|
||||||
|
|
||||||
|
Transform visual = GetOrCreatePrimitive(root, "Button Visual", PrimitiveType.Cube);
|
||||||
|
visual.localPosition = Vector3.zero;
|
||||||
|
visual.localRotation = Quaternion.identity;
|
||||||
|
visual.localScale = new Vector3(0.8f, 0.8f, 0.3f);
|
||||||
|
NetworkButtonInteractable button = GetOrAdd<NetworkButtonInteractable>(root.gameObject);
|
||||||
|
SerializedObject serialized = new(button);
|
||||||
|
SetString(serialized, "interactionLabel", label);
|
||||||
|
SetFloat(serialized, "maximumInteractionDistance", 3.5f);
|
||||||
|
serialized.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TMP_Text CreateTimerText(Transform parent)
|
||||||
|
{
|
||||||
|
Transform root = GetOrCreateChild(parent, "Timer Display");
|
||||||
|
root.localPosition = new Vector3(2.45f, 2.25f, -1.35f);
|
||||||
|
root.localRotation = Quaternion.Euler(0f, 180f, 0f);
|
||||||
|
root.localScale = Vector3.one * 0.12f;
|
||||||
|
TextMeshPro text = GetOrAdd<TextMeshPro>(root.gameObject);
|
||||||
|
text.alignment = TextAlignmentOptions.Center;
|
||||||
|
text.fontSize = 4f;
|
||||||
|
text.text = "INSERT ROCKET";
|
||||||
|
text.rectTransform.sizeDelta = new Vector2(12f, 3f);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ConfigureMapMarker()
|
||||||
|
{
|
||||||
|
MapController map = Object.FindFirstObjectByType<MapController>(FindObjectsInactive.Include);
|
||||||
|
if (map == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Transform root = map.transform.Find("Impact Markers");
|
||||||
|
if (root == null)
|
||||||
|
{
|
||||||
|
root = new GameObject("Impact Markers").transform;
|
||||||
|
root.SetParent(map.transform, false);
|
||||||
|
}
|
||||||
|
MapImpactMarkerView view = GetOrAdd<MapImpactMarkerView>(root.gameObject);
|
||||||
|
view.Configure(map.CoordinateSystem);
|
||||||
|
|
||||||
|
SerializedObject serializedMap = new(map);
|
||||||
|
SetObject(serializedMap, "impactMarkerView", view);
|
||||||
|
serializedMap.ApplyModifiedPropertiesWithoutUndo();
|
||||||
|
EditorUtility.SetDirty(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EnsureDataFolder()
|
||||||
|
{
|
||||||
|
if (!AssetDatabase.IsValidFolder("Assets/CONTENT/Game/Data"))
|
||||||
|
AssetDatabase.CreateFolder("Assets/CONTENT/Game", "Data");
|
||||||
|
if (!AssetDatabase.IsValidFolder(DataFolder))
|
||||||
|
AssetDatabase.CreateFolder("Assets/CONTENT/Game/Data", "Cannon");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Transform GetOrCreateChild(Transform parent, string name)
|
||||||
|
{
|
||||||
|
Transform existing = parent.Find(name);
|
||||||
|
if (existing != null)
|
||||||
|
return existing;
|
||||||
|
GameObject child = new(name);
|
||||||
|
child.transform.SetParent(parent, false);
|
||||||
|
Undo.RegisterCreatedObjectUndo(child, $"Create {name}");
|
||||||
|
return child.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Transform GetOrCreatePrimitive(
|
||||||
|
Transform parent,
|
||||||
|
string name,
|
||||||
|
PrimitiveType primitiveType)
|
||||||
|
{
|
||||||
|
Transform existing = parent.Find(name);
|
||||||
|
if (existing != null)
|
||||||
|
return existing;
|
||||||
|
GameObject primitive = GameObject.CreatePrimitive(primitiveType);
|
||||||
|
primitive.name = name;
|
||||||
|
primitive.transform.SetParent(parent, false);
|
||||||
|
Undo.RegisterCreatedObjectUndo(primitive, $"Create {name}");
|
||||||
|
return primitive.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static T GetOrAdd<T>(GameObject target) where T : Component
|
||||||
|
{
|
||||||
|
T component = target.GetComponent<T>();
|
||||||
|
return component != null ? component : Undo.AddComponent<T>(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SetObject(SerializedObject target, string property, Object value) =>
|
||||||
|
target.FindProperty(property).objectReferenceValue = value;
|
||||||
|
private static void SetFloat(SerializedObject target, string property, float value) =>
|
||||||
|
target.FindProperty(property).floatValue = value;
|
||||||
|
private static void SetBool(SerializedObject target, string property, bool value) =>
|
||||||
|
target.FindProperty(property).boolValue = value;
|
||||||
|
private static void SetString(SerializedObject target, string property, string value) =>
|
||||||
|
target.FindProperty(property).stringValue = value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fe3002dfbec1fe1458dcd6058b1486c3
|
||||||
@@ -0,0 +1,388 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public enum CannonState : byte
|
||||||
|
{
|
||||||
|
Empty,
|
||||||
|
Loaded,
|
||||||
|
Countdown
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CannonShotState : INetworkSerializable, IEquatable<CannonShotState>
|
||||||
|
{
|
||||||
|
public uint Sequence;
|
||||||
|
public MissileType MissileType;
|
||||||
|
|
||||||
|
public CannonShotState(uint sequence, MissileType missileType)
|
||||||
|
{
|
||||||
|
Sequence = sequence;
|
||||||
|
MissileType = missileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||||
|
where T : IReaderWriter
|
||||||
|
{
|
||||||
|
serializer.SerializeValue(ref Sequence);
|
||||||
|
serializer.SerializeValue(ref MissileType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(CannonShotState other) =>
|
||||||
|
Sequence == other.Sequence && MissileType == other.MissileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[RequireComponent(typeof(NetworkObject))]
|
||||||
|
public sealed class NetworkCannonController : NetworkBehaviour
|
||||||
|
{
|
||||||
|
private const ulong EmptyRocketId = ulong.MaxValue;
|
||||||
|
|
||||||
|
[Header("Configuration")]
|
||||||
|
[SerializeField] private CannonConfig config;
|
||||||
|
|
||||||
|
[Header("Rocket Slot")]
|
||||||
|
[SerializeField] private CannonRocketSlot rocketSlot;
|
||||||
|
[SerializeField] private Transform rocketSnapPoint;
|
||||||
|
[SerializeField, Min(0.5f)] private float maximumTakeDistance = 4f;
|
||||||
|
[SerializeField, Min(0f)] private float minimumReinsertBlockDuration = 0.1f;
|
||||||
|
|
||||||
|
[Header("Output")]
|
||||||
|
[SerializeField] private MapController mapController;
|
||||||
|
[SerializeField] private NetworkShockwaveEmitter shotShockwave;
|
||||||
|
|
||||||
|
private readonly NetworkVariable<CannonState> state = new(
|
||||||
|
CannonState.Empty,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
private readonly NetworkVariable<ulong> loadedRocketId = new(
|
||||||
|
EmptyRocketId,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
private readonly NetworkVariable<float> selectedDelay = new(
|
||||||
|
5f,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
private readonly NetworkVariable<double> fireAt = new(
|
||||||
|
0d,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
private readonly NetworkVariable<CannonShotState> lastShot = new(
|
||||||
|
default,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
|
||||||
|
private NetworkRocketItem loadedRocket;
|
||||||
|
private IWorkstationItemControl loadedRocketControl;
|
||||||
|
private ulong blockedRocketId = EmptyRocketId;
|
||||||
|
private Collider[] blockedRocketColliders;
|
||||||
|
private double unblockAfter;
|
||||||
|
|
||||||
|
public CannonState State => state.Value;
|
||||||
|
public CannonConfig Config => config;
|
||||||
|
public NetworkRocketItem LoadedRocket => loadedRocket != null
|
||||||
|
? loadedRocket
|
||||||
|
: ResolveLoadedRocket();
|
||||||
|
public float SelectedDelay => selectedDelay.Value;
|
||||||
|
public double FireAt => fireAt.Value;
|
||||||
|
public float RemainingSeconds => State == CannonState.Countdown && NetworkManager != null
|
||||||
|
? Mathf.Max(0f, (float)(fireAt.Value - NetworkManager.ServerTime.Time))
|
||||||
|
: 0f;
|
||||||
|
public bool CanTakeRocket => IsSpawned && State == CannonState.Loaded &&
|
||||||
|
loadedRocketId.Value != EmptyRocketId;
|
||||||
|
public CannonShotState LastShot => lastShot.Value;
|
||||||
|
|
||||||
|
public event Action Changed;
|
||||||
|
public event Action<CannonShotState> ShotFired;
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (rocketSnapPoint == null && rocketSlot != null)
|
||||||
|
rocketSnapPoint = rocketSlot.SnapPoint;
|
||||||
|
rocketSlot?.Configure(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
state.OnValueChanged += HandleStateChanged;
|
||||||
|
loadedRocketId.OnValueChanged += HandleRocketIdChanged;
|
||||||
|
selectedDelay.OnValueChanged += HandleDelayChanged;
|
||||||
|
fireAt.OnValueChanged += HandleFireAtChanged;
|
||||||
|
lastShot.OnValueChanged += HandleShotChanged;
|
||||||
|
|
||||||
|
rocketSlot?.Configure(this);
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
state.Value = CannonState.Empty;
|
||||||
|
loadedRocketId.Value = EmptyRocketId;
|
||||||
|
selectedDelay.Value = config != null ? config.DefaultDelay : 5f;
|
||||||
|
fireAt.Value = 0d;
|
||||||
|
}
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
state.OnValueChanged -= HandleStateChanged;
|
||||||
|
loadedRocketId.OnValueChanged -= HandleRocketIdChanged;
|
||||||
|
selectedDelay.OnValueChanged -= HandleDelayChanged;
|
||||||
|
fireAt.OnValueChanged -= HandleFireAtChanged;
|
||||||
|
lastShot.OnValueChanged -= HandleShotChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateReinsertBlock();
|
||||||
|
if (State == CannonState.Countdown &&
|
||||||
|
NetworkManager != null && NetworkManager.ServerTime.Time >= fireAt.Value)
|
||||||
|
{
|
||||||
|
FireOnServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned || LoadedRocket == null ||
|
||||||
|
rocketSnapPoint == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
loadedRocket.transform.SetPositionAndRotation(
|
||||||
|
rocketSnapPoint.position,
|
||||||
|
rocketSnapPoint.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryInsertRocket(NetworkRocketItem rocket)
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned || State != CannonState.Empty ||
|
||||||
|
rocket == null || !rocket.IsSpawned || rocket.Definition == null ||
|
||||||
|
rocket.NetworkObjectId == blockedRocketId ||
|
||||||
|
(config != null && !config.AcceptDefectiveRockets && rocket.IsDefective) ||
|
||||||
|
(config != null && !config.AcceptCriticallyDamagedRockets &&
|
||||||
|
rocket.IsCriticallyDamaged) ||
|
||||||
|
!WorkstationItemControlUtility.TryGetControl(
|
||||||
|
rocket.GetComponent<NetworkItem>(),
|
||||||
|
out IWorkstationItemControl control) ||
|
||||||
|
!control.TryAcquireExternalControl())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedRocket = rocket;
|
||||||
|
loadedRocketControl = control;
|
||||||
|
loadedRocketId.Value = rocket.NetworkObjectId;
|
||||||
|
state.Value = CannonState.Loaded;
|
||||||
|
SnapLoadedRocket();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestTakeRocket()
|
||||||
|
{
|
||||||
|
if (CanTakeRocket)
|
||||||
|
RequestTakeRocketRpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestSetShotDelay(float seconds)
|
||||||
|
{
|
||||||
|
if (!IsSpawned)
|
||||||
|
return;
|
||||||
|
if (IsServer)
|
||||||
|
SetShotDelayOnServer(seconds);
|
||||||
|
else
|
||||||
|
RequestSetShotDelayRpc(seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestAdjustShotDelay(float delta)
|
||||||
|
{
|
||||||
|
RequestSetShotDelay(SelectedDelay + delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestIncreaseShotDelay()
|
||||||
|
{
|
||||||
|
RequestAdjustShotDelay(config != null ? config.AdjustmentStep : 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestDecreaseShotDelay()
|
||||||
|
{
|
||||||
|
RequestAdjustShotDelay(-(config != null ? config.AdjustmentStep : 1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RequestStartCountdown()
|
||||||
|
{
|
||||||
|
if (!IsSpawned)
|
||||||
|
return;
|
||||||
|
if (IsServer)
|
||||||
|
TryStartCountdownOnServer();
|
||||||
|
else
|
||||||
|
RequestStartCountdownRpc();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFireRequested(bool isActivated)
|
||||||
|
{
|
||||||
|
if (isActivated && IsServer && IsSpawned)
|
||||||
|
TryStartCountdownOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Debug/Start Countdown (Server Play Mode)")]
|
||||||
|
private void StartCountdownFromContextMenu()
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
TryStartCountdownOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
|
private void RequestSetShotDelayRpc(float seconds) => SetShotDelayOnServer(seconds);
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
|
private void RequestStartCountdownRpc() => TryStartCountdownOnServer();
|
||||||
|
|
||||||
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||||
|
private void RequestTakeRocketRpc(RpcParams rpcParams = default)
|
||||||
|
{
|
||||||
|
if (!CanTakeRocket || rocketSlot == null ||
|
||||||
|
!TryGetPlayerObject(rpcParams.Receive.SenderClientId, out NetworkObject player))
|
||||||
|
return;
|
||||||
|
|
||||||
|
float maxDistanceSquared = maximumTakeDistance * maximumTakeDistance;
|
||||||
|
if ((player.transform.position - rocketSlot.InteractionPosition).sqrMagnitude >
|
||||||
|
maxDistanceSquared)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkRocketItem rocket = LoadedRocket;
|
||||||
|
IWorkstationItemControl control = loadedRocketControl ?? ResolveLoadedControl();
|
||||||
|
if (rocket == null || control == null ||
|
||||||
|
!control.TryReleaseExternalControlToPlayer(rpcParams.Receive.SenderClientId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
blockedRocketId = rocket.NetworkObjectId;
|
||||||
|
blockedRocketColliders = rocket.GetComponentsInChildren<Collider>(true);
|
||||||
|
unblockAfter = GetNetworkTime() + minimumReinsertBlockDuration;
|
||||||
|
ClearRocketSlot();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetShotDelayOnServer(float seconds)
|
||||||
|
{
|
||||||
|
if (!IsServer || State == CannonState.Countdown)
|
||||||
|
return;
|
||||||
|
selectedDelay.Value = config != null
|
||||||
|
? config.ClampDelay(seconds)
|
||||||
|
: Mathf.Clamp(seconds, 0f, 60f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryStartCountdownOnServer()
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned || State != CannonState.Loaded ||
|
||||||
|
LoadedRocket == null || NetworkManager == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fireAt.Value = NetworkManager.ServerTime.Time + selectedDelay.Value;
|
||||||
|
state.Value = CannonState.Countdown;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FireOnServer()
|
||||||
|
{
|
||||||
|
NetworkRocketItem rocket = LoadedRocket;
|
||||||
|
if (rocket == null || rocket.Definition == null)
|
||||||
|
{
|
||||||
|
ClearRocketSlot();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MissileType missileType = rocket.Definition.MissileType;
|
||||||
|
mapController?.RequestShot(missileType);
|
||||||
|
shotShockwave?.EmitShockwave();
|
||||||
|
lastShot.Value = new CannonShotState(lastShot.Value.Sequence + 1, missileType);
|
||||||
|
|
||||||
|
NetworkObject rocketObject = rocket.NetworkObject;
|
||||||
|
loadedRocket = null;
|
||||||
|
loadedRocketControl = null;
|
||||||
|
loadedRocketId.Value = EmptyRocketId;
|
||||||
|
fireAt.Value = 0d;
|
||||||
|
state.Value = CannonState.Empty;
|
||||||
|
|
||||||
|
if (rocketObject != null && rocketObject.IsSpawned)
|
||||||
|
rocketObject.Despawn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearRocketSlot()
|
||||||
|
{
|
||||||
|
loadedRocket = null;
|
||||||
|
loadedRocketControl = null;
|
||||||
|
loadedRocketId.Value = EmptyRocketId;
|
||||||
|
fireAt.Value = 0d;
|
||||||
|
state.Value = CannonState.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapLoadedRocket()
|
||||||
|
{
|
||||||
|
if (loadedRocket != null && rocketSnapPoint != null)
|
||||||
|
{
|
||||||
|
loadedRocket.transform.SetPositionAndRotation(
|
||||||
|
rocketSnapPoint.position,
|
||||||
|
rocketSnapPoint.rotation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkRocketItem ResolveLoadedRocket()
|
||||||
|
{
|
||||||
|
if (loadedRocketId.Value == EmptyRocketId || NetworkManager == null ||
|
||||||
|
NetworkManager.SpawnManager == null ||
|
||||||
|
!NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(
|
||||||
|
loadedRocketId.Value, out NetworkObject networkObject))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
loadedRocket = networkObject.GetComponent<NetworkRocketItem>();
|
||||||
|
return loadedRocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWorkstationItemControl ResolveLoadedControl()
|
||||||
|
{
|
||||||
|
NetworkRocketItem rocket = LoadedRocket;
|
||||||
|
WorkstationItemControlUtility.TryGetControl(
|
||||||
|
rocket != null ? rocket.GetComponent<NetworkItem>() : null,
|
||||||
|
out loadedRocketControl);
|
||||||
|
return loadedRocketControl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetPlayerObject(ulong clientId, out NetworkObject player)
|
||||||
|
{
|
||||||
|
player = null;
|
||||||
|
return NetworkManager != null &&
|
||||||
|
NetworkManager.ConnectedClients.TryGetValue(clientId, out NetworkClient client) &&
|
||||||
|
(player = client.PlayerObject) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateReinsertBlock()
|
||||||
|
{
|
||||||
|
if (blockedRocketId == EmptyRocketId || rocketSlot == null ||
|
||||||
|
GetNetworkTime() < unblockAfter ||
|
||||||
|
rocketSlot.OverlapsAny(blockedRocketColliders, 0.05f))
|
||||||
|
return;
|
||||||
|
|
||||||
|
blockedRocketId = EmptyRocketId;
|
||||||
|
blockedRocketColliders = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetNetworkTime() => NetworkManager != null
|
||||||
|
? NetworkManager.ServerTime.Time
|
||||||
|
: Time.timeAsDouble;
|
||||||
|
|
||||||
|
private void HandleStateChanged(CannonState _, CannonState __) => Changed?.Invoke();
|
||||||
|
private void HandleRocketIdChanged(ulong _, ulong __)
|
||||||
|
{
|
||||||
|
loadedRocket = null;
|
||||||
|
loadedRocketControl = null;
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
private void HandleDelayChanged(float _, float __) => Changed?.Invoke();
|
||||||
|
private void HandleFireAtChanged(double _, double __) => Changed?.Invoke();
|
||||||
|
private void HandleShotChanged(CannonShotState previous, CannonShotState current)
|
||||||
|
{
|
||||||
|
if (current.Sequence == previous.Sequence)
|
||||||
|
return;
|
||||||
|
ShotFired?.Invoke(current);
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8382605c6a901154783b2faf23067629
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c09a47e212ae515468d691409560c5d3
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 03ef14f0e5db05941afa0b0132e3bfb3
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class NetworkShockwaveEmitter : NetworkBehaviour
|
||||||
|
{
|
||||||
|
private const int MaximumOverlaps = 256;
|
||||||
|
|
||||||
|
[SerializeField] private ShockwaveProfile profile;
|
||||||
|
[SerializeField] private Transform origin;
|
||||||
|
[SerializeField] private LayerMask physicsLayers = ~0;
|
||||||
|
[SerializeField] private UnityEvent shockwaveEmitted;
|
||||||
|
|
||||||
|
private readonly NetworkVariable<uint> sequence = new(
|
||||||
|
0,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
|
||||||
|
private readonly Collider[] overlapBuffer = new Collider[MaximumOverlaps];
|
||||||
|
private readonly HashSet<Rigidbody> affectedBodies = new();
|
||||||
|
|
||||||
|
public ShockwaveProfile Profile => profile;
|
||||||
|
public Vector3 Origin => origin != null ? origin.position : transform.position;
|
||||||
|
public uint Sequence => sequence.Value;
|
||||||
|
|
||||||
|
public event System.Action Emitted;
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
sequence.OnValueChanged += HandleSequenceChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
sequence.OnValueChanged -= HandleSequenceChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EmitShockwave() => EmitShockwave(Origin);
|
||||||
|
|
||||||
|
public bool EmitShockwave(Vector3 worldOrigin)
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned || profile == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ApplyToPlayers(worldOrigin);
|
||||||
|
ApplyToPhysics(worldOrigin);
|
||||||
|
sequence.Value++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Debug/Emit Shockwave (Server Play Mode)")]
|
||||||
|
private void EmitFromContextMenu()
|
||||||
|
{
|
||||||
|
if (Application.isPlaying)
|
||||||
|
EmitShockwave();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyToPlayers(Vector3 worldOrigin)
|
||||||
|
{
|
||||||
|
foreach (PlayerImpactController player in
|
||||||
|
FindObjectsByType<PlayerImpactController>(FindObjectsSortMode.None))
|
||||||
|
{
|
||||||
|
if (!player.IsSpawned)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector3 offset = player.transform.position - worldOrigin;
|
||||||
|
float distance = offset.magnitude;
|
||||||
|
if (!profile.TryEvaluate(distance, out ShockwaveZone zone))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Vector3 direction = CreateDirection(offset, zone.UpwardBias);
|
||||||
|
player.ApplyImpactOnServer(direction * zone.PlayerImpulse, zone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyToPhysics(Vector3 worldOrigin)
|
||||||
|
{
|
||||||
|
int count = Physics.OverlapSphereNonAlloc(
|
||||||
|
worldOrigin,
|
||||||
|
profile.MaximumRadius,
|
||||||
|
overlapBuffer,
|
||||||
|
physicsLayers,
|
||||||
|
QueryTriggerInteraction.Ignore);
|
||||||
|
|
||||||
|
affectedBodies.Clear();
|
||||||
|
for (int index = 0; index < count; index++)
|
||||||
|
{
|
||||||
|
Collider hit = overlapBuffer[index];
|
||||||
|
Rigidbody body = hit != null ? hit.attachedRigidbody : null;
|
||||||
|
if (body == null || body.isKinematic || !affectedBodies.Add(body) ||
|
||||||
|
body.GetComponentInParent<PlayerImpactController>() != null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 offset = body.worldCenterOfMass - worldOrigin;
|
||||||
|
if (!profile.TryEvaluate(offset.magnitude, out ShockwaveZone zone) ||
|
||||||
|
zone.RigidbodyImpulse <= 0f)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.AddForce(
|
||||||
|
CreateDirection(offset, zone.UpwardBias) * zone.RigidbodyImpulse,
|
||||||
|
ForceMode.Impulse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 CreateDirection(Vector3 offset, float upwardBias)
|
||||||
|
{
|
||||||
|
if (offset.sqrMagnitude < 0.0001f)
|
||||||
|
offset = Vector3.up;
|
||||||
|
offset.Normalize();
|
||||||
|
return (offset + Vector3.up * Mathf.Clamp01(upwardBias)).normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleSequenceChanged(uint previous, uint current)
|
||||||
|
{
|
||||||
|
if (current == previous)
|
||||||
|
return;
|
||||||
|
Emitted?.Invoke();
|
||||||
|
shockwaveEmitted?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 43f56ec799f9aec4ca1bf372c88592cd
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public struct PlayerImpactState : INetworkSerializable, IEquatable<PlayerImpactState>
|
||||||
|
{
|
||||||
|
public uint Sequence;
|
||||||
|
public Vector3 Impulse;
|
||||||
|
public float ShakeAmplitude;
|
||||||
|
public float ShakeDuration;
|
||||||
|
public float ShakeFrequency;
|
||||||
|
public float ShakeSeed;
|
||||||
|
|
||||||
|
public PlayerImpactState(
|
||||||
|
uint sequence,
|
||||||
|
Vector3 impulse,
|
||||||
|
float shakeAmplitude,
|
||||||
|
float shakeDuration,
|
||||||
|
float shakeFrequency,
|
||||||
|
float shakeSeed)
|
||||||
|
{
|
||||||
|
Sequence = sequence;
|
||||||
|
Impulse = impulse;
|
||||||
|
ShakeAmplitude = shakeAmplitude;
|
||||||
|
ShakeDuration = shakeDuration;
|
||||||
|
ShakeFrequency = shakeFrequency;
|
||||||
|
ShakeSeed = shakeSeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||||
|
where T : IReaderWriter
|
||||||
|
{
|
||||||
|
serializer.SerializeValue(ref Sequence);
|
||||||
|
serializer.SerializeValue(ref Impulse);
|
||||||
|
serializer.SerializeValue(ref ShakeAmplitude);
|
||||||
|
serializer.SerializeValue(ref ShakeDuration);
|
||||||
|
serializer.SerializeValue(ref ShakeFrequency);
|
||||||
|
serializer.SerializeValue(ref ShakeSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(PlayerImpactState other) =>
|
||||||
|
Sequence == other.Sequence && Impulse == other.Impulse &&
|
||||||
|
ShakeAmplitude.Equals(other.ShakeAmplitude) &&
|
||||||
|
ShakeDuration.Equals(other.ShakeDuration) &&
|
||||||
|
ShakeFrequency.Equals(other.ShakeFrequency) &&
|
||||||
|
ShakeSeed.Equals(other.ShakeSeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class PlayerImpactController : NetworkBehaviour, IPlayerSystem
|
||||||
|
{
|
||||||
|
private readonly NetworkVariable<PlayerImpactState> impactState = new(
|
||||||
|
default,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
|
||||||
|
private PlayerController movement;
|
||||||
|
private PlayerCameraShakeController cameraShake;
|
||||||
|
|
||||||
|
public uint LastImpactSequence => impactState.Value.Sequence;
|
||||||
|
public event Action<PlayerImpactState> ImpactApplied;
|
||||||
|
|
||||||
|
public void Initialize(PlayerFacade facade)
|
||||||
|
{
|
||||||
|
movement = GetComponent<PlayerController>();
|
||||||
|
cameraShake = GetComponent<PlayerCameraShakeController>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
impactState.OnValueChanged += HandleImpactChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
impactState.OnValueChanged -= HandleImpactChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ApplyImpactOnServer(Vector3 impulse, ShockwaveZone zone)
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
impactState.Value = new PlayerImpactState(
|
||||||
|
impactState.Value.Sequence + 1,
|
||||||
|
impulse,
|
||||||
|
zone.CameraShakeAmplitude,
|
||||||
|
zone.CameraShakeDuration,
|
||||||
|
zone.CameraShakeFrequency,
|
||||||
|
UnityEngine.Random.Range(0f, 1000f));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleImpactChanged(PlayerImpactState previous, PlayerImpactState current)
|
||||||
|
{
|
||||||
|
if (current.Sequence == 0 || current.Sequence == previous.Sequence)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ImpactApplied?.Invoke(current);
|
||||||
|
if (!IsOwner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
movement?.AddImpulse(current.Impulse);
|
||||||
|
cameraShake?.PlayShake(
|
||||||
|
current.ShakeAmplitude,
|
||||||
|
current.ShakeDuration,
|
||||||
|
current.ShakeFrequency,
|
||||||
|
current.ShakeSeed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6a7ddedc58d3467449dfbe83af1c9c51
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[RequireComponent(typeof(NetworkRocketItem), typeof(NetworkShockwaveEmitter))]
|
||||||
|
public sealed class RocketImpactExplosion : NetworkBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkRocketItem rocket;
|
||||||
|
[SerializeField] private NetworkShockwaveEmitter shockwave;
|
||||||
|
[SerializeField] private bool despawnAfterExplosion = true;
|
||||||
|
[SerializeField, Min(0f)] private float despawnDelay = 0.15f;
|
||||||
|
|
||||||
|
private bool exploded;
|
||||||
|
|
||||||
|
private void Reset() => ResolveReferences();
|
||||||
|
private void Awake() => ResolveReferences();
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
rocket.CriticalImpact += HandleCriticalImpact;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
if (rocket != null)
|
||||||
|
rocket.CriticalImpact -= HandleCriticalImpact;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCriticalImpact()
|
||||||
|
{
|
||||||
|
if (!IsServer || exploded)
|
||||||
|
return;
|
||||||
|
|
||||||
|
exploded = true;
|
||||||
|
shockwave.EmitShockwave(rocket.LastCriticalImpactPoint);
|
||||||
|
if (despawnAfterExplosion)
|
||||||
|
StartCoroutine(DespawnAfterDelay());
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerator DespawnAfterDelay()
|
||||||
|
{
|
||||||
|
if (despawnDelay > 0f)
|
||||||
|
yield return new WaitForSeconds(despawnDelay);
|
||||||
|
|
||||||
|
if (IsServer && NetworkObject != null && NetworkObject.IsSpawned)
|
||||||
|
NetworkObject.Despawn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveReferences()
|
||||||
|
{
|
||||||
|
if (rocket == null)
|
||||||
|
rocket = GetComponent<NetworkRocketItem>();
|
||||||
|
if (shockwave == null)
|
||||||
|
shockwave = GetComponent<NetworkShockwaveEmitter>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ac11f143457835f46a1a2defaa624354
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
public struct ShockwaveZone
|
||||||
|
{
|
||||||
|
[Min(0.1f)] public float Radius;
|
||||||
|
[Min(0f)] public float PlayerImpulse;
|
||||||
|
[Min(0f)] public float RigidbodyImpulse;
|
||||||
|
[Range(0f, 1f)] public float UpwardBias;
|
||||||
|
[Min(0f)] public float CameraShakeAmplitude;
|
||||||
|
[Min(0f)] public float CameraShakeDuration;
|
||||||
|
[Min(0.1f)] public float CameraShakeFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CreateAssetMenu(
|
||||||
|
fileName = "ShockwaveProfile",
|
||||||
|
menuName = "FORT-BO/Effects/Shockwave Profile")]
|
||||||
|
public sealed class ShockwaveProfile : ScriptableObject
|
||||||
|
{
|
||||||
|
[Tooltip("Zones are evaluated from the smallest radius to the largest.")]
|
||||||
|
[SerializeField] private ShockwaveZone[] zones = Array.Empty<ShockwaveZone>();
|
||||||
|
|
||||||
|
public float MaximumRadius
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
float maximum = 0f;
|
||||||
|
foreach (ShockwaveZone zone in zones)
|
||||||
|
maximum = Mathf.Max(maximum, zone.Radius);
|
||||||
|
return maximum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryEvaluate(float distance, out ShockwaveZone result)
|
||||||
|
{
|
||||||
|
result = default;
|
||||||
|
float bestRadius = float.PositiveInfinity;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
foreach (ShockwaveZone zone in zones)
|
||||||
|
{
|
||||||
|
if (distance > zone.Radius || zone.Radius >= bestRadius)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
result = zone;
|
||||||
|
bestRadius = zone.Radius;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValidate()
|
||||||
|
{
|
||||||
|
for (int index = 0; index < zones.Length; index++)
|
||||||
|
{
|
||||||
|
ShockwaveZone zone = zones[index];
|
||||||
|
zone.Radius = Mathf.Max(0.1f, zone.Radius);
|
||||||
|
zone.PlayerImpulse = Mathf.Max(0f, zone.PlayerImpulse);
|
||||||
|
zone.RigidbodyImpulse = Mathf.Max(0f, zone.RigidbodyImpulse);
|
||||||
|
zone.CameraShakeAmplitude = Mathf.Max(0f, zone.CameraShakeAmplitude);
|
||||||
|
zone.CameraShakeDuration = Mathf.Max(0f, zone.CameraShakeDuration);
|
||||||
|
zone.CameraShakeFrequency = Mathf.Max(0.1f, zone.CameraShakeFrequency);
|
||||||
|
zones[index] = zone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 96e16f6f88696ea4c8b884f3e7cef9f7
|
||||||
@@ -5,14 +5,20 @@ using UnityEngine.Events;
|
|||||||
|
|
||||||
public sealed class NetworkButtonInteractable : NetworkInteractable
|
public sealed class NetworkButtonInteractable : NetworkInteractable
|
||||||
{
|
{
|
||||||
|
[SerializeField] private UnityEvent pressed = new();
|
||||||
[SerializeField] private UnityEvent<bool> buttonStateChanged;
|
[SerializeField] private UnityEvent<bool> buttonStateChanged;
|
||||||
|
|
||||||
private readonly NetworkVariable<bool> isPressed = new(
|
private readonly NetworkVariable<bool> isPressed = new(
|
||||||
false,
|
false,
|
||||||
NetworkVariableReadPermission.Everyone,
|
NetworkVariableReadPermission.Everyone,
|
||||||
NetworkVariableWritePermission.Server);
|
NetworkVariableWritePermission.Server);
|
||||||
|
private readonly NetworkVariable<uint> pressSequence = new(
|
||||||
|
0,
|
||||||
|
NetworkVariableReadPermission.Everyone,
|
||||||
|
NetworkVariableWritePermission.Server);
|
||||||
|
|
||||||
public bool IsPressed => isPressed.Value;
|
public bool IsPressed => isPressed.Value;
|
||||||
|
public uint PressSequence => pressSequence.Value;
|
||||||
|
|
||||||
public event Action Pressed;
|
public event Action Pressed;
|
||||||
public event Action<bool> StateChanged;
|
public event Action<bool> StateChanged;
|
||||||
@@ -20,24 +26,35 @@ public sealed class NetworkButtonInteractable : NetworkInteractable
|
|||||||
public override void OnNetworkSpawn()
|
public override void OnNetworkSpawn()
|
||||||
{
|
{
|
||||||
isPressed.OnValueChanged += HandleStateChanged;
|
isPressed.OnValueChanged += HandleStateChanged;
|
||||||
|
pressSequence.OnValueChanged += HandlePressSequenceChanged;
|
||||||
ApplyState(isPressed.Value);
|
ApplyState(isPressed.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnNetworkDespawn()
|
public override void OnNetworkDespawn()
|
||||||
{
|
{
|
||||||
isPressed.OnValueChanged -= HandleStateChanged;
|
isPressed.OnValueChanged -= HandleStateChanged;
|
||||||
|
pressSequence.OnValueChanged -= HandlePressSequenceChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CanInteractOnServer(ulong senderClientId) => true;
|
protected override bool CanInteractOnServer(ulong senderClientId) => true;
|
||||||
|
|
||||||
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
||||||
{
|
{
|
||||||
Pressed?.Invoke();
|
|
||||||
isPressed.Value = !isPressed.Value;
|
isPressed.Value = !isPressed.Value;
|
||||||
|
pressSequence.Value++;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleStateChanged(bool _, bool currentValue) => ApplyState(currentValue);
|
private void HandleStateChanged(bool _, bool currentValue) => ApplyState(currentValue);
|
||||||
|
|
||||||
|
private void HandlePressSequenceChanged(uint previous, uint current)
|
||||||
|
{
|
||||||
|
if (current == previous)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Pressed?.Invoke();
|
||||||
|
pressed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
private void ApplyState(bool currentValue)
|
private void ApplyState(bool currentValue)
|
||||||
{
|
{
|
||||||
StateChanged?.Invoke(currentValue);
|
StateChanged?.Invoke(currentValue);
|
||||||
|
|||||||
@@ -404,6 +404,12 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
|||||||
{
|
{
|
||||||
bool shouldBeKinematic = IsExternallyControlled || !IsServer;
|
bool shouldBeKinematic = IsExternallyControlled || !IsServer;
|
||||||
|
|
||||||
|
if (IsExternallyControlled && !physicsBody.isKinematic)
|
||||||
|
{
|
||||||
|
physicsBody.linearVelocity = Vector3.zero;
|
||||||
|
physicsBody.angularVelocity = Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
if (shouldBeKinematic)
|
if (shouldBeKinematic)
|
||||||
{
|
{
|
||||||
physicsBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
physicsBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||||
@@ -417,11 +423,6 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
|||||||
physicsBody.collisionDetectionMode = ResolveDynamicCollisionDetectionMode();
|
physicsBody.collisionDetectionMode = ResolveDynamicCollisionDetectionMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsExternallyControlled)
|
|
||||||
{
|
|
||||||
physicsBody.linearVelocity = Vector3.zero;
|
|
||||||
physicsBody.angularVelocity = Vector3.zero;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HasAnyDragger)
|
if (HasAnyDragger)
|
||||||
|
|||||||
@@ -37,8 +37,7 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
|||||||
if (!TryGetPlayerObject(senderClientId, out NetworkObject playerObject))
|
if (!TryGetPlayerObject(senderClientId, out NetworkObject playerObject))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
float maximumDistanceSquared = maximumInteractionDistance * maximumInteractionDistance;
|
if (!IsWithinInteractionDistance(playerObject.transform.position))
|
||||||
if ((playerObject.transform.position - transform.position).sqrMagnitude > maximumDistanceSquared)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!CanInteractOnServer(senderClientId))
|
if (!CanInteractOnServer(senderClientId))
|
||||||
@@ -47,6 +46,28 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
|||||||
InteractOnServer(senderClientId, playerObject);
|
InteractOnServer(senderClientId, playerObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool IsWithinInteractionDistance(Vector3 playerPosition)
|
||||||
|
{
|
||||||
|
float maximumDistanceSquared = maximumInteractionDistance * maximumInteractionDistance;
|
||||||
|
if ((playerPosition - transform.position).sqrMagnitude <= maximumDistanceSquared)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
foreach (Collider interactionCollider in GetComponentsInChildren<Collider>(true))
|
||||||
|
{
|
||||||
|
if (interactionCollider == null || !interactionCollider.enabled ||
|
||||||
|
!interactionCollider.gameObject.activeInHierarchy)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector3 closestPoint = interactionCollider.ClosestPoint(playerPosition);
|
||||||
|
if ((playerPosition - closestPoint).sqrMagnitude <= maximumDistanceSquared)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual bool CanInteractOnServer(ulong senderClientId) => false;
|
protected virtual bool CanInteractOnServer(ulong senderClientId) => false;
|
||||||
protected virtual void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { }
|
protected virtual void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { }
|
||||||
|
|
||||||
@@ -58,4 +79,3 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
|||||||
return playerObject != null;
|
return playerObject != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ public sealed class RocketItemDefinition : ItemDefinition
|
|||||||
{
|
{
|
||||||
[Tooltip("Stable rocket family id used by launchers and orders.")]
|
[Tooltip("Stable rocket family id used by launchers and orders.")]
|
||||||
[SerializeField] private string rocketTypeId;
|
[SerializeField] private string rocketTypeId;
|
||||||
|
[Tooltip("Combat type sent to the map when this rocket is fired.")]
|
||||||
|
[SerializeField] private MissileType missileType = MissileType.Standard;
|
||||||
[SerializeField, Min(0.1f)] private float criticalImpactSpeed = 7f;
|
[SerializeField, Min(0.1f)] private float criticalImpactSpeed = 7f;
|
||||||
|
|
||||||
public string RocketTypeId => string.IsNullOrWhiteSpace(rocketTypeId)
|
public string RocketTypeId => string.IsNullOrWhiteSpace(rocketTypeId)
|
||||||
? ItemId
|
? ItemId
|
||||||
: rocketTypeId;
|
: rocketTypeId;
|
||||||
|
public MissileType MissileType => missileType;
|
||||||
public float CriticalImpactSpeed => criticalImpactSpeed;
|
public float CriticalImpactSpeed => criticalImpactSpeed;
|
||||||
|
|
||||||
protected override void OnValidate()
|
protected override void OnValidate()
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public sealed class PlayerCameraShakeController : MonoBehaviour, IPlayerSystem
|
||||||
|
{
|
||||||
|
private Transform cameraTransform;
|
||||||
|
private Vector3 baseLocalPosition;
|
||||||
|
private float amplitude;
|
||||||
|
private float frequency;
|
||||||
|
private float seed;
|
||||||
|
private float endsAt;
|
||||||
|
private float startedAt;
|
||||||
|
private float duration;
|
||||||
|
|
||||||
|
public bool IsShaking => cameraTransform != null && Time.time < endsAt;
|
||||||
|
|
||||||
|
public void Initialize(PlayerFacade facade)
|
||||||
|
{
|
||||||
|
FirstPersonCameraController cameraController =
|
||||||
|
GetComponent<FirstPersonCameraController>();
|
||||||
|
cameraTransform = cameraController != null && cameraController.PlayerCamera != null
|
||||||
|
? cameraController.PlayerCamera.transform
|
||||||
|
: GetComponentInChildren<Camera>(true)?.transform;
|
||||||
|
|
||||||
|
if (cameraTransform != null)
|
||||||
|
baseLocalPosition = cameraTransform.localPosition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PlayShake(
|
||||||
|
float newAmplitude,
|
||||||
|
float newDuration,
|
||||||
|
float newFrequency,
|
||||||
|
float newSeed = 0f)
|
||||||
|
{
|
||||||
|
if (cameraTransform == null || newAmplitude <= 0f || newDuration <= 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float remainingStrength = IsShaking
|
||||||
|
? amplitude * Mathf.Clamp01((endsAt - Time.time) / Mathf.Max(0.01f, duration))
|
||||||
|
: 0f;
|
||||||
|
amplitude = Mathf.Max(remainingStrength, newAmplitude);
|
||||||
|
duration = newDuration;
|
||||||
|
frequency = Mathf.Max(0.1f, newFrequency);
|
||||||
|
seed = newSeed;
|
||||||
|
startedAt = Time.time;
|
||||||
|
endsAt = Time.time + newDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
if (cameraTransform == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!IsShaking)
|
||||||
|
{
|
||||||
|
cameraTransform.localPosition = baseLocalPosition;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
float normalizedTime = Mathf.Clamp01((Time.time - startedAt) / Mathf.Max(0.01f, duration));
|
||||||
|
float strength = amplitude * (1f - normalizedTime) * (1f - normalizedTime);
|
||||||
|
float sample = (Time.time - startedAt) * frequency;
|
||||||
|
Vector3 noise = new(
|
||||||
|
Mathf.PerlinNoise(seed, sample) * 2f - 1f,
|
||||||
|
Mathf.PerlinNoise(seed + 17.31f, sample) * 2f - 1f,
|
||||||
|
Mathf.PerlinNoise(seed + 39.77f, sample) * 2f - 1f);
|
||||||
|
cameraTransform.localPosition = baseLocalPosition + noise * strength;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable()
|
||||||
|
{
|
||||||
|
if (cameraTransform != null)
|
||||||
|
cameraTransform.localPosition = baseLocalPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b12d3c124e93b5f4f9c06c7e870d4068
|
||||||
@@ -13,10 +13,16 @@ public sealed class PlayerController : NetworkBehaviour, IPlayerSystem
|
|||||||
[SerializeField, Min(0.1f)] private float jumpHeight = 1.4f;
|
[SerializeField, Min(0.1f)] private float jumpHeight = 1.4f;
|
||||||
[SerializeField, Min(0.1f)] private float gravity = 20f;
|
[SerializeField, Min(0.1f)] private float gravity = 20f;
|
||||||
|
|
||||||
|
[Header("External impulses")]
|
||||||
|
[SerializeField, Min(0.1f)] private float impulseDamping = 9f;
|
||||||
|
|
||||||
private CharacterController characterController;
|
private CharacterController characterController;
|
||||||
private Vector3 verticalVelocity;
|
private Vector3 verticalVelocity;
|
||||||
|
private Vector3 externalImpulseVelocity;
|
||||||
private bool isInitialized;
|
private bool isInitialized;
|
||||||
|
|
||||||
|
public Vector3 ExternalImpulseVelocity => externalImpulseVelocity;
|
||||||
|
|
||||||
public void Initialize(PlayerFacade facade)
|
public void Initialize(PlayerFacade facade)
|
||||||
{
|
{
|
||||||
characterController = GetComponent<CharacterController>();
|
characterController = GetComponent<CharacterController>();
|
||||||
@@ -39,6 +45,14 @@ public sealed class PlayerController : NetworkBehaviour, IPlayerSystem
|
|||||||
transform.Rotate(0f, yawDelta, 0f, Space.Self);
|
transform.Rotate(0f, yawDelta, 0f, Space.Self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddImpulse(Vector3 velocityChange)
|
||||||
|
{
|
||||||
|
if (!isInitialized || !IsOwner)
|
||||||
|
return;
|
||||||
|
|
||||||
|
externalImpulseVelocity += velocityChange;
|
||||||
|
}
|
||||||
|
|
||||||
private void SimulateMovement(Vector2 moveInput, bool wantsToSprint, bool wantsToJump)
|
private void SimulateMovement(Vector2 moveInput, bool wantsToSprint, bool wantsToJump)
|
||||||
{
|
{
|
||||||
if (moveInput.sqrMagnitude > 1f)
|
if (moveInput.sqrMagnitude > 1f)
|
||||||
@@ -54,6 +68,12 @@ public sealed class PlayerController : NetworkBehaviour, IPlayerSystem
|
|||||||
|
|
||||||
float speed = wantsToSprint ? sprintSpeed : walkSpeed;
|
float speed = wantsToSprint ? sprintSpeed : walkSpeed;
|
||||||
Vector3 horizontalDirection = transform.right * moveInput.x + transform.forward * moveInput.y;
|
Vector3 horizontalDirection = transform.right * moveInput.x + transform.forward * moveInput.y;
|
||||||
characterController.Move((horizontalDirection * speed + verticalVelocity) * Time.deltaTime);
|
characterController.Move(
|
||||||
|
(horizontalDirection * speed + verticalVelocity + externalImpulseVelocity) *
|
||||||
|
Time.deltaTime);
|
||||||
|
externalImpulseVelocity = Vector3.MoveTowards(
|
||||||
|
externalImpulseVelocity,
|
||||||
|
Vector3.zero,
|
||||||
|
impulseDamping * Time.deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,6 +80,9 @@ public sealed class PlayerInteractionController : NetworkBehaviour, IPlayerSyste
|
|||||||
for (int hitIndex = 0; hitIndex < hitCount; hitIndex++)
|
for (int hitIndex = 0; hitIndex < hitCount; hitIndex++)
|
||||||
{
|
{
|
||||||
RaycastHit hit = interactionHits[hitIndex];
|
RaycastHit hit = interactionHits[hitIndex];
|
||||||
|
if (hit.collider != null && hit.collider.transform.IsChildOf(transform))
|
||||||
|
continue;
|
||||||
|
|
||||||
IInteractable interactable = FindInteractable(hit.collider);
|
IInteractable interactable = FindInteractable(hit.collider);
|
||||||
if (interactable == null)
|
if (interactable == null)
|
||||||
{
|
{
|
||||||
|
|||||||
+6365
-45785
File diff suppressed because one or more lines are too long
@@ -1,8 +1,15 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 6e59c59b81ab47f9b6ec5781fa725d2c
|
guid: 6e59c59b81ab47f9b6ec5781fa725d2c
|
||||||
timeCreated: 1484171296
|
timeCreated: 1484171296
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
TextScriptImporter:
|
TextScriptImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Fonts/LiberationSans - OFL.txt
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: e3265ab4bf004d28a9537516768c1c75
|
guid: e3265ab4bf004d28a9537516768c1c75
|
||||||
timeCreated: 1484171297
|
timeCreated: 1484171297
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
TrueTypeFontImporter:
|
TrueTypeFontImporter:
|
||||||
serializedVersion: 2
|
serializedVersion: 2
|
||||||
fontSize: 16
|
fontSize: 16
|
||||||
@@ -17,3 +17,10 @@ TrueTypeFontImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Fonts/LiberationSans.ttf
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ fileFormatVersion: 2
|
|||||||
guid: 731f1baa9d144a9897cb1d341c2092b8
|
guid: 731f1baa9d144a9897cb1d341c2092b8
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
timeCreated: 1442040525
|
timeCreated: 1442040525
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
|
|||||||
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: e73a58f6e2794ae7b1b7e50b7fb811b0
|
guid: e73a58f6e2794ae7b1b7e50b7fb811b0
|
||||||
timeCreated: 1484172806
|
timeCreated: 1484172806
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF -
|
||||||
|
Drop Shadow.mat
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
+68
-1081
File diff suppressed because it is too large
Load Diff
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 2e498d1c8094910479dc3e1b768306a4
|
guid: 2e498d1c8094910479dc3e1b768306a4
|
||||||
timeCreated: 1484171803
|
timeCreated: 1484171803
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF -
|
||||||
|
Fallback.asset
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
+9
-1
@@ -1,8 +1,16 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 79459efec17a4d00a321bdcc27bbc385
|
guid: 79459efec17a4d00a321bdcc27bbc385
|
||||||
timeCreated: 1484172856
|
timeCreated: 1484172856
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF -
|
||||||
|
Outline.mat
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 8f586378b4e144a9851e7b34d9b748ee
|
guid: 8f586378b4e144a9851e7b34d9b748ee
|
||||||
timeCreated: 1484171803
|
timeCreated: 1484171803
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Fonts & Materials/LiberationSans SDF.asset
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -6,3 +6,10 @@ TextScriptImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/LineBreaking Following Characters.txt
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -6,3 +6,10 @@ TextScriptImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/LineBreaking Leading Characters.txt
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ fileFormatVersion: 2
|
|||||||
guid: 512a49d95c0c4332bdd98131869c23c9
|
guid: 512a49d95c0c4332bdd98131869c23c9
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
timeCreated: 1441876896
|
timeCreated: 1441876896
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: c41005c129ba4d66911b75229fd70b45
|
guid: c41005c129ba4d66911b75229fd70b45
|
||||||
timeCreated: 1480316912
|
timeCreated: 1480316912
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Sprite Assets/EmojiOne.asset
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,15 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8a731a09451cba54096a9002cd9b9be5
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 11400000
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Sprite Assets/MixedIndexTest.asset
|
||||||
|
uploadId: 691001
|
||||||
@@ -2,7 +2,7 @@ fileFormatVersion: 2
|
|||||||
guid: 4aecb92fff08436c8303b10eab8da368
|
guid: 4aecb92fff08436c8303b10eab8da368
|
||||||
folderAsset: yes
|
folderAsset: yes
|
||||||
timeCreated: 1441876950
|
timeCreated: 1441876950
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
DefaultImporter:
|
DefaultImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
|
|||||||
@@ -6,3 +6,10 @@ NativeFormatImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/Style Sheets/Default Style Sheet.asset
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
fileFormatVersion: 2
|
fileFormatVersion: 2
|
||||||
guid: 3f5b5dff67a942289a9defa416b206f3
|
guid: 3f5b5dff67a942289a9defa416b206f3
|
||||||
timeCreated: 1436653997
|
timeCreated: 1436653997
|
||||||
licenseType: Pro
|
licenseType: Store
|
||||||
NativeFormatImporter:
|
NativeFormatImporter:
|
||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Resources/TMP Settings.asset
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/SDFFunctions.hlsl
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_Bitmap-Custom-Atlas.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_Bitmap-Mobile.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_Bitmap.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF Overlay.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF SSD.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ ScriptedImporter:
|
|||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP LIT.shadergraph
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ ScriptedImporter:
|
|||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-HDRP UNLIT.shadergraph
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Masking.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile Overlay.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile SSD.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile-2-Pass.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Mobile.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Surface-Mobile.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-Surface.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ ScriptedImporter:
|
|||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-URP Lit.shadergraph
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -8,3 +8,10 @@ ScriptedImporter:
|
|||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
script: {fileID: 11500000, guid: 625f186215c104763be7675aa2d941aa, type: 3}
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF-URP Unlit.shadergraph
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_SDF.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMP_Sprite.shader
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData: Version 2.0
|
userData: Version 2.0
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMPro.cginc
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMPro_Mobile.cginc
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMPro_Properties.cginc
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -7,3 +7,10 @@ ShaderImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Shaders/TMPro_Surface.cginc
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
@@ -5,3 +5,10 @@ TextScriptImporter:
|
|||||||
userData:
|
userData:
|
||||||
assetBundleName:
|
assetBundleName:
|
||||||
assetBundleVariant:
|
assetBundleVariant:
|
||||||
|
AssetOrigin:
|
||||||
|
serializedVersion: 1
|
||||||
|
productId: 127325
|
||||||
|
packageName: Particle Pack
|
||||||
|
packageVersion: 3.1
|
||||||
|
assetPath: Assets/TextMesh Pro/Sprites/EmojiOne Attribution.txt
|
||||||
|
uploadId: 691001
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user