Refactored Ghost Entities

Game Context was creating an empty OrbitalEntity for every planet; this was an ugly hack so I refactored it, as well as refactoring some object references to GUIDs as they should be
This commit is contained in:
hyperion51 2020-05-04 00:07:36 +01:00
commit 4f29bf24d0
13 changed files with 199 additions and 191 deletions

View file

@ -10,21 +10,23 @@ public class AgentController
public Entity Entity { get; }
public EntityAgent EntityAgent { get; }
public GameContext Context { get; }
public ZoneData Zone { get; set; }
public Guid Zone { get; set; }
public Guid HomeZone;
private Locomotion _locomotion;
private VelocityMatch _velocityMatch;
private Random _random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
private KeyValuePair<DatabaseEntry, Entity> _objective;
private Guid _targetOrbit;
public AgentController(GameContext context, ZoneData zone, Entity entity)
public AgentController(GameContext context, Guid zone, Entity entity)
{
Entity = entity;
Zone = zone;
Context = context;
EntityAgent = new EntityAgent(context, zone, entity);
_locomotion = new Locomotion(entity);
_velocityMatch = new VelocityMatch(entity);
_velocityMatch = new VelocityMatch(context, entity);
_velocityMatch.OnMatch += () =>
{
EntityAgent.CurrentBehavior = _locomotion;
@ -37,7 +39,6 @@ public class AgentController
public void Update(float delta)
{
EntityAgent.Update(delta);
var distance = length(_locomotion.Objective.Position - Entity.Position);
// Context.Log(
// $"Agent Mode: {EntityAgent.CurrentBehavior.GetType()} " +
// $"Agent Distance: {distance} " +
@ -46,9 +47,11 @@ public class AgentController
if (EntityAgent.CurrentBehavior == _locomotion)
{
_velocityMatch.Objective = _locomotion.Objective;
var orbitData = Context.Cache.Get<OrbitData>(_targetOrbit);
_velocityMatch.TargetOrbit = _targetOrbit;
var matchDistanceTime = _velocityMatch.MatchDistanceTime;
_locomotion.LeadTime = matchDistanceTime.y;
_locomotion.Objective = Context.GetOrbitPosition(_targetOrbit) + Context.GetOrbitVelocity(_targetOrbit) * matchDistanceTime.y;
var distance = length(_locomotion.Objective - Entity.Position);
if (distance < matchDistanceTime.x)
EntityAgent.CurrentBehavior = _velocityMatch;
}
@ -56,8 +59,7 @@ public class AgentController
private void RandomTarget()
{
var entities = Context.ZoneContents[Zone].Where(kvp=>!(kvp.Key is ShipData)).ToArray();
_objective = entities[_random.NextInt(entities.Length)];
_locomotion.Objective = _objective.Value;
var entities = Context.ZonePlanets[Zone];
_targetOrbit = Context.Cache.Get<PlanetData>(entities[_random.NextInt(entities.Length)]).Orbit;
}
}

View file

@ -1,4 +1,5 @@
using System.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
public class EntityAgent
@ -7,9 +8,9 @@ public class EntityAgent
public Entity Entity { get; }
public GameContext Context { get; }
public ZoneData Zone { get; set; }
public Guid Zone { get; set; }
public EntityAgent(GameContext context, ZoneData zone, Entity entity)
public EntityAgent(GameContext context, Guid zone, Entity entity)
{
Entity = entity;
Zone = zone;

View file

@ -6,8 +6,7 @@ using static Unity.Mathematics.math;
public class Locomotion : AgentBehavior
{
public Entity Objective;
public float LeadTime;
public float2 Objective;
private Entity _entity;
private IAnalogBehavior _thrust;
private IAnalogBehavior _turning;
@ -23,9 +22,9 @@ public class Locomotion : AgentBehavior
public override void Update(float delta)
{
if (Objective != null && _thrust != null && _turning != null)
if (_thrust != null && _turning != null)
{
float2 diff = Objective.Position + Objective.Velocity * LeadTime - _entity.Position;
float2 diff = Objective - _entity.Position;
float2 tangential = _entity.Velocity - dot(normalize(diff), normalize(_entity.Velocity)) * _entity.Velocity;
float angleDiff = _entity.Direction.AngleDiff(normalize(diff) - tangential * TangentSensitivity);
_entity.Axes[_turning] = clamp(-angleDiff * Sensitivity, -1, 1);

View file

@ -8,16 +8,19 @@ using static Unity.Mathematics.math;
public class VelocityMatch : AgentBehavior
{
public event Action OnMatch;
public Entity Objective;
public Guid TargetOrbit;
private Entity _entity;
private IAnalogBehavior _thrust;
private IAnalogBehavior _turning;
private const float TurningSensitivity = 1;
private const float ThrustSensitivity = 1;
private const float TargetThreshold = .1f;
public GameContext Context { get; }
public VelocityMatch(Entity entity)
public VelocityMatch(GameContext context, Entity entity)
{
Context = context;
_entity = entity;
_thrust = entity.Axes.Keys.FirstOrDefault(x => x is Thruster);
_turning = entity.Axes.Keys.FirstOrDefault(x => x is Turning);
@ -25,9 +28,9 @@ public class VelocityMatch : AgentBehavior
public override void Update(float delta)
{
if (Objective != null && _thrust != null && _turning != null)
if (TargetOrbit != Guid.Empty && _thrust != null && _turning != null)
{
var deltaV = Objective.Velocity - _entity.Velocity;
var deltaV = Context.GetOrbitVelocity(TargetOrbit) - _entity.Velocity;
if(length(deltaV) < TargetThreshold)
OnMatch?.Invoke();
var angleDiff = _entity.Direction.AngleDiff(deltaV);
@ -41,7 +44,7 @@ public class VelocityMatch : AgentBehavior
get
{
var velocity = length(_entity.Velocity);
var deltaV = Objective.Velocity - _entity.Velocity;
var deltaV = Context.GetOrbitVelocity(TargetOrbit) - _entity.Velocity;
var stoppingTime = length(deltaV) / (_entity.Context.Evaluate((_thrust.Data as ThrusterData).Thrust, _thrust.Item, _entity) / _entity.Mass);
var stoppingDistance = stoppingTime * (velocity / 2);

View file

@ -10,8 +10,10 @@ using Unity.Mathematics;
using static Unity.Mathematics.math;
[MessagePackObject,
Union(0, typeof(Ship)),
Union(1, typeof(OrbitalEntity)),
JsonObject(MemberSerialization.OptIn), JsonConverter(typeof(JsonKnownTypesConverter<Entity>))]
public class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
public abstract class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
{
[IgnoreMember] public Dictionary<IAnalogBehavior, float> AxisOverrides = new Dictionary<IAnalogBehavior, float>();
[IgnoreMember] public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
@ -24,6 +26,9 @@ public class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
[IgnoreMember] public Dictionary<IAnalogBehavior, float> Axes;
public readonly List<ItemInstance> Cargo;
public readonly List<Gear> EquippedItems;
public Entity Parent;
public List<Entity> Children = new List<Entity>();
public Guid Zone;
[IgnoreMember] protected List<IBehavior> Behaviors;
@ -37,6 +42,7 @@ public class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
public Entity(GameContext context, Gear hull, IEnumerable<Gear> items, IEnumerable<ItemInstance> cargo)
{
ID = Guid.NewGuid();
Context = context;
EquippedItems = items.ToList();
@ -72,7 +78,8 @@ public class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
{
Mass = Hull?.Mass ?? 0 +
EquippedItems.Sum(i => i.Mass) +
Cargo.Sum(ii => ii.Mass);
Cargo.Sum(ii => ii.Mass) +
Children.Sum(c=>c.Mass);
SpecificHeat = (Hull?.HeatCapacity ?? 0) +
EquippedItems.Sum(i => i.HeatCapacity) +
@ -108,6 +115,12 @@ public class Entity : DatabaseEntry, IMessagePackSerializationCallbackReceiver
foreach (var item in EquippedItems.Where(item => item.ItemData.Performance(Temperature) < .01f))
item.Durability -= delta;
if (Parent != null)
{
Position = Parent.Position;
Velocity = Parent.Velocity;
}
}
public void OnBeforeSerialize()

View file

@ -2,14 +2,17 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using Random = Unity.Mathematics.Random;
public class GameContext
{
public Random Random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
public List<AgentController> Agents = new List<AgentController>();
public Dictionary<ZoneData, Dictionary<DatabaseEntry, Entity>> ZoneContents = new Dictionary<ZoneData, Dictionary<DatabaseEntry, Entity>>();
public Dictionary<Guid, Guid[]> ZonePlanets = new Dictionary<Guid, Guid[]>();
public Dictionary<Guid, Dictionary<Guid, Entity>> ZoneEntities = new Dictionary<Guid, Dictionary<Guid, Entity>>();
public Dictionary<string, GalaxyMapLayerData> MapLayers = new Dictionary<string, GalaxyMapLayerData>();
private Action<string> _logger;
@ -20,6 +23,10 @@ public class GameContext
private double _time;
private float _deltaTime;
private GlobalData _globalData;
private HashSet<Guid> _orbits = new HashSet<Guid>();
private Dictionary<Guid, float2> _orbitVelocities = new Dictionary<Guid, float2>();
private Dictionary<Guid, float2> _orbitPositions = new Dictionary<Guid, float2>();
private Dictionary<Guid, float2> _previousOrbitPositions = new Dictionary<Guid, float2>();
public GlobalData GlobalData => _globalData ?? (_globalData = Cache.GetAll<GlobalData>().FirstOrDefault());
public DatabaseCache Cache { get; }
@ -34,6 +41,13 @@ public class GameContext
}
}
public float2 WormholePosition(Guid zone, Guid other)
{
var zoneData = Cache.Get<ZoneData>(zone);
var direction = Cache.Get<ZoneData>(other).Position - zoneData.Position;
return normalize(direction) * zoneData.Radius * .95f;
}
// private readonly Dictionary<CraftedItemData, int> Tier = new Dictionary<CraftedItemData, int>();
public GameContext(DatabaseCache cache, Action<string> logger)
@ -53,56 +67,81 @@ public class GameContext
_logger(s);
}
public IEnumerable<Entity> ZoneEntities(ZoneData zone)
public IEnumerable<Entity> GetEntities(Guid zone)
{
return ZoneContents[zone].Values;
return ZoneEntities[zone].Values;
}
public Dictionary<DatabaseEntry, Entity> InitializeZone(Guid zoneID)
public void InitializeZone(Guid zoneID)
{
var contents = new Dictionary<DatabaseEntry, Entity>();
var zoneData = Cache.Get<ZoneData>(zoneID);
// TODO: Associate planets with stored entities for planetary colonies
var planetsData = zoneData.Planets.Select(id => Cache.Get<PlanetData>(id)).ToArray();
var planetEntities = planetsData.ToDictionary(p => p,
p => new OrbitalEntity(this, null, Enumerable.Empty<Gear>(), Enumerable.Empty<ItemInstance>(), p.Belt ? Cache.Get<OrbitData>(p.Orbit).Parent : p.Orbit));
foreach(var kvp in planetEntities)
contents.Add(kvp.Key, kvp.Value);
foreach (var orbit in zoneData.Orbits) _orbits.Add(orbit);
var stationsData = zoneData.Stations.Select(id => Cache.Get<StationData>(id));
var stationEntities = stationsData.ToDictionary(s => s,
s => new OrbitalEntity(this, null, Enumerable.Empty<Gear>(), Enumerable.Empty<ItemInstance>(), s.Parent));
foreach(var kvp in stationEntities)
contents.Add(kvp.Key, kvp.Value);
// TODO: Associate planets with stored entities for planetary colonies
ZonePlanets[zoneID] = zoneData.Planets;
ZoneContents.Add(zoneData, contents);
return contents;
// TODO: Load stored entities
}
public void Update()
{
_previousOrbitPositions = _orbitPositions;
_orbitPositions = new Dictionary<Guid, float2>(_orbits.Count);
foreach(var orbit in _orbits)
{
_orbitPositions[orbit] = GetOrbitPosition(orbit);
_orbitVelocities[orbit] = _previousOrbitPositions.ContainsKey(orbit)
? _orbitPositions[orbit] - _previousOrbitPositions[orbit]
: float2.zero;
}
foreach (var agent in Agents)
{
agent.Update(_deltaTime);
}
OrbitalEntity.ClearOrbits();
foreach (var kvp in ZoneContents)
foreach (var kvp in ZoneEntities)
{
var zone = kvp.Key;
var entities = kvp.Value.Values;
foreach (var entity in entities)
{
entity.Update(_deltaTime);
}
foreach (var entity in kvp.Value.Values) entity.Update(_deltaTime);
}
}
public void UnloadZone(ZoneData zone)
public void UnloadZone(Guid zone)
{
ZoneContents.Remove(zone);
var zoneData = Cache.Get<ZoneData>(zone);
foreach (var orbit in zoneData.Orbits) _orbits.Remove(orbit);
ZoneEntities.Remove(zone);
}
// Determine orbital position recursively, caching parent positions to avoid repeated calculations
public float2 GetOrbitPosition(Guid orbit)
{
// Root orbit is fixed at center
if(orbit==Guid.Empty)
return float2.zero;
if (!_orbitPositions.ContainsKey(orbit))
{
var orbitData = Cache.Get<OrbitData>(orbit);
_orbitPositions[orbit] = GetOrbitPosition(orbitData.Parent) + (orbitData.Period < .01f ? float2.zero :
OrbitData.Evaluate((float) (Time / -orbitData.Period * GlobalData.OrbitSpeedMultiplier + orbitData.Phase)) *
orbitData.Distance);
}
var position = _orbitPositions[orbit];
if (float.IsNaN(position.x))
{
Log("Orbit position is NaN, something went very wrong!");
return float2.zero;
}
return _orbitPositions[orbit];
}
public float2 GetOrbitVelocity(Guid orbit)
{
return _orbitVelocities.ContainsKey(orbit) ? _orbitVelocities[orbit] : float2.zero;
}
// public int ItemTier(CraftedItemData itemData)

View file

@ -95,22 +95,25 @@ public class GlobalData : DatabaseEntry
[InspectableField, JsonProperty("beltMassExponent"), Key(27)]
public float BeltMassExponent = 100f;
[InspectableField, JsonProperty("asteroidSizeMin"), Key(28)]
[InspectableField, JsonProperty("asteroidSizeMin"), Key(28)]
public float AsteroidSizeMin = 2f;
[InspectableField, JsonProperty("asteroidSizeMax"), Key(29)]
[InspectableField, JsonProperty("asteroidSizeMax"), Key(29)]
public float AsteroidSizeMax = 10f;
[InspectableField, JsonProperty("asteroidSizeExponent"), Key(30)]
[InspectableField, JsonProperty("asteroidSizeExponent"), Key(30)]
public float AsteroidSizeExponent = 2f;
[InspectableField, JsonProperty("asteroidRotationSpeed"), Key(31)]
[InspectableField, JsonProperty("asteroidRotationSpeed"), Key(31)]
public float AsteroidRotationSpeed = 2f;
[InspectableField, JsonProperty("dockingDistance"), Key(32)]
public float DockingDistance = 10;
[InspectableField, JsonProperty("dockingDistance"), Key(32)]
public float DockingDistance = 25;
[InspectableField, JsonProperty("orbitSpeedMultiplier"), Key(33)]
[InspectableField, JsonProperty("warpDistance"), Key(33)]
public float WarpDistance = 25;
[InspectableField, JsonProperty("orbitSpeedMultiplier"), Key(34)]
public float OrbitSpeedMultiplier = .25f;
public float PlanetRadius(float mass)

View file

@ -8,45 +8,15 @@ using float2 = Unity.Mathematics.float2;
public class OrbitalEntity : Entity
{
private Guid _orbitData;
private static Dictionary<Guid, float2> _orbitPositions = new Dictionary<Guid, float2>();
public OrbitalEntity(GameContext context, Gear hull, IEnumerable<Gear> items, IEnumerable<ItemInstance> cargo, Guid orbit) : base(context, hull, items, cargo)
{
_orbitData = orbit;
}
public static void ClearOrbits()
{
_orbitPositions.Clear();
}
// Determine orbital position recursively, caching parent positions to avoid repeated calculations
private float2 GetOrbitPosition(Guid orbit)
{
// Root orbit is fixed at center
if(orbit==Guid.Empty)
return float2.zero;
if (!_orbitPositions.ContainsKey(orbit))
{
var orbitData = Context.Cache.Get<OrbitData>(orbit);
_orbitPositions[orbit] = GetOrbitPosition(orbitData.Parent) + (orbitData.Period < .01f ? float2.zero :
OrbitData.Evaluate((float) (Context.Time / -orbitData.Period * Context.GlobalData.OrbitSpeedMultiplier + orbitData.Phase)) *
orbitData.Distance);
}
var position = _orbitPositions[orbit];
if (float.IsNaN(position.x))
{
Context.Log("Orbit position is NaN, something went very wrong!");
return float2.zero;
}
return _orbitPositions[orbit];
}
public override void Update(float delta)
{
var newPosition = GetOrbitPosition(_orbitData);
var newPosition = Context.GetOrbitPosition(_orbitData);
Velocity = (newPosition - Position) / delta;
Position = newPosition;
base.Update(delta);

View file

@ -9,29 +9,26 @@ using static Unity.Mathematics.math;
[RethinkTable("Galaxy"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ZoneData : DatabaseEntry, INamedEntry
{
[JsonProperty("name"), Key(1)]
[JsonProperty("name"), Key(1)]
public string Name;
[JsonProperty("position"), Key(2)]
[JsonProperty("position"), Key(2)]
public float2 Position = 500;
[JsonProperty("wormholes"), Key(3)]
public List<Guid> Wormholes = new List<Guid>();
[JsonProperty("stations"), Key(4)]
public List<Guid> Stations = new List<Guid>();
[JsonProperty("planets"), Key(5)]
public List<Guid> Planets = new List<Guid>();
[JsonProperty("orbits"), Key(6)]
public List<Guid> Orbits = new List<Guid>();
[JsonProperty("radius"), Key(7)]
[JsonProperty("radius"), Key(3)]
public float Radius = 2000;
[JsonProperty("visited"), Key(8)]
[JsonProperty("visited"), Key(4)]
public bool Visited;
[JsonProperty("wormholes"), Key(5)]
public List<Guid> Wormholes = new List<Guid>();
[JsonProperty("planets"), Key(6)]
public Guid[] Planets;// = new List<Guid>();
[JsonProperty("orbits"), Key(7)]
public List<Guid> Orbits = new List<Guid>();
[IgnoreMember] public string EntryName
{

View file

@ -104,7 +104,7 @@ public class ZoneGenerator
return planetData;
}).ToArray();
zone.Planets = planetsData.Select(pd => pd.ID).ToList();
zone.Planets = planetsData.Select(pd => pd.ID).ToArray();
zone.Orbits = orbitData.Select(od => od.ID).ToList();
}