UnityLearn – Beginner Programming – Finite State Machine – Pt. 02 – Finite State Machines

Novemeber 18, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Finite State Machines

Building the Machine

This part of the tutorial has a lot more hands on parts, so I skip some of the sections for note purposes when there is not much substance to them other than following inputs.

A key in finite state machines is that an object can only ever be in exactly one state at a time. This helps ensure each state be completely self contained.

Elements of a Finite State Machine:
Context: maintains an instance of a concrete state as the current state
Abstract State: defines an interface which encapsulates behaviors common to all concrete states
Concrete State: implements behaviors specific to a particular state of context

To get started in the tutorial, they created a public abstract class PlayerBaseState which will be the abstract state for this example. PlayerController_FSM is the context. They note that while in this case all the abstract state methods take the PlayerController_FSM (the context) in as a parameter in this case, that does not necessarily have to be the case for the general FSM pattern.

Concrete States

It is noted that the context in a FSM needs to hold a reference to a concrete state as the current state. This is done in the example by creating a variable which holds that of the type that is our abstract state, which is PlayerBaseState in this case. They then create a method called TransitionToState which takes a PlayerBaseState in as a parameter. It then sets the currentState to that parameter state, and then calls the new state’s EnterState method (all states have this method as it is dictated by the abstract class they all implement). This determines what actions should be done immediately upon entering this new state.

Example:

public void TransitionToState(PlayerBaseState state)
{
currentState = state;
currentState.EnterState(this);
}

The tutorial also shows a way to take control of the context’s general Unity methods and pass the work on to the concrete states instead. This example did this with Update and OnCollisionEnter. The abstract state, and in turn, all of the concrete states, have their own Update and OnCollisionEnter method. The context, PlayerController_FSM, then simply calls currentState.Update(this) in its Update method, and currentState.OnCollisionEnter(this) in its OnCollisionEnter method, so that the current concrete state’s logic for these methods are used without flooding the context itself with any more code.

Since it is necessary that your context has some initial state, they do this by simply calling the TransitionToState method within the Start method and entering the IdleState. IdleState is the initial state for this case.

Beginning the Implementation

Important benefits seen using this system:
While working on the concrete classes themselves, we never needed to go back to the PlayerController_FSM class (the context) to modify any code there. The entire behvior is handled within the concrete states and is abstracted from the character controller (the context). Setting expressions was much easier as no checks are needed and this can just be set in the EnterState method of each concrete state.

It is already clear that this method removes a lot of boolean checks from the overall code, and helps organize the code by ensuring any logic about a state is contained within the class for that state itself (with less bleeding into the code of other states).

Continuing the Implementation

It is worth noting that PlayerController_FSM holds a reference to every concrete state except the spinning state. This was done because they actually have the jumping state create a new spinning state on transition each time it is invoked. They apparently do this so that the local field for rotation within the spinning state is reset to 0 each time it is called, but it seems like there would be other ways to do this that seem less wasteful (such as resetting it to 0 when exiting the state). I am also not sure if this is intended behavior, but the spin also immediately cancels upon contacting the ground (resetting the player rotation to 0) with this setup, where as in the previous behavioral setup the spin completed even if the player contacted the ground.

Module Conclusion

Benefits of FSM:

  • More modular
  • Easier to read and maintain
  • Less difficult to debug
  • More extensible

Cons of FSM:

  • Take time to setup initially
  • More moving parts
  • Potentially less performant

Just something very notable with this approach, it seems much harder for me to break than the naive implementation. If I spammed key presses (like pressing jump and duck a lot) with the naive approach, sometimes I could break the system and have the player stuck in the duck position or be ducking while jumping. I have not been able to break it at all with the full FSM setup, which makes sense since transition behaviors solely exist within the states themselves so these inputs cannot be jumbled in any way.

SUMMARY

