• Programmer code on a screen

Lightweight ScriptableObject Events in Unity

October 7th, 2022|Tags: , , , |0 Comments

There’s loads of sources on this topic, even on Unity’s own Learn page! My goal with this code was to create a lightweight version that I can drop into any project if I need a quick event system.  Most of the code examples I’ve found online have been based around wiring up events using the Inspector, or having a one-to-one relationship between each Event and its Listener(s).  The system below replaces the GameListenerEvent middleman class from the Unity Learn example with the standard Action type, letting you assign your delegate for each Event Listener in your code. 

The code below creates base classes for Action, Action<T1>, and Action<T1,T2> delegates, similar to the UnityAction versions (which you could probably replace Action with, if you want to remove your dependence on the System library).  You can go ahead and add T3 and beyond if you find the need for them.

Use the standard OnEnable() => eventObject.RegisterListener(delegate); and OnDisable() => eventObject.UnregisterListener(delegate); syntax to register your Listeners.

This code was adapted from the code in the discussion located here.

using System;
using System.Collections.Generic;
using UnityEngine;
public abstract class BaseGameEvent : ScriptableObject
{
#if UNITY_EDITOR
[TextArea] public string developerDescription = "";
#endif
private readonly List<Action> listeners = new ();
public void Raise()
{
for (var i = listeners.Count 1; i >= 0; i)
{
listeners[i].Invoke();
}
}
public void RegisterListener(Action listener)
{
if (!listeners.Contains(listener)) { listeners.Add(listener); }
}
public void UnregisterListener(Action listener)
{
if (listeners.Contains(listener)) { listeners.Remove(listener); }
}
}
public abstract class BaseGameEvent<T1> : BaseGameEvent
{
private readonly List<Action<T1>> listeners = new ();
public void Raise(T1 t1)
{
for (var i = listeners.Count 1; i >= 0; i)
{
listeners[i].Invoke(t1);
}
}
public void RegisterListener(Action<T1> listener)
{
if (!listeners.Contains(listener)) { listeners.Add(listener); }
}
public void UnregisterListener(Action<T1> listener)
{
if (listeners.Contains(listener)) { listeners.Remove(listener); }
}
}
public abstract class BaseGameEvent<T1,T2> : BaseGameEvent
{
private readonly List<Action<T1,T2>> listeners = new ();
public void Raise(T1 t1, T2 t2)
{
for (var i = listeners.Count 1; i >= 0; i)
{
listeners[i].Invoke(t1, t2);
}
}
public void RegisterListener(Action<T1,T2> listener)
{
if (!listeners.Contains(listener)) { listeners.Add(listener); }
}
public void UnregisterListener(Action<T1,T2> listener)
{
if (listeners.Contains(listener)) { listeners.Remove(listener); }
}
}
/* USE:
*************
[CreateAssetMenu()]
public class GameEvent : BaseGameEvent {}
public class IntGameEvent : BaseGameEvent<int> {}
public class IntStringGameEvent : BaseGameEvent<int,string> {}
**************
*/
///
/// examples of the three components required to utilize the system:
///
// the Scriptable Object Event definition
[CreateAssetMenu]
public class CoinGameEvent : BaseGameEvent<int>
{}
// the MonoBehaviour on each Coin to collect
public class Coin : MonoBehaviour
{
// the Scriptable Object Event we created via the Create Asset menu
[SerializeField] private CoinGameEvent coinEvent;
// raise our coin event whenever the user picks up a coin
void OnTriggerEnter2D(Collider2D other)
{
coinEvent.Raise(1);
Destroy(gameobject);
}
}
// this can also be a ScriptableObject since they still have OnEnable() and OnDisable() methods
public class CoinManager : MonoBehaviour
{
int coins;
// register to Listen for the Coin Event
void OnEnable() => coinEvent.RegisterListener(AddCoins);
void OnDisable() => coinEvent.UnregisterListener(AddCoins);
// add the number of coins in the Event to our total
void AddCoins(int value) => coins += value;
}

Projectile Arcing in Unity with Animation Curves

July 21st, 2022|Tags: , , , , , |0 Comments

I recently had to figure out how to move a projectile in an Arc for a project I’m working on and found that a Google search was harder than expected, so I’m writing this up in that hopes that it might help someone else! For starters, take a look at this post on how to do it in 2D, as well as some explanation on the math behind it. For our purposes, we’re going to let Unity handle calculating the arc by simply using an Animation Curve. (Kind of hilariously, I spent part of the past weekend learning Animation Curves in Unity, only to have it be the subject of my next Maya class come Monday morning. Hindsight!)

