20
Aug

Unity Programming Tutorial Series | RDR2 in Unity – Part 2


Hey there, my name is Charles and this is
part 2 of a series in which I’m using Unity to recreate the Cores System from Red Dead
Redemption 2. In part 1 we started off by creating a fully
functional UI to display the Health Core. In this video we’ll be bringing that UI
to life by implementing health regeneration. If you haven’t already, I highly recommend
watching part 1 so it’s easier to follow along. Otherwise, let’s get right into it. Just like last time, let’s begin by reviewing
the user story that we’re gonna work on. As a player who wants to focus on combat,
I want Arthur’s health ring to regenerate based on the value of his health core so he
can survive longer without using consumables. Now that seems pretty self explanatory but
we’ve also got some acceptance criteria and notes that’ll guide us once we get to
work. Given that his health core is full, Arthur’s
health ring should regenerate at maximum efficiency. With half a health core, it’s regeneration
rate should be halved. And given that his health core is empty, Arthur’s
health should not regenerate at all. The regeneration rate should be tunable and
the health ring should not regenerate past it’s capacity. This information is designed to answer any
questions that may arise during development. And you’ll notice that all of the wording
is focused on the functionality and behavior that we’ll be adding to the project, rather
than the implementation details. This is an important part of the scrum methodology. How this behaviour gets implemented is up
to us, the developers, after we do some technical planning. But that’s outside the scope of this video. I’ve already done some basic white boarding
and have a pretty decent idea of how I’m going to implement this. But if you’re interested in learning more
about the process I’m using here, which is called Scrum, then let me know in the comments. I got some great feedback in part 1 and I’d
love to hear more about what the community would like to see in the future. In the meantime, let’s go ahead and get
to work on this user story. First things first, we’re definitely gonna
need a script to drive this behavior. So let’s create a MonoBehaviour called “HealthSystem”
and add it to the root GameObject of the Health Core UI. Then open it up in your code editor. Now to be honest, I’m not 100% sure how
this code is going to look. But, I know from my whiteboarding session
that we’ll need to at least expose the following fields. A FloatValue for the ring value, the ring
capacity, and the core value. And a float for the regeneration rate. I also know that all of the work is gonna
happen in the Update method, so let’s implement that, as well. By the way, I implemented that Update method
using a feature of the code editor that I use which is called Rider. A lot of you have commented on it and I just
thought I’d take a moment to say that Rider is an amazing tool and I highly recommend
it. I’ll leave a link in the description if
you’re interested in checking it out. Now, again, I’m exactly not sure how I’m
gonna write this. And when that’s the case I usually use Test-Driven
Development to tease out an implementation. So let’s create a test class called “HealthSystemTests”
and add a base case. For our user story the base case is that when
Arthur’s health core is empty, his health ring doesn’t regenerate. So let’s create a test method called “EmptyCore_NoRegeneration”. I’m using a naming convention that places
the starting state on the left of an underscore and the resulting (or expected) state on the
right. Now since our code will live in the Update
method, we’ll need to make this a Unity test by marking it with the UnityTest attribute
and having it return an IEnumerator. Yielding to null ensures that just a single
update is called. Now we can instantiate an instance of HealthSystem
and initialize all of it’s fields. Being that this is the base case, we should
set the core and ring values to 0 and the ring capacity and regeneration rate to 1. Lastly, we just need to add an assertion. Let’s assert that after one update has elapsed
the ring value is still equal to 0. And we’ll pass in float’s Epsilon value
as the delta. This is just something we have to do when
working with floating point values. Alright, now we can run our first test. And it passes, but that doesn’t mean we’re
ready to move on. We haven’t written any actual logic. We need to introduce something that implements
the actual behavior. So let’s switch back to the HealthSystem
class and add a simple guard clause to the Update method. If the Health Core’s value equals 0 then
return. Now we can rerun the test. And it still passes. But Rider is warning me about this comparison. Again, we’re dealing with float values so
we’ll need to add a more sophisticated comparison that takes a delta into account. Okay we’ve refactored the coe so let’s
rerun the test one more time. Beautiful! Let’s switch back to the HealthSystemTest
class and tease out some more of this algorithm. We know that if the Health Core is full then
the Health Ring should regenerate at full efficiency. So let’s create a test called “FullCore_MaxRegenerationRate”. And we can just copy and paste the logic from
our base case test. Except we’ll need to adjust these values. The Core value should be equal to 1 this time. And after one Update call we would expect
that the Health Ring’s value should be equal to delta time so let’s update the Assert,
as well. This is because the regeneration rate is set
to 1 per second and delta time represents how much time, in seconds, has elapsed since
the previous frame. Let’s run that test. And it failed. Which is what you’d expect when doing Test-Driven
Development. See, a failing test is the first step in the
Red-Green-Refactor cycle. If you’d like to learn more about Test-Driven
Development then you should check out my series called TDD in Unity. It walks through the entire process of Test-Driven
Development with a practical example and provides some tips for doing TDD specifically in Unity. Let’s get this test to pass by adding some
logic to the HealthSystem class. Starting simple, I know we can go green if
we set the Ring Value to delta time. But that’s obviously not the right algorithm. Delta time is a good start but we aren’t
factoring in the Core value or the Regeneration rate. So why don’t we go ahead and include those
in the calculation. Alright, looking good. But we’re setting the Ring to the same value
every update. We need to be adding the value instead. Much better, let’s run the test. Beautiful. Let’s switch back to the test class and
tease out some more functionality following the same process. The Health System needs to stop regeneration
once Arthur’s health reaches its capacity so let’s add a test called “FullHealth_NoRegeneration”. Again, copy the code from the previous test. And go ahead and max out all of the values,
set the regeneration rate to 1, and assert that the Ring value is equal to 1. I say max out because, like Image’s fill
value, our Core and Ring values are going to range from 0 to 1. Now, we’ll need to add some more sophisticated
tooling and checks to our code for that eventually. But that work falls outside of the scope of
this user story so we won’t worry about that right now. In any case, let’s test and watch it go
red. Perfect! Let’s switch back to the Health System class
and introduce another guard clause that returns when the Health Ring value is equal to its
capacity. Great, the test passes! But Rider is warning me about this float comparison
so we can just do another quick refactor. And the test stays green. I love it! I think it’s time to take this bad boy for
a spin. Switch back to Unity and make sure that the
value of the Health Core is set to 1, Health Capacity is 0.5, and Health ring is 0. Now run the scene. Uh oh, what happened there? It shouldn’t have regenerated past the halfway
point. I think we’re gonna need to gather some
more information to debug this. So let’s switch back to the code editor
and write the new value to the console. That way we’ll know exactly what’s happening
during each frame. Perfect. Now switch back to Unity, reset the values,
and run the scene again. Let’s examine the logs. Ah ha! Check it out. Our guard clause only returns when the Health
ring value is equal to its capacity. But according to the log, that never happens. We’re gonna need to fix the algorithm so
let’s switch back to the code editor and write a new test. This time we’ll name our test “AlmostFull_RegeneratesToFull”. You see, we’re gonna need to handle the
case where the next tick of Update would cause the health ring to overfill. So let’s copy the code from the previous
test and set the health ring value to 0.999. And, of course, it fails. Which is exactly what we need to drive us
forward. So, back in the Health System class, we’ll
need to make sure that we’re never overshooting the health ring’s capacity. And one way to do that is by calling Math.Min
and passing in both the capacity and our new value. Rerun the test. Nice. And a quick spot check in Unity. Beautiful. And with that I think we can officially call
this user story complete. If you’re interested in grabbing the code
then head on over to my Patreon page where you download it free of charge. And, as a bonus to my Tier 2 Patrons, I’ve
gone ahead and refactored the unit tests to remove some of the duplication. If you’re interested in checking that bonus
code out, please consider becoming a Patron today. I’ll have a link to both of those in the
description. Alright, that’s it for part 2. In part 3 we’ll keep this momentum going
and knock out the last bit of the health system’main functionality. If you enjoyed this video, please leave a
like and a comment letting me know what you thought. And for more Unity videos and tutorials just
like this one, don’t forget to subscribe with notifications on. Thanks for watching and I’ll catch you in
the next video.

