Doers of Stuff.org

A place to Do Stuff and see Stuff Done…

Are You Alive?

Functions vs Methods: Challenge Are You Alive?

The Functions vs. Methods section of the GameDevHQ, “Unity C# Survival Guide” is pretty straightforward, especially having already worked through most of the “2D Game Development Module.” So of course, I felt it necessary to do more than just complete the challenge. This was the challenge:

Create a program that checks if the player is dead or not. When you hit the space key, you should damage the player by a random amount. If the player is dead (health less than 1), print out “The player has died!”

Bonus: Prevent the damage function from being called once the player is dead and no negative health values.

GameDevHQ, Unity C# Survival Guide, Functions vs Methods, Challenge Are You Alive?

As I said, not much to it. My first version was done pretty much in the same manner as guided in the “2D Game Development” module.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AmIDeadYet : MonoBehaviour
{
    [SerializeField]
    private int  playerHealth     = 100;
    [SerializeField]
    private bool playerStillAlive = true;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (  Input.GetKeyDown(KeyCode.Space)
           && playerHealth > 0 ) { playerHealth -= DoDamage(); }
        if ( playerStillAlive && playerHealth < 1 ) 
        { 
            playerStillAlive = false;
            Debug.Log("Player has died!");
        }
    }

    private int DoDamage()
    {
        return Random.Range(0, playerHealth + 1);
    }
}

The code is simple and revolves around two variables; an integer indicating player health level and a Boolean indicating whether the player is alive or dead. For simplicity, I just attached the script to the Main Camera. Each time the Update() method ran it checked to see if the Space key had been pressed and if the playerStillAlive Boolean was True. The playerStillAlive variable check satisfies the Bonus part of the challenge.

If both conditions are met, then damage is deducted from the playerHealth integer. Putting this in a method was unnecessary, but I wanted to play with return values a little and besides, this section was specifically on Functions and Methods.

As an aside, my calculation for the random damage level might not be so obvious. The “expected” solution was to set an arbitrary upper and lower bound. I chose instead to set the upper bound as being no greater than the current playerHealth value. This effectively means the player health will never go below zero. Again, the “expected” solution was to simply check for the player health to be less than zero and reset it to zero.

Finally, check for playerHealth < 1 and playerStillAlive is True. If so, set playerStillAlive to False and print out "Player has died!" as required.

In this solution, the playerStillAlive Boolean us used only to keep the Debug.Log() statement from firing 60 times a second. Doing this ensures the message prints only once. Of course, I could have also used this variable instead of playerHealth in the previous if statement but chose not to.

Now, there are several things that bother me about the code above. Sometimes when I’m working on the GameDevHQ training courses, I will just plow my way through them. Other times, I like to stop and consider coding style and organization. For instance, does not a player health value of zero imply the player is dead? Maybe, maybe not, depending on the game. But in this case, it does. Also, what about code encapsulation and other fancy-pants concepts? What about future-proofing the code and leaving space for more complex requirements to come later? What about code readability? The above code seems to be a bit “See Spot run!” in style.

I waffled a bit and decided to just watch the Code Review section with the above code as-is. Almost immediately, the instructor replaced the Boolean variable with a method. Done! I thought and paused the video so I could rework it.

First to go was the Boolean. I deleted it and created a method by the same name with its return value based solely on the playerHealth variable.

    private bool playerStillAlive() { return playerHealth > 0; }

Now, I could have gone for a more verbose if statement, setting a local variable and returning that variable. Or I could have used two return statements, one of which short-circuited the other. However, I found the conciseness of this statement appealing.

Making this change allowed me to eliminate the second if statement from above. Additionally, I took the moment to change the check in the first if statement from playerHealth directly to playerStillAlive().

        if (  Input.GetKeyDown(KeyCode.Space)
           && playerStillAlive() ) { playerHealth -= DoDamage(); }

Next was the DoDamage() method. I kinda hated that one the moment I wrote it. First of all, it was poorly named. It wasn’t really doing the damage, it was calculating it. Second, I did not like the fact my mainline of code was performing calculations directly. The DoDamage() method ought to be taking care of everything to do with damage caused.

So, take the DoDamage() method and rename it to CalculateDamage() so it is more descriptive.

    private int  CalculateDamage()  { return Random.Range(0, playerHealth + 1); }

Then, create a new DoDamage() method, that actually goes and does the damage required. NOTE: This also gives me a much more sensible place to announce when the player has died.

    private void DoDamage()
    {
        playerHealth -= CalculateDamage();
        if ( !playerStillAlive() ) { Debug.Log("Player has died!"); }
    }

Then, modify the if statement appropriately.

        if (  Input.GetKeyDown(KeyCode.Space)
           && playerStillAlive ) { DoDamage(); }

As you can see, our if statement is becoming much more readable but still contains a little too much “how” and not enough “what” to suit me. I mean, why are we checking for the Space key being pressed? Why does that make any sense (other than being the requirement, that is)? What exactly does pressing the Space key imply? Well, in this case, it means the player was successfully attacked. So why don’t we just say that? Why check for a key press when we can check if our player was successfully attacked or not? Doing so nets us the following:

    void Update()
    {
        if (  playerStillAlive()
           && AttackSuccessful() ) { DoDamage(); }
    }

    private bool playerStillAlive() { return playerHealth > 0; }

    private bool AttackSuccessful() { return Input.GetKeyDown(KeyCode.Space); }

Now, we know from reading the code (not a requirements document, or even a comment) what we are checking for in language appropriate to the application, rather than just the code. The following named methods, then describe what each of those things means in code. In other words, the entire logic of this code is contained in a single if statement. If the player is still alive and the attack is successful, do some damage. That’s it. The rest is just details.

Notice also I reversed the order of the checks. Probably does not mean much, but if you check AttackSuccessful() first, you still always have to check playerStillAlive(). On the other hand, if you check playerStillAlive() first and the player is dead, the check of AttackSuccessful() is skipped.

Finally, since I was going to all this trouble to basically write “self-documenting code,” what was I really doing in the DoDamage() method when I checked for the player being dead? In this case, the game is not actually ended. All we are supposed to do is announce that the player has died. So again, let’s write it that way.

    private void DoDamage()
    {
        playerHealth -= CalculateDamage();
        if ( !playerStillAlive() ) { publishObituary(); }
    }

    private void publishObituary()  { Debug.Log("Player has died!"); }

So, if the player is no longer alive, we publish an obituary, which in this case contains nothing more than the statement, “Player has died!”

The full code then comes out, like this:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AmIDeadYet : MonoBehaviour
{
    [SerializeField]
    private int playerHealth = 100;

    void Update()
    {
        if (  playerStillAlive()
           && AttackSuccessful() ) { DoDamage(); }
    }

    private bool playerStillAlive() { return playerHealth > 0; }

    private bool AttackSuccessful() { return Input.GetKeyDown(KeyCode.Space); }

    private void DoDamage()
    {
        playerHealth -= CalculateDamage();
        if ( !playerStillAlive() ) { publishObituary(); }
    }

    private int  CalculateDamage()  { return Random.Range(0, playerHealth + 1); }

    private void publishObituary()  { Debug.Log("Player has died!"); }
}

So, is this more readable to you? Maybe it is, maybe it isn’t. To me, readability is a balance between expressiveness and conciseness. I do not value one over the other. However, I tend to recommend new programmers err on the side of expressiveness and make their code more verbose and explicit and avoid the coding party tricks of excessively concise code.

Leave a Reply

Are You Alive?

by Robert time to read: 6 min
0