Lesson 46 - Understanding Properties

Tutorial Series: Introduction to Unity with C# Series

Previous Article  |  Next Article


Transcript

I briefly mentioned properties in an earlier lesson where I basically told you to take properties to be just like fields whenever you see them in the Unity API. When you see that get, set stuff. Well, now you are going to learn the important differences between fields and properties. Even if you probably won't end up making your own properties very much, if at all, when programming within Unity. I'll later go onto some scenarios for why you would use properties in your game. Fields just let you assign assign or retrieve values, right? Right. Properties let you assign or retrieve values, but they also let you add code to the property itself that executes whenever you are attempting to assign or retrieve a value. This is done using something like a special method that's local to the property called a getter and setter. The getter executes when you are retrieving or getting a value. While the setter runs while you are trying to assign or set a value. Commonly, you can use this extra functionality of properties as a gatekeeper. To prohibit values that will crash your app, or screw it up in some way.

Gatekeeping is especially important whenever you're exposing something to either outside users, a form input that sets a variable value, for example. Or even if you're exposing these properties to members of your own team who don't realize they shouldn't put a certain value into a field. Or perhaps even to protect yourself. You, your future you, from who might forget what should or shouldn't go into that field. Chances are, you're not going to expose anything to the end user, or another member of your team, or have many scenarios where you need to protect yourself from making an illegal set to a field. However, outside of gatekeeping you can use the get and set to use properties for something like, imagine having an enemies hit points decreasing, taking damage, and incorporate some code right into the getter or setter that causes the sprite to flicker. Rather than just put that in the Update() directly, or into it's own method, just bind that to the property itself, to the getter or setter. First, what we'll do to demonstrate how to create and use properties, we'll setup in the Test folder a new script called Properties, and attach it to this Test game object. I'll drag it right here on our Test GameObject.

I'm going to mute the audio because it is probably going to get in our way because we're just going to be interested in the console in this lesson. In this script, let's create a simple property and I'll explain everything as we go along. We're creating a field here, an ordinary field. Except I'm putting an underscore there. I'll explain why later, called _userMessage. Then, we have another, what looks like a field, but it's a property, called uppercase UserMessage. Then there is going to be a code block that belongs to this property. And we'll implement the get, or the getter code block, as I mentioned earlier. That will just return the field userMessage. The setter, set in other words, will have a little conditional check, to make sure it's not getting set with something that we don't want to be set. To symbolize that we'll just have a simple message "I h4x0r You!", and user message will say "Hacking attempt prevented." This doesn't have an immediate analogy or implication to game design, but the principal, hopefully, will be instructive and you'll understand this gatekeeping aspect of properties from this example. I'll explain that in a second. Then, in Start() we'll try to set the property, just by assigning to it.

That's how you set properties just like you set fields. Except, with properties you have to go through the getter setter. In this case, the setter. So, I'll type in that message, “I h4x04 You!”, has to be exactly as the string here, or else it won't be detected, that pseudo hacking attempt, And we'll just, in the Debug.Log() access the getter to retrieve that value from that property. Nothing too out of our perview at this point. Put the termination semicolon there. Here we'll say, UserMessage = "Hi there" and we'll output that to the Debug.Log() Let's run this. Looking at the console, that play delay thing, we'll clean that all up in a final lesson where we tidy up a bunch of stuff with our project in the next few lessons. There we go, it says, “Hacking attempt prevented” and “Hi there.” So the way this works, is uppercase U, UserMessage, is the property which acts as a gatekeeper to the field, which is typically referred to as the backing field. And when we attempt to set or write to the backing field, the property set first checks to make sure there is no illegal attempt being made in setting the value. The term value here, it implies whatever value we're attempting to write the backing field whenever we're trying to set this property, someone code with the assignment operator. This is what value will take on, whatever is attempting to be set.

Similarly, when we attempt to read the backing field, such as with the Debug.Log() we're just retrieving the field. The property's get can run any special code you want. Here, we're just returning the backing field. Nothing special is really in the getter right now. It's just returning it. But, we can do anything we want. Which, we'll demonstrate a little bit later in our project here. Now, theoretically, we shouldn't ever directly access the backing field. The _userMessage, right? That's meant to be kept behind the gatekeeper property. We should always go through the property if it's gatekeeping to it. That's one of the reasons we see the underscore as a naming convention, to warn us that it's a backing field to a property and not to touch it. It's just a naming convention, though. Theoretically, you can name your backing field whatever you want. Another similar convention is, if outside classes need to read or write values, expose only the property, so in other words mark the property as public and the backing field as private. You do this, so you never even do see with IntelliSense and such, the backing field. Of course, both can be private if you don't need any outside classes to access the property. Both the backing field and the property can be set to private.