Using state machine systems appear way easier to use and build on than the “naive” approach of basic boolean behaviors (with lots of if statements and boolean checks). Not only was I very excited about how much easier this appears to work as a scalable option, it also just worked better and more cleanly when it was all put together.

The other version had small bugs that would pop up if you spammed all the different action keys (such as getting stuck ducking or being ducked in a jump), which were possible just because the key presses would get recorded before reaching the bools or if statements that should be telling them that they are not proper options. These very separated states make that type of error impossible as it is only concerned with a single state at a time.

This type of system just seems much cleaner, more organized, and less error prone than what I have done before and I am very excited to try and build a system like this for my own project (for both players and enemy AI).

UnityLearn – Beginner Programming – Finite State Machine – Pt. 01 – Managing State

Novemeber 15, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Managing State

Project Overview

This part of the tutorial has a lot more hands on parts, so I skip some of the sections for note purposes when there is not much substance to them other than following inputs.

The basics covered in this section are:
What is state and how to manage it
Finite State Machine pattern
Build your own finite state machine

Introduction

State: condition of something variable
State Examples: Game state, Player state, NPC State

Finite State Machine: abstract machine that can be in exactly one of a finite number of states at any given time

Parts of a Finite State Machine:
  • List of possible states
  • Conditions for transitioning between those states
  • State its in when initialized (initial state)

Naive Approach to Managing State

This naive approach focuses on boolean states and if staements. It uses a lot of if and else if statements in the Update method to determine what state the player is in and if/when/how they can switch to another state. Even with two states this becomes tedious and somewhat difficult to read. This example is just to emphasize the use of proper finite state machines.

Actions, Triggers, & Conditions

Look at your actions as a set of: actions, triggers, conditions.
Example for Arthur jumping:

  • Actions: Arthur jumps; jumping expression
  • Triggers: Spacebar is pressed
  • Conditions: Arthur is not jumping

Continuing to follow the naive state management approach, we see that everytime we add a new state it makes all snippets about other states more complex and harder to follow. This is very clear that this will become unmanagealbe with only a few states even.

Module Overview

The biggest issue with the naive approach is the interdependent logic of the various states. It makes each state exponentially harder to work with with every state that is added, so it is very limited on its scalability. This does not even come with a benefit to readability, as it also becomes difficult to read quickly.

SUMMARY

Using the naive approach (boolean fields and if/else statements) to manage state is only really useable for extremely simple cases. As soon as you reach 3 or 4 states with even small amounts of logic to manage them, this approach becomes very awkward and unwieldy. Fininte State Machines should hopefully open up a better way to manage more states with better scalability and allow for more complexity with better readability.

Programming A* in Unity

November 14, 2019

A* Programming

Unity

A* Pathfinding (E01: algorithm explanation)

Tutorial #1 – Link

By: Sebastian Lague


Unity – A Star Pathfinding Tutorial

Tutorial #2 – Link

By: Daniel


Unity3D – How to Code Astar A*

Tutorial #3 – Link

By: Coding With Unity


I have used A* before, but I would like to learn how to setup my own system using it so that I can make my own alterations to it. I would like to explore some of the AI methods and techniques I have discovered in my AI class and use that to alter a foundational pathfinding system built with A* to come up with some interesting ways to influence pathfinding in games. I would eventually like to have the AI “learn” from the player’s patterns in some way to influenece their overall “A* type” pathfinding to find different ways to approach or avoid the player.

UnityLearn – Beginner Programming – Observer Pattern – Pt. 03 – The Observer Pattern

Novemeber 7, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

The Observer Pattern

The Observer Pattern

Observer Pattern: software design pattern in which an object, called the subject, maintains a list of dependents, called observers, and notifies them of any state change, usually by calling one of their methods

Observer Pattern Anatomy

Subject

  • collection of observers
  • method: AddObserver
  • method: RemoveObserver
  • method: NotifyObservers

Observer

  • method: Notify (what to do when notified)

