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/Mesh/CirclePlane12v.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
|
||||
foundCompatibleSetup: 1
|
||||
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 =
|
||||
"Assets/CONTENT/Game/Prefabs/Interaction/PhysicalLever.prefab";
|
||||
private const string EnemyBalancePath =
|
||||
"Assets/CONTENT/FEATURES/MapRadar/Configs/MapEnemyBalance.asset";
|
||||
|
||||
[MenuItem("FORT-BO/Map Radar/Configure Network Prototype")]
|
||||
public static void ConfigureNetworkPrototype()
|
||||
@@ -31,6 +33,7 @@ public static class MapRadarNetworkSetup
|
||||
GetOrAdd<NetworkObject>(mapController.gameObject);
|
||||
NetworkBaseHealth baseHealth = GetOrAdd<NetworkBaseHealth>(mapController.gameObject);
|
||||
MapControlBinder binder = GetOrAdd<MapControlBinder>(mapController.gameObject);
|
||||
MapEnemyBalanceConfig enemyBalance = GetOrCreateEnemyBalance();
|
||||
|
||||
Transform controlsRoot = GetOrCreateChild(
|
||||
mapController.transform.root,
|
||||
@@ -62,6 +65,7 @@ public static class MapRadarNetworkSetup
|
||||
SetObject(map, "roundGameLoop",
|
||||
Object.FindFirstObjectByType<RoundGameLoopController>());
|
||||
SetObject(map, "baseHealth", baseHealth);
|
||||
SetObject(map, "enemyBalance", enemyBalance);
|
||||
map.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
SerializedObject binding = new(binder);
|
||||
@@ -82,10 +86,24 @@ public static class MapRadarNetworkSetup
|
||||
|
||||
Debug.Log(
|
||||
"[Map] Network prototype configured: X/Y wheels, fire lever, " +
|
||||
"scan button and base health are connected.",
|
||||
"scan button, enemy balance and base health are connected.",
|
||||
mapController);
|
||||
}
|
||||
|
||||
private static MapEnemyBalanceConfig GetOrCreateEnemyBalance()
|
||||
{
|
||||
MapEnemyBalanceConfig config =
|
||||
AssetDatabase.LoadAssetAtPath<MapEnemyBalanceConfig>(EnemyBalancePath);
|
||||
if (config != null)
|
||||
return config;
|
||||
|
||||
config = ScriptableObject.CreateInstance<MapEnemyBalanceConfig>();
|
||||
AssetDatabase.CreateAsset(config, EnemyBalancePath);
|
||||
EditorUtility.SetDirty(config);
|
||||
AssetDatabase.SaveAssets();
|
||||
return config;
|
||||
}
|
||||
|
||||
private static NetworkPhysicalWheelInteractable CreateWheel(
|
||||
Transform parent,
|
||||
string name,
|
||||
|
||||
@@ -9,12 +9,35 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
[SerializeField] private NetworkPhysicalLeverInteractable fireLever;
|
||||
[SerializeField] private NetworkButtonInteractable scanButton;
|
||||
|
||||
private bool controlsBound;
|
||||
private bool initialValuesApplied;
|
||||
|
||||
private void Reset() => mapController = GetComponent<MapController>();
|
||||
|
||||
private void OnEnable()
|
||||
private void OnEnable() => TryBindControls();
|
||||
|
||||
private void OnDisable() => UnbindControls();
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TryBindControls();
|
||||
|
||||
if (!controlsBound || initialValuesApplied ||
|
||||
horizontalWheel == null || verticalWheel == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
initialValuesApplied = true;
|
||||
HandleHorizontalChanged(horizontalWheel.NormalizedValue);
|
||||
HandleVerticalChanged(verticalWheel.NormalizedValue);
|
||||
}
|
||||
|
||||
private void TryBindControls()
|
||||
{
|
||||
if (controlsBound || !AreNetworkObjectsReady())
|
||||
return;
|
||||
|
||||
if (horizontalWheel != null)
|
||||
horizontalWheel.ValueChanged += HandleHorizontalChanged;
|
||||
if (verticalWheel != null)
|
||||
@@ -23,10 +46,15 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
fireLever.StateChanged += HandleFireChanged;
|
||||
if (scanButton != null)
|
||||
scanButton.Pressed += HandleScanPressed;
|
||||
|
||||
controlsBound = true;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
private void UnbindControls()
|
||||
{
|
||||
if (!controlsBound)
|
||||
return;
|
||||
|
||||
if (horizontalWheel != null)
|
||||
horizontalWheel.ValueChanged -= HandleHorizontalChanged;
|
||||
if (verticalWheel != null)
|
||||
@@ -35,20 +63,18 @@ public sealed class MapControlBinder : MonoBehaviour
|
||||
fireLever.StateChanged -= HandleFireChanged;
|
||||
if (scanButton != null)
|
||||
scanButton.Pressed -= HandleScanPressed;
|
||||
|
||||
controlsBound = false;
|
||||
initialValuesApplied = false;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
private bool AreNetworkObjectsReady()
|
||||
{
|
||||
if (initialValuesApplied || mapController == null || !mapController.IsSpawned ||
|
||||
horizontalWheel == null || verticalWheel == null ||
|
||||
!horizontalWheel.IsSpawned || !verticalWheel.IsSpawned)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
initialValuesApplied = true;
|
||||
HandleHorizontalChanged(horizontalWheel.NormalizedValue);
|
||||
HandleVerticalChanged(verticalWheel.NormalizedValue);
|
||||
return mapController != null && mapController.IsSpawned &&
|
||||
(horizontalWheel == null || horizontalWheel.IsSpawned) &&
|
||||
(verticalWheel == null || verticalWheel.IsSpawned) &&
|
||||
(fireLever == null || fireLever.IsSpawned) &&
|
||||
(scanButton == null || scanButton.IsSpawned);
|
||||
}
|
||||
|
||||
private void HandleHorizontalChanged(float value) =>
|
||||
|
||||
@@ -14,11 +14,11 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private MapPlayer mapPlayer;
|
||||
[SerializeField] private RoundGameLoopController roundGameLoop;
|
||||
[SerializeField] private NetworkBaseHealth baseHealth;
|
||||
[SerializeField] private MapEnemyBalanceConfig enemyBalance;
|
||||
|
||||
[Header("Target System")]
|
||||
[SerializeField] private MapTargetController targetController;
|
||||
[SerializeField] private Transform targetParent;
|
||||
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
|
||||
|
||||
[Header("Radar System")]
|
||||
[SerializeField] private MapRadarController radarController;
|
||||
@@ -30,11 +30,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private Transform relayY;
|
||||
[SerializeField] private Transform crosshairVisual;
|
||||
|
||||
[Header("Pseudo Shot")]
|
||||
[Header("Shot Rules")]
|
||||
[SerializeField, Min(0.01f)] private float hitRadius = 0.65f;
|
||||
[SerializeField, Min(1)] private int shotDamage = 1;
|
||||
[SerializeField] private MissileType pseudoMissileType = MissileType.Standard;
|
||||
[SerializeField] private bool ignoreMissileTypeForPrototype = true;
|
||||
[FormerlySerializedAs("ignoreMissileTypeForPrototype")]
|
||||
[SerializeField] private bool ignoreMissileTypeRequirements = true;
|
||||
|
||||
[Header("Network Events")]
|
||||
[SerializeField] private UnityEvent<int> waveStarted;
|
||||
@@ -42,6 +43,9 @@ public sealed class MapController : NetworkBehaviour
|
||||
[SerializeField] private UnityEvent allTargetsResolved;
|
||||
[SerializeField] private UnityEvent allTargetsDestroyed;
|
||||
|
||||
[Header("Shot Presentation")]
|
||||
[SerializeField] private MapImpactMarkerView impactMarkerView;
|
||||
|
||||
private readonly NetworkVariable<Vector2> crosshairCoordinates = new(
|
||||
new Vector2(5f, 5f), NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
@@ -77,6 +81,10 @@ public sealed class MapController : NetworkBehaviour
|
||||
public int CurrentWave => currentWave.Value;
|
||||
public int ActiveTargetCount => activeTargets.Count;
|
||||
public bool WaveIsActive => waveIsActive;
|
||||
public MapEnemyBalanceConfig EnemyBalance => enemyBalance;
|
||||
public float HitRadius => hitRadius;
|
||||
public int ShotDamage => shotDamage;
|
||||
public bool IgnoreMissileTypeRequirements => ignoreMissileTypeRequirements;
|
||||
public double RadarCooldownRemaining => IsSpawned && NetworkManager != null
|
||||
? Math.Max(0d, radarReadyAt.Value - NetworkManager.ServerTime.Time)
|
||||
: 0d;
|
||||
@@ -90,7 +98,6 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
simulationStep = Mathf.Max(0.02f, simulationStep);
|
||||
hitRadius = Mathf.Max(0.01f, hitRadius);
|
||||
shotDamage = Mathf.Max(1, shotDamage);
|
||||
}
|
||||
@@ -143,11 +150,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
if (!IsServer || !IsSpawned || !waveIsActive)
|
||||
return;
|
||||
|
||||
float step = enemyBalance != null ? enemyBalance.SimulationStep : 0.1f;
|
||||
simulationAccumulator += Time.deltaTime;
|
||||
while (simulationAccumulator >= simulationStep)
|
||||
while (simulationAccumulator >= step)
|
||||
{
|
||||
simulationAccumulator -= simulationStep;
|
||||
SimulateTargets(simulationStep);
|
||||
simulationAccumulator -= step;
|
||||
SimulateTargets(step);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,11 +184,20 @@ public sealed class MapController : NetworkBehaviour
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (IsServer)
|
||||
ResolvePseudoShotOnServer();
|
||||
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||
else
|
||||
RequestPseudoFireRpc();
|
||||
}
|
||||
|
||||
public bool RequestShot(MissileType missileType)
|
||||
{
|
||||
if (!IsSpawned || !IsServer || missileType == MissileType.None)
|
||||
return false;
|
||||
|
||||
ResolveShotOnServer(missileType, ignoreMissileTypeRequirements);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RequestRadarScan()
|
||||
{
|
||||
if (!IsSpawned)
|
||||
@@ -224,7 +241,8 @@ public sealed class MapController : NetworkBehaviour
|
||||
private void SetCrosshairCoordinatesRpc(Vector2 coordinates) =>
|
||||
SetCrosshairCoordinatesOnServer(coordinates);
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestPseudoFireRpc() => ResolvePseudoShotOnServer();
|
||||
private void RequestPseudoFireRpc() =>
|
||||
ResolveShotOnServer(pseudoMissileType, ignoreMissileTypeRequirements);
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestRadarScanRpc() => ScanRadarOnServer();
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
@@ -253,10 +271,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private bool StartNextWaveOnServer()
|
||||
{
|
||||
if (!IsServer || targetController == null ||
|
||||
if (!IsServer || targetController == null || enemyBalance == null ||
|
||||
!targetController.HasValidConfiguration())
|
||||
{
|
||||
Debug.LogWarning("[Map] Cannot start target wave: map or target configs are incomplete.", this);
|
||||
Debug.LogWarning(
|
||||
"[Map] Cannot start target wave: map, enemy balance or target configs are incomplete.",
|
||||
this);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -268,19 +288,18 @@ public sealed class MapController : NetworkBehaviour
|
||||
waveIsActive = true;
|
||||
simulationAccumulator = 0f;
|
||||
|
||||
for (int index = 0; index < targetController.TargetsPerWave; index++)
|
||||
for (int index = 0; index < enemyBalance.TargetsPerWave; index++)
|
||||
{
|
||||
int configIndex = targetController.GetRandomValidConfigIndex();
|
||||
TargetConfig config = targetController.GetConfig(configIndex);
|
||||
if (config == null)
|
||||
continue;
|
||||
|
||||
Vector2 coordinates = new(
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
|
||||
Vector2 coordinates = CreateSpawnCoordinates();
|
||||
activeTargets.Add(new MapTargetNetworkState(
|
||||
nextTargetId++, configIndex, coordinates,
|
||||
config.Health, config.TimeBeforeAttack));
|
||||
enemyBalance.GetHealth(config),
|
||||
enemyBalance.GetAttackDelay(config)));
|
||||
initialTargetCount++;
|
||||
}
|
||||
|
||||
@@ -303,11 +322,12 @@ public sealed class MapController : NetworkBehaviour
|
||||
}
|
||||
|
||||
bool isAirTarget = config.TargetPrefab is AirTarget;
|
||||
if (isAirTarget && mapPlayer != null && config.MovementSpeed > 0f)
|
||||
float movementSpeed = enemyBalance.GetMovementSpeed(config);
|
||||
if (isAirTarget && mapPlayer != null && movementSpeed > 0f)
|
||||
{
|
||||
Vector2 destination = mapPlayer.MapCoordinates;
|
||||
state.Coordinates = Vector2.MoveTowards(
|
||||
state.Coordinates, destination, config.MovementSpeed * deltaTime);
|
||||
state.Coordinates, destination, movementSpeed * deltaTime);
|
||||
if (Vector2.Distance(state.Coordinates, destination) <= config.StoppingDistance)
|
||||
{
|
||||
ResolveEscapedTarget(index, state, config);
|
||||
@@ -333,7 +353,9 @@ public sealed class MapController : NetworkBehaviour
|
||||
|
||||
private void ResolveEscapedTarget(int index, MapTargetNetworkState target, TargetConfig config)
|
||||
{
|
||||
int damage = config != null ? config.BaseDamage : 1;
|
||||
int damage = enemyBalance != null
|
||||
? enemyBalance.GetBaseDamage(config)
|
||||
: config != null ? config.BaseDamage : 1;
|
||||
escapedTargetCount++;
|
||||
activeTargets.RemoveAt(index);
|
||||
if (baseHealth != null)
|
||||
@@ -341,11 +363,15 @@ public sealed class MapController : NetworkBehaviour
|
||||
Debug.Log($"[Map] Target {target.TargetId} reached the base. Base damage: {damage}.", this);
|
||||
}
|
||||
|
||||
private void ResolvePseudoShotOnServer()
|
||||
private void ResolveShotOnServer(MissileType missileType, bool ignoreMissileType)
|
||||
{
|
||||
if (!IsServer || !waveIsActive)
|
||||
{
|
||||
PublishShotResult(false, -1, -1f);
|
||||
PublishShotResult(
|
||||
false, -1, crosshairCoordinates.Value, -1f, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] MISS with {missileType}: there is no active target wave.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,36 +391,67 @@ public sealed class MapController : NetworkBehaviour
|
||||
{
|
||||
MapTargetNetworkState target = activeTargets[nearestIndex];
|
||||
TargetConfig config = targetController.GetConfig(target.ConfigIndex);
|
||||
bool missileMatches = ignoreMissileTypeForPrototype || config == null ||
|
||||
bool missileMatches = ignoreMissileType || config == null ||
|
||||
config.RequiredMissileType == MissileType.None ||
|
||||
config.RequiredMissileType == pseudoMissileType;
|
||||
if (missileMatches)
|
||||
config.RequiredMissileType == missileType;
|
||||
if (!missileMatches)
|
||||
{
|
||||
PublishShotResult(
|
||||
false, target.TargetId, crosshairCoordinates.Value,
|
||||
nearestDistance, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] MISS target {target.TargetId}: {missileType} does not " +
|
||||
$"match required {config.RequiredMissileType}. Distance " +
|
||||
$"{nearestDistance:0.00} is inside hit radius {hitRadius:0.00}.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
target.Health -= shotDamage;
|
||||
if (target.Health <= 0)
|
||||
activeTargets.RemoveAt(nearestIndex);
|
||||
else
|
||||
activeTargets[nearestIndex] = target;
|
||||
|
||||
PublishShotResult(true, target.TargetId, nearestDistance);
|
||||
Debug.Log($"[Map] HIT target {target.TargetId} at {nearestDistance:0.00} map units.", this);
|
||||
PublishShotResult(
|
||||
true, target.TargetId, target.Coordinates,
|
||||
nearestDistance, missileType);
|
||||
Debug.Log(
|
||||
$"[Map] HIT target {target.TargetId} with {missileType} " +
|
||||
$"at {nearestDistance:0.00} map units. Damage: {shotDamage}.",
|
||||
this);
|
||||
TryCompleteWaveOnServer();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int missedTargetId = nearestIndex >= 0 ? activeTargets[nearestIndex].TargetId : -1;
|
||||
PublishShotResult(false, missedTargetId, nearestDistance);
|
||||
float printedDistance = float.IsPositiveInfinity(nearestDistance) ? -1f : nearestDistance;
|
||||
Debug.Log($"[Map] MISS at {crosshairCoordinates.Value}. Nearest distance: {printedDistance:0.00}.", this);
|
||||
PublishShotResult(
|
||||
false, missedTargetId, crosshairCoordinates.Value,
|
||||
nearestDistance, missileType);
|
||||
if (nearestIndex < 0)
|
||||
{
|
||||
Debug.Log($"[Map] MISS with {missileType}: no active targets.", this);
|
||||
return;
|
||||
}
|
||||
|
||||
private void PublishShotResult(bool hit, int targetId, float distance)
|
||||
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,
|
||||
Vector2 coordinates,
|
||||
float distance,
|
||||
MissileType missileType)
|
||||
{
|
||||
lastShotResult.Value = new MapShotResultState(
|
||||
lastShotResult.Value.Sequence + 1, hit, targetId,
|
||||
crosshairCoordinates.Value,
|
||||
float.IsPositiveInfinity(distance) ? -1f : distance);
|
||||
coordinates,
|
||||
float.IsPositiveInfinity(distance) ? -1f : distance,
|
||||
missileType);
|
||||
}
|
||||
|
||||
private void ScanRadarOnServer()
|
||||
@@ -428,11 +485,39 @@ public sealed class MapController : NetworkBehaviour
|
||||
$"escaped: {escapedTargetCount}.", this);
|
||||
}
|
||||
|
||||
private Vector2 CreateSpawnCoordinates()
|
||||
{
|
||||
Vector2 baseCoordinates = mapPlayer != null
|
||||
? mapPlayer.MapCoordinates
|
||||
: new Vector2(5f, 5f);
|
||||
Vector2 farthestCandidate = Vector2.zero;
|
||||
float farthestDistance = -1f;
|
||||
|
||||
for (int attempt = 0; attempt < enemyBalance.SpawnPositionAttempts; attempt++)
|
||||
{
|
||||
Vector2 candidate = new(
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum),
|
||||
UnityEngine.Random.Range(0f, MapCoordinateSystem.CoordinateMaximum));
|
||||
float distance = Vector2.Distance(candidate, baseCoordinates);
|
||||
if (distance >= enemyBalance.MinimumSpawnDistanceFromBase)
|
||||
return candidate;
|
||||
|
||||
if (distance > farthestDistance)
|
||||
{
|
||||
farthestDistance = distance;
|
||||
farthestCandidate = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return farthestCandidate;
|
||||
}
|
||||
|
||||
private void ConfigurePresentation()
|
||||
{
|
||||
mapPlayer?.Configure(coordinateSystem);
|
||||
targetController?.Configure(coordinateSystem, targetParent, mapPlayer);
|
||||
radarController?.Configure(coordinateSystem, radarMarkPrefab);
|
||||
impactMarkerView?.Configure(coordinateSystem);
|
||||
crosshair?.Configure(
|
||||
coordinateSystem, relayX, relayY, crosshairVisual, targetController);
|
||||
}
|
||||
@@ -498,6 +583,7 @@ public sealed class MapController : NetworkBehaviour
|
||||
return;
|
||||
ShotResolved?.Invoke(current);
|
||||
shotResolved?.Invoke(current.Hit);
|
||||
impactMarkerView?.Show(current);
|
||||
}
|
||||
|
||||
private void HandleResolvedSequenceChanged(uint previous, uint current)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "MapEnemyBalance",
|
||||
menuName = "Map Radar/Enemy Balance Config")]
|
||||
public sealed class MapEnemyBalanceConfig : ScriptableObject
|
||||
{
|
||||
[Header("Wave")]
|
||||
[SerializeField, Min(0)] private int targetsPerWave = 5;
|
||||
[SerializeField, Min(0.02f)] private float simulationStep = 0.1f;
|
||||
|
||||
[Header("Spawn")]
|
||||
[SerializeField, Range(0f, MapCoordinateSystem.CoordinateMaximum)]
|
||||
private float minimumSpawnDistanceFromBase = 5f;
|
||||
[SerializeField, Range(1, 128)] private int spawnPositionAttempts = 32;
|
||||
|
||||
[Header("Global Balance Multipliers")]
|
||||
[SerializeField, Min(0f)] private float movementSpeedMultiplier = 0.5f;
|
||||
[SerializeField, Min(0f)] private float attackDelayMultiplier = 1.5f;
|
||||
[SerializeField, Min(0.01f)] private float healthMultiplier = 1f;
|
||||
[SerializeField, Min(0f)] private float baseDamageMultiplier = 1f;
|
||||
|
||||
public int TargetsPerWave => Mathf.Max(0, targetsPerWave);
|
||||
public float SimulationStep => Mathf.Max(0.02f, simulationStep);
|
||||
public float MinimumSpawnDistanceFromBase => Mathf.Clamp(
|
||||
minimumSpawnDistanceFromBase,
|
||||
0f,
|
||||
MapCoordinateSystem.CoordinateMaximum);
|
||||
public int SpawnPositionAttempts => Mathf.Clamp(spawnPositionAttempts, 1, 128);
|
||||
public float MovementSpeedMultiplier => Mathf.Max(0f, movementSpeedMultiplier);
|
||||
public float AttackDelayMultiplier => Mathf.Max(0f, attackDelayMultiplier);
|
||||
public float HealthMultiplier => Mathf.Max(0.01f, healthMultiplier);
|
||||
public float BaseDamageMultiplier => Mathf.Max(0f, baseDamageMultiplier);
|
||||
|
||||
public float GetMovementSpeed(TargetConfig target) =>
|
||||
target != null ? target.MovementSpeed * MovementSpeedMultiplier : 0f;
|
||||
|
||||
public float GetAttackDelay(TargetConfig target) =>
|
||||
target != null ? target.TimeBeforeAttack * AttackDelayMultiplier : 0f;
|
||||
|
||||
public int GetHealth(TargetConfig target) => target != null
|
||||
? Mathf.Max(1, Mathf.RoundToInt(target.Health * HealthMultiplier))
|
||||
: 1;
|
||||
|
||||
public int GetBaseDamage(TargetConfig target) => target != null
|
||||
? Mathf.Max(0, Mathf.RoundToInt(target.BaseDamage * BaseDamageMultiplier))
|
||||
: 1;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
targetsPerWave = Mathf.Max(0, targetsPerWave);
|
||||
simulationStep = Mathf.Max(0.02f, simulationStep);
|
||||
minimumSpawnDistanceFromBase = Mathf.Clamp(
|
||||
minimumSpawnDistanceFromBase,
|
||||
0f,
|
||||
MapCoordinateSystem.CoordinateMaximum);
|
||||
spawnPositionAttempts = Mathf.Clamp(spawnPositionAttempts, 1, 128);
|
||||
movementSpeedMultiplier = Mathf.Max(0f, movementSpeedMultiplier);
|
||||
attackDelayMultiplier = Mathf.Max(0f, attackDelayMultiplier);
|
||||
healthMultiplier = Mathf.Max(0.01f, healthMultiplier);
|
||||
baseDamageMultiplier = Mathf.Max(0f, baseDamageMultiplier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1dfcdffb89f04cd468e4f8386b4f67e8
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class MapImpactMarkerView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private MapCoordinateSystem coordinateSystem;
|
||||
[SerializeField, Min(0.01f)] private float markerLength = 0.35f;
|
||||
[SerializeField, Min(0.005f)] private float markerThickness = 0.035f;
|
||||
[SerializeField, Min(0f)] private float surfaceOffset = 0.025f;
|
||||
[SerializeField, Min(0.1f)] private float lifetime = 5f;
|
||||
[SerializeField] private Color hitColor = new(1f, 0.1f, 0.05f, 1f);
|
||||
[SerializeField] private Color missColor = new(1f, 0.75f, 0f, 1f);
|
||||
[SerializeField] private bool showMisses = true;
|
||||
[SerializeField] private UnityEvent hitMarked;
|
||||
[SerializeField] private UnityEvent missMarked;
|
||||
|
||||
public void Configure(MapCoordinateSystem coordinates) => coordinateSystem = coordinates;
|
||||
|
||||
public void Show(MapShotResultState result)
|
||||
{
|
||||
if (coordinateSystem == null || (!result.Hit && !showMisses))
|
||||
return;
|
||||
|
||||
CreateMarker(result.Coordinates, result.Hit ? hitColor : missColor);
|
||||
if (result.Hit)
|
||||
hitMarked?.Invoke();
|
||||
else
|
||||
missMarked?.Invoke();
|
||||
}
|
||||
|
||||
private void CreateMarker(Vector2 coordinates, Color color)
|
||||
{
|
||||
Vector3 horizontal = coordinateSystem.BottomRight.position -
|
||||
coordinateSystem.BottomLeft.position;
|
||||
Vector3 vertical = coordinateSystem.TopLeft.position -
|
||||
coordinateSystem.BottomLeft.position;
|
||||
Vector3 normal = Vector3.Cross(horizontal, vertical).normalized;
|
||||
if (normal.sqrMagnitude < 0.5f)
|
||||
normal = transform.forward;
|
||||
|
||||
GameObject marker = new($"Impact {coordinates.x:0.0}, {coordinates.y:0.0}");
|
||||
marker.transform.SetParent(transform, true);
|
||||
marker.transform.SetPositionAndRotation(
|
||||
coordinateSystem.CoordinatesToWorldPosition(coordinates) + normal * surfaceOffset,
|
||||
Quaternion.LookRotation(normal, vertical.normalized));
|
||||
|
||||
CreateBar(marker.transform, 45f, color);
|
||||
CreateBar(marker.transform, -45f, color);
|
||||
StartCoroutine(DestroyAfterLifetime(marker));
|
||||
}
|
||||
|
||||
private void CreateBar(Transform parent, float angle, Color color)
|
||||
{
|
||||
GameObject bar = GameObject.CreatePrimitive(PrimitiveType.Cube);
|
||||
bar.name = "Marker Bar";
|
||||
bar.transform.SetParent(parent, false);
|
||||
bar.transform.localRotation = Quaternion.Euler(0f, 0f, angle);
|
||||
bar.transform.localScale = new Vector3(
|
||||
markerLength, markerThickness, markerThickness);
|
||||
Collider collider = bar.GetComponent<Collider>();
|
||||
if (collider != null)
|
||||
Destroy(collider);
|
||||
Renderer renderer = bar.GetComponent<Renderer>();
|
||||
if (renderer != null)
|
||||
renderer.material.color = color;
|
||||
}
|
||||
|
||||
private IEnumerator DestroyAfterLifetime(GameObject marker)
|
||||
{
|
||||
yield return new WaitForSeconds(lifetime);
|
||||
if (marker != null)
|
||||
Destroy(marker);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b3852b847aa89b48b9d38476e56250e
|
||||
@@ -5,8 +5,7 @@ using UnityEngine;
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class MapTargetController : MonoBehaviour
|
||||
{
|
||||
[Header("Wave Settings")]
|
||||
[SerializeField, Min(0)] private int targetsPerWave = 5;
|
||||
[Header("Target Types")]
|
||||
[SerializeField] private TargetConfig[] randomTargetConfigs = Array.Empty<TargetConfig>();
|
||||
[SerializeField] private bool showLiveTargetVisuals;
|
||||
|
||||
@@ -24,11 +23,9 @@ public sealed class MapTargetController : MonoBehaviour
|
||||
public IReadOnlyList<MapTarget> Targets => currentWaveTargets;
|
||||
public IReadOnlyList<TargetConfig> TargetConfigs => randomTargetConfigs;
|
||||
public int CurrentWave => currentWave;
|
||||
public int TargetsPerWave => targetsPerWave;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
targetsPerWave = Mathf.Max(0, targetsPerWave);
|
||||
currentWave = Mathf.Max(0, currentWave);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,19 +51,22 @@ public struct MapShotResultState : INetworkSerializable,
|
||||
public int TargetId;
|
||||
public Vector2 Coordinates;
|
||||
public float Distance;
|
||||
public MissileType MissileType;
|
||||
|
||||
public MapShotResultState(
|
||||
uint sequence,
|
||||
bool hit,
|
||||
int targetId,
|
||||
Vector2 coordinates,
|
||||
float distance)
|
||||
float distance,
|
||||
MissileType missileType)
|
||||
{
|
||||
Sequence = sequence;
|
||||
Hit = hit;
|
||||
TargetId = targetId;
|
||||
Coordinates = coordinates;
|
||||
Distance = distance;
|
||||
MissileType = missileType;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||
@@ -74,10 +77,11 @@ public struct MapShotResultState : INetworkSerializable,
|
||||
serializer.SerializeValue(ref TargetId);
|
||||
serializer.SerializeValue(ref Coordinates);
|
||||
serializer.SerializeValue(ref Distance);
|
||||
serializer.SerializeValue(ref MissileType);
|
||||
}
|
||||
|
||||
public bool Equals(MapShotResultState other) =>
|
||||
Sequence == other.Sequence && Hit == other.Hit &&
|
||||
TargetId == other.TargetId && Coordinates == other.Coordinates &&
|
||||
Distance.Equals(other.Distance);
|
||||
Distance.Equals(other.Distance) && MissileType == other.MissileType;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ GameObject:
|
||||
- component: {fileID: 6442729922619842682}
|
||||
- component: {fileID: 5032171862004131710}
|
||||
- component: {fileID: 5463195673691487104}
|
||||
- component: {fileID: 5685992952666159115}
|
||||
- component: {fileID: 5909870405075214492}
|
||||
m_Layer: 0
|
||||
m_Name: NetworkPlayer
|
||||
m_TagString: Untagged
|
||||
@@ -205,6 +207,7 @@ MonoBehaviour:
|
||||
sprintSpeed: 7
|
||||
jumpHeight: 1.4
|
||||
gravity: 20
|
||||
impulseDamping: 9
|
||||
--- !u!114 &3719056139727702052
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -219,6 +222,7 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
verticalLookLimit: 89
|
||||
remotePitchFollowSpeed: 20
|
||||
--- !u!114 &2519868927162778852
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -305,6 +309,31 @@ MonoBehaviour:
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
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
|
||||
GameObject:
|
||||
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
|
||||
worldPrefab: {fileID: 7456648717704822871, guid: 746f04d9ba6c87d4bae8a09287c5d090, type: 3}
|
||||
rocketTypeId: standard
|
||||
missileType: 1
|
||||
criticalImpactSpeed: 7
|
||||
|
||||
@@ -17,6 +17,8 @@ GameObject:
|
||||
- component: {fileID: 4269639932524941515}
|
||||
- component: {fileID: 6493104616022895181}
|
||||
- component: {fileID: 3635736001397410476}
|
||||
- component: {fileID: 633656625536386338}
|
||||
- component: {fileID: 4161244424843525666}
|
||||
m_Layer: 0
|
||||
m_Name: Ready-Rocket-Standart
|
||||
m_TagString: Untagged
|
||||
@@ -52,7 +54,7 @@ MonoBehaviour:
|
||||
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
GlobalObjectIdHash: 3332465624
|
||||
GlobalObjectIdHash: 246746440
|
||||
InScenePlacedSourceGlobalObjectIdHash: 0
|
||||
DeferredDespawnTick: 0
|
||||
Ownership: 1
|
||||
@@ -261,6 +263,44 @@ MonoBehaviour:
|
||||
onCriticalImpactSpeed:
|
||||
m_PersistentCalls:
|
||||
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
|
||||
PrefabInstance:
|
||||
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
|
||||
{
|
||||
[SerializeField] private UnityEvent pressed = new();
|
||||
[SerializeField] private UnityEvent<bool> buttonStateChanged;
|
||||
|
||||
private readonly NetworkVariable<bool> isPressed = new(
|
||||
false,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<uint> pressSequence = new(
|
||||
0,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
public bool IsPressed => isPressed.Value;
|
||||
public uint PressSequence => pressSequence.Value;
|
||||
|
||||
public event Action Pressed;
|
||||
public event Action<bool> StateChanged;
|
||||
@@ -20,24 +26,35 @@ public sealed class NetworkButtonInteractable : NetworkInteractable
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
isPressed.OnValueChanged += HandleStateChanged;
|
||||
pressSequence.OnValueChanged += HandlePressSequenceChanged;
|
||||
ApplyState(isPressed.Value);
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
isPressed.OnValueChanged -= HandleStateChanged;
|
||||
pressSequence.OnValueChanged -= HandlePressSequenceChanged;
|
||||
}
|
||||
|
||||
protected override bool CanInteractOnServer(ulong senderClientId) => true;
|
||||
|
||||
protected override void InteractOnServer(ulong senderClientId, NetworkObject playerObject)
|
||||
{
|
||||
Pressed?.Invoke();
|
||||
isPressed.Value = !isPressed.Value;
|
||||
pressSequence.Value++;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
StateChanged?.Invoke(currentValue);
|
||||
|
||||
@@ -404,6 +404,12 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
||||
{
|
||||
bool shouldBeKinematic = IsExternallyControlled || !IsServer;
|
||||
|
||||
if (IsExternallyControlled && !physicsBody.isKinematic)
|
||||
{
|
||||
physicsBody.linearVelocity = Vector3.zero;
|
||||
physicsBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
|
||||
if (shouldBeKinematic)
|
||||
{
|
||||
physicsBody.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative;
|
||||
@@ -417,11 +423,6 @@ public sealed class NetworkDraggableInteractable : NetworkInteractable, IWorksta
|
||||
physicsBody.collisionDetectionMode = ResolveDynamicCollisionDetectionMode();
|
||||
}
|
||||
|
||||
if (IsExternallyControlled)
|
||||
{
|
||||
physicsBody.linearVelocity = Vector3.zero;
|
||||
physicsBody.angularVelocity = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
if (HasAnyDragger)
|
||||
|
||||
@@ -37,8 +37,7 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
if (!TryGetPlayerObject(senderClientId, out NetworkObject playerObject))
|
||||
return;
|
||||
|
||||
float maximumDistanceSquared = maximumInteractionDistance * maximumInteractionDistance;
|
||||
if ((playerObject.transform.position - transform.position).sqrMagnitude > maximumDistanceSquared)
|
||||
if (!IsWithinInteractionDistance(playerObject.transform.position))
|
||||
return;
|
||||
|
||||
if (!CanInteractOnServer(senderClientId))
|
||||
@@ -47,6 +46,28 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
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 void InteractOnServer(ulong senderClientId, NetworkObject playerObject) { }
|
||||
|
||||
@@ -58,4 +79,3 @@ public class NetworkInteractable : NetworkBehaviour, IInteractable
|
||||
return playerObject != null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,14 @@ public sealed class RocketItemDefinition : ItemDefinition
|
||||
{
|
||||
[Tooltip("Stable rocket family id used by launchers and orders.")]
|
||||
[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;
|
||||
|
||||
public string RocketTypeId => string.IsNullOrWhiteSpace(rocketTypeId)
|
||||
? ItemId
|
||||
: rocketTypeId;
|
||||
public MissileType MissileType => missileType;
|
||||
public float CriticalImpactSpeed => criticalImpactSpeed;
|
||||
|
||||
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 gravity = 20f;
|
||||
|
||||
[Header("External impulses")]
|
||||
[SerializeField, Min(0.1f)] private float impulseDamping = 9f;
|
||||
|
||||
private CharacterController characterController;
|
||||
private Vector3 verticalVelocity;
|
||||
private Vector3 externalImpulseVelocity;
|
||||
private bool isInitialized;
|
||||
|
||||
public Vector3 ExternalImpulseVelocity => externalImpulseVelocity;
|
||||
|
||||
public void Initialize(PlayerFacade facade)
|
||||
{
|
||||
characterController = GetComponent<CharacterController>();
|
||||
@@ -39,6 +45,14 @@ public sealed class PlayerController : NetworkBehaviour, IPlayerSystem
|
||||
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)
|
||||
{
|
||||
if (moveInput.sqrMagnitude > 1f)
|
||||
@@ -54,6 +68,12 @@ public sealed class PlayerController : NetworkBehaviour, IPlayerSystem
|
||||
|
||||
float speed = wantsToSprint ? sprintSpeed : walkSpeed;
|
||||
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++)
|
||||
{
|
||||
RaycastHit hit = interactionHits[hitIndex];
|
||||
if (hit.collider != null && hit.collider.transform.IsChildOf(transform))
|
||||
continue;
|
||||
|
||||
IInteractable interactable = FindInteractable(hit.collider);
|
||||
if (interactable == null)
|
||||
{
|
||||
|
||||
+6363
-45783
File diff suppressed because one or more lines are too long
@@ -1,8 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e59c59b81ab47f9b6ec5781fa725d2c
|
||||
timeCreated: 1484171296
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
guid: e3265ab4bf004d28a9537516768c1c75
|
||||
timeCreated: 1484171297
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
TrueTypeFontImporter:
|
||||
serializedVersion: 2
|
||||
fontSize: 16
|
||||
@@ -17,3 +17,10 @@ TrueTypeFontImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
folderAsset: yes
|
||||
timeCreated: 1442040525
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
||||
+9
-1
@@ -1,8 +1,16 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e73a58f6e2794ae7b1b7e50b7fb811b0
|
||||
timeCreated: 1484172806
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
guid: 2e498d1c8094910479dc3e1b768306a4
|
||||
timeCreated: 1484171803
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
guid: 79459efec17a4d00a321bdcc27bbc385
|
||||
timeCreated: 1484172856
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
guid: 8f586378b4e144a9851e7b34d9b748ee
|
||||
timeCreated: 1484171803
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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
|
||||
folderAsset: yes
|
||||
timeCreated: 1441876896
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c41005c129ba4d66911b75229fd70b45
|
||||
timeCreated: 1480316912
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
folderAsset: yes
|
||||
timeCreated: 1441876950
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
|
||||
@@ -6,3 +6,10 @@ NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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
|
||||
guid: 3f5b5dff67a942289a9defa416b206f3
|
||||
timeCreated: 1436653997
|
||||
licenseType: Pro
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleVariant:
|
||||
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:
|
||||
assetBundleVariant:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleVariant:
|
||||
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:
|
||||
assetBundleVariant:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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:
|
||||
assetBundleName:
|
||||
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