Another interesting thing you can do is mark the getter and setter themselves with an accessibility modifier. For example you can say, allow outside classes to retrieve the property's value, the get, but only let the class it belongs to, to set its value. You do something like this. Can only be privately set, but publicly this value can be returned to outside classes. It inherits that property, but this explicitly overrides that for this set. You'll sometimes see properties in Unity which behave exactly like this. They let you just get the value, but they don't let you set it. With that understanding under our belts, we can disable this Test script and let's just find a way to work into our game project. Going back to our GameStartManager, we had this part here. Now what if we wanted to reset the Timer once it reaches a certain point? We could put in a ternary, like this, LoopTimer, and then conditional, if (LoopTimer >3000) for no particular reason, it will reset to 0, otherwise we'll just increment it. LoopTimer = LoopTimer + 1. That actually lets us get rid of this. We can simplify this a little bit more, at least in the Update() method, by employing a property. What I'm going to do next isn't maybe going to look a lot more elegant but, perhaps you might think that the functionality of a Timer resetting and incrementing, it doesn't really belong directly in the Update() method. It's, after all, just functionality specific to the Timer itself.

If we make the LoopTimer a property, we can instead move this functionality into the property's getter, as follows. Let's have a backing field. That's also a naming convention for the backing field, is a lowercase first letter. Specifically if it's private, but I find it's easier to identify as a backing field when you do that. In the getter we'll take this, except now, we're referencing the backing field. Getting the backing field. As we're getting it, we're checking to see if it's greater than 3000. If it is, it resets to 0. Otherwise, it increments. We're doing it right in getter. You might have thought it should have been done in the setter, but you don't have to do it that way. In this case it has added functionality by doing the getter. Then for the setter. Make that really simple. Take the backing field and give it whatever value is implied. That will be the only time we employ the setter. Of course, we see a squiggly red underline here. That's because we're attempting to get something where we have to return the value. We want to return _loopTimer. That finalizes the functionality of the getter. Here, we don't need the ternary operator anymore. Let's just, for testing purposes, in the Debug.Log(LoopTimer) input the LoopTimer, just to see this functionality in effect outputting to the console. I'm going to go to the ProtoStart screen. Because this GameStartManager is running on this screen. We watch this as it increments to 3000. That's going to take too long. Let's change this to 1000. At 1000, it should reset to 0.

This is all done through the getter, rather than having anything in the update method itself. There we go, it reset. Our Update() method's kept a little cleaner and that functionality, that's specific to just the timer, is contained all within the property's getter. Because we're getting it every time we're doing this check. Now I don't blame you if you feel that your eyes are kind of hurting a bit after seeing all this ugliness of properties, and for this reason I don't think you'll use properties all that much. It's often just a lot of clutter in your class for little benefit. Of course, there are many cases in which it can make your job easier. And Unity itself uses properties extensively, so I thought I'd show you properties for those reasons. One last thing about properties, and that is a short-hand called Auto-Implemented Properties. This is an auto-implemented property. Back to our Test folder. Let's go to our properties script. Just say, string, we'll call it AutoImplementedProperty, get, set. You probably noticed this all throughout the Unity API itself, if I go through any class, you'll see a bunch of auto-implemented properties. Those get, set, you'll see it all the time. What this does is it creates a backing field, invisibly, in the background. It also contains, again, invisibly, the most basic property settings for the getter and setter. This property, if you wanted it to be a full property, and not an auto-implemented property, it would be represented like this.

I'll show you a code snippet, just sets up a basic property. Type in ‘prop full’ and then [Tab] [Tab] I'm kind of making a mess of things here. There you go, that's what this is doing. It's just a really handy short-hand for creating a property with a basic getter that's just returning the backing field and a setter that's setting the implied value. If you think about it, this isn't doing anything particularly special that an ordinary field wouldn't do. It has no specific implementation details in the getter and setter. What's the use of an auto-implemented property? Basically the idea is, once you choose fields over properties in your project and you go far down that development path with that choice, it's not always easy to convert those fields to properties because of some differences with how properties can be used. Some technical differences, such as, you can assign properties directly with default values, but there's many others that if you were to try to change your field to properties it will just make a mess. The idea is it's probably best to always use properties for the potential benefits you may get down the line, if you find you need to use them. However, look at all that ugly syntax. Who wants to clutter up their projects for the potential benefit of something you might never even need?

