Skip to content

Commit

Permalink
Improved projectile trail fading & projectile models
Browse files Browse the repository at this point in the history
  • Loading branch information
ari-steas committed Jan 7, 2024
1 parent 1c4d17b commit 2b41d57
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public Projectile(SerializableProjectile projectile)
SyncUpdate(projectile);
}

public Projectile(int DefinitionId)
public Projectile(int DefinitionId, long firer = 0, Vector3D InitialVelocity = new Vector3D())
{
if (!ProjectileDefinitionManager.HasDefinition(DefinitionId))
{
Expand All @@ -73,6 +73,9 @@ public Projectile(int DefinitionId)
Definition = ProjectileDefinitionManager.GetDefinition(DefinitionId);

Velocity = Definition.PhysicalProjectile.Velocity;
this.Firer = firer;
this.InheritedVelocity = InitialVelocity;

RemainingImpacts = Definition.Damage.MaxImpacts;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ internal class ProjectileDefinitionManager
},
Visual = new Visual()
{
Model = "",
Model = "Models\\Weapons\\Projectile_Missile.mwm",
TrailTexture = MyStringId.GetOrCompute("WeaponLaser"),
TrailFadeTime = 2,
TrailFadeTime = 0,
TrailLength = 1,
TrailWidth = 0.1f,
TrailColor = new VRageMath.Vector4(255, 255, 255, 255),
//TrailColor = new VRageMath.Vector4(61, 24, 24, 200),
AttachedParticle = "Smoke_Missile",
ImpactParticle = "Explosion_LargeCaliberShell_Backup",
VisibleChance = 1,
Expand Down
62 changes: 41 additions & 21 deletions Heart Module/Data/Scripts/HeartModule/Projectiles/ProjectileDraw.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,83 @@
using Heart_Module.Data.Scripts.HeartModule.Debug;
using Sandbox.Game.Entities;
using Sandbox.ModAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using VRage;
using VRage.Game;
using VRage.Game.Entity;
using VRage.Game.Models;
using VRage.ModAPI;
using VRageMath;
using VRageRender;

namespace Heart_Module.Data.Scripts.HeartModule.Projectiles
{
partial class Projectile
{
MyBillboard ProjectileBillboard;
MyEntity ProjectileEntity;
MyEntity ProjectileEntity = new MyEntity();
MyParticleEffect ProjectileEffect;
uint RenderId = 0;
Dictionary<MyTuple<Vector3D, Vector3D>, long> TrailFade = new Dictionary<MyTuple<Vector3D, Vector3D>, long>();
Dictionary<MyTuple<Vector3D, Vector3D>, float> TrailFade = new Dictionary<MyTuple<Vector3D, Vector3D>, float>();
MatrixD ProjectileMatrix = MatrixD.Identity;

internal void InitDrawing()
{
ProjectileBillboard = new MyBillboard();
ProjectileEntity = new MyEntity();
RenderId = ProjectileEntity.Render.GetRenderObjectID();
if (Definition.Visual.HasModel)
{
ProjectileEntity.Init(null, Definition.Visual.Model, null, null);
ProjectileEntity.Render.CastShadows = false;
ProjectileEntity.IsPreview = true;
ProjectileEntity.Save = false;
ProjectileEntity.SyncFlag = false;
ProjectileEntity.NeedsWorldMatrix = false;
ProjectileEntity.Flags |= EntityFlags.IsNotGamePrunningStructureObject;
MyEntities.Add(ProjectileEntity, true);
RenderId = ProjectileEntity.Render.GetRenderObjectID();
}
else
RenderId = uint.MaxValue;
}

public void DrawUpdate(float delta)
public void DrawUpdate(float deltaTick, float deltaDraw)
{
Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * delta)) * delta;
MatrixD matrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up));
// deltaTick is the current offset between tick and draw, to account for variance between FPS and tickrate
Vector3D visualPosition = Position + (InheritedVelocity + Direction * (Velocity + Definition.PhysicalProjectile.Acceleration * deltaTick)) * deltaTick;
ProjectileMatrix = MatrixD.CreateWorld(visualPosition, Direction, Vector3D.Cross(Direction, Vector3D.Up));

// Temporary debug draw
//DebugDraw.AddPoint(visualPosition, Color.Green, 0.000001f);

if (Definition.Visual.AttachedParticle != "" && !HeartData.I.IsPaused)
if (Definition.Visual.HasAttachedParticle && !HeartData.I.IsPaused)
{
if (ProjectileEffect == null)
MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref matrix, ref visualPosition, RenderId, out ProjectileEffect);
else
ProjectileEffect.WorldMatrix = matrix;
MyParticlesManager.TryCreateParticleEffect(Definition.Visual.AttachedParticle, ref MatrixD.Identity, ref Vector3D.Zero, RenderId, out ProjectileEffect);
if (RenderId == uint.MaxValue)
ProjectileEffect.WorldMatrix = ProjectileMatrix;
}