An interface is a good way to ensure observers have the types of methods you need to exist when notified by the subject.

Implementing the Observer Pattern

This was an example of how to setup a basic observer pattern using the example project. First they created the interface for the observers, which was named IEndGameObserver. This simply held an empty method named Notify().

They then used the GameSceneController script as the subject for the observer pattern. To do so, they created a list of IEndGameObserver objects, created a method AddObserver which could add to that list, created a method RemoveObserver which could remove from that list, and finally a method called NotifyObservers that was a simple foreach loop that went through each IEndGameObserver in the list and called their Notify() method.

NotifyObservers then just needs to be called when the event or state is reached where the observers need to be informed that a change has occurred. Since this example was to inform objects of when the game ended, NotifyObservers was called within the GameSceneController script when the player ran out of lives.

Concrete Observers

Concrete Observers: just objects that implement the observer interface

This example showed that with this setup, either the observer or the subject can be used to add observers to the subject’s collection of observers. The HUDController already had a reference to the GameSceneController (subject) so it made sense to just have it add itself to the observer collection through that reference.

While the EnemyController and PowerupController do not have reference to the GameSceneController, the GameSceneController has references to them created on instantiating them. These could then be used with the GameSceneController to add them to the observer collection upon instantiation.

***Could possibly use this observer pattern in my thesis project to easily collect all of the various Create classes into the Scenario classes (and potentially again to collect the Scenario classes into the SystemManager).

Removing Observers

To avoid nullreferenceexceptions, you must ensure that observers that are destroyed are also removed from the subject’s collection of observers.

To apply this to the example, we added a method called RemoveAndDestroy to both the PowerupController and the EnemyController class which both removed it from the observer collection and destroyed it. This method was exactly the same for both, so this could indicate that it should be added to the interface itself to keep it consistent amongst all observers using this interface.

The Observer Pattern and C# Events

Example Problem with C# Events:
The ProjectileController has an event that occurs when it reaches the bounds of the screen. The PlayerController subscribes to this event by adding one of its methods to this event. There are some cases where the projectile exists while the player is destroyed, so when the projectile goes out of bounds (and calls its event), it tries to invoke a method from the destroyed PlayerController class, which gives an error.

Similar to the observer pattern where we want to remove observers from the collection when they are destroyed to prevent errors, we want to unsubscribe classes from events when they are destroyed. This ensures that they are revmoved from the events invocation list so that they do not try to call methods of destroyed objects which will create errors.

The Publisher Subscriber Pattern

Publisher Subscriber Pattern: similar to Observer Pattern, but adds another entity, the broker, which is in charge of keeping track of and notifying subscribers.

Observer Pattern has a single subject, but publisher subscriber pattern can have any number of publishers. Each of these publishers has a reference to the broker to notify it when a noteworthy happening occurs.

Example:
They created a class called EventBroker that just contained a public static event ProjectileOutOfBounds and a public static method named CallProjectileOutOfBounds (which just checked that the event wasn’t empty and then called that event). The PlayerController subscribes to this event by adding its EnableProjectile method to it on Start (and removes it OnDisable), both of which are very direct since everything is public static in EventBroker. Finally, the ProjectileController invokes the event CallProjectileOutOfBounds when the projectile leaves the screen, which now invokes the PlayerController’s EnableProjectile method.

This setup allows the ProjectileController to effectively communicate with the PlayerController without references to each other in anyway by going through the EventBroker.

SUMMARY

Using delegates, events, and actions can provide a clean and effective way to communicate important happenings throughout many objects and have them act accordingly. The Observer Pattern uses a single subject and multiple observers which receive information from the subject when to do something. The Publisher Subscriber system is similar, but adds a middle man entity of a broker which communicates the information between the publisher and subscribers without the publishers or subscribers needing any direct reference between each other.

HFFWS Thesis Project – Theory for General Generation System Architecture

November 6, 2019

HFFWS Puzzle Generation System