This is the code I’m using, which is hyper-specific to my project, but we can break it down in principle. (I’ll cover ScriptableObject TypeObjects in a later post, but that’s what’s going on here; Projectile is a MonoBehaviour and the class below is a ScriptableObject)

  • Line 7: CurrentTime += Time.deltaTime in Update(). Duration is how long the projectile should take from firing to reach its target. linearT calculates the ratio from 0 to 1 of how close to done we are, given a desired Duration.
  • Line 9: PositionXZ() is an extension function I wrote that returns a Vector3 with its Y value set to 0. This function handles moving the projectile towards the target on the X and Z axes, using Lerp and linearT to interpolate smoothly.
  • Line 11: does the same thing but only for the Y axis. This ensures that we end up at the correct height for the target, in case the start and end Y positions are different.
  • Line 13: uses the same 0 to 1 value in linearT to evaluate the animation curve and return a height offset value. This is multiplied by curveMaxHeight to determine the effective height at the top of the parabola.
  • Line 15: adds the Lerped Y to the Offset Y to get the new effective Y. (If we’re Lerping from 0y to 0y, baseY will be 0y throughout, so the arc offset will determine the Y value alone.)

And here’s the animation curve stored in the curveHeight field:

You’ll want to manually clamp the points on the X axis at 0 and 1 to a value of 0. This ensures that at the start and end, no height is added to the projectile. Add a key point at 0.55 and set its value to .95. Set the Start and End tangents to linear; you want the projectile to shoot up towards the peak and come down towards the target quickly. In the middle, you can keep the point at Auto; grab the tangent handle and turn it clockwise so that the right hand side of the parabola is as close to a 45 degree angle as you can get it, which should push the peak to touching 1 on the Y axis.

This system works beautifully, because the offset for the mortar in the Animation Curve matches what you expect it to look like in the game, which I find makes it easier to wrap your head around how it works. You can play around with the tangents if you want to extend that hang at the top, but this works pretty well as is.

Looks pretty good, and no tricky math required… it should also be adaptable for other projectiles like rockets or arrows if you flatten out the Animation Curve. Try it out!

Edit 2022/08/20: I randomly stumbled across https://blog.terresquall.com/2019/11/coding-projectiles-for-your-tower-defense-game-part-2/ and thought it was a great explanation of the same concept; so for further reading, check it out.

More Portfolio Updates

July 8th, 2022|Tags: |0 Comments

Filled out a few more pages on the portfolio so it’s fully complete. Check ’em out!

The Adventures of Robo Boy in Cloud City, an adorable platformer written in Construct that will challenge you!


Odds & Evens: 2048 Plus, my first mobile game that you can still download from the Google Play Store!


Zap City, a top-down 2D game where you must use your chain lightning ability to defeat enemies and solve puzzles as you try to escape the city.


  • black laptop beside black computer mouse inside room

New Site Design!

June 28th, 2022|Tags: |0 Comments

Spent the last few days working on a full overhaul of the site using the Avada theme… Really enjoyed all the different ways to customize everything (and I barely scratched the surface of what’s possible–I still have classwork to do, after all!)  Realized that having a shiny site that I’m proud of and that’s easy to add content to will make me more likely to update this regularly.  So, yay.  Be back soon to continue filling in those portfolio projects! 🙃

Portfolio Updates

June 26th, 2022|Tags: , |0 Comments

Added Portfolio post types to my WordPress setup and started filling in some work over on the Portfolio page. Check it out!

Some highlights:

Spiced Pumpking, a Monopoly-meets-Spaceteam-esque game that has you battling for supremacy of the Pumpkin Spiced Latte market.


A Sunset Rendezvous, a simple 2D top-down game in the style of the original Zelda.


The Great Hunt, a prehistoric dice war game that could be played by hunters on the go.


My Drawing Bootcamp Sketches, where you can see me do my best to draw (I definitely improved a lot by the end!).


I’ll be filling in the rest of the current projects in the upcoming days and will update here once it’s all done. I challenged myself to post once per module during this summer semester, so that’s twice a week! So, more to come soon. Cheers!

VIEW RECENT WORK

Want to Work Together?

Tell me more about your project

LET’S TALK!