Dev Log

Jeremy Wolf Jeremy Wolf

Knowing When A Coroutine Finishes

Did you know that a coroutine can yield until another coroutine finishes? I didn’t. Let’s talk about it and why it’s useful.

Backstory

A few weeks back, I was working with some 3rd party code that heavily used coroutines. In one place it had a “chain” of coroutines that each called another coroutine sometimes multiple coroutines and this went 5-6 coroutines deep.

First. Yuck.

Second. Holy cow.

For what I was working on, I needed to know when the process (all the coroutines) had finished. The “usual” and often suggested method on the interwebs is to add a class-wide boolean to track when the coroutine is complete - you set it to true when you start and set it to false when you finish.

I’ve never liked this approach, but sometimes it’s good enough.

In the case that I was working with, the boolean approach just wasn’t practical or at least was going to be extra icky. And I definitely wasn’t going to add a boolean parameter to each coroutine and try to pass it through… No chance.

So I got to wondering if there was a better way. It turns out there is. And somehow I’d missed it up until now.

Ironically, if I’d looked closer at the coroutines in the 3rd party code I would have seen the solution in action. Oops… You win some you lose some.

A Better Way

So this may sound like just passing the buck. BUT! You can have a coroutine yield until another coroutine is finished.

(Seriously, how did I not know about this?)

This means in my case, a cluster of sequential coroutines, I can create yet another coroutine and have it yield until the cluster finishes it’s business. So if the coroutines exit early and DON’T make it to the end of the chain - for some reason. I’ll still know that the coroutines are done. I may not know why, but I know it’s done. And that’s a hugely useful thing!

Plus!

We can throw in a function to call when the coroutines have done their business. Frankly, this is often what we really want to do - wait until the coroutine is done and then run some other code.

In my opinion, this works and it works well. But I think it also opens the doors to better code structure (assuming it’s your code and you can change it).

Better Still!

A chain of coroutines makes it hard to debug and in my opinion and makes harder it than necessary to follow the flow of the code. So with what we know now, or at least with what I know now, we could restructure the code and make it easier to read.

So instead of one coroutine calling the next coroutine, I can call them all, sequentially, from within a single wrapping or master coroutine. This avoids the hard to follow “chain” AND it avoids a huge monolithic coroutine (i.e. just making it all one coroutine).

In fact, I used this exact approach in my current personal project where I had several actions that needed to happen sequentially, but also over a controlled period of time. Launching a missile requires a lot of moving pieces - it is actually rocket science!

Launching an ICBM from a missile SILO


Next Level

Coroutine with callback

But we can go one step further!

If you’ve spent any time on my channel or discord you know I love actions and events. I rarely pass up a chance to use them and this is no different!

Rather than have a set function called when the coroutines finish, we can pass in an action that will act as a callback.

Meaning the same coroutine can run, but with a different reaction when it’s finished. This can be super useful if the coroutine is public (yes, coroutines can be started from other classes) or if it is somehow getting invoked by different objects or for different reasons.

I generally don’t love making coroutines public, not sure why, just don’t. But it’s easy enough to add a public function.

Passing in a callback function

Either way, passing in an action (i.e. function) is easy and makes for very useful code and potentially reusable code.

Read More
Jeremy Wolf Jeremy Wolf

Unity Input Event Handlers - Or Adding Juice the Easy Way

Need to add a little juice to your UI? Maybe your player needs to interact with scene objects? Or maybe you want to create a dynamic or customizable UI? This can feel hard or just plain confusing to add to your project. Not to mention a lot of the solutions out there are more complex than they need to be!

Using Unity’s Event Handlers can simplify and clean up your code while offering better functionality than other solutions. I’ve seen a lot of solutions out there to move scene objects, create inventory UI, or make draggable UI. Many or maybe most of those solutions are overly complicated because they don’t make full use of Unity’s event handlers (or the Pointer Event Data class).

Did I mention these handlers work with both the “new” and the “old” input systems. So learn them once and use them with either system.So let’s take a look at what they can do!

If you just want to see the example code, you can find it here on GitHub.

Input Event Handlers

Event handlers are added by including using UnityEngine.EventSystems and then implementing one or more of the interfaces. For example, IPointerEnterHandler will require an OnPointerEnter function to be added. No surprise - this function will then get called when the point enters (the rect transform of) the UI element.

The interfaces and corresponding functions work on scene objects. But! The scene will need a camera with a physics raycaster and more on that as we move along.

Below are the supported events (out of the box) from Unity:


Example Disclaimer

The examples below are intended to be simple and show what CAN be done. There will be edge cases and extra logic needed for most implementations. My hope is that these examples show you a different way to do some of these things - a simpler and cleaner way. The examples also make use of DoTween to add a little juice to the examples. If you’re not using it, I’d recommend it, but it’s optional all the same.

Also in the examples, each of the functions being used corresponds to an interface that needs to be implemented. If you have the function, but it’s not getting called double check that you have implemented the interface in the class.


UI Popup

A simple use case of the event handlers is a UI popup to show the player information about an object that the pointer is hovering over. This can be accomplished by using the IPointerEnter and IPointerExit interfaces. For my example, I choose to invoke a static event when the pointer enters the object (to open a popup) and when the pointer exits (to close the popup). Using events has the added benefit that other systems beyond the popup menu can also be aware of the event/action - which is huge and can allow more polish and juice to be added. It also means that information about the event and the object can be passed with the event.

In my particular case, the popup UI element is listening to these events and since the PointerEventData is being passed with the event, the popup UI element can appear on-screen near the object. In my case rather than place the popup window at the same location as the pointer I’m using a small offset.

This code is placed on objects - to enable popup

Physics Raycaster

If you want or need the Event Handlers to work on scene objects (like the example above) you will need to add a Physics Raycaster to your camera.

This is pretty straight forward, with the possible exception of the layer mask. You will need to do some sorting of layers in your scene and edit the layer mask accordingly if you are getting unwanted interactions.

For example in my project game units have a “Unit Detection” object on them which includes a large sphere collider. This is used to detect opposing units when they get close. The “Unit Detection” object is on a different layer to avoid unwanted interactions between scene objects. In my case, I also wanted to turn off this layer in the physics raycaster layer mask - as the extra colliders were blocking the detection of the pointer on the small collider surrounding the actual unit.

This code is placed on the popup window itself


Drag and Drop

This came up in my Grub Gaunlet game from a tester. Originally I had buttons at the top that when you clicked them a new game element appeared in the middle of the screen. This worked and was fine for the game jam, but being able to drag and drop the object is more intuitive and feels a whole lot better. So how do you do that with a button (or image)? Three event handlers make this really easy.

This goes on the UI element and needs to have the prefab variable set in the inspector

First, when the pointer is down on the UI element a new prefab instance is created and the “objectBeingPlaced” variable is set. Setting this variable allows us to track and manipulate the object that is being placed.

Then when the pointer comes up objectBeingPlaced is set to null to effectively place the object.

But the real magic here is in the OnUpdateSelected function. This is called “every tick” - effectively working as an update function. To my understanding, this is only called while the object is selected - so this is no longer called once the pointer is up or at the very least when the next object is selected. I haven’t done any testing, but I’d guess there are slight performance gains using this approach vs. an update function on each button. Not to mention this just feels a whole lot cleaner.

Inside the OnUpdateSelected function, we check if objectBeingPlaced is null, if it’s not then we want to move the object. To move it we’re going to do some raycasting. To keep things simple, I’ll create a plane and raycast against it. This limits the movement to the plane, but I think that’ll cover most use cases.

This is SO much simpler and cleaner than what I’ve done in the past.

If you haven’t seen the Plane class, I just discovered it a few weeks back, the plane is defined by a normal vector and a point on the plane. It also has a built in raycast function which is much simpler to use than the physics raycaster - albeit also more limited in functionality.


Double Click

How about a double click? There are a LOT of solutions out there that are way more complex than what appears to be needed. All kinds of coroutines, updates, variables…. You just don’t need it. Unity gives us a built-in way to register click count. So let’s make use of it.

The real star of the show in the code is the OnPointerClick function and the PointerEventData that is passed into the function. here all we need to do is check if eventData.clickCount is equal to 2. If it is then there was a double click.

Could it be much easier?

In addition, this should work with UI and scene objects (need a physics raycaster) equally well.

The rest of the code presented just adds a bit of juice and some player feedback. We cache the scale of the object in the Start function. Then when the pointer enters the object we tween the scale up and likewise when the pointer exits we tween the scale back down to its original size.

As a side note registering the double click did not work for me with the new input system version 1.0.2. An update to 1.3 fixed the issue. There was no issue with the “old input system.”


Moving Scene Objects

Okay, so what if you want to move an object around in the scene, but that object is already in the scene? This is very similar to the example above, however (in my experience) we need an extra step.

We need to set the selected gameObject - without doing this the OnUpdateSelected function will not get called as the event system doesn’t seem to automatically set a scene object as selected.

Setting the selected object needs to happen in the OnPointerDown function. Then in the OnPointerUp function, the selected object gets set to null - this prevents any unwanted interactions from the object still being the “selected” object.

The other bit that I’ve added is the OnCancel function (and interface). This gets invoked when the player presses the cancel button - which by default is set as the escape key. If this is pressed I return the gameObject to its starting location and again set the selected object to null. This is a “nice to have” and really easy to add.

Dragging UI Objects

Who doesn’t like a draggable window? Once again these are easy to create using a handful of event handlers.

hierarchyLet’s get right to the star of the show, which is the OnBeginDrag and OnDrag functions. When the drag begins we want to calculate an offset between the pointer and the location of the object. This prevents the object from “snapping onto the pointer” which doesn’t feel great doubly so if the object is large.

Next, we need to set the object to be the last sibling. Since UI objects are drawn in the order that they are in the hierarchy this helps to ensure the object being dragged is on top. If you have a more complex UI structure you may need to get more clever with this and change the parent transform as well (we do this a bit in the next example).

Then!

In the OnDrag function, we simply we simply set the position (excuse the typo - no need for the double transform call) to the position of the pointer minus the offset. And that’s all it takes to drag a UI object.

But! I did add a bit more juice. The OnPointEnter and OnPointer Exit functions tween the scale of the object to give a little extra feedback. Then in OnEndDrag I play a simple SFX to give yet a bit more polish.

Drag and Drop “Inventory”

There is a Unity package with this prefab in the Github repo (link at the top)

Creating a full inventory system is much more complicated than this example. BUT! This example should be a good foundation for the UI part of an inventory system or a similar system that allows players to move UI objects. That said this is definitely the most complex of all the examples and it requires two classes. One is on the moveable object and the other is on the slot itself.

The UI structure also requires a bit of setup to work. In my case, I’ve used a grid (over there —>) with white slots (image) to drop in an item. The slots themselves have a vertical layout group - this helps snap the item into place and makes sure that it fills the slot.

Basic Setup of the Inventory Slot Object

Inventory Slot Component

The slots also have the “Inventory Slot” component attached. This is the simpler of the two bits of code so let’s start there.

The inventory slot makes use of the IDropHandler interface. This requires the OnDrop function - which gets called when another object gets dropped on it. In this case, all we want to do is set the parent of the object being dragged to the slot it was dropped on. And thankfully our event data has a reference to the object being dropped - once again keeping things clean and simple.

There are a ton of edge cases that aren’t addressed with this solution and are beyond the scope of this tutorial. For example: Checking if the slot is full. Limiting slots to certain types of objects. Stacking objects…

Okay. Now the more complicated bit. The inventory tile itself. The big idea here is we want to drag the tile around, keep it visible (last sibling) and we need to toggle off the raycast target while dragging so that the inventory slot can register the OnDrop event. Also, if the player stops dragging the item and it’s not on top of an inventory slot then we’re going to send the item back to its starting slot.

At the top, there are two variables. The first tracks the offset between the item and the pointer, just like in the previous example. The second will track which slot (parent) the item started in.

Then OnBeginDrag, we set the starting slot variable, set the parent to the root object (canvas) and set this object to the last sibling. These last two steps help to keep the item visible and dragging above other UI objects. We then cache the offset and set the raycast target to false. This needs to be set to false to ensure that OnDrop is called consistently on the inventory slot - i.e. it only gets called if the raycast can hit the slot and isn’t blocked by the object being dragged.

An important note on the raycast target: RaycastTarget needs to be set to false for all child objects too. In my case, I turned this off manually in the text object - but if you have a more complex object a Canvas Group component can be used to toggle this property for all child objects.

Moving on to the OnDrag function, this looks just like the example above, where we set the position of the object to the pointer position minus the offset.

Finally, the OnEndDrag function is where we need to toggle the raycastTarget back on so that we can move it again later. Also now that the dragging has ended we want to see if the current parent of the item is an inventory slot. If it is - it’s all good - if not we want to set the parent back to the starting slot. Because of the vertical layout group setting the parent will snap the position of the item back to it’s starting position. It’s worth noting that OnEndDrag (item) gets called after OnDrop (slot) which is why this works.

Note: I also added a SFX to the OnEndDrag. This is optional and can be done in a lot of different ways.

Pointer Event Data

I had hoped to go into a bit more detail on the Pointer Event Data class, but this post is already feeling a bit long. That said there is a ton of functionality in that class that can make adding functionality to Event Handlers so much easier. I’d also argue that a lot of the properties are mostly self explanatory. So I’ll cut and paste the basic documentation with a link to the page here.

Properties

button The InputButton for this event.

clickCount Number of clicks in a row.

clickTime The last time a click event was sent.

delta Pointer delta since last update.

dragging Determines whether the user is dragging the mouse or trackpad.

enterEventCamera The camera associated with the last OnPointerEnter event.

hovered List of objects in the hover stack.

lastPress The GameObject for the last press event.

pointerCurrentRaycast RaycastResult associated with the current event.

pointerDrag The object that is receiving OnDrag.

ointerEnter The object that received 'OnPointerEnter'.

pointerId Identification of the pointer.

pointerPress The GameObject that received the OnPointerDown.

pointerPressRaycast Returns the RaycastResult associated with a mouse click, gamepad button press or screen touch.

position Current pointer position.