General System Architecture

General Notes

Pulley Varieties

I wanted to start with sketching out the general structure of a single puzzle type to help me get a better idea of what a useful general structure might look like for the overall puzzle generating system I want to make.

  • Hook on an end to latch onto other objects
  • Object to move can be used as a platform
  • Open heavy door
  • Focus on rotation of wheel
  • Build a pulley
  • (Rope puzzle) Move two rope conjoined objects somewhere to accomplish a goal

Breakdown of what I can currently alter/generate in rope/pulley tool

  • Objects (at ends of rope)
  • Objects (for wheels)
  • Length (of rope)
  • Position (of rope)
  • Position(s) (of wheel(s))

I then quickly sketched out what I could change with the current controllable parameters to match the various puzzle variations (I only did the first three for now just to work it into testing quicker).

Hook on end

  • Objects at ends
    • Hook
    • Grabbing Mass (i.e. small sphere)
  • Outside objects
    • Objects that need pulled by hook

Objects attached can be used as platforms

  • Objects at ends
    • Platform type objects
  • Outside objects
    • Mass objects to influence pulley movement

Open heavy door

  • Objects at ends
    • Heavy Door
  • Outside objects
    • Mass objects to influence pulley movement

Architecture Design (1st Pass)

The overall flow of information is:
GenerationManager -> ScenarioManager -> “Create” type classes

GenerationManager

Passes down highest level information to ScenarioManager. This will include puzzle type, variation type within that puzzle type, and any other high level information such as overall positioning or how many scenarios in total to generate.

ScenarioManager

Will take that information and use it to determine which “Create” type classes will need to be used (or have the option of being used), how many times to use each, and what other information needs to be passed on to them (the “Create” type classes) to properly build out the desired scenario.

“Create” type Class

Use the information passed down to them from the ScenarioManager to create objects that satisfy the needs of the desired scenario. Within these bounds, in then adds variation to what it creates to vary its creations while still meeting the requirements given by the ScenarioManager

All the “Create” type classes will need an interface or inheritance of some kind to make them all similar types of objects to make grouping them and using them a bit easier.

UnityLearn – Beginner Programming – Observer Pattern – Pt. 02 – Working with Multiple Subscriptions

Novemeber 5, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Working with Multiple Subscriptions

Creating a Parameterized Event

Parameterized Event: an event that takes in at least one parameter

Points of Reference

A subscriber must have a reference to the publisher (class raising the event) in order to be able to subscribe to that event.

The general pattern for making delegates and events from how they have done it so far is that you create the overall delegate type that you will want the events to use outside of a class definition and public (so that all classes that want to make an event of this delegate type can use this single delegate type as a reference). The events themselves are then within the class definition, and use that newly created delegate type.

Example Breakdown

I broke down the tutorial example in order to better understand it.

The parameterized event is created in the EnemyController script. This is done by creating a delegate called EnemyDestroyedHandler outside of the class definition in the EnemyController script (this delegate is of type void and takes an int parameter, so any event using this must do follow the same signature). They then created a public event of type EnemyDestroyedHandler named EnemyDestroyed within the class definition of EnemyController.

Since subscribers need references to the publishers in order to subscribe to their events, they go to the GameSceneController class to subscribe to the EnemyDestroyed event since it already has references to the EnemyController class in its spawning methods. Here they add a method (Enemy_EnemyDestroyed, the default name) from within the GameSceneController class to the EnemyDestroyed event. It matches the signature (return type void, with a single paramter of type int). Now, when the EnemyDestroyed event is called in the EnemyController script, the parameter input given there is the same parameter input used for all methods subscribed to that event. So in EnemyController, EnemyDestroyed(pointValue) ends up calling Enemy_EnemyDestroyed(pointValue) from within the GameSceneController class.