Auto-Implemented properties is kind of a compromise where you can basically have all of your fields become properties, at a moments notice, at the cost of very little extra typing up-front. If you ever need to turn those properties, to turn them into full properties down the line, go on right on ahead. You know it's going to work, it won't break anything else in your code because the rest of your code, you've written around those auto-implemented properties, have always assumed that those class level variables are properties, not field to begin with. Another common benefit of Auto-Implemented Properties is you can have separate accessibility levels for getting and setting values. For example, we have in the CubeController. We have this static CoolCounter. It needs to be publicly visible. But, its values only set within the CubeController itself. By turning into an Auto-Implemented property we can do this, {get; private set} That creates that functionality where outside classes can only get it, but it can only be privately set.

That's about all I want to say about properties. Again, you probably won't be using them in your projects. Not unless you're working within a team or perhaps you are making a component. A Unity component that you want to share on the internet and other developers can consume that component and use it within their game projects. Otherwise, just stick with fields and you'll be good to go.


Related Articles in this Tutorial:

Lesson 1 - Who This Course is For

Lesson 2 - What to Expect from this Course

Lesson 3 - Installation and Getting Started

Lesson 4 - Starting the First Project

Lesson 5 - Prototype Workflow

Lesson 6 - Basic Code Review

Lesson 7 - Game Loop Primer

Lesson 8 - Prototyping Continued

Lesson 9 - C# Fundamentals and Hello World

Lesson 10 - Variables and Operations

Lesson 11 - Variables and Operations Continued

Lesson 12 - Floats, Bools and Casting

Lesson 13 - If Statement Conditionals

Lesson 14 - If Statements Continued

Lesson 15 - Complex Evaluations and States

Lesson 16 - Code Syntax vs. Style

Lesson 17 - Variable Scope

Lesson 18 - Object-Oriented Programming Intro

Lesson 19 - OOP, Access Modifiers, Instantiation

Lesson 20 - Object Containment and Method Returns

Lesson 21 - "Has-A" Object Containment

Lesson 22 - "Is-A" Inheritance Containment

Lesson 23 - Static Fields and Methods

Lesson 24 - Method Inputs and Returns

Lesson 25 - Reference vs. Value Types

Lesson 26 - Introduction to Polymorphism

Lesson 27 - Navigating the Unity API

Lesson 28 - Applying What You've Learned and Refactoring

Lesson 29 - Constructors, Local Variables in the Update Method

Lesson 30 - Collecting Collectibles, Items and Powerups

Lesson 31 - Spawning and Managing Prefab Powerups

Lesson 32 - Implementing Powerup State Logic

Lesson 33 - Displaying Text, OnGUI, Method Overloading

Lesson 34 - Referencing Instantiated GameObjects, Parenting

Lesson 35 - Understanding the Lerp Method

Lesson 36 - Creating Pseudo Animations in Code

Lesson 37 - Understanding Generic Classes and Methods

Lesson 38 - Animations Using SpriteSheets and Animator

Lesson 39 - Working with Arrays and Loops

Lesson 40 - Debugging Unity Projects with Visual Studio

Lesson 41 - Camera Movement and LateUpdate

Lesson 42 - Playing Audio Clips

Lesson 43 - Routing Audio, Mixers and Effects

Lesson 44 - Adding Scoring Mechanics and Enhancements

Lesson 45 - Scene Loading and Game Over Manager

Lesson 46 - Understanding Properties

Lesson 47 - Controller Mapping and Input Manager

Lesson 48 - Understanding Enums

Lesson 49 - Dealing with Null References

Lesson 50 - Handling Variable Framerates with time.DeltaTime

Lesson 51 - Preparing the Project for Final Build

Lesson 52 - Final Build and Project Settings

Lesson 53 - Introduction to the Unity Physics Engine

Lesson 54 - Understanding FixedUpdate vs. Update

Lesson 55 - Movement Using Physics

Lesson 56 - Attack Script and Collision Events with OnCollisionEnter2D

Lesson 57 - Projectiles and Stomping Attack

Lesson 58 - Parallax Background and Scrolling Camera

Lesson 59 - Infinitely Tiling Background Sprites

Lesson 60 - OOP Enemy Classes

Lesson 61 - OOP Enemy Classes Continued

Lesson 62 - Trigger Colliders and Causing Damage

Lesson 63 - Multi-Dimensional Arrays and Procedural Platforms

Lesson 64 - Finishing Touches

Lesson 65 - Series Wrap


Comments

Please login or register to add a comment