Sync daemon authority migration
This commit is contained in:
parent
030b4b5d74
commit
39a0dc8737
295 changed files with 25121 additions and 7360 deletions
|
|
@ -73,7 +73,7 @@ public class ActionBarConsumableBinding : ActionBarBinding
|
|||
|
||||
public override void Activate()
|
||||
{
|
||||
Entity.TryActivateConsumable(Target);
|
||||
ActionGameManager.Instance.TryRequestDaemonActionBarConsumable(TargetItemKey);
|
||||
}
|
||||
|
||||
public override void Deactivate()
|
||||
|
|
@ -162,13 +162,13 @@ public class ActionBarGearBinding : ActionBarBinding
|
|||
public override void Activate()
|
||||
{
|
||||
Active = true;
|
||||
Behavior.Activate();
|
||||
ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, true);
|
||||
}
|
||||
|
||||
public override void Deactivate()
|
||||
{
|
||||
Active = false;
|
||||
Behavior.Deactivate();
|
||||
ActionGameManager.Instance.TryRequestDaemonActionBarBehavior(EquipmentIndex, BehaviorIndex, false);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
|
|
@ -191,18 +191,12 @@ public class ActionBarWeaponGroupBinding : ActionBarBinding
|
|||
|
||||
public override void Activate()
|
||||
{
|
||||
foreach (var weapon in Entity.WeaponGroups[Group].weapons)
|
||||
{
|
||||
weapon.Activate();
|
||||
}
|
||||
ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, true);
|
||||
}
|
||||
|
||||
public override void Deactivate()
|
||||
{
|
||||
foreach (var weapon in Entity.WeaponGroups[Group].weapons)
|
||||
{
|
||||
weapon.Deactivate();
|
||||
}
|
||||
ActionGameManager.Instance.TryRequestDaemonActionBarWeaponGroup(Group, false);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
|
|
|
|||
184
Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs
Normal file
184
Assets/Scripts/Gameplay/AetheriaDaemonIndirectRenderer.cs
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
using System.Collections.Generic;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
public sealed class AetheriaDaemonIndirectRenderer : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private AetheriaDaemonObserver observer;
|
||||
|
||||
[SerializeField]
|
||||
private AetheriaDaemonRenderAssetCatalog assetCatalog;
|
||||
|
||||
[SerializeField]
|
||||
private bool logMissingAssets;
|
||||
|
||||
[SerializeField]
|
||||
private Camera targetCamera;
|
||||
|
||||
private MaterialPropertyBlock _properties;
|
||||
private readonly Dictionary<int, AetheriaDaemonRenderGroupBatch> _batchesByGroupId =
|
||||
new Dictionary<int, AetheriaDaemonRenderGroupBatch>();
|
||||
private readonly HashSet<int> _seenGroupIds = new HashSet<int>();
|
||||
private readonly List<int> _staleGroupIds = new List<int>();
|
||||
private static readonly int MatrixBufferPropertyId =
|
||||
Shader.PropertyToID(AetheriaDaemonRenderBuffer.MatrixBufferPropertyName);
|
||||
private static readonly int InstanceCountPropertyId =
|
||||
Shader.PropertyToID(AetheriaDaemonRenderBuffer.InstanceCountPropertyName);
|
||||
|
||||
public int LastDrawnCount { get; private set; }
|
||||
public bool HasDrawTarget =>
|
||||
observer != null &&
|
||||
assetCatalog != null;
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
observer = GetComponent<AetheriaDaemonObserver>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
DrawLatest();
|
||||
}
|
||||
|
||||
public bool DrawLatest()
|
||||
{
|
||||
LastDrawnCount = 0;
|
||||
if (!HasDrawTarget)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!observer.HasRenderNativeView)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_properties ??= new MaterialPropertyBlock();
|
||||
var drewAny = false;
|
||||
var index = observer.LastSoaIndex;
|
||||
var view = observer.LastRenderNativeView;
|
||||
_seenGroupIds.Clear();
|
||||
foreach (var renderGroup in index.RenderGroups)
|
||||
{
|
||||
_seenGroupIds.Add(renderGroup.GroupId);
|
||||
if (!assetCatalog.TryResolveMesh(renderGroup.MeshKey, out var mesh) ||
|
||||
!assetCatalog.TryResolveMaterial(renderGroup.MaterialKey, out var material))
|
||||
{
|
||||
if (logMissingAssets)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"Could not resolve daemon render group {renderGroup.GroupId} " +
|
||||
$"mesh='{renderGroup.MeshKey}' material='{renderGroup.MaterialKey}'.");
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!_batchesByGroupId.TryGetValue(renderGroup.GroupId, out var batch))
|
||||
{
|
||||
batch = new AetheriaDaemonRenderGroupBatch();
|
||||
_batchesByGroupId.Add(renderGroup.GroupId, batch);
|
||||
}
|
||||
|
||||
if (batch.LastUploadedGeneration != view.Generation)
|
||||
{
|
||||
batch.UploadLatest(view, renderGroup, mesh, renderGroup.DefaultScale);
|
||||
}
|
||||
|
||||
if (!batch.HasGpuBuffers)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_properties.Clear();
|
||||
_properties.SetInt(InstanceCountPropertyId, batch.InstanceCount);
|
||||
_properties.SetBuffer(MatrixBufferPropertyId, batch.MatrixBuffer);
|
||||
|
||||
Graphics.DrawMeshInstancedIndirect(
|
||||
mesh,
|
||||
renderGroup.SubMeshIndex,
|
||||
material,
|
||||
GetDrawBounds(renderGroup),
|
||||
batch.IndirectArgsBuffer,
|
||||
0,
|
||||
_properties,
|
||||
GetShadowCastingMode(renderGroup),
|
||||
renderGroup.ReceiveShadows,
|
||||
renderGroup.Layer,
|
||||
targetCamera);
|
||||
|
||||
LastDrawnCount += batch.InstanceCount;
|
||||
drewAny = true;
|
||||
}
|
||||
|
||||
ReleaseStaleBatches();
|
||||
return drewAny;
|
||||
}
|
||||
|
||||
private Bounds GetDrawBounds(AetheriaRuntimeDaemonRenderGroupDocument renderGroup)
|
||||
{
|
||||
return new Bounds(
|
||||
new Vector3(
|
||||
renderGroup.BoundsCenterX,
|
||||
renderGroup.BoundsCenterY,
|
||||
renderGroup.BoundsCenterZ),
|
||||
new Vector3(
|
||||
renderGroup.BoundsSizeX,
|
||||
renderGroup.BoundsSizeY,
|
||||
renderGroup.BoundsSizeZ));
|
||||
}
|
||||
|
||||
private static ShadowCastingMode GetShadowCastingMode(AetheriaRuntimeDaemonRenderGroupDocument renderGroup)
|
||||
{
|
||||
switch (renderGroup.ShadowMode)
|
||||
{
|
||||
case AetheriaRuntimeDaemonRenderShadowModes.Off:
|
||||
return ShadowCastingMode.Off;
|
||||
case AetheriaRuntimeDaemonRenderShadowModes.TwoSided:
|
||||
return ShadowCastingMode.TwoSided;
|
||||
case AetheriaRuntimeDaemonRenderShadowModes.ShadowsOnly:
|
||||
return ShadowCastingMode.ShadowsOnly;
|
||||
default:
|
||||
return ShadowCastingMode.On;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
foreach (var batch in _batchesByGroupId.Values)
|
||||
{
|
||||
batch.Dispose();
|
||||
}
|
||||
|
||||
_batchesByGroupId.Clear();
|
||||
_seenGroupIds.Clear();
|
||||
}
|
||||
|
||||
private void ReleaseStaleBatches()
|
||||
{
|
||||
if (_batchesByGroupId.Count == _seenGroupIds.Count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_staleGroupIds.Clear();
|
||||
foreach (var groupId in _batchesByGroupId.Keys)
|
||||
{
|
||||
if (!_seenGroupIds.Contains(groupId))
|
||||
{
|
||||
_staleGroupIds.Add(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _staleGroupIds.Count; i++)
|
||||
{
|
||||
var groupId = _staleGroupIds[i];
|
||||
_batchesByGroupId[groupId].Dispose();
|
||||
_batchesByGroupId.Remove(groupId);
|
||||
}
|
||||
|
||||
_staleGroupIds.Clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 66b38a2f45120a041941eae4412f7da1
|
||||
158
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs
Normal file
158
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
using System;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaDaemonObserver : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private bool pollInUpdate = true;
|
||||
|
||||
[SerializeField]
|
||||
private float pollIntervalSeconds = 0.05f;
|
||||
|
||||
[SerializeField]
|
||||
private bool logChanges;
|
||||
|
||||
[SerializeField]
|
||||
private string clientId = "unity-observer";
|
||||
|
||||
private readonly AetheriaRuntimeDaemonObservationCursor _cursor = new AetheriaRuntimeDaemonObservationCursor();
|
||||
private float _nextPollTime;
|
||||
private AetheriaRuntimeDaemonOperationClient _operationClient;
|
||||
private AetheriaDaemonOperations _operations;
|
||||
private AetheriaDaemonSoaMemoryMap _soaMemoryMap;
|
||||
private AetheriaDaemonRenderNativeView _renderNativeView;
|
||||
|
||||
public AetheriaRuntimeObservedDaemonState LastObservedState { get; private set; }
|
||||
public AetheriaRuntimeDaemonObservationResult LastObservation { get; private set; }
|
||||
public long LastFrameId => _cursor.LastFrameId;
|
||||
public long LastSoaGeneration => _cursor.LastSoaGeneration;
|
||||
public bool HasAuthoritativeState => LastObservedState != null && LastObservedState.IsAuthoritative;
|
||||
public bool HasSoaView => LastObservedState != null && LastObservedState.HasSoaView;
|
||||
public AetheriaRuntimeDaemonSoaViewIndex LastSoaIndex =>
|
||||
LastObservedState?.SoaIndex ?? AetheriaRuntimeDaemonSoaViewIndex.Empty;
|
||||
public AetheriaDaemonSoaMemoryMap LastSoaMemoryMap => _soaMemoryMap;
|
||||
public AetheriaDaemonRenderNativeView LastRenderNativeView => _renderNativeView;
|
||||
public bool HasRenderNativeView => _renderNativeView.IsCreated;
|
||||
public AetheriaDaemonOperations Operations => _operations ??= new AetheriaDaemonOperations(this);
|
||||
|
||||
public event Action<AetheriaRuntimeObservedDaemonState, AetheriaRuntimeDaemonObservationResult> ObservedDaemonStateChanged;
|
||||
|
||||
internal AetheriaRuntimeDaemonCommandEnvelope SendOperation(
|
||||
AetheriaRuntimeDaemonCommandKinds kind,
|
||||
Action<AetheriaRuntimeDaemonCommandDocument> configure = null)
|
||||
{
|
||||
_operationClient ??= new AetheriaRuntimeDaemonOperationClient(
|
||||
ActionGameManager.RuntimeStateFilePath,
|
||||
clientId,
|
||||
LastObservedState?.Frame.SessionId ?? "local");
|
||||
|
||||
var command = _operationClient.Create(kind, LastObservedState);
|
||||
configure?.Invoke(command);
|
||||
if (!_operationClient.TrySend(command, out var envelope, out var error))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Failed to submit Aetheria daemon operation {kind}: {error}");
|
||||
}
|
||||
|
||||
if (logChanges)
|
||||
{
|
||||
Debug.Log($"Submitted Aetheria daemon operation {envelope!.Kind}: {envelope.CommandId}");
|
||||
}
|
||||
|
||||
return envelope!;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!pollInUpdate || Time.unscaledTime < _nextPollTime)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nextPollTime = Time.unscaledTime + Mathf.Max(0.001f, pollIntervalSeconds);
|
||||
Poll();
|
||||
}
|
||||
|
||||
public bool Poll()
|
||||
{
|
||||
if (!AetheriaRuntimeStateReader.TryReadObservedDaemonState(
|
||||
ActionGameManager.RuntimeStateFilePath,
|
||||
out var observed))
|
||||
{
|
||||
LastObservation = _cursor.Observe(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = _cursor.Observe(observed);
|
||||
LastObservedState = observed;
|
||||
LastObservation = result;
|
||||
|
||||
if (result.SoaViewChanged)
|
||||
{
|
||||
RemapSoaView(observed);
|
||||
}
|
||||
|
||||
if (result.Changed)
|
||||
{
|
||||
ObservedDaemonStateChanged?.Invoke(observed, result);
|
||||
if (logChanges)
|
||||
{
|
||||
Debug.Log(
|
||||
$"Observed Aetheria daemon frame {result.FrameId} " +
|
||||
$"soa generation {result.SoaGeneration} authoritative={result.IsAuthoritative}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ResetObservation()
|
||||
{
|
||||
_cursor.Reset();
|
||||
DisposeSoaMemoryMap();
|
||||
LastObservedState = null;
|
||||
LastObservation = null;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
DisposeSoaMemoryMap();
|
||||
}
|
||||
|
||||
private void RemapSoaView(AetheriaRuntimeObservedDaemonState observed)
|
||||
{
|
||||
DisposeSoaMemoryMap();
|
||||
if (observed == null || !observed.HasSoaView)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (AetheriaDaemonSoaMemoryMap.TryOpen(observed.SoaIndex, out var map, out var error))
|
||||
{
|
||||
_soaMemoryMap = map;
|
||||
if (!AetheriaDaemonRenderNativeView.TryCreate(observed.SoaIndex, map, out _renderNativeView))
|
||||
{
|
||||
_renderNativeView = default;
|
||||
if (logChanges)
|
||||
{
|
||||
Debug.LogWarning("Mapped Aetheria daemon SoA memory, but no complete render native view was published.");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (logChanges)
|
||||
{
|
||||
Debug.LogWarning($"Could not map Aetheria daemon SoA view read-only: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
private void DisposeSoaMemoryMap()
|
||||
{
|
||||
_renderNativeView = default;
|
||||
_soaMemoryMap?.Dispose();
|
||||
_soaMemoryMap = null;
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta
Normal file
2
Assets/Scripts/Gameplay/AetheriaDaemonObserver.cs.meta
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4f5008a2876ee014fb9e442f1f6103e0
|
||||
81
Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs
Normal file
81
Assets/Scripts/Gameplay/AetheriaDaemonRenderAssetCatalog.cs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "AetheriaDaemonRenderAssetCatalog",
|
||||
menuName = "Aetheria/Daemon Render Asset Catalog")]
|
||||
public sealed class AetheriaDaemonRenderAssetCatalog : ScriptableObject
|
||||
{
|
||||
[SerializeField]
|
||||
private MeshEntry[] meshes = Array.Empty<MeshEntry>();
|
||||
|
||||
[SerializeField]
|
||||
private MaterialEntry[] materials = Array.Empty<MaterialEntry>();
|
||||
|
||||
public bool TryResolveMesh(string key, out Mesh mesh)
|
||||
{
|
||||
mesh = null;
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < meshes.Length; i++)
|
||||
{
|
||||
if (string.Equals(meshes[i].Key, key, StringComparison.Ordinal))
|
||||
{
|
||||
mesh = meshes[i].Mesh;
|
||||
return mesh != null;
|
||||
}
|
||||
}
|
||||
|
||||
mesh = Resources.Load<Mesh>(NormalizeResourceKey(key));
|
||||
return mesh != null;
|
||||
}
|
||||
|
||||
public bool TryResolveMaterial(string key, out Material material)
|
||||
{
|
||||
material = null;
|
||||
if (string.IsNullOrWhiteSpace(key))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < materials.Length; i++)
|
||||
{
|
||||
if (string.Equals(materials[i].Key, key, StringComparison.Ordinal))
|
||||
{
|
||||
material = materials[i].Material;
|
||||
return material != null;
|
||||
}
|
||||
}
|
||||
|
||||
material = Resources.Load<Material>(NormalizeResourceKey(key));
|
||||
return material != null;
|
||||
}
|
||||
|
||||
private static string NormalizeResourceKey(string key)
|
||||
{
|
||||
const string resourcesPrefix = "resources://";
|
||||
if (key.StartsWith(resourcesPrefix, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return key.Substring(resourcesPrefix.Length);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct MeshEntry
|
||||
{
|
||||
public string Key;
|
||||
public Mesh Mesh;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private struct MaterialEntry
|
||||
{
|
||||
public string Key;
|
||||
public Material Material;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8fa370b1ea2904d40ae295007ac76b48
|
||||
294
Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs
Normal file
294
Assets/Scripts/Gameplay/AetheriaDaemonRenderBuffer.cs
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaDaemonRenderBuffer : MonoBehaviour
|
||||
{
|
||||
public const string MatrixBufferPropertyName = "_AetheriaObjectToWorld";
|
||||
public const string InstanceCountPropertyName = "_AetheriaInstanceCount";
|
||||
|
||||
private const int MatrixStrideBytes = 64;
|
||||
private const int DrawMeshInstancedIndirectArgsCount = 5;
|
||||
|
||||
[SerializeField]
|
||||
private AetheriaDaemonObserver observer;
|
||||
|
||||
[SerializeField]
|
||||
private Mesh indirectArgsMesh;
|
||||
|
||||
[SerializeField]
|
||||
private int innerLoopBatchCount = 64;
|
||||
|
||||
[SerializeField]
|
||||
private bool updateInLateUpdate;
|
||||
|
||||
private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount];
|
||||
private NativeArray<float4x4> _nativeMatrices;
|
||||
private GraphicsBuffer _matrixBuffer;
|
||||
private GraphicsBuffer _indirectArgsBuffer;
|
||||
private long _allocatedGeneration = long.MinValue;
|
||||
private int _allocatedCount;
|
||||
|
||||
public GraphicsBuffer MatrixBuffer => _matrixBuffer;
|
||||
public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer;
|
||||
public int InstanceCount { get; private set; }
|
||||
public long LastUploadedFrameId { get; private set; }
|
||||
public long LastUploadedGeneration { get; private set; }
|
||||
public bool HasRenderableView => observer != null && observer.HasRenderNativeView;
|
||||
public bool HasGpuBuffers =>
|
||||
_matrixBuffer != null &&
|
||||
_matrixBuffer.IsValid() &&
|
||||
InstanceCount > 0;
|
||||
|
||||
private static readonly int MatrixBufferPropertyId = Shader.PropertyToID(MatrixBufferPropertyName);
|
||||
private static readonly int InstanceCountPropertyId = Shader.PropertyToID(InstanceCountPropertyName);
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
observer = GetComponent<AetheriaDaemonObserver>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (updateInLateUpdate)
|
||||
{
|
||||
UploadLatest();
|
||||
}
|
||||
}
|
||||
|
||||
public bool UploadLatest()
|
||||
{
|
||||
return UploadLatest(indirectArgsMesh);
|
||||
}
|
||||
|
||||
public bool UploadLatest(Mesh argsMesh)
|
||||
{
|
||||
return UploadLatest(argsMesh, -1);
|
||||
}
|
||||
|
||||
public bool UploadLatest(Mesh argsMesh, int renderGroupId)
|
||||
{
|
||||
return UploadLatest(argsMesh, renderGroupId, 0);
|
||||
}
|
||||
|
||||
public bool UploadLatest(Mesh argsMesh, int renderGroupId, int subMeshIndex)
|
||||
{
|
||||
if (!HasRenderableView)
|
||||
{
|
||||
InstanceCount = 0;
|
||||
UpdateIndirectArgs(null, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
var view = observer.LastRenderNativeView;
|
||||
EnsureCapacity(view);
|
||||
if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0)
|
||||
{
|
||||
InstanceCount = 0;
|
||||
UpdateIndirectArgs(argsMesh, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = observer.LastSoaIndex;
|
||||
var defaultScale = ResolveDefaultScale(index, renderGroupId);
|
||||
var renderLod = ResolveRenderLod(index, renderGroupId);
|
||||
var count = renderGroupId >= 0 && view.HasRenderGroupId
|
||||
? BuildRenderGroupMatrices(view, renderGroupId, renderLod, defaultScale)
|
||||
: BuildAllMatrices(view, renderLod, defaultScale);
|
||||
|
||||
_matrixBuffer.SetData(_nativeMatrices, 0, 0, count);
|
||||
InstanceCount = count;
|
||||
LastUploadedFrameId = view.FrameId;
|
||||
LastUploadedGeneration = view.Generation;
|
||||
UpdateIndirectArgs(argsMesh, count, subMeshIndex);
|
||||
return true;
|
||||
}
|
||||
|
||||
private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float defaultScale)
|
||||
{
|
||||
if (view.HasRenderVisibility || renderLod >= 0)
|
||||
{
|
||||
return BuildCompactedMatrices(view, -1, renderLod, defaultScale);
|
||||
}
|
||||
|
||||
var count = math.min(view.Count, _nativeMatrices.Length);
|
||||
var handle = view.ScheduleBuildRenderMatrices(
|
||||
_nativeMatrices,
|
||||
defaultScale,
|
||||
innerLoopBatchCount);
|
||||
handle.Complete();
|
||||
return count;
|
||||
}
|
||||
|
||||
private int BuildRenderGroupMatrices(
|
||||
AetheriaDaemonRenderNativeView view,
|
||||
int renderGroupId,
|
||||
int renderLod,
|
||||
float defaultScale)
|
||||
{
|
||||
return BuildCompactedMatrices(view, renderGroupId, renderLod, defaultScale);
|
||||
}
|
||||
|
||||
private int BuildCompactedMatrices(
|
||||
AetheriaDaemonRenderNativeView view,
|
||||
int renderGroupId,
|
||||
int renderLod,
|
||||
float defaultScale)
|
||||
{
|
||||
using var writtenCount = new NativeArray<int>(1, Allocator.TempJob);
|
||||
var job = new AetheriaDaemonRenderGroupMatrixJob
|
||||
{
|
||||
PositionX = view.PositionX,
|
||||
PositionY = view.PositionY,
|
||||
PositionZ = view.PositionZ,
|
||||
RotationRadians = view.RotationRadians,
|
||||
PhysicsBodyRadius = view.PhysicsBodyRadius,
|
||||
RenderScale = view.RenderScale,
|
||||
RenderVisibility = view.RenderVisibility,
|
||||
RenderLod = view.RenderLod,
|
||||
RenderGroupId = view.RenderGroupId,
|
||||
Matrices = _nativeMatrices,
|
||||
WrittenCount = writtenCount,
|
||||
TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0,
|
||||
TargetRenderLod = renderLod,
|
||||
HasRotation = view.HasRotation,
|
||||
HasPhysicsRadius = view.HasPhysicsRadius,
|
||||
HasRenderScale = view.HasRenderScale,
|
||||
HasRenderVisibility = view.HasRenderVisibility,
|
||||
HasRenderLod = view.HasRenderLod,
|
||||
HasRenderGroupFilter = renderGroupId >= 0,
|
||||
HasRenderLodFilter = renderLod >= 0,
|
||||
UniformScale = defaultScale
|
||||
};
|
||||
|
||||
job.Schedule().Complete();
|
||||
return math.clamp(writtenCount[0], 0, _nativeMatrices.Length);
|
||||
}
|
||||
|
||||
private static float ResolveDefaultScale(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId)
|
||||
{
|
||||
if (index != null &&
|
||||
renderGroupId >= 0 &&
|
||||
index.TryGetRenderGroup(renderGroupId, out var renderGroup))
|
||||
{
|
||||
return math.max(renderGroup.DefaultScale, 0.0001f);
|
||||
}
|
||||
|
||||
var renderGroups = index?.RenderGroups;
|
||||
if (renderGroups != null && renderGroups.Count == 1)
|
||||
{
|
||||
return math.max(renderGroups[0].DefaultScale, 0.0001f);
|
||||
}
|
||||
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
private static int ResolveRenderLod(AetheriaRuntimeDaemonSoaViewIndex index, int renderGroupId)
|
||||
{
|
||||
if (index != null &&
|
||||
renderGroupId >= 0 &&
|
||||
index.TryGetRenderGroup(renderGroupId, out var renderGroup))
|
||||
{
|
||||
return renderGroup.Lod;
|
||||
}
|
||||
|
||||
var renderGroups = index?.RenderGroups;
|
||||
if (renderGroups != null && renderGroups.Count == 1)
|
||||
{
|
||||
return renderGroups[0].Lod;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void ApplyTo(MaterialPropertyBlock properties)
|
||||
{
|
||||
if (properties == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
properties.SetInt(InstanceCountPropertyId, InstanceCount);
|
||||
if (HasGpuBuffers)
|
||||
{
|
||||
properties.SetBuffer(MatrixBufferPropertyId, _matrixBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ReleaseBuffers();
|
||||
InstanceCount = 0;
|
||||
LastUploadedFrameId = 0;
|
||||
LastUploadedGeneration = 0;
|
||||
}
|
||||
|
||||
private void EnsureCapacity(AetheriaDaemonRenderNativeView view)
|
||||
{
|
||||
var count = math.max(0, view.Count);
|
||||
if (_nativeMatrices.IsCreated &&
|
||||
_allocatedGeneration == view.Generation &&
|
||||
_allocatedCount == count)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ReleaseBuffers();
|
||||
_allocatedGeneration = view.Generation;
|
||||
_allocatedCount = count;
|
||||
if (count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nativeMatrices = new NativeArray<float4x4>(
|
||||
count,
|
||||
Allocator.Persistent,
|
||||
NativeArrayOptions.UninitializedMemory);
|
||||
_matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, count, MatrixStrideBytes);
|
||||
_indirectArgsBuffer = new GraphicsBuffer(
|
||||
GraphicsBuffer.Target.IndirectArguments,
|
||||
DrawMeshInstancedIndirectArgsCount,
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
private void UpdateIndirectArgs(Mesh mesh, int instanceCount)
|
||||
{
|
||||
UpdateIndirectArgs(mesh, instanceCount, 0);
|
||||
}
|
||||
|
||||
private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex)
|
||||
{
|
||||
if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var safeSubMeshIndex = mesh != null
|
||||
? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1))
|
||||
: 0;
|
||||
_indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[1] = (uint)math.max(0, instanceCount);
|
||||
_indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[4] = 0;
|
||||
_indirectArgsBuffer.SetData(_indirectArgs);
|
||||
}
|
||||
|
||||
private void ReleaseBuffers()
|
||||
{
|
||||
if (_nativeMatrices.IsCreated)
|
||||
{
|
||||
_nativeMatrices.Dispose();
|
||||
}
|
||||
|
||||
_matrixBuffer?.Release();
|
||||
_matrixBuffer = null;
|
||||
_indirectArgsBuffer?.Release();
|
||||
_indirectArgsBuffer = null;
|
||||
_allocatedGeneration = long.MinValue;
|
||||
_allocatedCount = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 9cba277ef259bee438796ab4c0f3df64
|
||||
177
Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs
Normal file
177
Assets/Scripts/Gameplay/AetheriaDaemonRenderGroupBatch.cs
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
using System;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaDaemonRenderGroupBatch : IDisposable
|
||||
{
|
||||
private const int MatrixStrideBytes = 64;
|
||||
private const int DrawMeshInstancedIndirectArgsCount = 5;
|
||||
|
||||
private readonly uint[] _indirectArgs = new uint[DrawMeshInstancedIndirectArgsCount];
|
||||
private NativeArray<float4x4> _nativeMatrices;
|
||||
private GraphicsBuffer _matrixBuffer;
|
||||
private GraphicsBuffer _indirectArgsBuffer;
|
||||
private int _capacity;
|
||||
|
||||
public int GroupId { get; private set; }
|
||||
public int InstanceCount { get; private set; }
|
||||
public long LastUploadedFrameId { get; private set; }
|
||||
public long LastUploadedGeneration { get; private set; }
|
||||
public GraphicsBuffer MatrixBuffer => _matrixBuffer;
|
||||
public GraphicsBuffer IndirectArgsBuffer => _indirectArgsBuffer;
|
||||
public bool HasGpuBuffers =>
|
||||
_matrixBuffer != null &&
|
||||
_matrixBuffer.IsValid() &&
|
||||
_indirectArgsBuffer != null &&
|
||||
_indirectArgsBuffer.IsValid() &&
|
||||
InstanceCount > 0;
|
||||
|
||||
public bool UploadLatest(
|
||||
AetheriaDaemonRenderNativeView view,
|
||||
AetheriaRuntimeDaemonRenderGroupDocument renderGroup,
|
||||
Mesh mesh,
|
||||
float uniformScale)
|
||||
{
|
||||
if (!view.IsCreated || mesh == null)
|
||||
{
|
||||
InstanceCount = 0;
|
||||
UpdateIndirectArgs(mesh, 0, renderGroup?.SubMeshIndex ?? 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
var targetCapacity = renderGroup.InstanceCount >= 0
|
||||
? math.min(renderGroup.InstanceCount, view.Count)
|
||||
: view.Count;
|
||||
EnsureCapacity(targetCapacity);
|
||||
if (!_nativeMatrices.IsCreated || _nativeMatrices.Length == 0)
|
||||
{
|
||||
InstanceCount = 0;
|
||||
UpdateIndirectArgs(mesh, 0, renderGroup.SubMeshIndex);
|
||||
return false;
|
||||
}
|
||||
|
||||
var count = view.HasRenderGroupId
|
||||
? BuildRenderGroupMatrices(view, renderGroup.GroupId, renderGroup.Lod, uniformScale)
|
||||
: BuildAllMatrices(view, renderGroup.Lod, uniformScale);
|
||||
|
||||
_matrixBuffer.SetData(_nativeMatrices, 0, 0, count);
|
||||
GroupId = renderGroup.GroupId;
|
||||
InstanceCount = count;
|
||||
LastUploadedFrameId = view.FrameId;
|
||||
LastUploadedGeneration = view.Generation;
|
||||
UpdateIndirectArgs(mesh, count, renderGroup.SubMeshIndex);
|
||||
return count > 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_nativeMatrices.IsCreated)
|
||||
{
|
||||
_nativeMatrices.Dispose();
|
||||
}
|
||||
|
||||
_matrixBuffer?.Release();
|
||||
_matrixBuffer = null;
|
||||
_indirectArgsBuffer?.Release();
|
||||
_indirectArgsBuffer = null;
|
||||
_capacity = 0;
|
||||
InstanceCount = 0;
|
||||
}
|
||||
|
||||
private int BuildAllMatrices(AetheriaDaemonRenderNativeView view, int renderLod, float uniformScale)
|
||||
{
|
||||
return BuildCompactedMatrices(view, -1, renderLod, uniformScale);
|
||||
}
|
||||
|
||||
private int BuildRenderGroupMatrices(
|
||||
AetheriaDaemonRenderNativeView view,
|
||||
int renderGroupId,
|
||||
int renderLod,
|
||||
float uniformScale)
|
||||
{
|
||||
return BuildCompactedMatrices(view, renderGroupId, renderLod, uniformScale);
|
||||
}
|
||||
|
||||
private int BuildCompactedMatrices(
|
||||
AetheriaDaemonRenderNativeView view,
|
||||
int renderGroupId,
|
||||
int renderLod,
|
||||
float uniformScale)
|
||||
{
|
||||
using var writtenCount = new NativeArray<int>(1, Allocator.TempJob);
|
||||
var job = new AetheriaDaemonRenderGroupMatrixJob
|
||||
{
|
||||
PositionX = view.PositionX,
|
||||
PositionY = view.PositionY,
|
||||
PositionZ = view.PositionZ,
|
||||
RotationRadians = view.RotationRadians,
|
||||
PhysicsBodyRadius = view.PhysicsBodyRadius,
|
||||
RenderScale = view.RenderScale,
|
||||
RenderVisibility = view.RenderVisibility,
|
||||
RenderLod = view.RenderLod,
|
||||
RenderGroupId = view.RenderGroupId,
|
||||
Matrices = _nativeMatrices,
|
||||
WrittenCount = writtenCount,
|
||||
TargetRenderGroupId = renderGroupId >= 0 ? (uint)renderGroupId : 0,
|
||||
TargetRenderLod = renderLod,
|
||||
HasRotation = view.HasRotation,
|
||||
HasPhysicsRadius = view.HasPhysicsRadius,
|
||||
HasRenderScale = view.HasRenderScale,
|
||||
HasRenderVisibility = view.HasRenderVisibility,
|
||||
HasRenderLod = view.HasRenderLod,
|
||||
HasRenderGroupFilter = renderGroupId >= 0,
|
||||
HasRenderLodFilter = renderLod >= 0,
|
||||
UniformScale = uniformScale
|
||||
};
|
||||
|
||||
job.Schedule().Complete();
|
||||
return math.clamp(writtenCount[0], 0, _nativeMatrices.Length);
|
||||
}
|
||||
|
||||
private void EnsureCapacity(int capacity)
|
||||
{
|
||||
capacity = math.max(0, capacity);
|
||||
if (_nativeMatrices.IsCreated && _capacity == capacity)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dispose();
|
||||
_capacity = capacity;
|
||||
if (capacity <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_nativeMatrices = new NativeArray<float4x4>(
|
||||
capacity,
|
||||
Allocator.Persistent,
|
||||
NativeArrayOptions.UninitializedMemory);
|
||||
_matrixBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, capacity, MatrixStrideBytes);
|
||||
_indirectArgsBuffer = new GraphicsBuffer(
|
||||
GraphicsBuffer.Target.IndirectArguments,
|
||||
DrawMeshInstancedIndirectArgsCount,
|
||||
sizeof(uint));
|
||||
}
|
||||
|
||||
private void UpdateIndirectArgs(Mesh mesh, int instanceCount, int subMeshIndex)
|
||||
{
|
||||
if (_indirectArgsBuffer == null || !_indirectArgsBuffer.IsValid())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var safeSubMeshIndex = mesh != null
|
||||
? math.clamp(subMeshIndex, 0, math.max(0, mesh.subMeshCount - 1))
|
||||
: 0;
|
||||
_indirectArgs[0] = mesh != null ? mesh.GetIndexCount(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[1] = (uint)math.max(0, instanceCount);
|
||||
_indirectArgs[2] = mesh != null ? mesh.GetIndexStart(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[3] = mesh != null ? mesh.GetBaseVertex(safeSubMeshIndex) : 0;
|
||||
_indirectArgs[4] = 0;
|
||||
_indirectArgsBuffer.SetData(_indirectArgs);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 0038a222765ff82468c22a516cfcdbc1
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
[BurstCompile]
|
||||
public struct AetheriaDaemonRenderGroupMatrixJob : IJob
|
||||
{
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionX;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionY;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionZ;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> RotationRadians;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PhysicsBodyRadius;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> RenderScale;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<byte> RenderVisibility;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<int> RenderLod;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<uint> RenderGroupId;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeArray<float4x4> Matrices;
|
||||
|
||||
public NativeArray<int> WrittenCount;
|
||||
public uint TargetRenderGroupId;
|
||||
public int TargetRenderLod;
|
||||
public bool HasRotation;
|
||||
public bool HasPhysicsRadius;
|
||||
public bool HasRenderScale;
|
||||
public bool HasRenderVisibility;
|
||||
public bool HasRenderLod;
|
||||
public bool HasRenderGroupFilter;
|
||||
public bool HasRenderLodFilter;
|
||||
public float UniformScale;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
var written = 0;
|
||||
for (var index = 0; index < PositionX.Length; index++)
|
||||
{
|
||||
if (HasRenderGroupFilter && RenderGroupId[index] != TargetRenderGroupId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasRenderVisibility && RenderVisibility[index] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (HasRenderLodFilter && (!HasRenderLod || RenderLod[index] != TargetRenderLod))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (written >= Matrices.Length)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var position = new float3(PositionX[index], PositionY[index], PositionZ[index]);
|
||||
var rotation = HasRotation
|
||||
? quaternion.RotateY(RotationRadians[index])
|
||||
: quaternion.identity;
|
||||
var scaleValue = HasRenderScale
|
||||
? math.max(RenderScale[index], 0.0001f)
|
||||
: HasPhysicsRadius
|
||||
? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f)
|
||||
: math.max(UniformScale, 0.0001f);
|
||||
|
||||
Matrices[written] = float4x4.TRS(position, rotation, new float3(scaleValue));
|
||||
written++;
|
||||
}
|
||||
|
||||
WrittenCount[0] = written;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 146aa8d6d00cc1a44b44a5f83d69edea
|
||||
49
Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs
Normal file
49
Assets/Scripts/Gameplay/AetheriaDaemonRenderMatrixJob.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
[BurstCompile]
|
||||
public struct AetheriaDaemonRenderMatrixJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionX;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionY;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PositionZ;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> RotationRadians;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> PhysicsBodyRadius;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<float> RenderScale;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeArray<float4x4> Matrices;
|
||||
|
||||
public bool HasRotation;
|
||||
public bool HasPhysicsRadius;
|
||||
public bool HasRenderScale;
|
||||
public float UniformScale;
|
||||
|
||||
public void Execute(int index)
|
||||
{
|
||||
var position = new float3(PositionX[index], PositionY[index], PositionZ[index]);
|
||||
var rotation = HasRotation
|
||||
? quaternion.RotateY(RotationRadians[index])
|
||||
: quaternion.identity;
|
||||
var scaleValue = HasRenderScale
|
||||
? math.max(RenderScale[index], 0.0001f)
|
||||
: HasPhysicsRadius
|
||||
? math.max(PhysicsBodyRadius[index] * 2.0f, 0.0001f)
|
||||
: math.max(UniformScale, 0.0001f);
|
||||
|
||||
Matrices[index] = float4x4.TRS(position, rotation, new float3(scaleValue));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: f6b3b3dc2e2a29245a24b772e2cb87b4
|
||||
248
Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs
Normal file
248
Assets/Scripts/Gameplay/AetheriaDaemonRenderNativeView.cs
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
public struct AetheriaDaemonRenderNativeView
|
||||
{
|
||||
public long FrameId;
|
||||
public long Generation;
|
||||
public int Count;
|
||||
public NativeArray<float> PositionX;
|
||||
public NativeArray<float> PositionY;
|
||||
public NativeArray<float> PositionZ;
|
||||
public NativeArray<float> RotationRadians;
|
||||
public NativeArray<float> VelocityX;
|
||||
public NativeArray<float> VelocityY;
|
||||
public NativeArray<float> VelocityZ;
|
||||
public NativeArray<float> PhysicsBodyRadius;
|
||||
public NativeArray<float> PhysicsBodyMass;
|
||||
public NativeArray<float> PhysicsBodyInverseMass;
|
||||
public NativeArray<float> RenderScale;
|
||||
public NativeArray<byte> RenderVisibility;
|
||||
public NativeArray<int> RenderLod;
|
||||
public NativeArray<uint> RenderGroupId;
|
||||
|
||||
public bool IsCreated =>
|
||||
Count > 0 &&
|
||||
PositionX.IsCreated &&
|
||||
PositionY.IsCreated &&
|
||||
PositionZ.IsCreated;
|
||||
|
||||
public bool HasRotation => RotationRadians.IsCreated;
|
||||
public bool HasVelocity => VelocityX.IsCreated && VelocityY.IsCreated && VelocityZ.IsCreated;
|
||||
public bool HasPhysicsRadius => PhysicsBodyRadius.IsCreated;
|
||||
public bool HasPhysicsMass => PhysicsBodyMass.IsCreated;
|
||||
public bool HasPhysicsInverseMass => PhysicsBodyInverseMass.IsCreated;
|
||||
public bool HasRenderScale => RenderScale.IsCreated;
|
||||
public bool HasRenderVisibility => RenderVisibility.IsCreated;
|
||||
public bool HasRenderLod => RenderLod.IsCreated;
|
||||
public bool HasRenderGroupId => RenderGroupId.IsCreated;
|
||||
|
||||
public JobHandle ScheduleBuildRenderMatrices(
|
||||
NativeArray<float4x4> matrices,
|
||||
float uniformScale = 1.0f,
|
||||
int innerLoopBatchCount = 64,
|
||||
JobHandle dependsOn = default)
|
||||
{
|
||||
if (!IsCreated)
|
||||
{
|
||||
return dependsOn;
|
||||
}
|
||||
|
||||
var count = math.min(Count, matrices.IsCreated ? matrices.Length : 0);
|
||||
if (count <= 0)
|
||||
{
|
||||
return dependsOn;
|
||||
}
|
||||
|
||||
var job = new AetheriaDaemonRenderMatrixJob
|
||||
{
|
||||
PositionX = PositionX,
|
||||
PositionY = PositionY,
|
||||
PositionZ = PositionZ,
|
||||
RotationRadians = RotationRadians,
|
||||
PhysicsBodyRadius = PhysicsBodyRadius,
|
||||
RenderScale = RenderScale,
|
||||
Matrices = matrices,
|
||||
HasRotation = HasRotation,
|
||||
HasPhysicsRadius = HasPhysicsRadius,
|
||||
HasRenderScale = HasRenderScale,
|
||||
UniformScale = uniformScale
|
||||
};
|
||||
|
||||
return job.Schedule(count, math.max(1, innerLoopBatchCount), dependsOn);
|
||||
}
|
||||
|
||||
public static bool TryCreate(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
AetheriaDaemonSoaMemoryMap map,
|
||||
out AetheriaDaemonRenderNativeView view)
|
||||
{
|
||||
view = default;
|
||||
if (index == null || map == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map.TryCreateFirstNativeArrayOfKind(
|
||||
index,
|
||||
AetheriaRuntimeDaemonSoaColumnKinds.PositionX,
|
||||
out view.PositionX) ||
|
||||
!map.TryCreateFirstNativeArrayOfKind(
|
||||
index,
|
||||
AetheriaRuntimeDaemonSoaColumnKinds.PositionY,
|
||||
out view.PositionY) ||
|
||||
!map.TryCreateFirstNativeArrayOfKind(
|
||||
index,
|
||||
AetheriaRuntimeDaemonSoaColumnKinds.PositionZ,
|
||||
out view.PositionZ))
|
||||
{
|
||||
view = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
view.Count = view.PositionX.Length;
|
||||
if (view.PositionY.Length != view.Count || view.PositionZ.Length != view.Count)
|
||||
{
|
||||
view = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians, view.Count, out view.RotationRadians);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityX, view.Count, out view.VelocityX);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityY, view.Count, out view.VelocityY);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.VelocityZ, view.Count, out view.VelocityZ);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyRadius, view.Count, out view.PhysicsBodyRadius);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyMass, view.Count, out view.PhysicsBodyMass);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.PhysicsBodyInverseMass, view.Count, out view.PhysicsBodyInverseMass);
|
||||
TryAssignOptionalColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderScale, view.Count, out view.RenderScale);
|
||||
TryAssignOptionalByteColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderVisibility, view.Count, out view.RenderVisibility);
|
||||
TryAssignOptionalIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderLod, view.Count, out view.RenderLod);
|
||||
TryAssignOptionalUIntColumn(index, map, AetheriaRuntimeDaemonSoaColumnKinds.RenderGroupId, view.Count, out view.RenderGroupId);
|
||||
|
||||
if ((view.VelocityX.IsCreated || view.VelocityY.IsCreated || view.VelocityZ.IsCreated) &&
|
||||
!view.HasVelocity)
|
||||
{
|
||||
view.VelocityX = default;
|
||||
view.VelocityY = default;
|
||||
view.VelocityZ = default;
|
||||
}
|
||||
|
||||
view.FrameId = index.View.FrameId;
|
||||
view.Generation = index.View.Generation;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool TryAssignOptionalColumn(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
AetheriaDaemonSoaMemoryMap map,
|
||||
string kind,
|
||||
int expectedCount,
|
||||
out NativeArray<float> array)
|
||||
{
|
||||
array = default;
|
||||
if (!index.TryGetFirstColumnOfKind(kind, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
|
||||
{
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array.Length == expectedCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryAssignOptionalUIntColumn(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
AetheriaDaemonSoaMemoryMap map,
|
||||
string kind,
|
||||
int expectedCount,
|
||||
out NativeArray<uint> array)
|
||||
{
|
||||
array = default;
|
||||
if (!index.TryGetFirstColumnOfKind(kind, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
|
||||
{
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array.Length == expectedCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryAssignOptionalIntColumn(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
AetheriaDaemonSoaMemoryMap map,
|
||||
string kind,
|
||||
int expectedCount,
|
||||
out NativeArray<int> array)
|
||||
{
|
||||
array = default;
|
||||
if (!index.TryGetFirstColumnOfKind(kind, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
|
||||
{
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array.Length == expectedCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryAssignOptionalByteColumn(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
AetheriaDaemonSoaMemoryMap map,
|
||||
string kind,
|
||||
int expectedCount,
|
||||
out NativeArray<byte> array)
|
||||
{
|
||||
array = default;
|
||||
if (!index.TryGetFirstColumnOfKind(kind, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map.TryCreateFirstNativeArrayOfKind(index, kind, out array))
|
||||
{
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (array.Length == expectedCount)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
array = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 48740dbd37e79954ca00a3f6bd7ccef1
|
||||
220
Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs
Normal file
220
Assets/Scripts/Gameplay/AetheriaDaemonSoaMemoryMap.cs
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.MemoryMappedFiles;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
|
||||
public unsafe sealed class AetheriaDaemonSoaMemoryMap : IDisposable
|
||||
{
|
||||
private readonly Dictionary<string, MappedBuffer> _buffersById =
|
||||
new Dictionary<string, MappedBuffer>(StringComparer.Ordinal);
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
private readonly List<AtomicSafetyHandle> _safetyHandles = new List<AtomicSafetyHandle>();
|
||||
#endif
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
private AetheriaDaemonSoaMemoryMap()
|
||||
{
|
||||
}
|
||||
|
||||
public static bool TryOpen(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
out AetheriaDaemonSoaMemoryMap map,
|
||||
out string error)
|
||||
{
|
||||
map = null;
|
||||
error = "";
|
||||
|
||||
if (index == null)
|
||||
{
|
||||
error = "No daemon SoA view index was provided.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!index.IsValid)
|
||||
{
|
||||
error = index.ValidationErrors.Count > 0
|
||||
? index.ValidationErrors[0]
|
||||
: "Daemon SoA view index is invalid.";
|
||||
return false;
|
||||
}
|
||||
|
||||
var opened = new AetheriaDaemonSoaMemoryMap();
|
||||
try
|
||||
{
|
||||
foreach (var buffer in index.View.Buffers)
|
||||
{
|
||||
if (!IsMappableReadOnlyBuffer(buffer))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
opened.OpenBuffer(buffer);
|
||||
}
|
||||
|
||||
map = opened;
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
opened.Dispose();
|
||||
error = exception.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryCreateNativeArray<T>(
|
||||
AetheriaRuntimeDaemonSoaColumnBinding binding,
|
||||
out NativeArray<T> array)
|
||||
where T : struct
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
array = default;
|
||||
|
||||
if (binding == null ||
|
||||
!binding.DirectMemoryCompatible ||
|
||||
!_buffersById.TryGetValue(binding.Buffer.BufferId, out var buffer))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var byteOffset = checked((ulong)binding.AbsoluteByteOffset);
|
||||
var byteLength = checked((ulong)Math.Max(0, binding.ByteLength));
|
||||
if (byteOffset + byteLength > buffer.ByteLength)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var pointer = buffer.Pointer + byteOffset;
|
||||
array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray<T>(
|
||||
pointer,
|
||||
binding.Column.ElementCount,
|
||||
Allocator.None);
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
var safetyHandle = AtomicSafetyHandle.Create();
|
||||
_safetyHandles.Add(safetyHandle);
|
||||
NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, safetyHandle);
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCreateFirstNativeArrayOfKind<T>(
|
||||
AetheriaRuntimeDaemonSoaViewIndex index,
|
||||
string kind,
|
||||
out NativeArray<T> array)
|
||||
where T : struct
|
||||
{
|
||||
ThrowIfDisposed();
|
||||
array = default;
|
||||
|
||||
if (index == null || !index.TryGetFirstColumnOfKind(kind, out var binding))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TryCreateNativeArray(binding, out array);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if ENABLE_UNITY_COLLECTIONS_CHECKS
|
||||
for (var i = 0; i < _safetyHandles.Count; i++)
|
||||
{
|
||||
AtomicSafetyHandle.Release(_safetyHandles[i]);
|
||||
}
|
||||
|
||||
_safetyHandles.Clear();
|
||||
#endif
|
||||
|
||||
foreach (var buffer in _buffersById.Values)
|
||||
{
|
||||
buffer.Dispose();
|
||||
}
|
||||
|
||||
_buffersById.Clear();
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
private void OpenBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(buffer.BufferId) ||
|
||||
_buffersById.ContainsKey(buffer.BufferId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var byteLength = checked((ulong)(buffer.ByteOffset + buffer.ByteLength));
|
||||
var memoryMappedFile = MemoryMappedFile.OpenExisting(
|
||||
buffer.Location,
|
||||
MemoryMappedFileRights.Read);
|
||||
var accessor = memoryMappedFile.CreateViewAccessor(0, (long)byteLength, MemoryMappedFileAccess.Read);
|
||||
|
||||
byte* pointer = null;
|
||||
accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref pointer);
|
||||
|
||||
_buffersById.Add(
|
||||
buffer.BufferId,
|
||||
new MappedBuffer(memoryMappedFile, accessor, pointer, byteLength));
|
||||
}
|
||||
|
||||
private static bool IsMappableReadOnlyBuffer(AetheriaRuntimeDaemonSoaBufferDocument buffer)
|
||||
{
|
||||
return buffer != null &&
|
||||
!buffer.ObserverWritable &&
|
||||
!string.IsNullOrWhiteSpace(buffer.Location) &&
|
||||
string.Equals(buffer.Backend, AetheriaRuntimeDaemonSoaBackends.MemoryMappedFile, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private void ThrowIfDisposed()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(AetheriaDaemonSoaMemoryMap));
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class MappedBuffer : IDisposable
|
||||
{
|
||||
private readonly MemoryMappedFile _memoryMappedFile;
|
||||
private readonly MemoryMappedViewAccessor _accessor;
|
||||
private bool _disposed;
|
||||
|
||||
public MappedBuffer(
|
||||
MemoryMappedFile memoryMappedFile,
|
||||
MemoryMappedViewAccessor accessor,
|
||||
byte* pointer,
|
||||
ulong byteLength)
|
||||
{
|
||||
_memoryMappedFile = memoryMappedFile;
|
||||
_accessor = accessor;
|
||||
Pointer = pointer;
|
||||
ByteLength = byteLength;
|
||||
}
|
||||
|
||||
public byte* Pointer { get; }
|
||||
public ulong ByteLength { get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_accessor.SafeMemoryMappedViewHandle.ReleasePointer();
|
||||
_accessor.Dispose();
|
||||
_memoryMappedFile.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: d8988c7d796df504dab64fd2c64eb868
|
||||
|
|
@ -1,15 +1,11 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class ArticulationPoint : MonoBehaviour
|
||||
{
|
||||
public Transform Target;
|
||||
|
||||
public int Group;
|
||||
|
||||
|
||||
public float YawMin;
|
||||
public float YawMax;
|
||||
|
||||
|
|
@ -26,26 +22,26 @@ public class ArticulationPoint : MonoBehaviour
|
|||
if (Target)
|
||||
{
|
||||
var targetLocal = transform.InverseTransformPoint(Target.position);
|
||||
|
||||
|
||||
var yaw = Vector2.SignedAngle(new Vector2(0, 1), new Vector2(targetLocal.x, targetLocal.z));
|
||||
var pitch = Vector2.SignedAngle(new Vector2(1, 0), new Vector2(targetLocal.z, targetLocal.y));
|
||||
|
||||
var targetYaw = clamp(_yaw - yaw, YawMin, YawMax);
|
||||
var targetPitch = clamp(_pitch - pitch, PitchMin, PitchMax);
|
||||
var targetYaw = Mathf.Clamp(_yaw - yaw, YawMin, YawMax);
|
||||
var targetPitch = Mathf.Clamp(_pitch - pitch, PitchMin, PitchMax);
|
||||
|
||||
if (abs(targetYaw - _yaw) < Speed * Time.deltaTime)
|
||||
if (Mathf.Abs(targetYaw - _yaw) < Speed * Time.deltaTime)
|
||||
_yaw = targetYaw;
|
||||
else _yaw = _yaw + sign(targetYaw - _yaw) * Speed * Time.deltaTime;
|
||||
else _yaw = _yaw + Mathf.Sign(targetYaw - _yaw) * Speed * Time.deltaTime;
|
||||
|
||||
if(_yaw < -360)
|
||||
if (_yaw < -360)
|
||||
_yaw += 360;
|
||||
if(_yaw > 360)
|
||||
if (_yaw > 360)
|
||||
_yaw -= 360;
|
||||
|
||||
if (abs(targetPitch - _pitch) < Speed * Time.deltaTime)
|
||||
if (Mathf.Abs(targetPitch - _pitch) < Speed * Time.deltaTime)
|
||||
_pitch = targetPitch;
|
||||
else _pitch = _pitch + sign(targetPitch - _pitch) * Speed * Time.deltaTime;
|
||||
|
||||
else _pitch = _pitch + Mathf.Sign(targetPitch - _pitch) * Speed * Time.deltaTime;
|
||||
|
||||
transform.localRotation = Quaternion.Euler(_pitch, _yaw, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,13 @@ using System.Linq;
|
|||
using GameCult.Aetheria.State.Unity;
|
||||
using UniRx;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class EntityInstance : MonoBehaviour
|
||||
{
|
||||
public MeshRenderer MapIcon;
|
||||
public Transform InfluencePrefab;
|
||||
public Transform PingPrefab;
|
||||
public Material InvisibleMaterial;
|
||||
public static Transform EffectManagerParent;
|
||||
public ShieldManager Shield;
|
||||
public HullCollider[] HullColliders;
|
||||
|
||||
|
|
@ -25,7 +21,6 @@ public class EntityInstance : MonoBehaviour
|
|||
public WeaponHardpoint[] WeaponHardpoints;
|
||||
public ArticulationPoint[] ArticulationPoints;
|
||||
|
||||
public GameObject DestroyEffect;
|
||||
|
||||
public event Action OnFadedOut;
|
||||
public event Action OnFadedIn;
|
||||
|
|
@ -40,41 +35,21 @@ public class EntityInstance : MonoBehaviour
|
|||
private bool _fadedElementsVisible = false;
|
||||
private bool _unfadedElementsVisible = false;
|
||||
private float _fadeTime;
|
||||
private bool _destroyed;
|
||||
private (Transform transform, MeshRenderer meshRenderer) _currentPing;
|
||||
private float _pingBrightness;
|
||||
private Sensor _sensor;
|
||||
private Transform _influenceInstance;
|
||||
|
||||
//private List<(GameObject source, EquippedItem item)> _audioSources = new List<(GameObject source, EquippedItem item)>();
|
||||
// private (Reactor reactor, GameObject sfxSource) _reactor;
|
||||
// private Dictionary<Radiator, GameObject> _radiatorSfx = new Dictionary<Radiator, GameObject>();
|
||||
|
||||
private static Dictionary<string, InstantWeaponEffectManager> _instantWeaponManagers = new Dictionary<string, InstantWeaponEffectManager>();
|
||||
private static Dictionary<string, ConstantWeaponEffectManager> _constantWeaponManagers = new Dictionary<string, ConstantWeaponEffectManager>();
|
||||
|
||||
public static void ClearWeaponManagers()
|
||||
{
|
||||
_instantWeaponManagers.Clear();
|
||||
_constantWeaponManagers.Clear();
|
||||
}
|
||||
|
||||
private static AetheriaRuntimeCatalogItem FindTypedHull(ItemInstance hull)
|
||||
{
|
||||
return ActionGameManager.RuntimeCatalog?.FindItem(hull?.ItemKey ?? "");
|
||||
}
|
||||
|
||||
private static Shape ToShape(int width, int height, IReadOnlyList<AetheriaRuntimeShapeCell> cells)
|
||||
{
|
||||
var shape = new Shape(width, height);
|
||||
foreach (var cell in cells)
|
||||
{
|
||||
if (cell.X >= 0 && cell.Y >= 0 && cell.X < width && cell.Y < height)
|
||||
shape[int2(cell.X, cell.Y)] = true;
|
||||
}
|
||||
private static Vector2 ToVector2(CultMath.float2 value) => new Vector2(value.x, value.y);
|
||||
private static Vector2 ToVector2(Unity.Mathematics.float2 value) => new Vector2(value.x, value.y);
|
||||
|
||||
return shape;
|
||||
}
|
||||
private static cfloat3 ToCult(Vector3 value) => new cfloat3(value.x, value.y, value.z);
|
||||
|
||||
public CompassIcon CompassIcon { get; set; }
|
||||
public Dictionary<HardpointData, Transform[]> Barrels { get; private set; }
|
||||
|
|
@ -186,81 +161,10 @@ public class EntityInstance : MonoBehaviour
|
|||
return;
|
||||
}
|
||||
|
||||
var hullShape = ToShape(typedHull.ShapeWidth, typedHull.ShapeHeight, typedHull.ShapeCells);
|
||||
var typedHardpoints = typedHull.Hardpoints.ToArray();
|
||||
|
||||
if(Shield)
|
||||
Shield.Entity = entity;
|
||||
foreach (var hullCollider in HullColliders) hullCollider.Entity = entity;
|
||||
|
||||
foreach (var item in entity.Equipment)
|
||||
{
|
||||
foreach (var behavior in item.Behaviors)
|
||||
{
|
||||
if (behavior is Sensor sensor)
|
||||
{
|
||||
_sensor = sensor;
|
||||
sensor.OnPingStart += () =>
|
||||
{
|
||||
var pingInstance = Instantiate(PingPrefab);
|
||||
var pingMesh = pingInstance.GetComponent<MeshRenderer>();
|
||||
pingInstance.position = entity.Position;
|
||||
_pingBrightness = pingMesh.material.GetFloat("_Depth");
|
||||
_currentPing = (pingInstance, pingMesh);
|
||||
};
|
||||
sensor.OnPingEnd += OnSensorPingEnd;
|
||||
}
|
||||
|
||||
if (behavior is InstantWeapon instantWeapon)
|
||||
{
|
||||
var effectPrefab = instantWeapon.EffectPrefab;
|
||||
if (!_instantWeaponManagers.ContainsKey(effectPrefab))
|
||||
{
|
||||
var managerPrefab = UnityHelpers.LoadAsset<InstantWeaponEffectManager>(effectPrefab);
|
||||
if(managerPrefab)
|
||||
{
|
||||
_instantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent));
|
||||
}
|
||||
else Debug.LogError($"No InstantWeaponEffectManager prefab found at path {effectPrefab}");
|
||||
}
|
||||
|
||||
instantWeapon.OnFire += () =>
|
||||
_instantWeaponManagers[effectPrefab].Fire(instantWeapon, item, this, entity.Target.Value != null && ZoneRenderer.EntityInstances.ContainsKey(entity.Target.Value) ? ZoneRenderer.EntityInstances[entity.Target.Value] : null);
|
||||
|
||||
if (behavior is ChargedWeapon chargedWeapon)
|
||||
{
|
||||
var chargeManager = _instantWeaponManagers[effectPrefab].GetComponent<ChargeEffectManager>();
|
||||
if (chargeManager)
|
||||
{
|
||||
chargedWeapon.OnStartCharging += () => chargeManager.StartCharging(chargedWeapon, item, this);
|
||||
chargedWeapon.OnStopCharging += () => chargeManager.StopCharging(chargedWeapon);
|
||||
chargedWeapon.OnCharged += () => chargeManager.Charged(chargedWeapon);
|
||||
chargedWeapon.OnFailed += () => chargeManager.Failed(chargedWeapon);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (behavior is ConstantWeapon constantWeapon)
|
||||
{
|
||||
var effectPrefab = constantWeapon.EffectPrefab;
|
||||
if (!_constantWeaponManagers.ContainsKey(effectPrefab))
|
||||
{
|
||||
var managerPrefab = UnityHelpers.LoadAsset<ConstantWeaponEffectManager>(effectPrefab);
|
||||
if(managerPrefab)
|
||||
{
|
||||
_constantWeaponManagers.Add(effectPrefab, Instantiate(managerPrefab, EffectManagerParent));
|
||||
}
|
||||
else Debug.LogError($"No ConstantWeaponEffectManager prefab found at path {effectPrefab}");
|
||||
}
|
||||
|
||||
constantWeapon.OnStartFiring += () =>
|
||||
_constantWeaponManagers[effectPrefab].StartFiring(constantWeapon, item, this, entity.Target.Value != null ? ZoneRenderer.EntityInstances[entity.Target.Value] : null);
|
||||
constantWeapon.OnStopFiring += () =>
|
||||
_constantWeaponManagers[effectPrefab].StopFiring(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
RadiatorMeshes = new Dictionary<Radiator, MeshRenderer>();
|
||||
Barrels = new Dictionary<HardpointData, Transform[]>();
|
||||
BarrelIndices = new Dictionary<HardpointData, int>();
|
||||
|
|
@ -289,128 +193,6 @@ public class EntityInstance : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
void DamageSchematic(float damage, Shape hitShape)
|
||||
{
|
||||
foreach (var v in hitShape.Coordinates)
|
||||
hitShape[v] = hitShape[v] && hullShape[v];
|
||||
|
||||
float hullDamage = 0;
|
||||
var damagePerCell = damage / hitShape.Coordinates.Length;
|
||||
foreach (var v in hitShape.Coordinates)
|
||||
{
|
||||
var d = damagePerCell;
|
||||
|
||||
// Subtract surface damage from armor, passing on the remainder to the item and then to the hull
|
||||
var prev = entity.Armor[v.x, v.y];
|
||||
entity.Armor[v.x, v.y] = max(prev - d, 0);
|
||||
entity.ArmorDamage.OnNext((v, d));
|
||||
d = max(d - prev, 0);
|
||||
|
||||
if (d > 0.1f)
|
||||
{
|
||||
var item = entity.GearOccupancy[v.x, v.y];
|
||||
if (item != null)
|
||||
{
|
||||
prev = item.EquippableItem.Durability;
|
||||
item.EquippableItem.Durability = max(prev - d, 0);
|
||||
entity.ItemDamage.OnNext((item, d));
|
||||
d = max(d - prev, 0);
|
||||
}
|
||||
}
|
||||
|
||||
hullDamage += d;
|
||||
}
|
||||
|
||||
if(hullDamage > .1f)
|
||||
{
|
||||
entity.Hull.Durability -= hullDamage;
|
||||
entity.HullDamage.OnNext(hullDamage);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var collider in HullColliders)
|
||||
{
|
||||
collider.Splash.Subscribe(splash =>
|
||||
{
|
||||
var hitShape = new Shape(hullShape.Width, hullShape.Height);
|
||||
foreach (var v in hullShape.Coordinates)
|
||||
{
|
||||
var localHitDirection = transform.InverseTransformDirection(splash.Direction);
|
||||
var direction = normalize(float2(localHitDirection.x, localHitDirection.z));
|
||||
var cellDot = dot(normalize(v - hullShape.CenterOfMass), direction);
|
||||
if (cellDot < 0) hitShape[v] = true;
|
||||
}
|
||||
DamageSchematic(splash.Damage, hitShape);
|
||||
});
|
||||
|
||||
collider.Hit.Subscribe(hit =>
|
||||
{
|
||||
Entity.IncomingHit.OnNext(hit.Source);
|
||||
var hardpointIndex = (int) hit.TexCoord.x - 1;
|
||||
|
||||
var hitShape = new Shape(hullShape.Width, hullShape.Height);
|
||||
|
||||
// U coordinate between 0-1 indicates a hit that didn't land directly on a hardpoint
|
||||
// Find the 2D position of the hit scaled to the schematic
|
||||
float2 hitPos = float2.zero;
|
||||
if (hardpointIndex < 0 || hardpointIndex >= typedHardpoints.Length)
|
||||
{
|
||||
hitPos = float2(hit.TexCoord.x * hullShape.Width, hit.TexCoord.y * hullShape.Height);
|
||||
// Search all schematic border cells for the cell which is closest to the hit position
|
||||
var hitCell = int2(-1);
|
||||
var distance = float.MaxValue;
|
||||
foreach (var v in hullShape.Coordinates)
|
||||
{
|
||||
var cellDist = lengthsq(hitPos - v);
|
||||
if (cellDist < distance)
|
||||
{
|
||||
distance = cellDist;
|
||||
hitCell = v;
|
||||
hitPos = v + float2(.5f);
|
||||
}
|
||||
}
|
||||
|
||||
hitShape[hitCell] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Collider UV coordinates starting with 1 correspond to hardpoint index
|
||||
var hardpoint = typedHardpoints[hardpointIndex];
|
||||
|
||||
// Obtain the hull coordinates of all cells occupied by the hardpoint
|
||||
var hardpointShape = ToShape(hardpoint.ShapeWidth, hardpoint.ShapeHeight, hardpoint.ShapeCells);
|
||||
var hardpointCells = hullShape.Inset(hardpointShape, int2(hardpoint.PositionX, hardpoint.PositionY));
|
||||
hitPos = hardpointCells.CenterOfMass;
|
||||
foreach (var v in hardpointCells.Coordinates)
|
||||
hitShape[v] = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Mathf.RoundToInt(hit.Spread); i++)
|
||||
{
|
||||
hitShape = hitShape.Expand();
|
||||
}
|
||||
|
||||
if (hit.Penetration > .5f)
|
||||
{
|
||||
// Find the local 2D vector corresponding to the direction of the incoming hit
|
||||
var localHitDirection = transform.InverseTransformDirection(hit.Direction);
|
||||
var penetrationVector = normalize(float2(localHitDirection.x, localHitDirection.z));
|
||||
// TODO: Bresenham's line algorithm
|
||||
// March a ray through the ship from the hit position
|
||||
var penetrationPoint = hitPos;
|
||||
var penetrationDistance = 0f;
|
||||
while (penetrationDistance < hit.Penetration && hullShape[int2(penetrationPoint)])
|
||||
{
|
||||
penetrationDistance += .5f;
|
||||
hitShape[int2(penetrationPoint)] = true;
|
||||
penetrationPoint += penetrationVector * .5f;
|
||||
}
|
||||
}
|
||||
|
||||
DamageSchematic(hit.Damage, hitShape);
|
||||
});
|
||||
}
|
||||
|
||||
LookAtPoint = new GameObject($"{entity.Name} Look Point").transform;
|
||||
|
||||
foreach (var articulationPoint in ArticulationPoints)
|
||||
|
|
@ -418,23 +200,6 @@ public class EntityInstance : MonoBehaviour
|
|||
articulationPoint.Target = LookAtPoint;
|
||||
}
|
||||
|
||||
_subscriptions.Add(Entity.HullDamage.Subscribe(_ =>
|
||||
{
|
||||
if (!_destroyed && Entity.Hull.Durability < .01f)
|
||||
{
|
||||
if (!this) return;
|
||||
if (ActionGameManager.Instance?.CommitEntityDestroyed(entity, ZoneRenderer) != true)
|
||||
return;
|
||||
|
||||
_destroyed = true;
|
||||
if (DestroyEffect != null)
|
||||
{
|
||||
var t = Instantiate(DestroyEffect).transform;
|
||||
t.position = transform.position;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (entity is OrbitalEntity orbital && orbital.IsSecureArea)
|
||||
{
|
||||
_influenceInstance = Instantiate(InfluencePrefab);
|
||||
|
|
@ -442,11 +207,6 @@ public class EntityInstance : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
private void OnSensorPingEnd()
|
||||
{
|
||||
Destroy(_currentPing.transform.gameObject);
|
||||
}
|
||||
|
||||
public Transform GetBarrel(HardpointData hardpoint)
|
||||
{
|
||||
if (Barrels.ContainsKey(hardpoint))
|
||||
|
|
@ -461,11 +221,6 @@ public class EntityInstance : MonoBehaviour
|
|||
|
||||
public virtual void Update()
|
||||
{
|
||||
if (_currentPing.transform)
|
||||
{
|
||||
_currentPing.transform.localScale = _sensor.PingRadius * Vector3.one;
|
||||
_currentPing.meshRenderer.material.SetFloat("_Depth", _pingBrightness * _sensor.PingBrightness);
|
||||
}
|
||||
if (_fading)
|
||||
{
|
||||
if (_fadingIn)
|
||||
|
|
@ -510,20 +265,23 @@ public class EntityInstance : MonoBehaviour
|
|||
|
||||
foreach (var x in Barrels)
|
||||
{
|
||||
Entity.HardpointTransforms[x.Key] = (x.Value[0].position, x.Value[0].forward);
|
||||
Entity.HardpointTransforms[x.Key] = (
|
||||
ToCult(x.Value[0].position),
|
||||
ToCult(x.Value[0].forward));
|
||||
}
|
||||
|
||||
LookAtPoint.position = transform.position + (Vector3) Entity.LookDirection *
|
||||
(Entity.Target.Value != null ? max(Entity.TargetRange,Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000);
|
||||
LocalSpace.localPosition = transform.position = Entity.Position;
|
||||
var entityPosition = (Vector3)AetheriaMath.ToUnity(Entity.CultPosition);
|
||||
var entityLookDirection = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection);
|
||||
|
||||
LookAtPoint.position = transform.position + entityLookDirection *
|
||||
(Entity.Target.Value != null ? Mathf.Max(Entity.TargetRange, Entity.ItemManager.GameplaySettings.ConvergenceMinimumDistance) : 10000);
|
||||
LocalSpace.localPosition = transform.position = entityPosition;
|
||||
if (_influenceInstance)
|
||||
_influenceInstance.position = new Vector3(Entity.Position.x, 0, Entity.Position.z);
|
||||
_influenceInstance.position = new Vector3(entityPosition.x, 0, entityPosition.z);
|
||||
}
|
||||
|
||||
public virtual void OnDestroy()
|
||||
{
|
||||
if(_sensor!=null)
|
||||
_sensor.OnPingEnd -= OnSensorPingEnd;
|
||||
if (_influenceInstance) Destroy(_influenceInstance.gameObject);
|
||||
Destroy(LocalSpace.gameObject);
|
||||
foreach(var x in _subscriptions)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.InputSystem;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class FieldDriver : MonoBehaviour
|
||||
{
|
||||
public Camera Camera;
|
||||
public float2 Push;
|
||||
public Vector2 Push;
|
||||
public float FrontTwist;
|
||||
public float RearTwist;
|
||||
public float TestMagnitude;
|
||||
|
|
@ -28,19 +24,6 @@ public class FieldDriver : MonoBehaviour
|
|||
public float MeleeAngle;
|
||||
public float MeleeAngleExponent;
|
||||
|
||||
public float TendrilBaseRadius = 3;
|
||||
public float TendrilTipRadius = .1f;
|
||||
public float TendrilExtensionExponent = .75f;
|
||||
public float TendrilExtendBaseAnimationExponent = .5f;
|
||||
public float TendrilBaseDamping = 2;
|
||||
public float TendrilTipRadiusAnimationExponent = 2f;
|
||||
public float TendrilFadeExponent = 2;
|
||||
// public float GrabScaleAnimationExponent = .5f;
|
||||
|
||||
public float GrabExtendTime;
|
||||
public float GrabEnvelopTime;
|
||||
public float GrabPullTime;
|
||||
|
||||
public RectTransform.Axis RefractionAxis = RectTransform.Axis.Horizontal;
|
||||
|
||||
private Material _field;
|
||||
|
|
@ -49,57 +32,36 @@ public class FieldDriver : MonoBehaviour
|
|||
private float _waveOffset;
|
||||
private float _meleeTime;
|
||||
private bool _meleeActive;
|
||||
private MeshCollider _collider;
|
||||
private Transform _grabObject;
|
||||
private Vector3 _grabObjectStartPos;
|
||||
private Vector3 _grabObjectStartTendrilBasePos;
|
||||
private Vector3 _grabObjectEndPos;
|
||||
private Vector3 _grabObjectVelocity;
|
||||
private float _grabObjectScale;
|
||||
private float _grabTime;
|
||||
private GrabPhase _grabPhase;
|
||||
private Vector3 _tendrilBasePos;
|
||||
private Vector3 _tendrilBendTarget;
|
||||
private Vector3 _tendrilTargetPos;
|
||||
private float _fadePoint;
|
||||
|
||||
private enum GrabPhase
|
||||
{
|
||||
Extend,
|
||||
Envelop,
|
||||
Pull
|
||||
}
|
||||
|
||||
private struct FieldHit
|
||||
{
|
||||
public float3 Position;
|
||||
public float3 Direction;
|
||||
public Vector3 Position;
|
||||
public Vector3 Direction;
|
||||
public float Magnitude;
|
||||
public float Time;
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
_collider = GetComponent<MeshCollider>();
|
||||
_field = GetComponent<MeshRenderer>().material;
|
||||
var clickableCollider = GetComponent<ClickableCollider>();
|
||||
if(clickableCollider!=null)
|
||||
{
|
||||
clickableCollider.OnClick += (_, _, ray, hit) =>
|
||||
{
|
||||
AddHit(hit.point, ray.direction, TestMagnitude);
|
||||
AddHit(hit.Point, ray.direction, TestMagnitude);
|
||||
};
|
||||
}
|
||||
_hitBuffer = new ComputeBuffer(MaxHits, 32);
|
||||
}
|
||||
|
||||
public void AddHit(float3 position, float3 direction, float magnitude)
|
||||
public void AddHit(Vector3 position, Vector3 direction, float magnitude)
|
||||
{
|
||||
if (_hits.Count >= MaxHits) return;
|
||||
var hit = new FieldHit
|
||||
{
|
||||
Position = Vector3.Scale(normalize(transform.InverseTransformPoint(position)), transform.localScale),
|
||||
Direction = normalize(transform.rotation * direction),
|
||||
Position = Vector3.Scale(transform.InverseTransformPoint(position).normalized, transform.localScale),
|
||||
Direction = (transform.rotation * direction).normalized,
|
||||
Magnitude = magnitude,
|
||||
Time = 0
|
||||
};
|
||||
|
|
@ -115,31 +77,21 @@ public class FieldDriver : MonoBehaviour
|
|||
_meleeTime = 0;
|
||||
}
|
||||
|
||||
public bool CanGrab => _grabObject == null;
|
||||
|
||||
public void GrabObject(Transform t, Vector3 v)
|
||||
{
|
||||
_grabPhase = GrabPhase.Extend;
|
||||
_grabObject = t;
|
||||
_grabObjectStartPos = t.position;
|
||||
_grabObjectVelocity = v;
|
||||
_grabObjectScale = t.localScale.x;
|
||||
_tendrilBasePos = _grabObjectStartTendrilBasePos = _collider.ClosestPoint(_grabObjectStartPos);
|
||||
_fadePoint = .999f;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
|
||||
var pushMag = min(length(Push), 1);
|
||||
var pushMag = Mathf.Min(Push.magnitude, 1);
|
||||
|
||||
_waveOffset = frac(_waveOffset + Time.deltaTime * FlowSpeed * pow(max(pushMag, max(abs(FrontTwist), abs(RearTwist))), FlowSpeedThrottleExponent));
|
||||
_waveOffset = Mathf.Repeat(
|
||||
_waveOffset + Time.deltaTime * FlowSpeed *
|
||||
Mathf.Pow(Mathf.Max(pushMag, Mathf.Max(Mathf.Abs(FrontTwist), Mathf.Abs(RearTwist))), FlowSpeedThrottleExponent),
|
||||
1);
|
||||
|
||||
var refractionRotation = Matrix4x4.Rotate(Camera.transform.rotation).inverse;
|
||||
if (RefractionAxis == RectTransform.Axis.Vertical) refractionRotation =
|
||||
Matrix4x4.Rotate(Quaternion.Euler(90, 0, 0)) * refractionRotation;
|
||||
|
||||
_field.SetFloat("_WaveOffset", _waveOffset * PI * 2);
|
||||
_field.SetFloat("_WaveOffset", _waveOffset * Mathf.PI * 2);
|
||||
_field.SetFloat("_Push", pushMag);
|
||||
_field.SetVector("_PushDirection", new Vector4(-Push.x, 0, -Push.y));
|
||||
_field.SetFloat("_TwistFront", FrontTwist);
|
||||
|
|
@ -168,8 +120,8 @@ public class FieldDriver : MonoBehaviour
|
|||
_meleeTime += Time.deltaTime / MeleeDuration;
|
||||
if (_meleeTime < 1)
|
||||
{
|
||||
_field.SetVector("_MeleeDirection", Quaternion.AngleAxis(sign(_meleeTime*2-1) * MeleeAngle * pow(abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward);
|
||||
_field.SetFloat("_MeleeDisplacement", pow(1 - 2 * abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange);
|
||||
_field.SetVector("_MeleeDirection", Quaternion.AngleAxis(Mathf.Sign(_meleeTime * 2 - 1) * MeleeAngle * Mathf.Pow(Mathf.Abs(_meleeTime * 2 - 1), MeleeAngleExponent), Vector3.up) * -Vector3.forward);
|
||||
_field.SetFloat("_MeleeDisplacement", Mathf.Pow(1 - 2 * Mathf.Abs(_meleeTime - .5f), MeleeRangeExponent) * MeleeRange);
|
||||
_field.SetFloat("_MeleeShape", MeleeShaping);
|
||||
_field.SetFloat("_MeleeFlattening", MeleeFlatness);
|
||||
}
|
||||
|
|
@ -179,73 +131,6 @@ public class FieldDriver : MonoBehaviour
|
|||
_field.SetFloat("_MeleeDisplacement", 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(_grabObject != null)
|
||||
{
|
||||
_grabTime += Time.deltaTime / _grabPhase switch
|
||||
{
|
||||
GrabPhase.Extend => GrabExtendTime,
|
||||
GrabPhase.Envelop => GrabEnvelopTime,
|
||||
GrabPhase.Pull => GrabPullTime,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
if (_grabTime > 1)
|
||||
{
|
||||
_grabTime = 0;
|
||||
if (_grabPhase == GrabPhase.Envelop)
|
||||
{
|
||||
_grabObjectEndPos = _grabObject.position;
|
||||
}
|
||||
if (_grabPhase == GrabPhase.Pull)
|
||||
{
|
||||
Destroy(_grabObject.gameObject);
|
||||
_grabObject = null;
|
||||
_field.SetFloat("_TendrilInfluence", 0);
|
||||
}
|
||||
else _grabPhase++;
|
||||
}
|
||||
|
||||
if (_grabObject != null)
|
||||
{
|
||||
_tendrilBasePos = AetheriaMath.Damp(_tendrilBasePos, _grabObject.position, TendrilBaseDamping, Time.deltaTime);
|
||||
switch (_grabPhase)
|
||||
{
|
||||
case GrabPhase.Extend:
|
||||
_tendrilBendTarget = _grabObjectStartPos;
|
||||
_grabObject.position += _grabObjectVelocity * Time.deltaTime;
|
||||
_tendrilTargetPos = lerp(
|
||||
lerp(_grabObjectStartTendrilBasePos, _grabObjectStartPos, _grabTime),
|
||||
lerp(_grabObjectStartPos, _grabObject.position, _grabTime),
|
||||
pow(_grabTime, TendrilExtensionExponent));
|
||||
_field.SetFloat("_TendrilInfluence", pow(_grabTime, TendrilExtendBaseAnimationExponent));
|
||||
_field.SetFloat("_TendrilSize", lerp(TendrilBaseRadius/2,TendrilBaseRadius,pow(_grabTime, TendrilExtendBaseAnimationExponent)));
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * pow(_grabTime, TendrilTipRadiusAnimationExponent) * TendrilTipRadius);
|
||||
break;
|
||||
case GrabPhase.Envelop:
|
||||
_tendrilBendTarget = _grabObjectStartPos;
|
||||
_tendrilTargetPos = _grabObject.position += _grabObjectVelocity * Time.deltaTime * (1-_grabTime);
|
||||
_field.SetFloat("_TendrilInfluence", 1);
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
|
||||
break;
|
||||
case GrabPhase.Pull:
|
||||
_tendrilTargetPos = _grabObject.position = lerp(_grabObjectEndPos, transform.position, _grabTime*_grabTime);
|
||||
_tendrilBendTarget = lerp(_grabObjectStartPos, _grabObjectEndPos, _grabTime*_grabTime);
|
||||
if (_fadePoint > .99 && transform.InverseTransformPoint(_grabObject.position).sqrMagnitude < 1)
|
||||
_fadePoint = _grabTime;
|
||||
_field.SetFloat("_TendrilInfluence", pow(smoothstep(1, _fadePoint, _grabTime), TendrilFadeExponent));
|
||||
_field.SetFloat("_TendrilSize", TendrilBaseRadius);
|
||||
_field.SetFloat("_TendrilRadius", _grabObjectScale * TendrilTipRadius);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
_field.SetVector("_TendrilBase", Vector3.Scale(transform.InverseTransformPoint(_tendrilBasePos).normalized, transform.localScale));
|
||||
_field.SetVector("_TendrilBend", Vector3.Scale(transform.InverseTransformPoint(_tendrilBendTarget), transform.localScale));
|
||||
_field.SetVector("_TendrilTarget", Vector3.Scale(transform.InverseTransformPoint(_tendrilTargetPos), transform.localScale));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float almostIdentity( float x )
|
||||
|
|
@ -258,20 +143,10 @@ public class FieldDriver : MonoBehaviour
|
|||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
private const int GIZMO_STEPS = 16;
|
||||
private void OnDrawGizmosSelected()
|
||||
private float SmoothStep(float edge0, float edge1, float x)
|
||||
{
|
||||
if (_grabObject != null)
|
||||
{
|
||||
Vector3 previous = _tendrilBasePos;
|
||||
for (int i = 1; i <= GIZMO_STEPS; i++)
|
||||
{
|
||||
var l = (float)i / GIZMO_STEPS;
|
||||
var next = AetheriaMath.GetQuadraticSplinePosition(_tendrilBasePos, _tendrilBendTarget, _tendrilTargetPos, l);
|
||||
Gizmos.DrawLine(previous, next);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
var t = Mathf.Clamp01((x - edge0) / (edge1 - edge0));
|
||||
return t * t * (3.0f - 2.0f * t);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ public class GameSettings : ScriptableObject
|
|||
public string DefaultBossSoundbank = "Boss_Metal";
|
||||
public string AmbienceSoundBank = "Ambience";
|
||||
public string StartingHullName = "Longinus";
|
||||
public float PickupLifetime = 30;
|
||||
public float LootDropProbability = .25f;
|
||||
public float LootDropVelocity = 25;
|
||||
public float HeatstrokePhasingFloor = 0;
|
||||
|
|
@ -86,4 +85,4 @@ public class BodySettingsCollection
|
|||
{
|
||||
public float MinimumMass;
|
||||
public CelestialBodySettings[] BodySettings;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
public class GridObject : MonoBehaviour
|
||||
|
|
@ -13,7 +11,6 @@ public class GridObject : MonoBehaviour
|
|||
public float Drag = .1f;
|
||||
public float LaunchDrag;
|
||||
|
||||
private float2 _selfVelocity;
|
||||
private float _timeOffset;
|
||||
|
||||
public Zone Zone { get; set; }
|
||||
|
|
@ -27,24 +24,6 @@ public class GridObject : MonoBehaviour
|
|||
void Update()
|
||||
{
|
||||
var t = transform;
|
||||
t.localRotation = Quaternion.Euler(sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, cos(Time.time - _timeOffset * RotationSpeed) * 90);
|
||||
|
||||
if (Zone == null) return;
|
||||
|
||||
var position = t.position;
|
||||
var gridHeight = Zone.GetHeight(position.Flatland()) + GridOffset;
|
||||
Velocity += Vector3.up * (sign(gridHeight - position.y) * GridAttraction * Time.deltaTime);
|
||||
Velocity *= max(0, 1 - LaunchDrag * Time.deltaTime);
|
||||
var normal = Zone.GetNormal(position.Flatland());
|
||||
var force = new float2(normal.x, normal.z);
|
||||
var forceMagnitude = lengthsq(force);
|
||||
if (forceMagnitude > .001f)
|
||||
{
|
||||
var fa = 1 / (1 - forceMagnitude) - 1;
|
||||
_selfVelocity += normalize(force) * Zone.Settings.GravityStrength * fa * Gravity;
|
||||
}
|
||||
|
||||
_selfVelocity *= max(0, 1 - Drag * Time.deltaTime);
|
||||
t.position = position + (Velocity + new Vector3(_selfVelocity.x, 0, _selfVelocity.y)) * Time.deltaTime;
|
||||
t.localRotation = Quaternion.Euler(Mathf.Sin(Time.time - _timeOffset * RotationSpeed) * 90, 0, Mathf.Cos(Time.time - _timeOffset * RotationSpeed) * 90);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,65 +1,65 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using UnityEngine;
|
||||
using float2 = Unity.Mathematics.float2;
|
||||
|
||||
public class HullCollider : MonoBehaviour
|
||||
{
|
||||
public Subject<HullHitEventArgs> Hit = new Subject<HullHitEventArgs>();
|
||||
public Subject<HullSplashEventArgs> Splash = new Subject<HullSplashEventArgs>();
|
||||
|
||||
private Renderer[] _renderers;
|
||||
|
||||
public Entity Entity { get; set; }
|
||||
|
||||
public void SendHit(float damage, float penetration, float spread, DamageType damageType, Entity source, Vector2 texCoord, Vector3 direction)
|
||||
|
||||
public Bounds YmirBounds => CalculateBounds(transform.position);
|
||||
|
||||
public float YmirPlanarRadius => Mathf.Max(YmirBounds.extents.x, YmirBounds.extents.z, 0.001f);
|
||||
|
||||
public void RefreshYmirBoundsSource()
|
||||
{
|
||||
Hit.OnNext(new HullHitEventArgs
|
||||
{
|
||||
Damage = damage,
|
||||
Penetration = penetration,
|
||||
Spread = spread,
|
||||
DamageType = damageType,
|
||||
Source = source,
|
||||
TexCoord = texCoord,
|
||||
Direction = direction
|
||||
});
|
||||
}
|
||||
|
||||
public void SendSplash(float damage, DamageType damageType, Entity source, Vector3 direction)
|
||||
{
|
||||
Splash.OnNext(new HullSplashEventArgs
|
||||
{
|
||||
Damage = damage,
|
||||
DamageType = damageType,
|
||||
Source = source,
|
||||
Direction = direction
|
||||
});
|
||||
_renderers = GetComponentsInChildren<Renderer>();
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision other)
|
||||
private void Awake()
|
||||
{
|
||||
Entity.Velocity += ((float3) other.impulse).xz / Time.deltaTime;
|
||||
RefreshYmirBoundsSource();
|
||||
}
|
||||
|
||||
public class HullHitEventArgs
|
||||
private void OnEnable()
|
||||
{
|
||||
public float Damage;
|
||||
public float Penetration;
|
||||
public float Spread;
|
||||
public DamageType DamageType;
|
||||
public Entity Source;
|
||||
public float2 TexCoord;
|
||||
public float3 Direction;
|
||||
if (_renderers == null || _renderers.Length == 0)
|
||||
RefreshYmirBoundsSource();
|
||||
}
|
||||
|
||||
public class HullSplashEventArgs
|
||||
|
||||
private Bounds CalculateBounds(Vector3 fallbackCenter)
|
||||
{
|
||||
public float Damage;
|
||||
public DamageType DamageType;
|
||||
public Entity Source;
|
||||
public float3 Direction;
|
||||
if (_renderers == null)
|
||||
RefreshYmirBoundsSource();
|
||||
|
||||
if (_renderers == null || _renderers.Length == 0)
|
||||
return new Bounds(fallbackCenter, Vector3.one);
|
||||
|
||||
var firstRenderer = FirstLiveRenderer();
|
||||
if (firstRenderer == null)
|
||||
return new Bounds(fallbackCenter, Vector3.one);
|
||||
|
||||
var bounds = firstRenderer.bounds;
|
||||
for (var i = 0; i < _renderers.Length; i++)
|
||||
{
|
||||
var renderer = _renderers[i];
|
||||
if (renderer == null || renderer == firstRenderer)
|
||||
continue;
|
||||
|
||||
bounds.Encapsulate(renderer.bounds);
|
||||
}
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
private Renderer FirstLiveRenderer()
|
||||
{
|
||||
for (var i = 0; i < _renderers.Length; i++)
|
||||
{
|
||||
if (_renderers[i] != null)
|
||||
return _renderers[i];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using TMPro;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
using static CultMath.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class ItemPickup : MonoBehaviour
|
||||
{
|
||||
|
|
@ -15,27 +15,28 @@ public class ItemPickup : MonoBehaviour
|
|||
|
||||
public ItemInstance Item { get; set; }
|
||||
public ZoneRenderer ZoneRenderer { get; set; }
|
||||
public float3 ViewOrigin { get; set; }
|
||||
public float3 ViewDirection { get; set; }
|
||||
public cfloat3 ViewOrigin { get; set; }
|
||||
public cfloat3 ViewDirection { get; set; }
|
||||
|
||||
private float _displayTime;
|
||||
|
||||
private void Update()
|
||||
{
|
||||
var diff = (float3) transform.position - ViewOrigin;
|
||||
var position = transform.position;
|
||||
var diff = new cfloat3(position.x, position.y, position.z) - ViewOrigin;
|
||||
var toThis = normalize(diff);
|
||||
var viewAngle = acos(Vector3.Dot(toThis, ViewDirection)) * Mathf.Rad2Deg;
|
||||
var viewAngle = acos(dot(toThis, ViewDirection)) * AetheriaMath.Rad2Deg;
|
||||
if (length(diff) < LabelDisplayMaxDistance && viewAngle < LabelDisplayAngle)
|
||||
_displayTime = Time.time;
|
||||
var targetAlpha = Time.time - _displayTime < LabelPersistDuration ? 1 : 0;
|
||||
var c = ScanLabel.color;
|
||||
c.a = c.a + sign(targetAlpha - c.a) * (Time.deltaTime / LabelFadeDuration);
|
||||
ScanLabel.color = c;
|
||||
ScanLabelContainer.rotation = Quaternion.LookRotation(-toThis);
|
||||
ScanLabelContainer.rotation = Quaternion.LookRotation((Vector3)AetheriaMath.ToUnity(-toThis));
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ZoneRenderer?.DestroyLoot(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
8
Assets/Scripts/Gameplay/Physics.meta
Normal file
8
Assets/Scripts/Gameplay/Physics.meta
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
fileFormatVersion: 2
|
||||
guid: baa9b1a7f453a7a4aafe9c44c9fba7c3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
865
Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs
Normal file
865
Assets/Scripts/Gameplay/Physics/AetheriaYmirPhysicsBridge.cs
Normal file
|
|
@ -0,0 +1,865 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public sealed class AetheriaYmirPhysicsBridge : MonoBehaviour
|
||||
{
|
||||
private const string ProjectileBodyId = "aetheria.projectile";
|
||||
private const string TargetBodyPrefix = "aetheria.target.";
|
||||
|
||||
private static AetheriaYmirPhysicsBridge _instance;
|
||||
|
||||
public string ServiceUrl = "http://127.0.0.1:8877/simulate/step";
|
||||
public string OverlapSphereUrl = "http://127.0.0.1:8877/query/overlap-sphere";
|
||||
public string OverlapCircleUrl = "http://127.0.0.1:8877/query/overlap-circle";
|
||||
public string CastCircleUrl = "http://127.0.0.1:8877/query/cast-circle";
|
||||
public string CastSphereUrl = "http://127.0.0.1:8877/query/cast-sphere";
|
||||
public bool EnableProjectileCutover = true;
|
||||
public float ProjectileRadius = 0.1f;
|
||||
public float ProjectileMass = 1.0f;
|
||||
public float TargetRadiusPadding = 0.1f;
|
||||
public int TimeoutMilliseconds = 20;
|
||||
public AetheriaDaemonObserver DaemonObserver;
|
||||
|
||||
private readonly List<YmirPhysicsBody> _projectileBodies = new List<YmirPhysicsBody>();
|
||||
private readonly List<YmirPhysicsBody> _targetBodies = new List<YmirPhysicsBody>();
|
||||
private readonly List<YmirPhysicsBody> _zoneBodies = new List<YmirPhysicsBody>();
|
||||
private readonly List<YmirPhysicsBody> _daemonBodies = new List<YmirPhysicsBody>();
|
||||
private readonly List<YmirSphereQueryBody> _clickableBodies = new List<YmirSphereQueryBody>();
|
||||
private readonly Dictionary<string, HullCollider> _hullBodyMap = new Dictionary<string, HullCollider>(StringComparer.Ordinal);
|
||||
private readonly Dictionary<string, ClickableCollider> _clickableBodyMap = new Dictionary<string, ClickableCollider>(StringComparer.Ordinal);
|
||||
|
||||
public static AetheriaYmirPhysicsBridge Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance != null) return _instance;
|
||||
|
||||
_instance = FindAnyObjectByType<AetheriaYmirPhysicsBridge>();
|
||||
if (_instance != null) return _instance;
|
||||
|
||||
var bridge = new GameObject("Aetheria Ymir Physics Bridge");
|
||||
DontDestroyOnLoad(bridge);
|
||||
_instance = bridge.AddComponent<AetheriaYmirPhysicsBridge>();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryStepProjectile(Projectile projectile, float deltaTime, out AetheriaYmirProjectileStep step)
|
||||
{
|
||||
step = default;
|
||||
if (!EnableProjectileCutover || projectile == null || deltaTime <= 0)
|
||||
return false;
|
||||
|
||||
var request = BuildProjectileRequest(projectile, deltaTime);
|
||||
if (request.world.bodies.Length == 0)
|
||||
return false;
|
||||
|
||||
YmirStepResult result;
|
||||
try
|
||||
{
|
||||
result = PostJson<YmirStepRequest, YmirStepResult>(ServiceUrl, request);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Debug.LogWarning($"Ymir projectile step failed: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result == null || result.world == null)
|
||||
return false;
|
||||
|
||||
var projectileBody = FindBody(result.world, ProjectileBodyId);
|
||||
if (projectileBody == null)
|
||||
return false;
|
||||
|
||||
step.Position = new Vector3(projectileBody.position.x, projectile.transform.position.y, projectileBody.position.y);
|
||||
step.Velocity = new Vector3(projectileBody.velocity.x, projectile.Velocity.y, projectileBody.velocity.y);
|
||||
|
||||
foreach (var contact in result.contacts ?? Array.Empty<YmirContactEvent>())
|
||||
{
|
||||
var otherBody = string.Equals(contact.bodyA, ProjectileBodyId, StringComparison.Ordinal)
|
||||
? contact.bodyB
|
||||
: string.Equals(contact.bodyB, ProjectileBodyId, StringComparison.Ordinal)
|
||||
? contact.bodyA
|
||||
: "";
|
||||
if (string.IsNullOrWhiteSpace(otherBody))
|
||||
continue;
|
||||
|
||||
var hull = ResolveTargetHull(projectile.TargetInstance, otherBody);
|
||||
if (hull == null)
|
||||
continue;
|
||||
|
||||
step.Hit = new AetheriaYmirProjectileHit(
|
||||
hull,
|
||||
new Vector3(contact.point.x, projectile.transform.position.y, contact.point.y),
|
||||
new Vector3(contact.normal.x, 0, contact.normal.y).normalized);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryOverlapDaemonBodies(
|
||||
Vector3 center,
|
||||
float radius,
|
||||
out IReadOnlyList<YmirCircleOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirCircleOverlapHit>();
|
||||
if (!TryBuildDaemonWorld(out var world) || radius <= 0)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleOverlapRequest
|
||||
{
|
||||
world = world,
|
||||
center = ToVec2(center),
|
||||
radius = radius
|
||||
};
|
||||
|
||||
return TryOverlapCircle(request, out hits);
|
||||
}
|
||||
|
||||
public bool TryCastDaemonBodies(
|
||||
Vector3 origin,
|
||||
Vector3 direction,
|
||||
float distance,
|
||||
float radius,
|
||||
out IReadOnlyList<YmirCircleCastHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirCircleCastHit>();
|
||||
if (!TryBuildDaemonWorld(out var world) || distance <= 0 || radius < 0)
|
||||
return false;
|
||||
|
||||
var planarDirection = new Vector3(direction.x, 0, direction.z);
|
||||
if (planarDirection.sqrMagnitude <= float.Epsilon)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleCastRequest
|
||||
{
|
||||
world = world,
|
||||
origin = ToVec2(origin),
|
||||
direction = ToVec2(planarDirection.normalized),
|
||||
distance = distance,
|
||||
radius = radius
|
||||
};
|
||||
|
||||
return TryCastCircle(request, "Ymir daemon body circle cast query", out hits);
|
||||
}
|
||||
|
||||
public bool TryOverlapTargetHulls(
|
||||
EntityInstance target,
|
||||
Vector3 center,
|
||||
float radius,
|
||||
out IReadOnlyList<AetheriaYmirOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<AetheriaYmirOverlapHit>();
|
||||
if (!EnableProjectileCutover || target == null || radius <= 0)
|
||||
return false;
|
||||
|
||||
var world = BuildTargetWorld(target);
|
||||
if (world.bodies.Length == 0)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleOverlapRequest
|
||||
{
|
||||
center = ToVec2(center),
|
||||
radius = radius,
|
||||
world = world
|
||||
};
|
||||
|
||||
if (!TryOverlapCircle(request, out var ymirHits))
|
||||
return false;
|
||||
|
||||
var resolved = new List<AetheriaYmirOverlapHit>();
|
||||
foreach (var hit in ymirHits)
|
||||
{
|
||||
var hull = ResolveTargetHull(target, hit.bodyId);
|
||||
if (hull == null)
|
||||
continue;
|
||||
|
||||
resolved.Add(new AetheriaYmirOverlapHit(
|
||||
hull,
|
||||
new Vector3(hit.point.x, center.y, hit.point.y),
|
||||
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
|
||||
hit.penetration,
|
||||
hit.distance));
|
||||
}
|
||||
|
||||
hits = resolved;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryOverlapSphere(
|
||||
IReadOnlyList<YmirSphereQueryBody> bodies,
|
||||
Vector3 center,
|
||||
float radius,
|
||||
out IReadOnlyList<YmirSphereOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirSphereOverlapHit>();
|
||||
if (bodies == null || bodies.Count == 0)
|
||||
return false;
|
||||
|
||||
var bodyArray = bodies as YmirSphereQueryBody[] ?? CopySphereQueryBodies(bodies);
|
||||
return TryOverlapSphere(bodyArray, center, radius, "Ymir sphere overlap query", out hits);
|
||||
}
|
||||
|
||||
public bool TryCastTargetHulls(
|
||||
EntityInstance target,
|
||||
Vector3 origin,
|
||||
Vector3 direction,
|
||||
float distance,
|
||||
float radius,
|
||||
out IReadOnlyList<AetheriaYmirCastHit> hits)
|
||||
{
|
||||
hits = Array.Empty<AetheriaYmirCastHit>();
|
||||
if (!EnableProjectileCutover || target == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f)
|
||||
return false;
|
||||
|
||||
var world = BuildTargetWorld(target);
|
||||
if (world.bodies.Length == 0)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleCastRequest
|
||||
{
|
||||
origin = ToVec2(origin),
|
||||
direction = ToVec2(direction.normalized),
|
||||
distance = distance,
|
||||
radius = Mathf.Max(0, radius),
|
||||
world = world
|
||||
};
|
||||
|
||||
if (!TryCastCircle(request, "Ymir target cast query", out var ymirHits))
|
||||
return false;
|
||||
|
||||
var resolved = new List<AetheriaYmirCastHit>();
|
||||
foreach (var hit in ymirHits)
|
||||
{
|
||||
var hull = ResolveTargetHull(target, hit.bodyId);
|
||||
if (hull == null)
|
||||
continue;
|
||||
|
||||
resolved.Add(new AetheriaYmirCastHit(
|
||||
hull,
|
||||
ResolveShield(hull),
|
||||
new Vector3(hit.point.x, origin.y, hit.point.y),
|
||||
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
|
||||
hit.distance));
|
||||
}
|
||||
|
||||
hits = resolved;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCastClickables(
|
||||
IReadOnlyList<ClickableCollider> clickables,
|
||||
Ray ray,
|
||||
float distance,
|
||||
out AetheriaYmirClickableHit hit)
|
||||
{
|
||||
hit = default;
|
||||
if (!EnableProjectileCutover || clickables == null || clickables.Count == 0 || distance <= 0)
|
||||
return false;
|
||||
|
||||
_clickableBodyMap.Clear();
|
||||
_clickableBodies.Clear();
|
||||
for (var i = 0; i < clickables.Count; i++)
|
||||
{
|
||||
var clickable = clickables[i];
|
||||
if (clickable == null || !clickable.isActiveAndEnabled)
|
||||
continue;
|
||||
|
||||
var bounds = clickable.ClickBounds;
|
||||
var bodyId = $"aetheria.clickable.{i}";
|
||||
_clickableBodyMap[bodyId] = clickable;
|
||||
_clickableBodies.Add(new YmirSphereQueryBody
|
||||
{
|
||||
id = bodyId,
|
||||
position = ToVec3(bounds.center),
|
||||
radius = Mathf.Max(0.001f, bounds.extents.magnitude)
|
||||
});
|
||||
}
|
||||
|
||||
if (_clickableBodies.Count == 0)
|
||||
return false;
|
||||
|
||||
var bodyArray = _clickableBodies.ToArray();
|
||||
var request = new YmirSphereCastRequest
|
||||
{
|
||||
origin = ToVec3(ray.origin),
|
||||
direction = ToVec3(ray.direction.normalized),
|
||||
distance = distance,
|
||||
radius = 0,
|
||||
bodies = bodyArray
|
||||
};
|
||||
|
||||
if (!TryCastSphere(request, "Ymir clickable cast query", out var ymirHits))
|
||||
return false;
|
||||
|
||||
foreach (var resultHit in ymirHits)
|
||||
{
|
||||
if (!_clickableBodyMap.TryGetValue(resultHit.bodyId, out var clickable) || clickable == null)
|
||||
continue;
|
||||
|
||||
hit = new AetheriaYmirClickableHit(
|
||||
clickable,
|
||||
ToVector3(resultHit.point),
|
||||
ToVector3(resultHit.normal).normalized,
|
||||
resultHit.distance);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryOverlapZoneHulls(
|
||||
ZoneRenderer zoneRenderer,
|
||||
Entity sourceEntity,
|
||||
Vector3 center,
|
||||
float radius,
|
||||
out IReadOnlyList<AetheriaYmirOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<AetheriaYmirOverlapHit>();
|
||||
if (!EnableProjectileCutover || zoneRenderer == null || radius <= 0)
|
||||
return false;
|
||||
|
||||
_hullBodyMap.Clear();
|
||||
var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap);
|
||||
if (world.bodies.Length == 0)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleOverlapRequest
|
||||
{
|
||||
center = ToVec2(center),
|
||||
radius = radius,
|
||||
world = world
|
||||
};
|
||||
|
||||
if (!TryOverlapCircle(request, out var ymirHits))
|
||||
return false;
|
||||
|
||||
var resolved = new List<AetheriaYmirOverlapHit>();
|
||||
foreach (var hit in ymirHits)
|
||||
{
|
||||
if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull))
|
||||
continue;
|
||||
|
||||
resolved.Add(new AetheriaYmirOverlapHit(
|
||||
hull,
|
||||
new Vector3(hit.point.x, center.y, hit.point.y),
|
||||
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
|
||||
hit.penetration,
|
||||
hit.distance));
|
||||
}
|
||||
|
||||
hits = resolved;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryCastZoneHulls(
|
||||
ZoneRenderer zoneRenderer,
|
||||
Entity sourceEntity,
|
||||
Vector3 origin,
|
||||
Vector3 direction,
|
||||
float distance,
|
||||
float radius,
|
||||
out IReadOnlyList<AetheriaYmirCastHit> hits)
|
||||
{
|
||||
hits = Array.Empty<AetheriaYmirCastHit>();
|
||||
if (!EnableProjectileCutover || zoneRenderer == null || distance <= 0 || direction.sqrMagnitude <= 0.000001f)
|
||||
return false;
|
||||
|
||||
_hullBodyMap.Clear();
|
||||
var world = BuildZoneWorld(zoneRenderer, sourceEntity, _hullBodyMap);
|
||||
if (world.bodies.Length == 0)
|
||||
return false;
|
||||
|
||||
var request = new YmirCircleCastRequest
|
||||
{
|
||||
origin = ToVec2(origin),
|
||||
direction = ToVec2(direction.normalized),
|
||||
distance = distance,
|
||||
radius = Mathf.Max(0, radius),
|
||||
world = world
|
||||
};
|
||||
|
||||
if (!TryCastCircle(request, "Ymir zone cast query", out var ymirHits))
|
||||
return false;
|
||||
|
||||
var resolved = new List<AetheriaYmirCastHit>();
|
||||
foreach (var hit in ymirHits)
|
||||
{
|
||||
if (!_hullBodyMap.TryGetValue(hit.bodyId ?? "", out var hull))
|
||||
continue;
|
||||
|
||||
resolved.Add(new AetheriaYmirCastHit(
|
||||
hull,
|
||||
ResolveShield(hull),
|
||||
new Vector3(hit.point.x, origin.y, hit.point.y),
|
||||
new Vector3(hit.normal.x, 0, hit.normal.y).normalized,
|
||||
hit.distance));
|
||||
}
|
||||
|
||||
hits = resolved;
|
||||
return true;
|
||||
}
|
||||
|
||||
private YmirStepRequest BuildProjectileRequest(Projectile projectile, float deltaTime)
|
||||
{
|
||||
var transform = projectile.transform;
|
||||
_projectileBodies.Clear();
|
||||
_projectileBodies.Add(new YmirPhysicsBody
|
||||
{
|
||||
id = ProjectileBodyId,
|
||||
position = ToVec2(transform.position),
|
||||
velocity = ToVec2(projectile.Velocity),
|
||||
direction = ToVec2(transform.forward),
|
||||
angularVelocity = 0,
|
||||
torque = 0,
|
||||
momentOfInertia = 1,
|
||||
radius = Mathf.Max(ProjectileRadius, 0.001f),
|
||||
mass = Mathf.Max(ProjectileMass, 0.001f),
|
||||
isStatic = false,
|
||||
restitution = 0
|
||||
});
|
||||
|
||||
var target = projectile.TargetInstance;
|
||||
if (target == null)
|
||||
{
|
||||
return new YmirStepRequest
|
||||
{
|
||||
deltaTime = deltaTime,
|
||||
world = new YmirWorld
|
||||
{
|
||||
time = projectile.YmirWorldTime,
|
||||
bodies = _projectileBodies.ToArray(),
|
||||
fields = Array.Empty<YmirRadialField>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_projectileBodies.AddRange(BuildTargetWorld(target).bodies);
|
||||
return new YmirStepRequest
|
||||
{
|
||||
deltaTime = deltaTime,
|
||||
world = new YmirWorld
|
||||
{
|
||||
time = projectile.YmirWorldTime,
|
||||
bodies = _projectileBodies.ToArray(),
|
||||
fields = Array.Empty<YmirRadialField>()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private YmirWorld BuildTargetWorld(EntityInstance target)
|
||||
{
|
||||
_targetBodies.Clear();
|
||||
for (var i = 0; i < target.HullColliders.Length; i++)
|
||||
{
|
||||
var hull = target.HullColliders[i];
|
||||
if (hull == null)
|
||||
continue;
|
||||
|
||||
var bounds = hull.YmirBounds;
|
||||
_targetBodies.Add(new YmirPhysicsBody
|
||||
{
|
||||
id = TargetBodyPrefix + i,
|
||||
position = ToVec2(bounds.center),
|
||||
velocity = default,
|
||||
direction = ToVec2(hull.transform.forward),
|
||||
angularVelocity = 0,
|
||||
torque = 0,
|
||||
momentOfInertia = 1,
|
||||
radius = hull.YmirPlanarRadius + TargetRadiusPadding,
|
||||
mass = 1,
|
||||
isStatic = true,
|
||||
restitution = 0
|
||||
});
|
||||
}
|
||||
|
||||
return new YmirWorld
|
||||
{
|
||||
time = 0,
|
||||
bodies = _targetBodies.ToArray(),
|
||||
fields = Array.Empty<YmirRadialField>()
|
||||
};
|
||||
}
|
||||
|
||||
private YmirWorld BuildZoneWorld(
|
||||
ZoneRenderer zoneRenderer,
|
||||
Entity sourceEntity,
|
||||
Dictionary<string, HullCollider> bodyMap)
|
||||
{
|
||||
_zoneBodies.Clear();
|
||||
var entityIndex = 0;
|
||||
foreach (var entityInstance in zoneRenderer.EntityInstances.Values)
|
||||
{
|
||||
if (entityInstance == null || entityInstance.Entity == sourceEntity)
|
||||
continue;
|
||||
|
||||
for (var hullIndex = 0; hullIndex < entityInstance.HullColliders.Length; hullIndex++)
|
||||
{
|
||||
var hull = entityInstance.HullColliders[hullIndex];
|
||||
if (hull == null)
|
||||
continue;
|
||||
|
||||
var bounds = hull.YmirBounds;
|
||||
var bodyId = $"aetheria.entity.{entityIndex}.hull.{hullIndex}";
|
||||
bodyMap[bodyId] = hull;
|
||||
_zoneBodies.Add(new YmirPhysicsBody
|
||||
{
|
||||
id = bodyId,
|
||||
position = ToVec2(bounds.center),
|
||||
velocity = default,
|
||||
direction = ToVec2(hull.transform.forward),
|
||||
angularVelocity = 0,
|
||||
torque = 0,
|
||||
momentOfInertia = 1,
|
||||
radius = hull.YmirPlanarRadius + TargetRadiusPadding,
|
||||
mass = 1,
|
||||
isStatic = true,
|
||||
restitution = 0
|
||||
});
|
||||
}
|
||||
|
||||
entityIndex++;
|
||||
}
|
||||
|
||||
return new YmirWorld
|
||||
{
|
||||
time = 0,
|
||||
bodies = _zoneBodies.ToArray(),
|
||||
fields = Array.Empty<YmirRadialField>()
|
||||
};
|
||||
}
|
||||
|
||||
private bool TryBuildDaemonWorld(out YmirWorld world)
|
||||
{
|
||||
world = null;
|
||||
var observer = ResolveDaemonObserver();
|
||||
if (observer == null || !observer.HasRenderNativeView)
|
||||
return false;
|
||||
|
||||
var view = observer.LastRenderNativeView;
|
||||
if (!view.IsCreated || !view.HasPhysicsRadius)
|
||||
return false;
|
||||
|
||||
_daemonBodies.Clear();
|
||||
for (var i = 0; i < view.Count; i++)
|
||||
{
|
||||
var radius = view.PhysicsBodyRadius[i];
|
||||
if (radius <= 0)
|
||||
continue;
|
||||
|
||||
var inverseMass = view.HasPhysicsInverseMass ? view.PhysicsBodyInverseMass[i] : 1.0f;
|
||||
var mass = view.HasPhysicsMass && view.PhysicsBodyMass[i] > 0
|
||||
? view.PhysicsBodyMass[i]
|
||||
: inverseMass > 0
|
||||
? 1.0f / inverseMass
|
||||
: 1.0f;
|
||||
var rotation = view.HasRotation ? view.RotationRadians[i] : 0;
|
||||
|
||||
_daemonBodies.Add(new YmirPhysicsBody
|
||||
{
|
||||
id = $"aetheria.daemon.body.{i}",
|
||||
position = new YmirVec2 { x = view.PositionX[i], y = view.PositionZ[i] },
|
||||
velocity = view.HasVelocity
|
||||
? new YmirVec2 { x = view.VelocityX[i], y = view.VelocityZ[i] }
|
||||
: default,
|
||||
direction = new YmirVec2 { x = Mathf.Sin(rotation), y = Mathf.Cos(rotation) },
|
||||
angularVelocity = 0,
|
||||
torque = 0,
|
||||
momentOfInertia = 1,
|
||||
radius = radius,
|
||||
mass = Mathf.Max(mass, 0.001f),
|
||||
isStatic = view.HasPhysicsInverseMass && inverseMass <= 0,
|
||||
restitution = 0
|
||||
});
|
||||
}
|
||||
|
||||
if (_daemonBodies.Count == 0)
|
||||
return false;
|
||||
|
||||
world = new YmirWorld
|
||||
{
|
||||
time = Time.time,
|
||||
bodies = _daemonBodies.ToArray(),
|
||||
fields = Array.Empty<YmirRadialField>()
|
||||
};
|
||||
return true;
|
||||
}
|
||||
|
||||
private AetheriaDaemonObserver ResolveDaemonObserver()
|
||||
{
|
||||
if (DaemonObserver != null)
|
||||
return DaemonObserver;
|
||||
|
||||
DaemonObserver = FindAnyObjectByType<AetheriaDaemonObserver>();
|
||||
return DaemonObserver;
|
||||
}
|
||||
|
||||
private bool TryOverlapSphere(
|
||||
YmirSphereQueryBody[] bodies,
|
||||
Vector3 center,
|
||||
float radius,
|
||||
string failureContext,
|
||||
out IReadOnlyList<YmirSphereOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirSphereOverlapHit>();
|
||||
if (!EnableProjectileCutover || bodies == null || bodies.Length == 0 || radius <= 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var result = PostJson<YmirSphereOverlapRequest, YmirSphereOverlapResult>(OverlapSphereUrl, new YmirSphereOverlapRequest
|
||||
{
|
||||
center = ToVec3(center),
|
||||
radius = radius,
|
||||
bodies = bodies
|
||||
});
|
||||
hits = result.hits ?? Array.Empty<YmirSphereOverlapHit>();
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Debug.LogWarning($"{failureContext} failed: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCastSphere(
|
||||
YmirSphereCastRequest request,
|
||||
string failureContext,
|
||||
out IReadOnlyList<YmirSphereCastHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirSphereCastHit>();
|
||||
if (!EnableProjectileCutover || request == null || request.bodies == null || request.bodies.Length == 0 || request.distance <= 0 || request.radius < 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var result = PostJson<YmirSphereCastRequest, YmirSphereCastResult>(CastSphereUrl, request);
|
||||
hits = result.hits ?? Array.Empty<YmirSphereCastHit>();
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Debug.LogWarning($"{failureContext} failed: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryOverlapCircle(
|
||||
YmirCircleOverlapRequest request,
|
||||
out IReadOnlyList<YmirCircleOverlapHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirCircleOverlapHit>();
|
||||
if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.radius <= 0)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(OverlapCircleUrl))
|
||||
{
|
||||
Debug.LogWarning("Ymir circle overlap query failed: daemon URL is not configured.");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var result = PostJson<YmirCircleOverlapRequest, YmirCircleOverlapResult>(OverlapCircleUrl, request);
|
||||
hits = result.hits ?? Array.Empty<YmirCircleOverlapHit>();
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Debug.LogWarning($"Ymir circle overlap query failed: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryCastCircle(
|
||||
YmirCircleCastRequest request,
|
||||
string failureContext,
|
||||
out IReadOnlyList<YmirCircleCastHit> hits)
|
||||
{
|
||||
hits = Array.Empty<YmirCircleCastHit>();
|
||||
if (!EnableProjectileCutover || request == null || request.world == null || request.world.bodies == null || request.world.bodies.Length == 0 || request.distance <= 0 || request.radius < 0)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
var result = PostJson<YmirCircleCastRequest, YmirCircleCastResult>(CastCircleUrl, request);
|
||||
hits = result.hits ?? Array.Empty<YmirCircleCastHit>();
|
||||
return true;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
Debug.LogWarning($"{failureContext} failed: {error.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static YmirSphereQueryBody[] CopySphereQueryBodies(IReadOnlyList<YmirSphereQueryBody> bodies)
|
||||
{
|
||||
var copy = new YmirSphereQueryBody[bodies.Count];
|
||||
for (var i = 0; i < copy.Length; i++)
|
||||
copy[i] = bodies[i];
|
||||
return copy;
|
||||
}
|
||||
|
||||
private TResult PostJson<TRequest, TResult>(string url, TRequest request)
|
||||
where TResult : class
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url))
|
||||
throw new InvalidOperationException("Ymir daemon URL is not configured.");
|
||||
|
||||
var json = JsonUtility.ToJson(request);
|
||||
var bytes = Encoding.UTF8.GetBytes(json);
|
||||
var http = (HttpWebRequest)WebRequest.Create(url);
|
||||
http.Method = "POST";
|
||||
http.ContentType = "application/json; charset=utf-8";
|
||||
http.ContentLength = bytes.Length;
|
||||
http.Timeout = TimeoutMilliseconds;
|
||||
http.ReadWriteTimeout = TimeoutMilliseconds;
|
||||
|
||||
using (var stream = http.GetRequestStream())
|
||||
stream.Write(bytes, 0, bytes.Length);
|
||||
|
||||
using var response = (HttpWebResponse)http.GetResponse();
|
||||
using var reader = new StreamReader(response.GetResponseStream());
|
||||
var payload = reader.ReadToEnd();
|
||||
if (string.IsNullOrWhiteSpace(payload))
|
||||
throw new InvalidDataException("Ymir daemon returned an empty response.");
|
||||
|
||||
var result = JsonUtility.FromJson<TResult>(payload);
|
||||
if (result == null)
|
||||
throw new InvalidDataException("Ymir daemon returned a response that could not be parsed.");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static YmirPhysicsBody FindBody(YmirWorld world, string bodyId)
|
||||
{
|
||||
foreach (var body in world.bodies ?? Array.Empty<YmirPhysicsBody>())
|
||||
{
|
||||
if (string.Equals(body.id, bodyId, StringComparison.Ordinal))
|
||||
return body;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HullCollider ResolveTargetHull(EntityInstance target, string bodyId)
|
||||
{
|
||||
if (!bodyId.StartsWith(TargetBodyPrefix, StringComparison.Ordinal))
|
||||
return null;
|
||||
|
||||
return int.TryParse(bodyId.Substring(TargetBodyPrefix.Length), out var index) &&
|
||||
index >= 0 &&
|
||||
index < target.HullColliders.Length
|
||||
? target.HullColliders[index]
|
||||
: null;
|
||||
}
|
||||
|
||||
private static ShieldManager ResolveShield(HullCollider hull)
|
||||
{
|
||||
var instance = hull != null ? hull.GetComponentInParent<EntityInstance>() : null;
|
||||
return instance != null ? instance.Shield : null;
|
||||
}
|
||||
|
||||
private static YmirVec2 ToVec2(Vector3 value) => new YmirVec2 { x = value.x, y = value.z };
|
||||
|
||||
private static YmirVec2 ToVec2(CultMath.float2 value) => new YmirVec2 { x = value.x, y = value.y };
|
||||
|
||||
private static YmirVec3 ToVec3(Vector3 value) => new YmirVec3 { x = value.x, y = value.y, z = value.z };
|
||||
|
||||
private static Vector3 ToVector3(YmirVec3 value) => new Vector3(value.x, value.y, value.z);
|
||||
|
||||
private static Bounds ObjectBounds(GameObject gameObject, Vector3 fallbackCenter)
|
||||
{
|
||||
var renderers = gameObject.GetComponentsInChildren<Renderer>();
|
||||
if (renderers.Length == 0)
|
||||
return new Bounds(fallbackCenter, Vector3.one);
|
||||
|
||||
var bounds = renderers[0].bounds;
|
||||
for (var i = 1; i < renderers.Length; i++)
|
||||
bounds.Encapsulate(renderers[i].bounds);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
}
|
||||
|
||||
public struct AetheriaYmirProjectileStep
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Vector3 Velocity;
|
||||
public AetheriaYmirProjectileHit Hit;
|
||||
public bool HasHit => Hit.Hull != null;
|
||||
}
|
||||
|
||||
public readonly struct AetheriaYmirProjectileHit
|
||||
{
|
||||
public AetheriaYmirProjectileHit(HullCollider hull, Vector3 point, Vector3 normal)
|
||||
{
|
||||
Hull = hull;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
}
|
||||
|
||||
public HullCollider Hull { get; }
|
||||
public Vector3 Point { get; }
|
||||
public Vector3 Normal { get; }
|
||||
}
|
||||
|
||||
public readonly struct AetheriaYmirOverlapHit
|
||||
{
|
||||
public AetheriaYmirOverlapHit(HullCollider hull, Vector3 point, Vector3 normal, float penetration, float distance)
|
||||
{
|
||||
Hull = hull;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
Penetration = penetration;
|
||||
Distance = distance;
|
||||
}
|
||||
|
||||
public HullCollider Hull { get; }
|
||||
public Vector3 Point { get; }
|
||||
public Vector3 Normal { get; }
|
||||
public float Penetration { get; }
|
||||
public float Distance { get; }
|
||||
}
|
||||
|
||||
public readonly struct AetheriaYmirCastHit
|
||||
{
|
||||
public AetheriaYmirCastHit(HullCollider hull, ShieldManager shield, Vector3 point, Vector3 normal, float distance)
|
||||
{
|
||||
Hull = hull;
|
||||
Shield = shield;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
Distance = distance;
|
||||
}
|
||||
|
||||
public HullCollider Hull { get; }
|
||||
public ShieldManager Shield { get; }
|
||||
public Vector3 Point { get; }
|
||||
public Vector3 Normal { get; }
|
||||
public float Distance { get; }
|
||||
}
|
||||
|
||||
public readonly struct AetheriaYmirClickableHit
|
||||
{
|
||||
public AetheriaYmirClickableHit(ClickableCollider clickable, Vector3 point, Vector3 normal, float distance)
|
||||
{
|
||||
Clickable = clickable;
|
||||
Point = point;
|
||||
Normal = normal;
|
||||
Distance = distance;
|
||||
}
|
||||
|
||||
public ClickableCollider Clickable { get; }
|
||||
public Vector3 Point { get; }
|
||||
public Vector3 Normal { get; }
|
||||
public float Distance { get; }
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 8d2521ff2dd1835498a2c00656d4bff6
|
||||
|
|
@ -1,79 +1,42 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using UnityEngine;
|
||||
|
||||
public class ShieldManager : MonoBehaviour
|
||||
{
|
||||
public Prototype ShieldPrototype;
|
||||
public float CollisionHitDuration = 3;
|
||||
public float LootInteractionCooldown = 0.25f;
|
||||
public float ShieldCollisionCooldown = 0.1f;
|
||||
|
||||
public Entity Entity { get; set; }
|
||||
|
||||
private void OnCollisionEnter(Collision other)
|
||||
private bool IsShieldActive()
|
||||
{
|
||||
var otherShield = other.collider.GetComponent<ShieldManager>();
|
||||
if (!otherShield)
|
||||
{
|
||||
var gridObject = other.collider.GetComponent<GridObject>();
|
||||
if (gridObject)
|
||||
{
|
||||
var itemPickup = other.collider.GetComponent<ItemPickup>();
|
||||
var mine = other.collider.GetComponent<Mine>();
|
||||
if (itemPickup)
|
||||
{
|
||||
if (ActionGameManager.Instance != null &&
|
||||
ActionGameManager.Instance.CommitLootPickup(Entity, itemPickup.Item))
|
||||
{
|
||||
// TODO: Pickup notification!
|
||||
Destroy(itemPickup.gameObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pickup failed notification!
|
||||
var cp = other.GetContact(0);
|
||||
gridObject.Velocity += cp.normal * 25;
|
||||
Debug.Log("Attempted item pickup, but no space in cargo bay!");
|
||||
}
|
||||
}
|
||||
else if (mine)
|
||||
{
|
||||
mine.Explode();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Shield collision occurred with grid object, but other collider isn't a mine or an item pickup!");
|
||||
}
|
||||
}
|
||||
else
|
||||
Debug.Log("Shield collision occurred, but other collider isn't a shield or a grid object!");
|
||||
return;
|
||||
}
|
||||
var contact = other.GetContact(0);
|
||||
var normal = normalize(float2(contact.normal.x, contact.normal.z));
|
||||
var tangent = normal.Rotate(ItemRotation.CounterClockwise);
|
||||
var v1n = dot(normal, Entity.Velocity);
|
||||
var v1t = dot(tangent, Entity.Velocity);
|
||||
var v2n = dot(normal, otherShield.Entity.Velocity);
|
||||
//var v2t = dot(tangent, otherShield.Entity.Velocity);
|
||||
|
||||
var v1np = PostCollisionVelocity(v1n, Entity.Mass, v2n, otherShield.Entity.Mass);
|
||||
Entity.Velocity = tangent * v1t + normal * v1np;
|
||||
if(Entity.Shield != null && Entity.Shield.Item.Active.Value) ShowHit(contact.point, CollisionHitDuration);
|
||||
return Entity?.Shield != null && Entity.Shield.Item.Active.Value;
|
||||
}
|
||||
|
||||
private float PostCollisionVelocity(float v1, float m1, float v2, float m2)
|
||||
private Bounds ShieldBounds()
|
||||
{
|
||||
return (v1 * (m1 - m2) + m2 * v2) / (m1 + m2);
|
||||
var renderers = GetComponentsInChildren<Renderer>();
|
||||
if (renderers.Length == 0)
|
||||
return new Bounds(transform.position, Vector3.one);
|
||||
|
||||
var bounds = renderers[0].bounds;
|
||||
for (var i = 1; i < renderers.Length; i++)
|
||||
bounds.Encapsulate(renderers[i].bounds);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private static float ShieldPlanarRadius(Bounds bounds)
|
||||
{
|
||||
return Mathf.Max(bounds.extents.x, bounds.extents.z, 0.001f);
|
||||
}
|
||||
|
||||
public void ShowHit(Vector3 point, float duration)
|
||||
{
|
||||
var shield = ShieldPrototype.Instantiate<ShieldAnimation>();
|
||||
shield.Direction = normalize(shield.transform.InverseTransformPoint(point));
|
||||
shield.Direction = shield.transform.InverseTransformPoint(point).normalized;
|
||||
shield.Duration = duration;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class ShieldTest : MonoBehaviour
|
||||
{
|
||||
|
|
@ -12,12 +8,13 @@ public class ShieldTest : MonoBehaviour
|
|||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
_velocity += -sign(transform.position.x) * Acceleration * Time.deltaTime;
|
||||
var previousX = transform.position.x;
|
||||
_velocity += -Mathf.Sign(transform.position.x) * Acceleration * Time.deltaTime;
|
||||
transform.position += Vector3.right * (_velocity * Time.deltaTime);
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision other)
|
||||
{
|
||||
_velocity = -_velocity;
|
||||
if (Mathf.Sign(transform.position.x) != Mathf.Sign(previousX))
|
||||
{
|
||||
_velocity = -_velocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using GameCult.Aetheria.State.Unity;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class ShipInstance : EntityInstance
|
||||
{
|
||||
|
|
@ -108,11 +105,11 @@ public class ShipInstance : EntityInstance
|
|||
base.Update();
|
||||
|
||||
TractorBeam.Power = Entity.TractorPower;
|
||||
TractorBeam.Direction = Entity.LookDirection;
|
||||
TractorBeam.Direction = (Vector3)AetheriaMath.ToUnity(Entity.CultLookDirection);
|
||||
|
||||
if (_aetherDrive != null)
|
||||
{
|
||||
var thrust = length(_aetherDrive.Drive.ThrustDirection);
|
||||
var thrust = CultMath.math.length(_aetherDrive.Drive.ThrustDirection);
|
||||
var forceOverLifetime = _aetherDrive.Particles.forceOverLifetime;
|
||||
forceOverLifetime.xMultiplier = _aetherDrive.Drive.ThrustDirection.x * _aetherDrive.BaseForce;
|
||||
forceOverLifetime.zMultiplier = _aetherDrive.Drive.ThrustDirection.y * _aetherDrive.BaseForce;
|
||||
|
|
@ -128,7 +125,8 @@ public class ShipInstance : EntityInstance
|
|||
emissionModule.rateOverTimeMultiplier = thrusterInstance.BaseEmission * thrusterInstance.Thruster.Axis * (item.Durability / GetMaxDurability(item));
|
||||
}
|
||||
|
||||
transform.rotation = Ship.Rotation;
|
||||
var rotation = Ship.Rotation;
|
||||
transform.rotation = new Quaternion(rotation.x, rotation.y, rotation.z, rotation.w);
|
||||
}
|
||||
|
||||
private float GetMaxDurability(ItemInstance item)
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
public class TimedDestroy : MonoBehaviour
|
||||
{
|
||||
public float Duration;
|
||||
|
||||
private float _startTime;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_startTime = Time.time;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if(Time.time - _startTime > Duration) Destroy(gameObject);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 5d5539a23b814254b038a16f32f1d8a9
|
||||
timeCreated: 1618453298
|
||||
|
|
@ -15,19 +15,6 @@ public class TractorBeam : MonoBehaviour
|
|||
{
|
||||
transform.rotation = Quaternion.LookRotation(Direction);
|
||||
var emission = ParticleSystem.emission;
|
||||
if (Power > .01f)
|
||||
{
|
||||
var objectFound = false;
|
||||
foreach (var o in Physics.SphereCastAll(transform.position, Radius, Direction, Distance, 1 << 21))
|
||||
{
|
||||
var gridObject = o.collider.GetComponent<GridObject>();
|
||||
if (gridObject == null) continue;
|
||||
objectFound = true;
|
||||
gridObject.Velocity += Vector3.Normalize(transform.position - gridObject.transform.position) * (Traction * Time.deltaTime);
|
||||
}
|
||||
|
||||
emission.rateOverTimeMultiplier = objectFound ? Power : 0;
|
||||
}
|
||||
else emission.rateOverTimeMultiplier = 0;
|
||||
emission.rateOverTimeMultiplier = Power > .01f ? Power : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class ConstantLaser : MonoBehaviour
|
||||
{
|
||||
|
|
@ -70,34 +67,34 @@ public class ConstantLaser : MonoBehaviour
|
|||
}
|
||||
|
||||
LineRenderer.SetPosition(0, transform.position);
|
||||
bool hitFound = false;
|
||||
foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17)))
|
||||
var hitFound = false;
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
ActionGameManager.Instance?.ZoneRenderer,
|
||||
SourceEntity,
|
||||
transform.position,
|
||||
transform.forward,
|
||||
Range,
|
||||
0,
|
||||
out var hits))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield)
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
|
||||
if (shield.Entity != SourceEntity)
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
LineRenderer.SetPosition(1, hit.point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
if (hull.Entity != SourceEntity)
|
||||
{
|
||||
hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
|
||||
LineRenderer.SetPosition(1, hit.point);
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
LineRenderer.SetPosition(1, hit.Point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
LineRenderer.SetPosition(1, hit.Point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hitFound)
|
||||
LineRenderer.SetPosition(1, transform.position + transform.forward * Range);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class ConstantLightning : MonoBehaviour
|
||||
{
|
||||
|
|
@ -51,28 +48,34 @@ public class ConstantLightning : MonoBehaviour
|
|||
}
|
||||
|
||||
Lightning.FixedEndpoint = false;
|
||||
var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17));
|
||||
foreach (var hit in hits)
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
Source?.ZoneRenderer,
|
||||
Source?.Entity,
|
||||
Barrel.position,
|
||||
Barrel.forward,
|
||||
Range,
|
||||
HitRadius,
|
||||
out var hits))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (shield.Entity == Source.Entity) continue;
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage * Time.deltaTime);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
if (hull.Entity == Source.Entity) continue;
|
||||
hull.SendHit(Damage * Time.deltaTime, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward);
|
||||
}
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
Lightning.FixedEndpoint = true;
|
||||
Lightning.EndPosition = hit.point;
|
||||
Lightning.FixedEndpoint = true;
|
||||
Lightning.EndPosition = hit.Point;
|
||||
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!Lightning.FixedEndpoint)
|
||||
Lightning.EndPosition = Barrel.position + Barrel.forward * Range;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,32 +1,26 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
public class ConstantParticleWeapon : MonoBehaviour
|
||||
{
|
||||
public ParticleSystem[] Particles;
|
||||
public float CastRadius = 0.25f;
|
||||
|
||||
public float Damage { get; set; }
|
||||
public float Range { get; set; }
|
||||
public DamageType DamageType { get; set; }
|
||||
public EntityInstance Source { get; set; }
|
||||
public EntityInstance Target { get; set; }
|
||||
|
||||
private bool _stopping;
|
||||
private float _emission;
|
||||
private List<ParticleSystem.Particle> _collisionParticles = new List<ParticleSystem.Particle>();
|
||||
private HullCollider _hull;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_emission = Particles[0].emission.rateOverTime.constant;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
foreach(var p in Particles)
|
||||
p.enableEmission = true;
|
||||
{
|
||||
var emission = p.emission;
|
||||
emission.enabled = true;
|
||||
}
|
||||
_stopping = false;
|
||||
}
|
||||
|
||||
|
|
@ -38,18 +32,6 @@ public class ConstantParticleWeapon : MonoBehaviour
|
|||
main.simulationSpace = ParticleSystemSimulationSpace.Custom;
|
||||
main.customSimulationSpace = Source.LocalSpace;
|
||||
}
|
||||
|
||||
var trigger = Particles[0].trigger;
|
||||
while(trigger.colliderCount > 0)
|
||||
trigger.RemoveCollider(0);
|
||||
|
||||
if (Target == null) return;
|
||||
|
||||
foreach (var collider in Target.HullColliders)
|
||||
{
|
||||
_hull = collider;
|
||||
trigger.AddCollider(collider.GetComponent<Collider>());
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
|
|
@ -60,27 +42,32 @@ public class ConstantParticleWeapon : MonoBehaviour
|
|||
GetComponent<Prototype>().ReturnToPool();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnParticleTrigger()
|
||||
{
|
||||
if (Target == null || !_hull) return;
|
||||
_collisionParticles.Clear();
|
||||
Particles[0].GetTriggerParticles(ParticleSystemTriggerEventType.Enter, _collisionParticles);
|
||||
if(_collisionParticles.Count > 0)
|
||||
if (_stopping || Target == null || Range <= 0)
|
||||
return;
|
||||
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastTargetHulls(
|
||||
Target,
|
||||
transform.position,
|
||||
transform.forward,
|
||||
Range,
|
||||
CastRadius,
|
||||
out var hits))
|
||||
{
|
||||
_hull.SendSplash(
|
||||
Damage * Time.deltaTime,
|
||||
DamageType,
|
||||
Source.Entity,
|
||||
(transform.position - Target.transform.position).normalized);
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
foreach(var p in Particles)
|
||||
p.enableEmission = false;
|
||||
{
|
||||
var emission = p.emission;
|
||||
emission.enabled = false;
|
||||
}
|
||||
_stopping = true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ public class ConstantParticleWeaponManager : ConstantWeaponEffectManager
|
|||
t.position = barrel.position;
|
||||
p.Damage = weapon.Damage;
|
||||
p.DamageType = weapon.DamageType;
|
||||
p.Range = weapon.Range;
|
||||
_weapons.Add(item, p);
|
||||
p.Initialize();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static Unity.Mathematics.noise;
|
||||
using Random = UnityEngine.Random;
|
||||
using static Noise1D;
|
||||
using float3 = Unity.Mathematics.float3;
|
||||
|
||||
public class GuidedProjectile : MonoBehaviour
|
||||
{
|
||||
|
|
@ -36,7 +31,7 @@ public class GuidedProjectile : MonoBehaviour
|
|||
private Vector3 _previousTargetPosition;
|
||||
|
||||
public Transform Target { get; set; }
|
||||
public float3 StartPosition { get; set; }
|
||||
public Vector3 StartPosition { get; set; }
|
||||
public float Range { get; set; }
|
||||
public Vector3 Velocity { get; set; }
|
||||
public float Damage { get; set; }
|
||||
|
|
@ -52,9 +47,7 @@ public class GuidedProjectile : MonoBehaviour
|
|||
_active = _alive = true;
|
||||
_phase = Random.value * 100;
|
||||
_prevDist = Single.MaxValue;
|
||||
Particles.startColor = Color.white;
|
||||
//var main = Particles.main;
|
||||
//main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white };
|
||||
SetParticleColor(Color.white);
|
||||
Particles.Clear(true);
|
||||
Particles.Play(true);
|
||||
}
|
||||
|
|
@ -72,18 +65,18 @@ public class GuidedProjectile : MonoBehaviour
|
|||
StartCoroutine(FadeOut());
|
||||
return;
|
||||
}
|
||||
var position = (float3) t.position;
|
||||
var position = t.position;
|
||||
|
||||
var targetPosition = TargetPosition?.Invoke() ?? Target.position;
|
||||
_targetVelocity = lerp(_targetVelocity, targetPosition - _previousTargetPosition, saturate(Time.deltaTime * 5));
|
||||
_targetVelocity = Vector3.Lerp(_targetVelocity, targetPosition - _previousTargetPosition, Mathf.Clamp01(Time.deltaTime * 5));
|
||||
_previousTargetPosition = targetPosition;
|
||||
targetPosition = AetheriaMath.FirstOrderIntercept(position,float3.zero, TopSpeed, targetPosition, _targetVelocity);
|
||||
targetPosition = AetheriaMath.FirstOrderIntercept(position, Vector3.zero, TopSpeed, targetPosition, _targetVelocity);
|
||||
|
||||
var diff = targetPosition - transform.position;
|
||||
var targetDist = diff.magnitude;
|
||||
var sourceDist = length(StartPosition.xz - position.xz);
|
||||
var sourceDist = Vector2.Distance(new Vector2(StartPosition.x, StartPosition.z), new Vector2(position.x, position.z));
|
||||
|
||||
if (sourceDist > Range || dot(diff,Velocity) < 0)
|
||||
if (sourceDist > Range || Vector3.Dot(diff, Velocity) < 0)
|
||||
{
|
||||
StartCoroutine(FadeOut());
|
||||
if (HitEffect != null)
|
||||
|
|
@ -98,8 +91,8 @@ public class GuidedProjectile : MonoBehaviour
|
|||
var targetDistFlat = diff.Flatland().magnitude;
|
||||
var curveLerp = 1 - targetDistFlat / (sourceDist + targetDistFlat);
|
||||
var dir = diff.normalized;
|
||||
var right = cross(dir, float3(0, 1, 0));
|
||||
var up = cross(dir, right);
|
||||
var right = Vector3.Cross(dir, Vector3.up);
|
||||
var up = Vector3.Cross(dir, right);
|
||||
|
||||
if (Children > 0 && SplitTime < curveLerp)
|
||||
{
|
||||
|
|
@ -108,9 +101,9 @@ public class GuidedProjectile : MonoBehaviour
|
|||
var child = ChildProjectile.Instantiate<GuidedProjectile>();
|
||||
child.transform.position = t.position;
|
||||
child.StartPosition = StartPosition;
|
||||
var randomDirection = normalize(Random.insideUnitCircle);
|
||||
var randomDirection = Random.insideUnitCircle.normalized;
|
||||
var perpendicularRandom = randomDirection.x * right + randomDirection.y * up;
|
||||
child.Velocity = normalize(lerp(perpendicularRandom, dir, SplitSeparationForwardness)) * length(Velocity) * SplitSeparationVelocity;
|
||||
child.Velocity = Vector3.Lerp(perpendicularRandom, dir, SplitSeparationForwardness).normalized * Velocity.magnitude * SplitSeparationVelocity;
|
||||
child.Range = Range;
|
||||
child.Damage = Damage / Children;
|
||||
child.Penetration = Penetration;
|
||||
|
|
@ -135,56 +128,53 @@ public class GuidedProjectile : MonoBehaviour
|
|||
}
|
||||
}
|
||||
|
||||
var dodge = normalize(lerp(
|
||||
normalize(right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))),
|
||||
Vector3.up, LiftCurve.Evaluate(curveLerp)));
|
||||
var dodge = Vector3.Lerp(
|
||||
(right * noise(Time.time * Frequency + _phase) + up * noise(Time.time * Frequency + (100 + _phase))).normalized,
|
||||
Vector3.up,
|
||||
LiftCurve.Evaluate(curveLerp)).normalized;
|
||||
var desired = Vector3.Slerp(dodge, dir, GuidanceCurve.Evaluate(curveLerp)).normalized * TopSpeed;
|
||||
var thrustCurve = ThrustCurve.Evaluate(curveLerp);
|
||||
var thrust = Thrust * thrustCurve;
|
||||
var c = Color.white * thrustCurve;
|
||||
c.a = 1;
|
||||
Particles.startColor = c;
|
||||
SetParticleColor(c);
|
||||
Velocity += (desired-Velocity).normalized * (thrust * Time.deltaTime);
|
||||
}
|
||||
|
||||
if(_alive)
|
||||
{
|
||||
var ray = new Ray(t.position, Velocity);
|
||||
foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17)))
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
ActionGameManager.Instance?.ZoneRenderer,
|
||||
SourceEntity,
|
||||
t.position,
|
||||
Velocity,
|
||||
Velocity.magnitude * Time.deltaTime,
|
||||
0,
|
||||
out var hits) &&
|
||||
hits.Count > 0)
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield)
|
||||
var hit = hits[0];
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
|
||||
if (shield.Entity != SourceEntity)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
}
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
else
|
||||
{
|
||||
if (hull.Entity != SourceEntity)
|
||||
{
|
||||
hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
|
||||
transform.position = hit.point;
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
}
|
||||
else// if (hit.transform.gameObject.layer == 1)
|
||||
{
|
||||
StartCoroutine(Kill());
|
||||
return;
|
||||
}
|
||||
|
||||
transform.position = hit.Point;
|
||||
StartCoroutine(Kill());
|
||||
|
||||
if (HitEffect != null)
|
||||
{
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.SetParent(hit.collider.transform);
|
||||
ht.position = hit.point;
|
||||
return;
|
||||
ht.SetParent(hull.transform);
|
||||
ht.position = hit.Point;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
t.position += Velocity * Time.deltaTime;
|
||||
|
|
@ -200,15 +190,19 @@ public class GuidedProjectile : MonoBehaviour
|
|||
var lerp = 1 - (Time.time - startTime) / FadeOutTime;
|
||||
var c = Color.white * lerp;
|
||||
c.a = 1;
|
||||
Particles.startColor = c;
|
||||
//var main = Particles.main;
|
||||
//main.startColor = new ParticleSystem.MinMaxGradient { mode = ParticleSystemGradientMode.Color, color = Color.white * lerp };
|
||||
SetParticleColor(c);
|
||||
yield return null;
|
||||
}
|
||||
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
|
||||
private void SetParticleColor(Color color)
|
||||
{
|
||||
var main = Particles.main;
|
||||
main.startColor = color;
|
||||
}
|
||||
|
||||
IEnumerator Kill()
|
||||
{
|
||||
_active = false;
|
||||
|
|
@ -223,4 +217,4 @@ public class GuidedProjectile : MonoBehaviour
|
|||
OnKill?.Invoke();
|
||||
GetComponent<Prototype>().ReturnToPool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UniRx;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
using cfloat3 = CultMath.float3;
|
||||
|
||||
public class GuidedProjectileManager : InstantWeaponEffectManager
|
||||
{
|
||||
|
|
@ -64,7 +63,13 @@ public class GuidedProjectileManager : InstantWeaponEffectManager
|
|||
p.Velocity = barrel.forward * weapon.Velocity;
|
||||
p.Thrust = weapon.EvaluateGuidedProjectileThrust();
|
||||
p.TopSpeed = weapon.EvaluateGuidedProjectileVelocity();
|
||||
p.TargetPosition = () => source.Entity.Position + length( (float3)source.LookAtPoint.position - source.Entity.Position) * source.Entity.LookDirection;
|
||||
p.TargetPosition = () =>
|
||||
{
|
||||
var lookPosition = source.LookAtPoint.position;
|
||||
var lookPoint = new cfloat3(lookPosition.x, lookPosition.y, lookPosition.z);
|
||||
var lookDistance = CultMath.math.length(lookPoint - source.Entity.CultPosition);
|
||||
return (Vector3)AetheriaMath.ToUnity(source.Entity.CultPosition + lookDistance * source.Entity.CultLookDirection);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class HitscanEffect : MonoBehaviour
|
||||
{
|
||||
|
|
@ -27,45 +24,47 @@ public class HitscanEffect : MonoBehaviour
|
|||
{
|
||||
_startTime = Time.time;
|
||||
var hitFound = false;
|
||||
var ray = new Ray(transform.position, transform.forward);
|
||||
foreach (var hit in Physics.RaycastAll(ray, Range, 1 | (1 << 17)))
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
ActionGameManager.Instance?.ZoneRenderer,
|
||||
SourceEntity,
|
||||
transform.position,
|
||||
transform.forward,
|
||||
Range,
|
||||
0,
|
||||
out var hits))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield)
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
|
||||
if (shield.Entity != SourceEntity)
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
hitFound = true;
|
||||
}
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull)
|
||||
{
|
||||
if (hull.Entity != SourceEntity)
|
||||
else
|
||||
{
|
||||
hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
|
||||
hitFound = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hitFound && HitEffect != null)
|
||||
{
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.SetParent(hit.collider.transform);
|
||||
ht.position = hit.point;
|
||||
if (hitFound && HitEffect != null)
|
||||
{
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.SetParent(hull.transform);
|
||||
ht.position = hit.Point;
|
||||
}
|
||||
|
||||
var length = (hit.Point - transform.position).magnitude;
|
||||
Line.SetPosition(1, Vector3.forward * length);
|
||||
var emission = LineEffect.emission;
|
||||
emission.rateOverTimeMultiplier = length;
|
||||
var shape = LineEffect.shape;
|
||||
shape.position = Vector3.forward * (length / 2);
|
||||
shape.scale = Vector3.one * (length / 2);
|
||||
break;
|
||||
}
|
||||
|
||||
var length = (hit.point - transform.position).magnitude;
|
||||
Line.SetPosition(1, Vector3.forward * length);
|
||||
var emission = LineEffect.emission;
|
||||
emission.rateOverTimeMultiplier = length;
|
||||
var shape = LineEffect.shape;
|
||||
shape.position = Vector3.forward * (length / 2);
|
||||
shape.scale = Vector3.one * (length / 2);
|
||||
}
|
||||
|
||||
if(!hitFound)
|
||||
{
|
||||
Line.SetPosition(1, Vector3.forward * Range);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Laser : MonoBehaviour
|
||||
{
|
||||
|
|
@ -36,34 +34,34 @@ public class Laser : MonoBehaviour
|
|||
}
|
||||
|
||||
LineRenderer.SetPosition(0, transform.position);
|
||||
bool hitFound = false;
|
||||
foreach (var hit in Physics.RaycastAll(new Ray(transform.position, transform.forward), Range, 1 | (1 << 17)))
|
||||
var hitFound = false;
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
ActionGameManager.Instance?.ZoneRenderer,
|
||||
SourceEntity,
|
||||
transform.position,
|
||||
transform.forward,
|
||||
Range,
|
||||
0,
|
||||
out var hits))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield)
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
|
||||
if (shield.Entity != SourceEntity)
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
LineRenderer.SetPosition(1, hit.point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
if (hull.Entity != SourceEntity)
|
||||
{
|
||||
hull.SendHit(Damage * (Time.deltaTime / Duration), Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, transform.forward);
|
||||
LineRenderer.SetPosition(1, hit.point);
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
LineRenderer.SetPosition(1, hit.Point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
LineRenderer.SetPosition(1, hit.Point);
|
||||
hitFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(!hitFound)
|
||||
LineRenderer.SetPosition(1, transform.position + transform.forward * Range);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,5 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using static Unity.Mathematics.noise;
|
||||
using Random = UnityEngine.Random;
|
||||
using static Unity.Mathematics.math;
|
||||
|
||||
public class Lightning : MonoBehaviour
|
||||
{
|
||||
|
|
@ -32,37 +25,44 @@ public class Lightning : MonoBehaviour
|
|||
_colliderHit = false;
|
||||
LightningCompute.OnLeaderComplete = null;
|
||||
LightningCompute.FixedEndpoint = false;
|
||||
var hits = Physics.SphereCastAll(Barrel.position, HitRadius, Barrel.forward, Range, 1 | (1 << 17));
|
||||
foreach (var hit in hits)
|
||||
_endpoint = Barrel.position + Barrel.forward * Range;
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryCastZoneHulls(
|
||||
Source?.ZoneRenderer,
|
||||
Source?.Entity,
|
||||
Barrel.position,
|
||||
Barrel.forward,
|
||||
Range,
|
||||
HitRadius,
|
||||
out var hits))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
if (shield.Entity == Source.Entity) continue;
|
||||
LightningCompute.OnLeaderComplete = () =>
|
||||
var hull = hit.Hull;
|
||||
var entity = hull.Entity;
|
||||
if (entity.Shield != null && entity.Shield.Item.Active.Value)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(hit.point, sqrt(Damage));
|
||||
};
|
||||
}
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
if (hull.Entity == Source.Entity) continue;
|
||||
LightningCompute.OnLeaderComplete = () =>
|
||||
hull.SendHit(Damage, Penetration, Spread, DamageType, Source.Entity, hit.textureCoord, Barrel.forward);
|
||||
}
|
||||
LightningCompute.OnLeaderComplete = () =>
|
||||
{
|
||||
hit.Shield?.ShowHit(hit.Point, Mathf.Sqrt(Damage));
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
LightningCompute.OnLeaderComplete = null;
|
||||
}
|
||||
|
||||
_colliderHit = true;
|
||||
_colliderLocalPosition = hit.collider.transform.InverseTransformPoint(hit.point);
|
||||
_colliderTransform = hit.collider.transform;
|
||||
LightningCompute.FixedEndpoint = true;
|
||||
break;
|
||||
_colliderHit = true;
|
||||
_colliderLocalPosition = hull.transform.InverseTransformPoint(hit.Point);
|
||||
_colliderTransform = hull.transform;
|
||||
LightningCompute.FixedEndpoint = true;
|
||||
_endpoint = hit.Point;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LightningCompute.OnPulseComplete = () =>
|
||||
GetComponent<Prototype>().ReturnToPool();
|
||||
LightningCompute.StartAnimation();
|
||||
_endpoint = Barrel.position + Barrel.forward * Range;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
|
|
@ -51,8 +49,6 @@ public class Mine : MonoBehaviour
|
|||
// Update is called once per frame
|
||||
void Update()
|
||||
{
|
||||
var position = transform.position;
|
||||
|
||||
if (Time.time - _startTime > ActivationDelay && !_active)
|
||||
{
|
||||
_active = true;
|
||||
|
|
@ -60,25 +56,11 @@ public class Mine : MonoBehaviour
|
|||
|
||||
if(_active)
|
||||
{
|
||||
_emission = lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10);
|
||||
_emission = Mathf.Lerp(_emission, _blastCountdown ? ArmedEmission : ActiveEmission, Time.deltaTime * 10);
|
||||
_pulseLerp += Time.deltaTime / (_blastCountdown ? ArmedCycleDuration : ActiveCycleDuration);
|
||||
_pulseLerp %= 1;
|
||||
if(!_blastCountdown)
|
||||
{
|
||||
foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1))
|
||||
{
|
||||
var hull = collider.GetComponent<HullCollider>();
|
||||
if (hull)
|
||||
{
|
||||
_blastCountdown = true;
|
||||
_blastTime = Time.time + BlastDelay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((position - Source.transform.position).magnitude > Range ||
|
||||
Time.time - _startTime > Lifetime ||
|
||||
_blastCountdown && Time.time > _blastTime)
|
||||
if (Time.time - _startTime > Lifetime)
|
||||
{
|
||||
Explode();
|
||||
}
|
||||
|
|
@ -89,21 +71,6 @@ public class Mine : MonoBehaviour
|
|||
public void Explode()
|
||||
{
|
||||
var position = transform.position;
|
||||
foreach (var collider in Physics.OverlapSphere(position, BlastRange, 1 | (1 << 17)))
|
||||
{
|
||||
var shield = collider.GetComponent<ShieldManager>();
|
||||
if (shield && (shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage);
|
||||
shield.ShowHit(position, sqrt(Damage));
|
||||
}
|
||||
var hull = collider.GetComponent<HullCollider>();
|
||||
if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
hull.SendSplash(Damage, DamageType, Source.Entity, (collider.transform.position - position).normalized);
|
||||
}
|
||||
}
|
||||
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.position = position;
|
||||
GetComponent<Prototype>().ReturnToPool();
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
public class MineManager : InstantWeaponEffectManager
|
||||
{
|
||||
public Prototype ProjectilePrototype;
|
||||
public bool InheritVelocity;
|
||||
|
||||
public override void Fire(InstantWeapon weapon, EquippedItem item, EntityInstance source, EntityInstance target)
|
||||
{
|
||||
|
|
@ -21,11 +18,9 @@ public class MineManager : InstantWeaponEffectManager
|
|||
p.GridObject.Velocity = Quaternion.Euler(
|
||||
Random.Range(-angle, angle),
|
||||
Random.Range(-angle, angle),
|
||||
Random.Range(-angle, angle)) *
|
||||
Random.Range(-angle, angle)) *
|
||||
barrel.forward *
|
||||
weapon.Velocity;
|
||||
if(InheritVelocity)
|
||||
p.GridObject.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y);
|
||||
p.Damage = weapon.Damage;
|
||||
p.Range = weapon.Range;
|
||||
p.DamageType = weapon.DamageType;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
public class ParticleChargeEffect : WeaponChargeEffect
|
||||
|
|
@ -21,7 +19,7 @@ public class ParticleChargeEffect : WeaponChargeEffect
|
|||
OverchargeEffect.Clear(true);
|
||||
FailureEffect.Clear(true);
|
||||
ChargeEffect.Play(true);
|
||||
ChargeEffect.enableEmission = true;
|
||||
SetEmission(ChargeEffect, true);
|
||||
|
||||
_overloaded = false;
|
||||
_charge = 0;
|
||||
|
|
@ -30,23 +28,23 @@ public class ParticleChargeEffect : WeaponChargeEffect
|
|||
public override void StopCharging()
|
||||
{
|
||||
if (_overloaded)
|
||||
OverchargeEffect.enableEmission = false;
|
||||
SetEmission(OverchargeEffect, false);
|
||||
else
|
||||
ChargeEffect.enableEmission = false;
|
||||
SetEmission(ChargeEffect, false);
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
|
||||
public override void Charged()
|
||||
{
|
||||
ChargeEffect.enableEmission = false;
|
||||
SetEmission(ChargeEffect, false);
|
||||
OverchargeEffect.Play(true);
|
||||
OverchargeEffect.enableEmission = true;
|
||||
SetEmission(OverchargeEffect, true);
|
||||
_overloaded = true;
|
||||
}
|
||||
|
||||
public override void Failed()
|
||||
{
|
||||
OverchargeEffect.enableEmission = false;
|
||||
SetEmission(OverchargeEffect, false);
|
||||
FailureEffect.Play(true);
|
||||
}
|
||||
|
||||
|
|
@ -57,11 +55,18 @@ public class ParticleChargeEffect : WeaponChargeEffect
|
|||
|
||||
if (!_overloaded)
|
||||
{
|
||||
ChargeEffect.playbackSpeed = _charge;
|
||||
var main = ChargeEffect.main;
|
||||
main.simulationSpeed = _charge;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator Kill()
|
||||
private static void SetEmission(ParticleSystem particleSystem, bool enabled)
|
||||
{
|
||||
var emission = particleSystem.emission;
|
||||
emission.enabled = enabled;
|
||||
}
|
||||
|
||||
private IEnumerator Kill()
|
||||
{
|
||||
while (ChargeEffect.particleCount > 0 || OverchargeEffect.particleCount > 0 || FailureEffect.particleCount > 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using UnityEngine;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
|
|
@ -28,7 +26,9 @@ public class Projectile : MonoBehaviour
|
|||
public float Spread { get; set; }
|
||||
public DamageType DamageType { get; set; }
|
||||
public Entity SourceEntity { get; set; }
|
||||
public EntityInstance TargetInstance { get; set; }
|
||||
public float Range { get; set; }
|
||||
public float YmirWorldTime { get; private set; }
|
||||
|
||||
// Start is called before the first frame update
|
||||
void Start()
|
||||
|
|
@ -51,68 +51,73 @@ public class Projectile : MonoBehaviour
|
|||
var t = transform;
|
||||
var position = t.position;
|
||||
Velocity -= Vector3.up * (Gravity * Time.deltaTime);
|
||||
Velocity *= max(0, 1 - Drag * Time.deltaTime);
|
||||
Velocity *= Mathf.Max(0, 1 - Drag * Time.deltaTime);
|
||||
var forward = Velocity.normalized;
|
||||
t.forward = forward;
|
||||
var ray = new Ray(position, Velocity);
|
||||
foreach (var hit in Physics.RaycastAll(ray, Velocity.magnitude * Time.deltaTime, 1 | (1 << 17)))
|
||||
if (!AetheriaYmirPhysicsBridge.Instance.TryStepProjectile(this, Time.deltaTime, out var ymirStep))
|
||||
{
|
||||
var shield = hit.collider.GetComponent<ShieldManager>();
|
||||
var hull = hit.collider.GetComponent<HullCollider>();
|
||||
if (shield)
|
||||
{
|
||||
if (!(shield.Entity.Shield != null && shield.Entity.Shield.Item.Active.Value && shield.Entity.Shield.CanTakeHit(DamageType, Damage))) continue;
|
||||
if (shield.Entity != SourceEntity)
|
||||
{
|
||||
shield.Entity.Shield.TakeHit(DamageType, Damage*DirectHitDamageMultiplier);
|
||||
shield.ShowHit(hit.point, sqrt(Damage * DirectHitDamageMultiplier));
|
||||
}
|
||||
}
|
||||
else if (hull && !(hull.Entity.Shield != null && hull.Entity.Shield.Item.Active.Value && hull.Entity.Shield.CanTakeHit(DamageType, Damage)))
|
||||
{
|
||||
if (hull.Entity != SourceEntity)
|
||||
{
|
||||
hull.SendHit(Damage*DirectHitDamageMultiplier, Penetration, Spread, DamageType, SourceEntity, hit.textureCoord, forward);
|
||||
transform.position = hit.point;
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
StartCoroutine(Kill());
|
||||
return;
|
||||
}
|
||||
|
||||
if (HitEffect != null)
|
||||
{
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.SetParent(hit.collider.transform);
|
||||
ht.position = hit.point;
|
||||
return;
|
||||
}
|
||||
Debug.LogWarning("Ymir projectile step unavailable; projectile killed instead of falling back to Unity physics.");
|
||||
StartCoroutine(Kill());
|
||||
return;
|
||||
}
|
||||
|
||||
transform.position += Velocity * Time.deltaTime;
|
||||
|
||||
YmirWorldTime += Time.deltaTime;
|
||||
Velocity = ymirStep.Velocity;
|
||||
t.position = ymirStep.Position;
|
||||
if (ymirStep.HasHit)
|
||||
{
|
||||
ApplyYmirHit(ymirStep, forward);
|
||||
return;
|
||||
}
|
||||
|
||||
var distanceTraveled = (transform.position - StartPosition).magnitude;
|
||||
if(distanceTraveled > Range)
|
||||
StartCoroutine(Kill());
|
||||
if (AirburstRange > 1 && distanceTraveled > AirburstDistance)
|
||||
{
|
||||
StartCoroutine(Kill());
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.position = t.position;
|
||||
foreach (var collider in Physics.OverlapSphere(t.position, AirburstRange, 1))
|
||||
if (HitEffect != null)
|
||||
{
|
||||
var hull = collider.GetComponent<HullCollider>();
|
||||
if (hull)
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.position = t.position;
|
||||
}
|
||||
|
||||
if (AetheriaYmirPhysicsBridge.Instance.TryOverlapTargetHulls(TargetInstance, t.position, AirburstRange, out var hits))
|
||||
{
|
||||
foreach (var hit in hits)
|
||||
{
|
||||
hull.SendSplash(Damage, DamageType, SourceEntity, (collider.transform.position - t.position).normalized);
|
||||
var direction = (hit.Hull.transform.position - t.position).normalized;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyYmirHit(AetheriaYmirProjectileStep step, Vector3 forward)
|
||||
{
|
||||
var hull = step.Hit.Hull;
|
||||
if (hull == null || hull.Entity == SourceEntity)
|
||||
return;
|
||||
|
||||
if (hull.Entity.Shield != null &&
|
||||
hull.Entity.Shield.Item.Active.Value)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
|
||||
transform.position = step.Hit.Point;
|
||||
if (HitEffect != null)
|
||||
{
|
||||
var ht = HitEffect.Instantiate<Transform>();
|
||||
ht.SetParent(hull.transform);
|
||||
ht.position = step.Hit.Point;
|
||||
}
|
||||
|
||||
StartCoroutine(Kill());
|
||||
}
|
||||
|
||||
IEnumerator Kill()
|
||||
{
|
||||
_alive = false;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using static Unity.Mathematics.math;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
public class ProjectileManager : InstantWeaponEffectManager
|
||||
|
|
@ -17,6 +15,7 @@ public class ProjectileManager : InstantWeaponEffectManager
|
|||
var barrel = source.GetBarrel(hp);
|
||||
var angle = weapon.Spread / 2;
|
||||
p.SourceEntity = source.Entity;
|
||||
p.TargetInstance = target;
|
||||
p.Velocity = Quaternion.Euler(
|
||||
Random.Range(-angle, angle),
|
||||
Random.Range(-angle, angle),
|
||||
|
|
@ -25,14 +24,16 @@ public class ProjectileManager : InstantWeaponEffectManager
|
|||
weapon.Velocity;
|
||||
p.StartPosition = p.transform.position = barrel.position + p.Velocity * (Random.value * Time.deltaTime);
|
||||
if(InheritVelocity)
|
||||
p.Velocity += new Vector3(source.Entity.Velocity.x, 0, source.Entity.Velocity.y);
|
||||
p.Velocity += (Vector3)AetheriaMath.ToUnityXZ(source.Entity.CultVelocity);
|
||||
p.Damage = weapon.Damage;
|
||||
p.Range = weapon.Range;
|
||||
p.Penetration = weapon.Penetration;
|
||||
p.Spread = weapon.DamageSpread;
|
||||
p.DamageType = weapon.DamageType;
|
||||
p.Zone = source.Entity.Zone;
|
||||
p.AirburstDistance = target != null ? length(source.Entity.Position - target.Entity.Position) : (weapon.Range * .75f);
|
||||
p.AirburstDistance = target != null
|
||||
? CultMath.math.length(source.Entity.CultPosition - target.Entity.CultPosition)
|
||||
: (weapon.Range * .75f);
|
||||
p.Trail.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue