Ep 4: The art of ship displacement. Setting Delegates and Events in Unity, clicking behavior components and a queue of actions for moving ships.
Helloooooo fellow ship spawners! You are arriving again to the dark and twisted devlog dimension for the development of Omega Gateway: WIP 4x sci fi game made by and for fans of classics like Master of Orion 2 and Star Trek: Birth of the Federation.
On the last log entry we managed to spawn a Ship entity using our newly drawn ugly UI. The existence of that ship is represented by the player’s icon on the sector that it is in.
Today we are going to work on getting that ship moving, but for that we need to set a few things in place first.
First of all, a event system for the turns:
I started the development trying out a Observer pattern structure for handling the turns:
This means that whenever we press the “next turn” button, this object - most likely the GameManager - will be in charge of calling all the subscribers in a list and letting them know through an interface that the turn has passed.
I almost went this way, but then I noticed that Unity has a system in place for this exact behavior: Unity’s Delegates.
It works basically the same, the only difference is that the “notifier” class has to have a delegate object and an event object.
Subscribers that want to know when a turn has changed have to add a method directly to it, and whenever it is called every subscriber will be called with the parameters you set.
For the UIManager for example, this could be the turn number, so we can change in the top right, like this:
With that out of the way, let's build some components to support selecting things and hovering on things.
I was playing around with several ideas over the week to see what’s the best way to handle these two things and keep the code clean.
So after some deliberation, this is the plan: For elements that show info when they are hovered, I’m just adding collider components and an OnMouseEnter and OnMouseExit actions on every class that need one.
I thought about going with an Interface like “iHoverable” but then I realized it didn't make a lot of sense for the limited amount of code we are adding here.
For the time being, when you are hovering one of these classes, you call the respective method and from that one the UI manager to set the info on a new textbox on the bottom of the screen. It's currently looking like this
I really like how it looks.
We can also set a hide method on the OnMouseExit so we clean that info box when we leave the object.
But now, going to selecting stuff: This one is more interesting, for several reasons:
First, selecting things will show more than a small info text; in fact, it should open its own info panel with its own characteristics: for instance, when selecting a System, it should show the planets it has and what features those have.
When selecting a fleet, it should show its composition, available actions and the possibility to move it around the map.
So this time, we ARE going to go for an interface… an ISelectable interface. Everything that can be selected has to implement this.
We are also going to build a component for selecting stuff and to hold the common behaviour of the art of selecting: like identifying if you are left or right clicking.
We will call that SelectableEntity, and the first thing it will try when it starts will be to get the ISelectable component: if it doesn't find it, then it shows an error. So you can't just add this on any object, it needs to implement that interface.
Then, the behaviour when clicking: We will use Unity’s EventSystem for understanding if the cursor is on top of a specific object.
Again, its showing a red underline but I promise its working.
So, If we left click on an ISelectable - so its Input dot GetMouseButtonDown ZERO - we are going to call one of the interface methods: OnSelect.
We will also do the same on the right click: mouse button down 1; and we will call the other method of the interface, OnAction.
So, any entity that wants to support being selected and being right clicked has to implement the interface ISelectable; and thus, has to override the methods OnSelect and OnAction.
In the future, as we mentioned, we will add some specific UI that will be shown for specific OnSelect actions, but let's work on moving the ships first.
Before that, a small change on how we define ships: In a past episode we created an abstract Ship Class and we were going to extend ship types by inheriting that class and adding particular behaviour.
However, we are changing the approach: Ship will be a concrete class, and every ship variation will be a different prefab variariant with different values and different class components if needed. This will allow us to take full advantage of Unity’s instantiation features and setting different models way more easily.
So, let's add the onselect behaviour to the ships, and for that we will add the new ISelectable interface to our ShipPresenceIndicator. We must implement the OnSelect and OnAction methods.
We can leave OnAction empty for now; but for onselect, we will create a small field on the game manager to support the idea of a “selected Iselectable”.
This will allow us to get what's selected from anywhere on the code, given that our game manager is a singleton.
So on our Onselect on the Ship indicator we just set itself as the new selected item.
Now, although the behaviour starts from the ship's presence, the action happens when you hover over a Sector:
So, on the OnHover method in the Galaxy Sector class (called by the OnMouseEnter event we defined earlier) we will call the game manager to get the selected ISelectable.
Then, we will use the IS keyword to understand if that ISelected is in fact a ShipIndicator. If it is, we will call a new method to handle a selected fleet.
To test if this works, we will draw a small circle in the middle of the sector and show it only if we are hovering over the sector, and hide it if we are not or if we don't have a ship indicator selected. As you can see, this works wonderfully.
Let’s get our ship to move, shall we? I have been doing some thinking about what’s the best way to execute the order between turns for ships, especially when having a long list of coordinates to follow.
Where should these “parameters” live? The plan will be the following: We will create an abstract ShipAction class that will hold a Dictionary with actions as keys, and an object as a value.
These will be the parameters that our action will read. We will also add an abstract doAction method that the concrete action type inheriting from ShipAction will have to implement with its respective parameters.
Then, we will create the MoveAction class that inherits from ShipAction: The constructor will just take the ship and the parameters, but the doAction method that we are overriding will do the magic.
Fair warning though: even when using the most of polymorphism with the object values like this, there are more elegant ways than passing on a dictionary with simple strings as keys, but for the time being this will test if this structure holds the test of time… and debugging.
But because we are using simple strings as keys and simple objects as values, we need to verify if we have everything we need to move the ship, so we check if among the keys of the parameters we have a “location” string, and if that key is a GalacticCoordinate class object.
This structural validation is all we do here, but the business logic (like for instance, if the coordinate is a valid spot for moving into with this ship) will be done on the ship logic itself.
If everything is valid, we call the move function of the ship with the coordinate as a parameter. Of course, we need to define it first.
It will be pretty simple though: Given that most of the heavy lifting regarding how we render the ships are handled already on the sector and the shipindicator class, we can take the ship from one sector and put it into another.
We will define the shipleftSector and shipArrivedToSector methods on the GalaxySector class, but as you can see, they just add or remove the ship, and then they call the function to render the ship presence icon if needed.
We are just missing one thing: how to pass the move action! Remember that the ISelectable interface also has an abstract method for right clicking? Let’s mess with that.
So, same way we did on the OnHover method, on the OnAction method we will check if we have a selected item from the Game Manager and check if it is a ShipIndicator.
We will wrap the code in this small private method that will just take the coordinate of the sector in a list and push it into a method for the indicator class to handle the action.
Why a list, you may be asking? Right now we are moving to one coordinate, but in the future we want to queue several ones, with a pathfinding algorithm. * spoilers *
Before we go to implement the movement though, we forgot to add something to hold the ShipActions we defined before on the ship:
Given that these actions make their effect once when you change turns, we can define a Queue of ShipActions as a new attribute in the Ship class.
Then we will have some simple methods to play with this: A queue action that just adds an action to the queue and a clear actions to clear them.
We must also not forget to subscribe the Ship class to the Game Manager’s on Next turn event with our own method, that will take the first action from the queue and execute it.
Sooooo, now that we can queue actions for ships, on the ShipIndicator class we can define the handleFleetMovement method. Here is where we should address things like the speed of the ship, but we can do that later.
For now, we will iterate through every coordinate we are sending as a path, and for each one we do an elegant call on a method using ForEach LinQ . We will tell all the ships in the list to clear their action queues, and then to add a new MoveAction.
This move action will have the ship itself as a parameter, and the Dictionary of parameters, defined previously with just the string “location” as a key and the coordinate we are going to as a value.
That should be it! The ship moves from one sector to the other between turns: The ship indicator checks for ships in the sector and renders the player owner's icon if there is one.
There is a clear business logic issue here though: the ships are teleporting to the indicated sector, instead of drawing a path there.
But worry not, my friends. We will implement a pathfinding A star algorithm in the next episode, and this will find the quickest way to reach a sector ignoring all the non “walkable” ones.
Thanks again for reading!
Don't forget that you can watch the video version of the log right here:
Goodbye my ship captains; stay safe and see you on the next episode!