Main

Creating an Event Driven UI | TDD in Unity [8]

Sign up for the Level 2 Game Dev Newsletter: http://eepurl.com/gGb8eP This is part 8 of Test-Driven Development in Unity. This Unity tutorial covers creating an event-driven user interface using the observer pattern. 👨💻 Join our community: https://discord.gg/NjjQ3BU ❤️ Support the channel: https://www.patreon.com/infalliblecode My Favorite Unity Assets 💯⤵️ 1️⃣ Odin Inspector: https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?aid=1100l3e8M 2️⃣ Shapes: https://assetstore.unity.com/packages/tools/particles-effects/shapes-173167?aid=1100l3e8M 3️⃣ Easy Save: https://assetstore.unity.com/packages/tools/input-management/easy-save-the-complete-save-load-asset-768?aid=1100l3e8M 4️⃣ Dialogue System for Unity: https://assetstore.unity.com/packages/tools/ai/dialogue-system-for-unity-11672?aid=1100l3e8M 5️⃣ Editor Console Pro: https://assetstore.unity.com/packages/tools/utilities/editor-console-pro-11889?aid=1100l3e8M ⚡ Learn more about Unity 3D Plus at http://prf.hn/click/camref:1100l3e8M/destination:https://store.unity.com/products/unity-plus 👋 Contact me directly at charles@infalliblecode.com * Disclosure: These are affiliate links, which means I'll receive a commission if you use them to make a purchase.

Infallible Code

6 years ago

Welcome to part 8 of test-driven development in unity So far we've created a player class to manage player health internally And a heart container class to display the player health on the user interface In this video we'll be using C#'s built-in implementation of the observer pattern to connect these two classes together But before we get started let's talk about the observer pattern The observer pattern is a design pattern in which an object called the subject Maintains a list of its dependent
s called observers and notifies them automatically of any state changes It enables you to define dynamic relationships between objects that can be changed at runtime This pattern is most commonly used to implement user interfaces It makes displaying things like ammunition, inventories, and of course health bars much easier We'll be using the observer pattern to create a relationship between the heart container and the player So it can react when the player's health changes Let's get into the cod
e The heart container should be notified whenever the player's health changes, so let's give the player class a couple events We'll call this first one "healed" the event keyword is syntactic sugar That'll allow us to take advantage of C sharps, built an implementation of the observer pattern objects in our system We'll be able to subscribe to the "healed" event and will be notified whenever it's invoked The event handler is a built-in delegate that we can provide custom arguments to Let's do th
at now We'll create a nested class called healed event args It'll need to implement event args so our event handler can take advantage of it For now the only argument we need to provide is the amount healed Let's give it a private setter and initialize it on the constructor Now we can define HealedEventArgs as the type our event handler will use for its arguments This will make it so that each method that subscribes to the healed event will need to have a HealedEventArgs argument in its signatur
e which introduces a nice bit of type safety Now our healed event is ready to be raised whenever the player is healed Let's switch over to player tests and create a test to drive the addition of that logic first we'll create a nested class called the healed event And a test called Raises_Event_On_Heal Working our way from the bottom Let's add an assert that verifies the healed event is raised by the player class whenever it's heal method is invoked Amount will store the value provided by the eve
nt args which in this case will be zero We'll initialize it to negative one, so we know whether or not the event is actually raised Next we'll need to actually instantiate a player and call its heal method Finally let's subscribe to our players healed event using a lambda expression This is where we'll set a mounts value using the args passed in from the event You'll recall that the args must be an instance of our custom healed event args class which exposes a property called amount All right, l
et's run the test Of course it fails Now we can switch back over to player and implement this Making this test pass, will be easy All we need to do is invoke healed Within the body of the heal method passing in this instance of player and a new instance of healed event arg For now we'll give its constructor the value that was passed in to heal And we also need to null check it in case there are no subscribers All right, let's run that test again and There you have it. We're almost ready to move
on to the "damaged" event, but first we need to handle overhealing Any amount that results from overhealing shouldn't be passed into the healed event if A player has max health then the value passed into healed should always be zero Let's switch back over to player tests and create a test to push us forward We'll call this test overhealing is ignored And we'll copy and paste the code from our previous test The difference however is that Player will be instantiated with a current health and max h
ealth of one Then we'll pass one into its heal method. In other words, we're trying to heal a player that's already at max health Let's run that test It fails because we expected over healing to be ignored we expected zero But check it out, the actual value is one Again, this will be an easy fix. Let's hop back on over to player Since we're already Truncating overhealing on current health using maths Min function, all we need to do is store the new health value in its own variable we'll call it
new health And use the difference between new health and current health for health event args All right we run the tests Like I said easy enough Now we can move on to the damaged event It'll follow the same principle, so we can actually move through this pretty quickly We'll use C#'s event keyword and event handler delegate to craft our "damaged" event We want to expose at least one custom argument, so let's create another nested class that implements event args Following the same naming convent
ion, we'll call it "DamagedEventArgs" For now it just needs to pass in the amount, so we'll create a property with a private setter and initialize it on the constructor With this in place we can define the argument type of the damaged events event handler Now to drive the process forward we'll switch over to player tests And create a nested class called the damaged event Within this class we'll create a new test called "Raises_Event_On_Damage" It'll contain a local variable called a mount that w
ill store the value that is passed in on the event We'll assert that its value is equal to zero Then we'll need to instantiate a new Player And call "Damage" Finally we'll subscribe lambda to the players damaged event that'll update the value of amount using the damage event args Okay run the test And As expected it fails, so let's switch back to Player within the damage method body will do a null check on damaged Then if it's not null we'll invoke it, passing in this instance of Player and a ne
w instance of DamagedEventArgs Everything passes, so let's create our last test of the entire series Much like last time. We'll call it overkill_is_ignored Copy the body of the previous test This time player will have zero health and we'll pass one into damage The test fails for the reason we expected Amount should be zero rather than than one, so let's switch back to Player Store the new health value into its own variable And then pass the difference between new health and current health for Da
magedEventArgs We run the tests and again there you have it Everything is finally in place so we can make all the necessary connections and button this series up We'll create these connections in the App class Which serves as the pre-loader for our game Currently it creates a heart container using images that are set in the editor and allows the user to manipulate heart container with the up and down arrows let's add a property to hold an instance of our Player and Instantiate it on the start me
thod We'll count each hit point as one heart piece Which is why we're passing in 20 on players constructor for current health and Max health There are five hearts in the scene giving us 20 heart pieces to work with Now we can make the heart container subscribe to the players healed and damaged events using lambda expressions On "Healed" the heart container will replenish itself using the HealedEventArgs And on "damaged" the heart container will deplete itself using the DamagedEventArgs We'll fin
ish this off by modifying the Update method to call heal on player when the up arrow is pressed And damage on player when the down arrow is pressed Now let's test her out I'll press the down arrow a couple of times And now the up-arrow We can test all sorts of values, so let's change the amount to five now we'll do some heals and cause some damage Great everything works as expected Isn't it amazing how much code? We were able to write and test without ever running our game This is a huge time sa
ver especially on large projects where startup takes a while or in instances where testing the code you're working on requires a lot of setup Not only do we gain productivity but following the test-driven development process sort of inform the way we wrote our code too As a result our player health and heart container implementations are completely decoupled And we have a test suite too! Can't forget that You see, test-driven development is about more than just tests But we'll talk about that in
another video For now it's time to wrap this series up I hope it was helpful and informative If you enjoyed it, please leave a like And if you have any questions feel free to leave a comment, and if you'd like more videos like this one Please don't forget to subscribe and turn notifications on See you in the next video

