Sync daemon authority migration

This commit is contained in:
Meta Vens 2026-06-21 09:37:54 +01:00
commit 39a0dc8737
295 changed files with 25121 additions and 7360 deletions

View file

@ -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()

View 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();
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 66b38a2f45120a041941eae4412f7da1

View 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;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4f5008a2876ee014fb9e442f1f6103e0

View 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;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8fa370b1ea2904d40ae295007ac76b48

View 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;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 9cba277ef259bee438796ab4c0f3df64

View 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);
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0038a222765ff82468c22a516cfcdbc1

View file

@ -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;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 146aa8d6d00cc1a44b44a5f83d69edea

View 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));
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f6b3b3dc2e2a29245a24b772e2cb87b4

View 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;
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 48740dbd37e79954ca00a3f6bd7ccef1

View 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;
}
}
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d8988c7d796df504dab64fd2c64eb868

View file

@ -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);
}
}

View file

@ -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)

View file

@ -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()

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: baa9b1a7f453a7a4aafe9c44c9fba7c3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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; }
}

View file

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 8d2521ff2dd1835498a2c00656d4bff6

View file

@ -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;
}
}

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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);
}
}

View file

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5d5539a23b814254b038a16f32f1d8a9
timeCreated: 1618453298

View file

@ -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;
}
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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();
}
}
}

View file

@ -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);
};
}
}
}

View file

@ -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);

View file

@ -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);

View file

@ -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()

View file

@ -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();

View file

@ -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;

View file

@ -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)
{

View file

@ -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;

View file

@ -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();
}
}