pressEventCamera The camera associated with the last OnPointerPress event.

pressPosition The screen space coordinates of the last pointer click.

rawPointerPress The object that the press happened on even if it can not handle the press event.

scrollDelta The amount of scroll since the last update.u

seDragThreshold Should a drag threshold be used?

Public Methods

IsPointerMovingIs the pointer moving.

IsScrollingIs scroll being used on the input device.

Inherited Members

Properties

used Is the event used?

currentInputModule A reference to the BaseInputModule that sent this event.

selectedObject The object currently considered selected by the EventSystem.

Public Methods

Reset Reset the event.

Use Use the event.

Read More
Jeremy Wolf Jeremy Wolf

*Quitting a Job I Love

This has nothing to do with game development or the OWS YouTube channel. I’m writing this to get my thoughts out. Nothing more. Nothing less.

Here’s how it all turned out

I’m one of those lucky people. I have a job that I love. I really do. It’s an amazing job. I’ve taught just about every level of math from Algebra to Differential Equations. I’ve taught physics, robotics, game design, and an art class using Blender. I’ve spent countless hours each fall riding with and coaching the competitive mountain bike team. I’ve spent many winter days on the ski hill trying to convince students that carving a turn on skis is more fun than just “pointing it.” I’ve helped to build up the robotics team from nothing to a team that is competitive at the state level. Every spring and fall, I’ve packed up a bus full of mountain bikers and headed out on week-long trips to the Colorado and Utah desert or to the beautiful mountains of Crested Butte. It’s an amazing job. I have poured my heart and soul into this school.

I don’t want to quit. But the job has taken a toll. I am tired. I am exhausted. I am burned out.

My school has a policy of not counting hours. There is no year-end evaluation or mechanism for feedback. This means no one knows how hard we actually work. This means there is no limit to how much we work. This means we can be asked to do more at any time with little or no compensation.

The school board is painted a rosy picture by the administration. Most teachers have been here for over a decade and many for over 2 decades. But things are changing. We grumble in private. When we do approach the administration we are told we are doing a good job and this is just what it takes to work at a boarding school (and there is truth to that). But our concerns are wiped away with excessive positivity or seemingly ignored. It doesn’t feel good. At a school that is about community and relationships, there is little to none of that sense of community between the administration and teaching staff.

As a school, we pride ourselves, and justifiably so, on the strong relationships with our students, but after two years of a pandemic, no administrator has truly taken the time to see how I’m doing personally or professionally. They are stressed and overworked too. I think the presumption is if I haven’t quit I’m doing okay.

We are a “family” when the school needs something from us and when we need something from the school we are told we are being “transactional.” We sign a contract in February that binds us to the school until the next June. There is no meaningful negotiation. No way to earn more (beyond our annual 3% raise). No promotions. No way to adjust our workload. No way to move off-campus. The only lever we have to pull to change our situation is to quit. If we do quit, we lose a paycheck, housing, utilities, food, and health insurance. It is terrifying to make a change and few of us do.

During my time here I have seen kids who barely knew how to mountain bike become state champion racers. I’ve seen aimless students discover computer science or physics or art and find a reason to go to college. I’ve seen kids that have been bullied in previous schools find friends and community. I’ve watched countless students discover a sport that has given them confidence and a sense of belonging.

We do amazing things for students and I love being a small part of this school. But like so many schools this work is done on the backs of the teachers.

In many ways, we are a rudderless ship. I can’t tell you the last time I saw an administrator in the classroom building to observe let alone when I last had any meaningful feedback. I couldn’t tell you what the mission and vision of the school are. I can’t tell you the school’s goals - other than to provide for students in any way possible and to fundraise for new buildings. We seem increasingly driven by budget and money. While I’m sure that is not 100% fair or even true, that is what it feels like, and what things feel like can be just as important or even more so than what is actually true.

While there is so much good at our school, there also feels like there is willful blindness to what is not working or feeling good. Throwing spouses off insurance, cancelation of sabbatical, no published pay scale, poor maternity leave, worse paternity leave, ever-increasing expectations and workloads, and most of all the lack of voice. As teachers, as professionals, as members of the community, we want to be heard. We want to have some agency.

Again I love my job. I do. It pisses me off. It makes me angry. But I love it. Like any relationship, it’s flawed. That’s okay. I would love to find a way forward, a way to make the job sustainable and not feel emotionally drained and burned out. But relationships that only go one way are dysfunctional.

I believe there are many at the school who do truly care about staff, but they are overworked and hamstrung by policies that make sense on paper but that forget that we are people, not cogs in a machine.

I have slowly come to peace with the situation. I am not entitled to having the school change. I can’t make the school change. All I can do is control how I react and what I do.

With a tear in my eye and a lump in my throat, I am pulling the only lever I have to pull. I am quitting.

Read More

Older Posts