Comments

@InfallibleCode

❤️ Consider supporting this channel by becoming a Patron: https://www.patreon.com/infalliblecode

@lechu2a

Great series! I followed it from the beginning, while typing along to better understand everything. I think TDD just clicked with me, its amazing how easier it is to refactor things while always being sure that everything still runs as planned. Thank you so much for this! I hope you plan to do more series such as this one on other topics like UniRx and Zenject. Cheers!

@cyclicyttrium4318

i discovred your channel from the comment on Brackeys video, while i was searching for almost 8 months about TDD in Unity and apparently this is the series that will help me sharpen my skills, Thank you so much for this content ! Subscribed :)

@SexyBlueTiger

Nice use of events :) It was a great series! Thanks so much for doing it. Can't wait to see what you do in the future :)

@MichaelBryne

Belated like and thanks for this whole series, it’s a game changer! Your efforts are very much appreciated.

@danielrousseau6541

im excited to start this

@thiagogustavo4712

ITS FUCKING AWESOME BOI

@nafurachan9061

Love the series so far! While I understand how to test modules by themselves, I'm still having a hard time figuring out how to test inter-module features. Like say, in game like super mario. How to test whether if mario step on the mushroom, they'll respond accordingly. I hope you'd make something like "Make a full game in TDD" series. It doesn't have to be something complex, a simple game like Unity's tanks or first level of Mario is sufficient I believe.

@moeenkamali1288

thanks for the great series. can you introduce more resources about tdd any playmode testing on unity as there are not lots of references?

@davidruiz6089

Great tutorial, I might try TDD in some of my future projects. One offtopic question though; what's the visual studio plugin you're using that draws an icon left to the line number when you define a class or method? (i.e. it draws the unity logo when you define Start() or Update()). Thank you!

@recursion27

Any particular reason you used the event keyword as opposed to Action?

@goldenimper503

Just a simple question! Is it necessary to use the EventHandler? Isn't it easier to just use Action or Func? What is the difference? Thanks!

@zeejenkins

What happens when someone accesses the CurrentHealth from inside the Event Handling method... The CurrentHealth will be incorrect because you are updating it AFTER invoking the event.

@Angry-Lynx

heh if only games would be so simple as with all online 'examples' and tutorials, there would be no problems of patterns and anti pattens :) My point is - syntetic and isolated cases have not much to do with realisty, especially games, that are extremely complex software.

@zagmodell

events, bleh