Pre-requisite: it is assumed that you have read through
the prior tutorials, and are familiar with basic I/O concepts, and how to
echo messages to the top and bottom of the screen in an XNACS1Lib-based game,
including data about the left thumbstick.
It may be useful to refer to refer to
the diagram illustrating the Draw-Update loop
(which explains that your
initialization code will be called once, and then the XNACS1Lib will repeatedly call your Update method
(after which it will Draw the screen) ), in order to have a clear picture of how your game executes.
Goals:
To gain experience writing code that interacts with users via the
XNACS1Lib
library.
In this tutorial, we will:
Examine some simple,
local
variables
Examine some
local
variables that are non-simple, in the sense of being
composed of several simple variables
Examine the assignment operator
Examine how to initialize variables in the same statement that declares
them.
1. Obtain the example code
Here is the
zip file
to the source
files and compiled executable of this example.
Download and unzip the zip file and you will see an
ExampleProgram
folder. Open the
ExampleProgram
folder, the
EXE
folder contains the compiled program and you can double click on the
.sln
file to work with the source code.
Once we compile and run this project, the program displays "
ThumbStick:
X is 0 Thumbstick: Y is 0
" at the top of the screen.
If you move the left thumbstick towards
the right, you'll see the X value change in response. If you move the left
thumbstick towards the left, you'll see the X value changes to a negative value.
Similarly, when you move the left thumbstick forwards & backwards (sometimes
described as "up and down"), you will see the Y values change appropriately.
2. Background
:
The output of this tutorial is exactly the same as the
output from the last version of the prior tutorial. Instead of focusing on
output, we will focus on variables. A
variable
is a block of space
in the computer's memory that contains a value of some sort. In other
words, a variable stores data / information. The value
that is stored in the space can be changed; the value stored in the variable can
vary.
Because contents of the block can vary, we can use that fact to store information about our
program that changes over time. For example, when the player begins a new
game, the player's score might start out at zero. In our program, we can
represent the player's score by creating a variable (a block of space) that
we'll name
score
, and we'll put the value zero into that space to store
the player's starting score. As the game goes on, maybe the player is
awarded 10 points for doing something, so our program will take the value
currently stored in the
score
variable (zero), add ten to that value (0 +
10 = 10), then store that new value back into the space named
score
.
When the program then displays the score on the screen, the player will now see
the new value (10), rather than the old value.
Because different information needs to be kept around for
different lengths of time, there are different types of variables. For
example, a player's score should exist throughout the entire game, so we will
need to use a long-lived
instance variable
to keep track of the score (we
will explore instance variables in a future tutorial). Another example is
that the game may need to (or want to) store the results of a complex
calculation, then output the calculation in the Update function. In which case, we only need that data to exist until we can output
the data, and then we don't need to store it anymore. As a matter of fact,
we want to NOT store that information any more because we don't need it, and it
would waste the computer's memory to store unneeded
information. What we would like to have is a variable that will
automatically
disappear once the Update method is done
. We will use a
local
variable
to store this short-lived data. In this case, "local" means
that the variable only exists within the method that declares (creates) it.
3. Examining The Program:
Let's examine
the C# source code that produces the behavior we see on-screen
InitializeWorld():
Since we want the lower-left corner to still
be at (0,0), and since we want the width of the window to be 100 units wide,
InitializeWorld looks the same as in previous tutorials.
Since our program is only using local variables, there's no need to
Initialize anything here. The Initialize step is only for data that will
exist throughout our program (game).
If we were to create a local variable in InitializeWorld then the local variable would automatically disappear when
InitializeWorld finishes.
protected
override
void
InitializeWorld()
{
World.SetWorldCoordinate(
new
Vector2
(0,0),
100.0f);
}
UpdateWorld():
This is where we read the current state of the left thumbstick, and then
display a message at the top of the screen telling the user about the
thumbstick.
There are a number of new things here, for right now, we'll start by
looking at the first Version, and ignore everything else in the method:
protected
override
void
UpdateWorld()
{
if
(GamePad.ButtonBackClicked())
this
.Exit();
// Version #1:
float
X;
float
Y;
X = GamePad.ThumbSticks.Left.X;
Y = GamePad.ThumbSticks.Left.Y;
EchoToTopStatus(
"ThumbStick:
X is "
+ X +
" Thumbstick: Y is "
+ Y);
}
The Update method starts with the line "
//
Version #1:
", which identifies this as the first of several variations
that you'll be looking at in this tutorial.
The next statement in the Update method
declares
(creates) a variable
named
X
. It does so by listing the
type
of the variable (
float
),
then choosing a name for the variable.
By defining the variable to be a
float
, we are telling
C# to reserve some space in the computer's memory that will be used to store
floating point numbers
, meaning numbers with stuff after the decimal
point. This includes positive and negative numbers.
Examples include 141.97, -141.97, 3.14159, 2.5, and 4.0. Note that
the last example (4.0) is actually an integer (a whole number) - while
float
s are
normally used in situations where we expect to have stuff after the decimal
point, we're not required to have non-zero digits after the decimal point.
Another common option for numbers is an
int
.
int
is short for
integer, meaning only whole numbers. Examples include 141, -141, 3, 2,
and 4.
In many console-based textbooks, it's very very common to use
int
variables.
In XNA, it's very very common to use
float
variables.
Both
float
s and
int
s are what C#
calls
simple data types
, or
simple types
. You can think of
a simple type as a space in the computer's memory that can hold
exactly 1
piece of data
. In this case, a single number (floating point number
or integer number). Below, we will examine C#'s
struct
types,
which are composed of several pieces of data. (And much later on, you'll see
C#'s
reference
types, such as classes)
In C#, variable names must start with a letter (upper or lower case) or
the underscore character ( _ ), and can consist only of letters, numbers, and
the underscore character. We chose to use the name
X
since this
block of space (this variable) will be used to store the current value of the
X part of the left thumbstick.
You are (generally) not allowed to duplicate names of local variables
within a given method. For example, the second variable we create in the
Update method is named
Y
, which is different from X.
Note that it's important to chose meaningful variable names. It's
very easy to create variables that are short, cryptic, and easy to forget.
Choosing good, meaningful names makes it easier for your coworkers to
understand what you're trying to do, easier for your teacher to understand
what you're trying to do (especially when grading! :) ), and will make
it easier for yourself to understand what you were trying to do when you look
at your code months after you first wrote it (perhaps while fixing a bug in
your old code, as part of your job)
Note that both the
X
and the
Y
variables are
local
variables, meaning
that we can use them as soon as we've declared them, and at any point between
the definition and the end of the method, but once the method ends, the space
that has been reserved to hold these local variables will disappear.
Technically, the computer's memory is still there, but C# will be using that
space in memory to store something else. So as far as we're concerned, the
local variables are gone.
Note that variable declaration is technically different than variable
definition. A variable's definition allocates space for the variable,
and also declares that the variable exists. In some languages (like C++)
a variable can also be separately declared to exist, but NOT have space
allocated for it in that separate declaration - a common example is that one
C++ file defines a variable, and code in a different file wants to use that
same variable then declares that same variable (but doesn't define it, since
the first file already created the space for it). This distinction is
less common in languages like C# and Java. As a result, most people use
the words 'variable declaration' and 'variable definition' interchangeably.
We will use these two terms interchangeably in these tutorials, as well.
The next statement (
float
Y;
) declares a floating-point variable named Y
The next statement (
X =
GamePad.ThumbSticks.Left.X;
)
assigns
the value that is currently
stored in
GamePad.ThumbSticks.Left.X
to the X variable that we just created.
In C#, the = symbol (a single = sign) is the
assignment operator
.
It assigns the value stored in whatever is on the right into the space
identified by whatever is on the left. It is NOT equality.
It may be helpful to think of the assignment operator as being like an
arrow, in order to remember which way the assignment goes:
Using the = symbol in this fashion is also done in C, and most other
languages that are derived from C, such as Java and C++.
Note that X now contains a COPY of the value that was in
GamePad.ThumbSticks.Left.X
.
Since it's a copy, even if the value that's stored in
GamePad.ThumbSticks.Left.X
changes,
the value that we have stored in X will NOT change. Similarly, we can
change the value in X and not worry about affecting the value that's in
GamePad.ThumbSticks.Left.X
.
The next statement (
Y =
GamePad.ThumbSticks.Left.Y;
) does the same thing for the Y variable
In C#, for any local variable, you MUST give the
local variable a value before you are allowed to use it.
That means that you MUST assign it a value before you can output the variable
in the EchoToTopStatus command. Try removing these two lines that we
just examined, and see if your program will still compile and run.
The final statement in this version (
EchoToTopStatus(
"ThumbStick:
X is "
+ X +
" Thumbstick: Y is "
+ Y);
)
then outputs the values currently stored in the X and Y variables to the top
of the screen.
You'll notice that this produces the exact same results as the final
version in the prior tutorial. Since the objective here is to start
looking at variables, it's ok for us to start with this small, contrived
example in order to clearly show the different steps in detail.
One thing that this example does do very nicely is to make the
EchoToTopStatus
command much easier
to read, since that line is now much shorter, and since the clearly named
variables make it easy to see what the programmer's intention is.
Once you're comfortable with version #1, you should comment that version out, and
uncomment Version #2, so that you are now looking at:
// Version #2:
float
X = GamePad.ThumbSticks.Left.X;
float
Y = GamePad.ThumbSticks.Left.Y;
EchoToTopStatus(
"ThumbStick:
X is "
+ X +
" Thumbstick: Y is "
+ Y);
Functionally, this is the same as the previous version, except with fewer
lines. It produces the same output, and actually goes through all the
same steps when the program is running. It's written in two fewer lines
of code, though.
In this version, we've combined the step of defining a variable:
float
X
= GamePad.ThumbSticks.Left.X;
with the step of assigning a value to the variable:
float
X
= GamePad.ThumbSticks.Left.X;
in order to both declare a local variable, and immediately give it an initial
value (immediately initialize it) on a single line:
float
X = GamePad.ThumbSticks.Left.X;
While functionally equivalent, it is sometimes handy to initialize a
variable in the same statement that declares it. You should be
comfortable reading (and writing) both code that uses either (or both)
techniques.
Once you're comfortable with version #2, you should comment that version out, and
uncomment Version #3, so that you are now looking at:
// Version #3:
Vector2
LeftThumb;
LeftThumb =
GamePad.ThumbSticks.Left;
EchoToTopStatus(
"ThumbStick:
X is "
+
LeftThumb
.X
+
" Thumbstick: Y is "
+ LeftThumb
.Y);
Functionally, this is equivalent to the last two versions. The major
difference here is that by creating a different type of local variable, we can
store the same data in a slightly different way. We'll be examining this
way mostly because it's a fairly common way to store data in XNA.
The next statement in the Update method
declares
(creates) a variable
named
LeftThumb
. It does so by listing the
type
of the
variable (
Vector2
),
then choosing a name for the variable.
By defining the variable to be a
Vector2
, we are
telling C# to reserve some space in the computer's memory that will be used to
store
a two-dimensional vector
. A two-dimensional vector is defined as
having an X part (that is a
float
), and a Y part (also a
float
).
You'll notice that a
Vector2
is composed of two simple variables (two
float
s).
Technically, a
Vector2
is a
struct
, which is short for
struct
ure, meaning that it is
composed of multiple parts (unlike a simple type, like a
float
, which is made
up of just one part)
In video-game programming, it's common to deal with (X,Y) pairs. So
common, in fact, that XNA defined this
Vector2
in order to
help us use (X,Y) pairs
quickly and easily.
Be aware that
Vector2
is a data type that is specific to XNA!
It is NOT normally a data type that you can use in, say, your Console programs
(or your ASP.Net programs, etc, etc).
Since you can also 'fake' a
Vector2
by creating two floats (one for the X part, and one for the Y
part), it's not really a huge problem, but if you do write a C# Console
program, be aware that
you can't use
Vector2
in that non-XNA program!
In contrast, a float is a simple variable, with a single piece of data, as
illustrated here:
We choose the name
LeftThumb
because this variable will store all
the information about the left thumbstick - the X and Y parts of the
thumbstick's current position. We chose
LeftThumb
instead
of just
Thumb
because there's also a right thumbstick, too.
There are many other, perfectly good names we could have chosen for this
variable. Don't feel like you have to copy these names exactly for your
variables - it's actually better to try making up your own names, to
get the hang of making up good variable names, than to blindly copy what's here.
Note that
LeftThumb
variable is a
local
variable, meaning
that we can use it as soon as we've declared it, and at any point between the
definition and the end of the method. But once the method ends, the
space that has been reserved to hold this local variable will disappear, just
like with
X
and
Y
.
The next statement (
LeftThumb =
GamePad.ThumbSticks.Left;
)
assigns
the value that is currently
stored in
GamePad.ThumbSticks.Left
to
the
LeftThumb
variable that we just created.
In C#, the = symbol (a single = sign) is the
assignment operator
.
It assigns the value stored in whatever is on the right into the space
identified by whatever is on the left. In the case of
struct
variables (but not
class
variables, which you'll see later), C# will
attempt to copy each part of the the thing on the right into each part of the
thing on the left, as illustrated with the red arrows in the following
picture:
Note that
LeftThumb
now contains a COPY of the value that was in
GamePad.ThumbSticks.Left
. Since
it's a copy, even if the value that's stored in
GamePad.ThumbSticks.Left
changes, the
value that we have stored in
LeftThumb
will NOT change.
Similarly, we can change the value in
LeftThumb
and not worry about
affecting the value that's in
GamePad.ThumbSticks.Left
.
The final statement in this version (
EchoToTopStatus(
"ThumbStick:
X is "
+
LeftThumb
.X
+
" Thumbstick: Y is "
+ LeftThumb
.Y);
) then outputs
the values currently stored in the X part of LeftThumb, and the Y part of
LeftThumb to the top of the screen.
Note that for a variable that's composed of other things (like a
Vector2
), we identify
the part that we want by separating the name of the variable (
LeftThumb
)
from the name of the part (
X
) using a dot/period (
.
-
LeftThumb
.
X
)
Once you're comfortable with version #3, you should comment that out, and
uncomment Version #4, so that you are now looking at:
// Version #4:
Vector2
LeftThumb = GamePad.ThumbSticks.Left;
EchoToTopStatus(
"ThumbStick:
X is "
+ LeftThumb.X +
" Thumbstick: Y is "
+ LeftThumb.Y);
Functionally, this is equivalent to the previous version.
This version demonstrates that you can also initialize struct (non-simple) variables when defining them, too. In general, if you can use the
assignment operator on variables of a particular type (
float
,
Vector2
),
then you can initialize variables of that type when you declare them.
A note on comparing the different versions:
All of these solutions are all
equivalent
- each
one produces the same results on-screen, and ultimately, each one causes the
computer to do in the same way. In one version, we might specifically
create storage space for both X and Y, in another, we create storage space for a
single thing, which in turn contains a separate X and Y. Similarly, we
might first declare, then later assign values to our variables, or we might do
that all in one step. So in a very real sense, the work that the computer
is doing is the same in all the solutions.
In this sense, no one version is "best". Each one may have
advantages over the other (and/or disadvantages relative to the other) in
terms of how easy it is for programmers to read and understand the code, or in
terms of the personal preferences of individual programmers. But no one
solution is so much better than the others that it's "best". This sort of
situation - multiple, more-or-less equivalent solutions to the same problem - is
very common in programming.
The important skill for you to develop is to be able to clearly understand the advantages/disadvantages
of a given solution, and to be able demonstrate that understanding by discussing
it, or explaining it. Ultimately, this skill will enable you to create a
great solution for problems that you haven't even seen yet :)
FURTHER EXERCISES::
Start from a blank starter project (1000.201, if you need it), and re-do
the code from memory as much as possible. On your first try, do what
you can, and keep the above code open so that when you get stuck, you can
quickly look up what you forgot (and that after you finish a line, so that
you can compare your line to the 'correct' line). On the next try, do
the same thing, but try to use the finished code less. Repeat this
until you can type everything, without referring the tutorial's code.
Repeat this exercise daily for several days, so that you really get the
hang of this. As you go on, periodically review this by re-doing this
exercise.
Using the same project that the above tutorial makes use of, modify the
program so that the code does NOT assign a value to a local variable, and yet
the code still tries to print the variable using EchoToTop/BottomStatus.
What happens?
When talking about variables, we can discuss their
scope
.
Loosely put, a variable's scope is the region of source code in which we can
use the variable (i.e., no compiler errors and no logical errors result from
using the variable).
In your own words, describe the scope of local variables. Feel free to
further research this idea in your textbook, online, etc.
Let's say that you have a program, which is included in the box below.
For each comment, you should be able to describe whether an instance
variable (such as
X
, which is declared in the UpdateWorld method) is in scope, or not.
// Location A: Is X in scope here?
using
System;
using
System.Collections.Generic;
using
Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Audio;
using
Microsoft.Xna.Framework.Content;
using
Microsoft.Xna.Framework.GamerServices;
using
Microsoft.Xna.Framework.Graphics;
using
Microsoft.Xna.Framework.Input;
using
Microsoft.Xna.Framework.Net;
using
Microsoft.Xna.Framework.Storage;
using
XNACS1Lib;
// Location B:
Is X in scope here?
namespace
LocalVariableAssignment
{
// Location C: Is X in scope here?
public
class
Game1
:
XNACS1Base
{
// Location D: Is X in scope here?
public
Game1()
:
base
(
new
Vector2
(0,
0), 100.0f)
{
// Location E: Is X in scope here?
}
protected
override
void
InitializeWorld()
{
// Location F: Is X in scope here?
}
protected
override
void
UpdateWorld()
{
if
(GamePad.ButtonBackClicked())
this
.Exit();
float
X;
float
Y;
X = GamePad.ThumbSticks.Left.X;
Y = GamePad.ThumbSticks.Left.Y;
// Location G: Is X in scope here?
EchoToTopStatus(
"ThumbStick:
X is "
+ X +
" Thumbstick: Y
is "
+ Y);
}
}
// Location H: Is X in scope here?
}
Examine the code given in the box below. Notice that inside the
InitializeWorld method, there's a variable named X, which is a
float
,
and was assigned the floating-point number 3.14159, and inside the
UpdateWorld method there's another variable named X, which is also a
float
, and
which is assigned a different value.
Is it legal to have these two local variables with the same name?
If the current value of
GamePad.ThumbSticks.Left.X
is .75 when UpdateWorld is run, what will be displayed at the top
of the screen? Why not 3.14159?
Will 3.14159 ever be displayed at the top of the screen?
using
System;
using
System.Collections.Generic;
using
Microsoft.Xna.Framework;
using
Microsoft.Xna.Framework.Audio;
using
Microsoft.Xna.Framework.Content;
using
Microsoft.Xna.Framework.GamerServices;
using
Microsoft.Xna.Framework.Graphics;
using
Microsoft.Xna.Framework.Input;
using
Microsoft.Xna.Framework.Net;
using
Microsoft.Xna.Framework.Storage;
using
XNACS1Lib;
namespace
LocalVariableAssignment
{
public
class
Game1
:
XNACS1Base
{
public
Game1()
:
base
(
new
Vector2
(0,
0), 100.0f)
{
}
protected
override
void
InitializeWorld()
{
float
X;
X = 3.14159;
}
protected
override
void
UpdateWorld()
{
if
(GamePad.ButtonBackClicked())
this
.Exit();
float
X;
float
Y;
X = GamePad.ThumbSticks.Left.X;
Y = GamePad.ThumbSticks.Left.Y;
EchoToTopStatus(
"ThumbStick:
X is "
+ X +
" Thumbstick: Y
is "
+ Y);
}
}
}
Using the same project that the above tutorial makes use of, modify the
program so that Version #2 is uncommented (i.e., it's active), but you DO
NOT COMMENT OUT Version #1. What happens when you try to compile your
program? In your own words, what do those errors mean?
Using the same project that the above tutorial makes use of, modify the
program so that Version #3 is uncommented (comment out everything else).
Compile and run your program, in order to make sure that everything works.
Once you've done that, try removing the .X / .Y when printing? What
happens when you try to compile your program? In your own words, what do
those errors mean?
List some differences between of Version #1 (or #2) vs. #3 (or
#4). Specifically, focus on the advantages, and disadvantages, that #1
(or #2) has over #3 (or #4).
Writing Code On Your Own: The Right Thumbstick
Start this exercise using
Exercise_
9
's starter project
,
which is a nearly identical copy of the project that was used in the above
tutorial. Change the program so that instead of updating the top
status bar based on the right thumbstick, instead of the left thumbstick.
You can access the right thumbstick using the phrase ,
GamePad.ThumbSticks.Right
in the
appropriate places.
The default keyboard to controller mapping assigned the
Left Arrow
key to mean
"push the thumbstick to the left", the
Right
key to mean "push the thumbstick
to the right", the
Up Arrow
key to mean "push the thumbstick up", and the
Down Arrow
key to mean "push the thumbstick to the down".