The GameSceneController also creates its own event of type EnemyDestroyedHandler named ScoreUpdatedOnKill. This uses the same delegate type created in the EnemyController script, so they just need to create the event here in the GameSceneController script. It should be noted that this can be done simply because they are using the same signature delegate type here, it does not necessarily even have to do with the fact that its a similar game related event. This event is then called within the Enemy_EnemyDestroyed method, which is subscribed to the EnemyController EnemyDestroyed event already. So this creates a chain of method calls from a single event.

This event, ScoreUpdatedOnKill, is used to update the HUD score value. To accomplish this, we need the HUDController class to subscribe to the event. So they go to the HUDController and create a reference to the GameSceneController class (since subscribers must have a reference to the publisher). Since the GameSceneController is an object that will persist throughout the entirety of the game, they simply create the reference to it and subscribe to the event within the GameSceneController within the Start method of the HUDController.

ScoreUpdatedOnKill, the GameSceneController event, is given the method GameSceneController_ScoreUpdatedOnKill from the HUDController. This method simply calls the method UpdateScore which changes the text to match the new score.

It is important to note that since this is a chain of events, as opposed to multiple subscribers subscribing to a single event, the value passed throughout the chain can change. The EnemyDestroyed event in EnemyController uses the int pointValue, so that the GameSceneController method Enemy_EnemyDestroyed subscribed to it adds that value to the totalPoints, which is held within the GameSceneController. The GameSceneController then passes totalPoints (not pointValue) into its event, ScoreUpdatedOnKill, which is the event the HUDController subscribes to in order to update the score. This makes sure it displays the new total score as opposed to just the value for the last score gained.

I wanted to make this difference clear since I also noted that multiple subscribers to a single parameterized event will all use the same parameters. This chain of events simply use the same delegate type because they just happen to want to use the same signature (return type void with a single int parameter) and nothing more.

Multiple Subscribers

You can have several classes subscribe to the same event.

What was weird for this example was that they just wanted to reenable the firing mechanism for the player when an enemy was destroyed, using the same ScoreUpdatedOnKill event. This event requires a method of return type void with an int parameter. So they made a method satisfying this, but it doesn’t use the int parameter at all. It simply calls another method that is just void return type with no input parameter, EnableProjectile (which lets the player fire again). Since you can simply subscribe to an event with a method that just calls another method, your method actually doing work does not particularly have to match the signature of the original delegate type.

Conclusion