if (Definition.Visual.TrailTexture != null && !HeartData.I.IsPaused)
TrailFade.Add(new MyTuple<Vector3D, Vector3D>(visualPosition, visualPosition + Direction * Definition.Visual.TrailLength), DateTime.Now.Ticks + (long)(TimeSpan.TicksPerSecond * Definition.Visual.TrailFadeTime));
UpdateTrailFade();
ProjectileEntity.WorldMatrix = ProjectileMatrix;

if (Definition.Visual.HasTrail && !HeartData.I.IsPaused)
TrailFade.Add(new MyTuple<Vector3D, Vector3D>(visualPosition, visualPosition + Direction * Definition.Visual.TrailLength), Definition.Visual.TrailFadeTime);
UpdateTrailFade(deltaDraw);
}

/// <summary>
/// Updates trail fade for this projectile.
/// </summary>
private void UpdateTrailFade()
private void UpdateTrailFade(float delta)
{
foreach (var positionTuple in TrailFade.Keys.ToList())
{
float percentage = (TrailFade[positionTuple] - DateTime.Now.Ticks) / (Definition.Visual.TrailFadeTime * TimeSpan.TicksPerSecond);
Vector4 fadedColor = Definition.Visual.TrailColor * percentage;
float lifetimePct = TrailFade[positionTuple] / Definition.Visual.TrailFadeTime;
Vector4 fadedColor = Definition.Visual.TrailColor * (Definition.Visual.TrailFadeTime == 0 ? 1 : lifetimePct);
MySimpleObjectDraw.DrawLine(positionTuple.Item1, positionTuple.Item2, Definition.Visual.TrailTexture, ref fadedColor, Definition.Visual.TrailWidth);
if (TrailFade[positionTuple] <= DateTime.Now.Ticks)

if (!HeartData.I.IsPaused)
TrailFade[positionTuple] -= delta;
if (TrailFade[positionTuple] <= 0)
TrailFade.Remove(positionTuple);
}
}
Expand All @@ -72,7 +92,6 @@ private void DrawImpactParticle(Vector3D ImpactPosition)
if (MyParticlesManager.TryCreateParticleEffect(Definition.Visual.ImpactParticle, ref matrix, ref ImpactPosition, uint.MaxValue, out hitEffect))
{
MyAPIGateway.Utilities.ShowNotification("Spawned particle at " + hitEffect.WorldMatrix.Translation);
//hitEffect.UserScale = av.AmmoDef.AmmoGraphics.Particles.Hit.Extras.Scale;
//hitEffect.Velocity = av.Hit.HitVelocity;

if (hitEffect.Loop)
Expand All @@ -83,6 +102,7 @@ private void DrawImpactParticle(Vector3D ImpactPosition)
internal void CloseDrawing()
{
ProjectileEffect?.Close();
ProjectileEntity.Close();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,16 @@ public class ProjectileManager : MySessionComponentBase
private Dictionary<ulong, List<uint>> ProjectileSyncStream = new Dictionary<ulong, List<uint>>();
public uint NextId { get; private set; } = 0;
private List<Projectile> QueuedCloseProjectiles = new List<Projectile>();
private float delta = 0;
private Stopwatch clock = Stopwatch.StartNew();
/// <summary>
/// Delta for engine ticks; 60tps
/// </summary>
private float deltaTick = 0;
/// <summary>
/// Delta for frames; varies
/// </summary>
private float deltaDraw = 0;
private Stopwatch clockTick = Stopwatch.StartNew();
private Stopwatch clockDraw = Stopwatch.StartNew();

public override void LoadData()
{
Expand Down Expand Up @@ -59,7 +67,7 @@ public override void UpdateAfterSimulation()
if (HeartData.I.IsSuspended) return;

// spawn projectiles at world origin for debugging. Don't actually do this to spawn projectiles, please.
if (j >= 25 && MyAPIGateway.Session.IsServer)
if (j >= 75 && MyAPIGateway.Session.IsServer)
{
j = 0;
try
Expand Down Expand Up @@ -89,12 +97,12 @@ public override void UpdateAfterSimulation()
j++;

// Delta time for tickrate-independent projectile movement
delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond;
deltaTick = clockTick.ElapsedTicks / (float)TimeSpan.TicksPerSecond;

// Tick projectiles
foreach (var projectile in ActiveProjectiles.Values)
{
projectile.TickUpdate(delta);
projectile.TickUpdate(deltaTick);
if (projectile.QueuedDispose)
QueuedCloseProjectiles.Add(projectile);
}
Expand Down Expand Up @@ -146,7 +154,7 @@ public override void UpdateAfterSimulation()

DamageHandler.Update();

clock.Restart();
clockTick.Restart();
}

private void SyncPlayerProjectiles(IMyPlayer player)
Expand Down Expand Up @@ -179,20 +187,21 @@ private void SyncPlayerProjectiles(IMyPlayer player)

public override void UpdatingStopped()
{
clock.Stop();
clockTick.Stop();
}

public override void Draw()
public override void Draw() // Called once per frame to avoid jitter
{
if (HeartData.I.IsSuspended) return;

if (MyAPIGateway.Utilities.IsDedicated) // We don't want to needlessly use server CPU time
if (HeartData.I.IsSuspended || MyAPIGateway.Utilities.IsDedicated) // We don't want to needlessly use server CPU time
return;

delta = clock.ElapsedTicks / (float)TimeSpan.TicksPerSecond;
// Triggered every frame, avoids jitter in projectiles
deltaTick = (float)clockTick.ElapsedTicks / TimeSpan.TicksPerSecond; // deltaTick is the current offset between tick and draw, to account for variance between FPS and tickrate
deltaDraw = (float)clockDraw.ElapsedTicks / TimeSpan.TicksPerSecond; // deltaDraw is a standard delta value based on FPS

foreach (var projectile in ActiveProjectiles.Values)
projectile.DrawUpdate(delta);
projectile.DrawUpdate(deltaTick, deltaDraw);

clockDraw.Restart();
}

public void UpdateProjectile(SerializableProjectile projectile)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,17 +71,21 @@ public struct PhysicalProjectile
}

[ProtoContract]
public struct Visual
public class Visual
{
[ProtoMember(1)] public string Model;
[ProtoMember(2)] public MyStringId TrailTexture;
[ProtoMember(7)] public float TrailLength;
[ProtoMember(9)] public float TrailWidth;
[ProtoMember(8)] public Vector4 TrailColor;
[ProtoMember(3)] public float TrailFadeTime;
[ProtoMember(7)] public float TrailLength = 0;
[ProtoMember(9)] public float TrailWidth = 0;
[ProtoMember(8)] public Vector4 TrailColor = Vector4.Zero;
[ProtoMember(3)] public float TrailFadeTime = 0;
[ProtoMember(4)] public string AttachedParticle;
[ProtoMember(5)] public string ImpactParticle;
[ProtoMember(6)] public float VisibleChance;
public bool HasModel => !Model?.Equals("") ?? false;
public bool HasTrail => TrailTexture != null && TrailLength > 0 && TrailWidth > 0 && TrailColor != null && TrailColor != Vector4.Zero;
public bool HasAttachedParticle => !AttachedParticle?.Equals("") ?? false;
public bool HasImpactParticle => !ImpactParticle?.Equals("") ?? false;
}

[ProtoContract]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public class SorterWeaponLogic : MyGameLogicComponent
public override void Init(MyObjectBuilder_EntityBase objectBuilder)
{
NeedsUpdate = MyEntityUpdateEnum.BEFORE_NEXT_FRAME;

ShootState.ValueChanged += OnShootStateChanged; // Attach the handler
}

Expand Down

0 comments on commit 2b41d57

Please sign in to comment.