SUPERCHARGING our C# WORKFLOW with custom FLOATS. Consolidating, and making your code better!
Today I wanted to share some custom logic I've been using in my project that's really been a shot in the arm in terms of keeping properties clear, concise, and ergonomic!
So let's discuss the "CountUpFloat" among others, and how they can fit into your workflows.
I hope in explaining this, it gives some further insights into component and code setups. Maybe some day I'll read from one of you guys a completely new class that I didn't even think to try!
The concept of these custom classes is to distill some bit of common gameplay logic that bloats our project, causes some additional edge case prone to bugs and distills it all into a singular entity that can never misbehave or break!
Have you ever iterated a variable twice and got wacky values? Or meant to increment it, but the incrementor was disabled and its fixed update no longer reached it? Or maybe you don't like having to have fixed updates on all your scripts just to keep asset specific timers going. Or added one to a value trying to make it a multiplier only to forget you already did and now everything is broken!? Or worse yet, you forgot to add one, you're dividing by zero somewhere, and now you're project just CRASHES!
All because you didn't have sound foudnational systems to take care of your data!
WELL YOU CAN END THE INSANITY NOW!
===============================================
[Serializable]
public class CountUpFloat
{
[SerializeField, HideInInspector] public float occuranceTime;
[ShowInInspector] public float timer => Time.time - occuranceTime;
public static implicit operator float(CountUpFloat timer)
{
return timer?.timer ?? 0;
}
public static implicit operator CountUpFloat(float countupDuration)
{
return new CountUpFloat { occuranceTime = Time.time - countupDuration };
}
}
===============================================
That's it! That's the entirity of the logic right there! That's my grand reveal of my gamedev knowledge to this point!
How do we chec the timer? Same as ever...
===============================================
if(sampleTimer > cooldown){
DoThing();
sampleTimer = 0; //SO SIMPLE!
}
===============================================
That's it! No incrementing necessary.
It's been funny learning this gamedev thing, when you're first starting out you think the best stuff is going to be some ultra advanced bit of logic that you could never in a billion years comprehend. You think that peaking under the hood of a game engine you're going to see mish mashes of maths that you could never grasp in your entire life written by erudite savants.
NOPE! It's actually friggin' insane how much room we have to grow, and how low the bar is for making some great new logics!
But I'm getting off topic, I should explain how this works!
IT'S SO SIMPLE! All this class does is it has a start point "occuranceTime", and any time it's accessed Time.time subtracts that occurance time and you get the sum total of how long it's been active.
LIKE HOW PAINFULLY SIMPLE IS THAT!? And yet I haven't seen these suckers used elsewhere!
Yes it's a TAD bit more expensive if you call it every frame, but still a cheap as dirt calculation, and the neat thing, is if you're not calling it every frame, IT'S TOTALLY OFF THE RADAR! You don't have to worry about the scripts incrimenting the property not getting to the increment logic for ANY reason at all, IT JUST WORKS. And best of all? You don't have all that excess logic cluttering your code! You don't need some bit of logic elsewhere where you have to type out
examplefloat += Time.deltaTime;
Full disclosure, I used AI to help me figure out how to get rid of error warnings when the property was declared as private, there were some null reference exceptions popping off, but whatever. It's so cool the sort of tiny new, revolutionary things we can make with such custom classes! I use this custom logic for all sorts of handy tools, like a CountDownFloat, and you can even use an explicit operator to return if it's still counting down or not!
Here's a few custom classes I've been using from time to time, ranging from floats that auto clamp between the 0 and 1 range, and probably the COOLEST of them all, the "LerpFloat" which so long as you access it every frame, it will AUTOMAGICALLY lerp to the value you assign it without any complex logical setups!
===============================================
[Serializable]
public class LerpTowardsFloat
{
[ShowInInspector] public float valueSetsInstant { get { LerpValue(); return actualValue; } set { actualValue = value; } }
public float valueTarget; //TARGET
public float actualValue; //Lerping Value
public int frameSet;
private bool alreadyLerped => frameSet == Time.frameCount;
[SerializeField] private float lerpRate = 0.03f;
public static implicit operator float(LerpTowardsFloat value)
{
return value.valueSetsInstant;
}
public void LerpValue()
{
if (alreadyLerped) return;
actualValue = Mathf.Lerp(actualValue, valueTarget, lerpRate);
frameSet = Time.frameCount;
}
}
[Serializable]
public class CountDownFloat //ALL PUBLIC! private seems to generate errors
{
[SerializeField, HideInInspector] public float destinationTime = 0;
[ShowInInspector] public float timer => destinationTime - Time.time;
public float v => timer;
public bool reachedZero => destinationTime - Time.time <= 0;
public bool countingDown => destinationTime - Time.time > 0;
public bool active => timer > 0;
public void SetMaxCountdownAgainstCurrentDuration(float countdownDuration = 0)
{
var newCountdownDuration = countdownDuration + Time.time;
destinationTime = zz.ChooseHighest(newCountdownDuration, destinationTime);
}
public static implicit operator float(CountDownFloat timer)
{
return timer?.timer ?? 0; // Null check to avoid exception
}
public static implicit operator bool(CountDownFloat timer)
{
return timer?.timer > 0; // Null check to avoid exception
}
public static implicit operator CountDownFloat(float countdownDuration)
{
return new CountDownFloat { destinationTime = countdownDuration + Time.time };
}
}
[Serializable]
public class AboveZeroFloat //ALL PUBLIC! private seems to generate errors
{
[ShowInInspector] public float value;
public static implicit operator float(AboveZeroFloat newValue)
{
return newValue.value;
}
public static implicit operator AboveZeroFloat(float newValue)
{
return new AboveZeroFloat { value = zz.ZeroFloor(newValue) };
}
}
[Serializable]
public class Clamped01Float
{
[ShowInInspector] public float value { get { return zz.Clamp(v); } set { v = zz.Clamp(value); } }
[HideInInspector] public float v;
public static implicit operator float(Clamped01Float clamped) => clamped.value;
public static implicit operator Clamped01Float(float newValue) => new Clamped01Float { value = newValue };
}
[Serializable]
public class FireOnce
{
public float lastCheckTime;
public float timeSinceLastCheck => Time.time - lastCheckTime;
public bool FiringOnce()
{
var fired = timeSinceLastCheck > Time.fixedDeltaTime * 1.5f;
lastCheckTime = Time.time;
return fired;
}
public static implicit operator bool(FireOnce fireOnce)
{
return fireOnce.FiringOnce();
}
}
===============================================
Probably my greatest regret throughout all this bitching and moaning, kicking and screaming we resign to in this blog, is that we've been remiss to catalogue and share great gamedev discoveries and insights that feed into improved workflows and better understandings of gameplay logics.
Had the inclination to post more whiney, industry damning shit, but I think we'll save that for another day. Let's just try to keep it positive for once, IllTemperedTuna, YOU FUCKING FUCK
Get SeaCrit
SeaCrit
Deceptively Deep!
Status | In development |
Author | illtemperedtuna |
Genre | Action, Role Playing, Shooter |
Tags | Beat 'em up, Casual, Indie, Roguelike, Roguelite, Side Scroller, Singleplayer |
Leave a comment
Log in with itch.io to leave a comment.