Actions (C#): types in the System namespace that allow you to encapsulate methods without explicitly defining a delegate

Actions are inherently delegate types so they keep you from having to separately define a delegate when creating an event. They mention that they will just use actions from here on for the course, so I assume this is generally better and cleaner practice this way. This does make more sense with my recent understanding that the delegate signature type is not particularly that crucial for the actual outcome when dealing with this subscriber and publisher pattern.

SUMMARY

Subscribers need a reference to their publisher in order to use their events, so look for places where you are already creating this reference for improved efficiency. An event can have multiple subscribers, so calling that event can call multiple methods from multiple objects. You can also create chains of actions that call methods while starting other events. Actions are a more compact and cleaner way to create an event without the need for separately creating the delegate.

UnityLearn – Beginner Programming – Observer Pattern – Pt. 01 – Handling Events

Novemeber 4, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Handling Events

The Demo Project

This section focused on handling inter-object communications. They covered direct object calls, tight coupling, and loose coupling. This topic was covered using mostly delegates and events.

Events: something that happens within the context of one object to be communicated to other

The first objective of the tutorial was to restric the rate of fire of the player ship so that it could only have one projectile on the screen at any time. They could not fire again until that projectile was destroyed (which in this case, only occurred once it left the screen).

Direct Object Calls

Direct Object Calls: directly make a call from within one object to a method in another object (usually through public methods)

Example: The ship needed to be able to fire again when the projectile reached the end of the screen. The projectile used a reference to the PlayerController to call a method within it when the projectile reached the end of the screen to reenable the firing mechanism.

Costs: This forces you to expose a lot of methods within objects so that others may access them.

Tight Coupling

Tight Coupling: making objects dependent on one another; can be done by having one object call a method found in another object

Costs:

  • Difficult to maintain and debug
  • Not easily scalable
  • Impedes collaboration (like Git or Collab)
  • Complicates Unit Testing

Delegates and Events

This section looks to use delegates and events to get around the issues found with direct object calls and tight coupling. These allow for loose coupling.

Important Notes
  • One way to think of delegates is that they are method variables.
  • All events have an underlying delegate type.

Example: The ProjectileController script created a public delegate and public event. It then called that event within its code somewhere (which would then execute any methods assigned to that event). The PlayerController assigned one of its methods to that public event when instantiating the projectile. The ProjectileController could then effectively call that PlayerController method by calling the event within it.

SUMMARY

Delegates and events can provide a nice way to create loose coupling, which can lead to better and easier to maintain ways to allow your objects to communicate between one another. Be careful with direct object calls and tight coupling as they can reduce encapsulation and lead to tricky debugging problems down the road.

UnityLearn – Beginner Programming – Tips & Considerations – Pt. 05

October 29, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Tips & Considerations

Unity’s Order of Events

Unity’s Order of Events:
Awake : OnEnable : Start : Update : OnDisable : OnDestroy

  • Awake: first function called when object is instantiated; true whether added to scene in editor or instantiated in code
  • OnEnable: event; fired when enabled gameObject is instantiated or when a disabled object is enabled
  • Start: after Awake, and OnEnable, but before the 1st frame update
  • Update: happens every frame after initialization methods
    • FixedUpdate: frame-independent and occurs before physics calculations are performed
    • LateUpdate: called once per frame after update method has completed execution
  • OnDisable: event; fires when object is disabled, or before it is destroyed
  • OnDestroy: execute when object is destroyed in code, or when scene containing it is unloaded

Reference Caching: creating a field for a reference and getting that reference once during initialization to use whenever that object is needed

This information is very useful to understand when you start referencing a lot of objects throughout your code and have a lot of pieces working together. This will help with avoiding errors, and debugging when those types of issues do come up. This is something I ran into a lot when working on my scene manager project as getting references was a bit of a pain and the timing was very crucial.

Using Attributes

Attributes (C#): powerful method of associating metadata, or declarative information, with code
Unity provides number of attributes to avoid mistakes and enhance functionality of inspector.

Some common attributes used in Unity include:

  • SerializeField: makes a field accessible to the inspector
  • Range
  • Header
  • Space
  • RequireComponent

Public fields are inherently accessible by the inspector, but many other access modifiers hide the field from the inspector (like private). To get around this, you add the SerializeField attribute.

The RequireComponent attribute takes in a type and automatically adds that component type to the same gameObject whenever this object is placed. It also ensures that the other component cannot be removed while this component exists on the gameObject.

UnityLearn – Beginner Programming – Delegates, Events, and Actions – Pt. 04

October 25, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Delegates, Events, and Actions

Delegates

Delegate: in C#, a type designed to hold a reference to a method in a delegate object

  • Delgates are created using the “delegate” keyword.
  • They are defined by their signature, meaning their return type.
  • Finally they have parameters which they take in, similar to methods.

Using a delegate allowed us to parameterize a method. Using the delegate as a parameter for a method also allows us to use any method which matches that delegate’s signature to satisfy the parameter.

These are some snippets from two scripts, GameSceneController and EnemyController, that show some basics of utilizing delegates:

– In GameSceneController script
public delegate void TextOutputHandler(string text);

public void OutputText (string output)
{
Debug.LogFormat(“{0} output by GameSceneController”, output);
}

– In EnemyController script
void Update()
{
MoveEnemy(gameSceneController.OutputText);
}

private void MoveEnemy(TextOutputHandler outputHandler)
{
transform.Translate(Vector2.down * Time.deltaTime, Space.World);

float bottom = transform.position.y – halfHeight;

if(bottom <= -gameSceneController.screenBounds.y)
{
outputHandler(“Enemy at bottom”);
gameSceneController.KillObject(this);
}
}

For example, in our case we created a public void delegate with a string parameter called TextOutputHandler. Then another one of our methods, MoveEnemy, took a TextOutputHandler as a parameter, named outputHandler. Any method matching the signature of the delegate (in this case, public void with input parameter string) can satisfy the input parameter for the MoveEnemy method. As can be seen in the example, whatever method is passed in will be given the string “Enemy at bottom”.

A delegate used this way is commonly known as a “Callback”.

Events

C# Events: enable a class or object to notify other classes or objects when something of interest occurs.
Publisher: class that sends the event
Subscriber: class that receives/handles the event

Things like Unity’s UI elements use events inherently. For example, the Button script uses events to tell scripts when to activate when a button is clicked. This is different from a basic way of doing inputs which checks every frame if a button is being pressed (which is called “polling”). This is also why creating UI elements in Unity automatically creates an EventSystem object for you.

In C#, events are declared using the “event” keyword, and all events have an underlying delegate type.
C# events are multicast delegates.
Multicast delegate: delegate that can reference multiple methods

To help with my understanding, I tried testing the setup without having an EnemyController parameter to see why it was needed. I discovered it was necessary to pass along the reference so the small event system knew which object to destroy when calling the EnemyAtBottom method. Using Destroy(this.gameObject) or Destroy(gameObject) both just destroyed the SceneController as opposed to the individual enemies. This also helped me understand that adding a method to an event does not pass the method over as an equivalent, it simply means that when that event is called, that any methods assigned to it are also called in their current location in a class. So even though the event was being called in the EnemyController script, the method I added to it was still called within the GameSceneController script, which makes sense.

Example:

In EnemyController script


public delegate void EnemyEscapedHandler(EnemyController enemy);

public class EnemyController : Shape, IKillable
{
public event EnemyEscapedHandler EnemyEscaped;

void Update()
{
MoveEnemy();
}

private void MoveEnemy()
{
transform.Translate(Vector2.down * Time.deltaTime, Space.World);

float bottom = transform.position.y – halfHeight;

if(bottom <= -gameSceneController.screenBounds.y)
{
if(EnemyEscaped != null)
{
EnemyEscaped(this);
}
// Can be simplified to:
// EnemyEscaped?.Invoke(this);
}
}
}

In GameSceneController script:


public class GameSceneController : MonoBehaviour
{
private IEnumerator SpawnEnemies()
{
WaitForSeconds wait = new WaitForSeconds(2);

while (true)
{
float horizontalPosition = Random.Range(-screenBounds.x, screenBounds.x);
Vector2 spawnPosition = new Vector2(horizontalPosition, screenBounds.y);

EnemyController enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);

enemy.EnemyEscaped += EnemyAtBottom;

yield return wait;
}
}

private void EnemyAtBottom(EnemyController enemy)
{
Destroy(enemy.gameObject);
Debug.Log(“Enemy escaped”);
}
}

I’ve simplified the scripts down to just the parts dealing with the events to make it easier to follow. As I understand it, we create the delegate: public delegate void EnemyEscapedHandler(EnemyController enemy) in the EnemyController script (but outside of the EnemyController class). Within the EnemyController class, we create an event of the type EnemyEscapedHandler, so this event can take on methods with the same signature as EnemyEscapedHandler. Within the MoveEnemy method, we invoke the EnemyEscaped event and satisfy its parameters by passing in this, which is the unique instance of the EnemyController script (after checking that there is a method assigned to this event).

Then in the GameSceneController script, we see that when we instantiate an enemy, we keep a reference to its EnemyController script. This is to assign the EnemyAtBottom method to each one’s EnemyEscaped event. Now anytime EnemyEscaped is called in the EnemyController script, it will then call the EnemyAtBottom script here, passing whatever parameter it (EnemyEscaped) has to the parameter for EnemyAtBottom. In this case, passing this in EnemyEscaped ensures that EnemyAtBottom knows which enemy to destroy.

Actions

Actions (C#): types in the System namespace that allow you to encapsulate methods without explicitly defining a delegate
In fact, they are delegates
Actions can be generic

An Action is just an event delegate that does not need another delegate to be created first to be used as a reference. The Action itself determines what parameters are necessary for the passed methods.

Thesis Project – Concepts for System to Control Script Timings

October 24, 2019

Working with the HFFWS

System for Properly Timing Running of Scripts

Discovering the Timing Issues with the HFFWS Tools

While working with the Rope script in the Human API, I was again encountering timing issues when trying to accomplish tasks through script. I was having trouble instantiating and connecting rigid bodies to the ends of the rope in script. I ran several tests to confirm the timing issues I was having again.

Test #1
  • 2 Rigid Body objects exist in scene
  • Script has 2 gameObject references for those objects
  • Script sets startBody and endBody references to those objects at Start

This did not work. The references could be seen as being properly assigned in the Rope script in the editor, but that connection was being made too late and having no affect on the objects.

Test #2
  • 2 Rigid Body objects exist in scene
  • Script has 2 gameObject references for those objects
  • Script sets startBody and endBody references to those objects at Awake

This did have the intended result of connecting the rigid body objects to the rope.

Test #3
  • All done in Awake
  • 2 Rigid Body objects instantiated into scene
  • Script sets startBody and endBody references to those objects at Awake

This did work once, although I had some issues repeating it afterward. The objects were prefabs which were instantiated in Awake. GameObject references were created for these, and used for the values of the Rope script startBody and endBody, all done in Awake as well.

Test #4
  • Start or Awake is irrelevant here
  • Object with Rope script starts deactivated
  • 2 Rigid Body objects instantiated into scene
  • Script sets startBody and endBody references to those objects
  • Rope object is then activated

This is the best solution that works, as it can be done in Awake or Start. This leads to a much more controllable environment which is ideal for our system. This will be the foundation for the main systematic approach.

System for Controlling Awake Calls for HFFWS Objects

Deactivating/Activating Objects or Enabling/Disabling Scripts

The combinations of all of these tests, most notably Test #4, showed that some important connections are made during the Awake phase of many HFFWS objects. It is important to note that Test #4 specifically indicates that a lot of the important functionality may be happening in the Awake method of the scripts of the individual objects themselves (which is important to differentiate from some system wide class that is making connections in Awake). This important differntiation leads to this option of simply deactivating objects with HFFWS scripts (or possibly disabling the scripts specifically) to force their Awake methods not to run before my systems are ready for them. I can run my scripts, and then activate the HFFWS objects so their Awake methods run at that point instead.

This concept seems the most promising for safely and consistently controlling the timing of my script elements versus those of the HFFWS objects (since I do not have access to their core scripts). As this is a common issue I have run into with many objects, it makes sense to make a small script to attach to any prefabs I will be instantiating which can just hold references to gameObjects and/or scripts that I will need to have deactivated or disabled initially, and then activate or enable after everything is properly set.

Script Execution Order

This was actually the first idea to get around these timing issues, but it seems less safe so it will most likely only be used as a last resort. The HFFWS package already has a lot of various scripts ordered in the script execution order of Unity, so there is already a precedent set by them for when a lot of things should run relative to each other.

To test that this worked in the first place, I ran a test with everything being set in Awake not working but moving it to the first script in the script execution order actually made it work. This was a similar test with connecting rigid bodies to a Rope script object.

This is not an ideal process to use however as it can easily lead to weird behavior that is very hard to debug and will not scale well in general. Because of these factors, I will mostly be looking to expand upon the other system concept.