Tags: , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , ,

7 Comments

  • Infallible Code says:

    ⬇️ Download the code for free at https://www.patreon.com/posts/24194724

    ⌨️ Like my code editor? Check out Rider* at https://assetstore.unity.com/packages/tools/utilities/rider-128844?aid=1100l3e8M&pubref=2019_3_pin

    ✅ Get Unity Pro* at https://store.unity.com/products/unity-pro?aid=1100l3e8M&pubref=2019_3_pin

    * Disclosure: this is an affiliate link, which means I'll receive a commission if you use it to make a purchase.

  • Rob Watling says:

    Really liking this approach to your development, seeing how other people work in scrums and how you approach Unit Testing monobehaviour code is really beneficial.
    Thanks as always!

  • 3Riders says:

    Why do use yield return null not at the end of caroutine? Caroutine stops when see yield return null, and code below won`t be executed, no?

  • kantrasha says:

    Paused the video at the 0:45 mark. User stories in game dev? As a software engineer that has started with the video game industry and has moved through a couple others since then, that got my attention. Gimmick worked, will browse through your videos.

  • pavão says:

    very nice content. Keep it up =) Can you explain more on using Story Points (if you use it at all)? I found this part of SCRUM methodology a little bit dificult for me…

  • Daniel Fletcher says:

    More scrum please

  • myblindy says:

    It hurts me every time you use float.Epsilon like that, it's by definition the smallest positive non-zero value a float can represent. If you're simply saying val < float.Epsilon, it's functionally identical to val == 0 (as long as it's positive), so you're not fixing the completely valid warning your IDE is giving you, it's just not smart enough to realize that. The real fix is to use a much, much larger tolerance value.

Leave a Reply

Your email address will not be published. Required fields are marked *