Skip to content

Commit

Permalink
DamageNumbers is now implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
hhyyrylainen committed Nov 20, 2021
1 parent 2c5b2c4 commit 404e26f
Show file tree
Hide file tree
Showing 4 changed files with 167 additions and 4 deletions.
1 change: 1 addition & 0 deletions DamageNumbers/Damage Numbers.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<RootNamespace>DamageNumbers</RootNamespace>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Reference Include="Thrive, Version=0.5.6.0, Culture=neutral, PublicKeyToken=null">
Expand Down
2 changes: 2 additions & 0 deletions DamageNumbers/Damage Numbers.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=unprojected/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
38 changes: 36 additions & 2 deletions DamageNumbers/DamageNumbers/DamageNumbers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ public class DamageNumbers : IMod
{
private FloatingDamageNumbers damageNumbers;

private IModInterface storedInterface;

/// <summary>
/// Called when the mod is being loaded
/// </summary>
Expand All @@ -19,8 +21,15 @@ public bool Initialize(IModInterface modInterface, ModInfo currentModInfo)
{
GD.Print("DamageNumbers mod is initializing");

// Store the mod interface for use later
storedInterface = modInterface;

// Setup our GUI control
damageNumbers = new FloatingDamageNumbers();

// Subscribe to the events we are interested in
modInterface.OnDamageReceived += OnDamageReceived;

// Success
return true;
}
Expand All @@ -33,18 +42,43 @@ public bool Unload()
{
GD.Print("DamageNumbers mod is unloading");

// Remember to unsubscribe from all the events we subscribed to in Initialize,
// otherwise mod unloading won't work correctly
storedInterface.OnDamageReceived -= OnDamageReceived;

// And release our mod interface reference
storedInterface = null;

// Release our other resources we created
damageNumbers.QueueFree();
damageNumbers = null;

// Success
return true;
}

/// <summary>
/// Called once initial node setup has finished and it is possible to add children to the root node
/// </summary>
/// <param name="currentScene">The scene we want to attach to, could also get these from the mod interface</param>
/// <param name="currentScene">
/// The scene we want might want to attach to, could also get these from the mod interface
/// </param>
/// <remarks>
/// <para>
/// As this mod wants to be always active we directly attach to the scene tree root to stay attached even when
/// game scenes are changed.
/// </para>
/// </remarks>
public void CanAttachNodes(Node currentScene)
{
GD.Print("DamageNumbers mod is attaching nodes");

currentScene.GetParent().AddChild(damageNumbers);
currentScene.GetTree().Root.AddChild(damageNumbers);
}

private void OnDamageReceived(Node damageReceiver, float amount, bool isPlayer)
{
if (damageReceiver is Spatial spatial)
damageNumbers.AddNumber(amount, spatial.GlobalTransform.origin);
}
}
130 changes: 128 additions & 2 deletions DamageNumbers/DamageNumbers/FloatingDamageNumbers.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Godot;

/// <summary>
Expand All @@ -11,19 +15,141 @@
/// </remarks>
public class FloatingDamageNumbers : Control
{
private const float NumberLifespan = 1.5f;

private static readonly Vector3 NumberVelocity = new(0, 10.0f, -0.5f);
private static readonly Vector3 VelocityRandomNess = new(0.05f, 5.0f, -1.0f);

private readonly List<FloatingNumber> activeNumbers = new();
private readonly Random random = new();

private Camera camera;

public override void _Ready()
{
GD.Print("FloatingDamageNumbers class loaded");

// Fullscreen
AnchorRight = 1;
AnchorBottom = 1;

// But don't block mouse input
MouseFilter = MouseFilterEnum.Ignore;

// For more easily seeing this Node in the Godot debugger we give us a name (needs to be unique in the parent
// node we attach to)
Name = nameof(FloatingDamageNumbers);
}

public override void _Process(float delta)
{
// When cameras are detached from the main scene they don't have Current set to false even if
// they aren't active
if (camera == null || (!camera.Current || !camera.IsInsideTree()))
{
TryGetCamera();

// No camera, we can't do anything
// TODO: would be good to still remove the existing numbers
if (camera == null)
return;

GD.Print("DamageNumbers found a camera");
}

var cameraTransform = camera.GlobalTransform;
var cameraTranslation = cameraTransform.origin;

// If we wanted to constraint the labels to the screen area we would need this variable
// var screenArea = GetViewport().GetVisibleRect();

foreach (var number in activeNumbers)
{
number.LifespanRemaining -= delta;
number.CurrentPosition += number.Velocity * delta;

var numberTranslation = number.CurrentPosition;

bool isBehindCamera = cameraTransform.basis.z.Dot(numberTranslation - cameraTranslation) > 0;

// Fade if close to camera
var distance = cameraTranslation.DistanceTo(numberTranslation);

float alpha = Mathf.Clamp(RangeLerp(distance, 0, 2, 0, 1), 0, 1);

// TODO: make it not start to fade immediately
alpha = Mathf.Min(alpha, number.LifespanRemaining / number.TotalLifespan);

var unprojectedPosition = camera.UnprojectPosition(numberTranslation);

// In the example project there's some fancy logic for keeping stuff on-screen edges if it would otherwise
// go off-screen
if (isBehindCamera)
{
number.AssociatedLabel.Visible = false;
}
else
{
number.AssociatedLabel.RectPosition = unprojectedPosition;
number.AssociatedLabel.Visible = true;
number.AssociatedLabel.SelfModulate = new Color(1, 1, 1, alpha);
}
}

var toRemove = activeNumbers.Where(n => n.LifespanRemaining < 0).ToList();

foreach (var number in toRemove)
{
number.AssociatedLabel.Free();
activeNumbers.Remove(number);
}
}

public void AddNumber(float damage, Vector3 position)
{
var label = new Label()
{
Text = "Test from a mod",
Text = Math.Round(damage, 1).ToString(CultureInfo.CurrentCulture),
};

// TODO: make the text label center better on the position instead of having the left edge of the number there

// TODO: change text colour based on the damage

AddChild(label);

activeNumbers.Add(new FloatingNumber
{
AssociatedLabel = label,
CurrentPosition = position,
LifespanRemaining = NumberLifespan,
TotalLifespan = NumberLifespan,
Velocity = NumberVelocity + VelocityRandomNess * (float)random.NextDouble(),
});
}

public override void _Process(float delta)
/// <summary>
/// Seems to be missing from Godot C#, helpfully provided on Godot Q & A site:
/// https://godotengine.org/qa/91310/where-is-range_lerp-in-c%23 by AlexTheRegent
/// Modified to conform to naming style.
/// </summary>
private static float RangeLerp(float value, float iStart, float iStop, float oStart, float oStop)
{
return oStart + (oStop - oStart) * value / (iStop - iStart);
}

private void TryGetCamera()
{
camera = GetViewport().GetCamera();
}

private class FloatingNumber
{
public Vector3 CurrentPosition;
public Vector3 Velocity;
public float LifespanRemaining;
public float TotalLifespan;

public Label AssociatedLabel;
}
}

0 comments on commit 404e26f

Please sign in to comment.