Uploaded by forjparknights

Quick Terraria-specific C# crash course

advertisement
Terraria-Specific C# Crash Course
By Kalciphoz
This is a tutorial that will delve into the basics of C# programming, simplified to the
bare basics needed for Terraria modding. The tutorial part is approximately twelve
pages long and it will likely take some degree of active effort to achieve its full
benefit. Some aspects of this tutorial are specific to tModLoader, but in the event of a
new API replacing this, these aspects will likely be updated accordingly. Means of
providing feedback on the guide are currently under evaluation.
Aim of this Tutorial
If you are a casual modder looking to play around with tModLoader and make some
low-effort additions, then you will likely find this guide ill-suited to your needs. If you
are a spriter, then this guide is not directed towards you, although a graphics
designer may benefit from some programming knowledge when cooperating with
programmers. This guide is mainly directed towards modders seeking to enhance
their coding ability to the point of being able to write their own code independently. If
you have previously been searching for code-snippets and copying it mostly
character by character, but wish to improve to the point of being able to add new
game mechanics, code boss behaviours or similar, then this guide is especially for
you. This guide does not currently aim to also teach the mathematics that might be
relevant to Terraria mods, though a section dedicated to directing readers towards
relevant resources is part of future plans for this guide. For now, I advise anybody
looking to code projectile or boss behaviours to familiarize themselves with
trigonometry and I advise everyone to learn about functions.
Background and Disclaimer
I made this guide upon request from another modder, looking to improve his coding
abilities in a general scope. While I am able to write code independently of help from
others and have attained the skills that many readers are here to learn of, I have no
formal training in programming, nor have I undergone education in programming; I
am mostly self-taught and have learned most of what I know with the purpose of
creating mods for Terraria. Though this guide might improve your general
programming ability, it will not be sufficient in scope to learn programming as a
general skill, but on the contrary, the aim is to be accessible to people who have
recently started Terraria modding and have no familiarity with general principles of
programming.
TUTORIAL FORMAT
This tutorial will go through some of the most basic concepts in C# programming.
The format consists of separate sections, each dedicated to a subject. They will
contain links to a syntax-highlighted pastebin, which you will examine with the help of
this guide.
Going through the explanations, you will frequently come across words marked with
an asterisk (*), signifying that the term is part of programming terminology, and that
you will find it in the pastebin link, written in all caps.
At the end of each section, you will find a list of some of the terminology used so far.
You will want to be familiar with all the listed terminology before moving on, but do
not worry, there is not much.
When reading the explanations, I strongly suggest you keep the relevant code
examples ready in another tab for quick referencing.
Additionally, you will come across links to pages that are not pastebin. Those links
are all optional and if you do not understand their content, just proceed with the
guide.
When looking at the pastebin links, look at the text box marked by “C#”
wherein some of the words are coloured, rather than the “RAW Paste Data”
VARIABLES AND PRIMITIVE TYPES
http://pastebin.com/XPSq1Abm/
Variables are named values that can be changed. Variable definitions* consist of, at
a minimum, a type* (int, in the example provided) and a name. I will further explain
types in later sections, but for now, we will focus on int, which is a numeric integer
value, going from -2,147,483,648 to 2,147,483,647, known as the minvalue and
maxvalue respectively. Variables of this type can be set to a specific value by writing
an equals sign (see variable initialization in the code example)
Null
Some types are nullable, meaning their values can be null (think n/a, essentially). In
the code example, the example_integer is null right after its definition. If you tried to
decrease or increase its value at this step, the program would give you a fatal error
known as the NullReference Exception. Fortunately, we can do something about this
by initializing* the variable. For the sake of convenience, this can be done in the
same step as the variable definition, as on the last line of the code example.
Two types of ‘types’
Variable types* can either be primitive types, or they can be classes. The int example
is a primitive type. They can be distinguished from classes by the fact that classes
are normally capitalized and highlighted in cyan, whereas primitive types are
highlighted in blue. Classes as types will be discussed in the next section, but we will
focus on primitive types for now.
Primitive types are written in lowercase and highlighted as blue when using Visual
Studio. Here’s a full list, but for general modding, you will only need these: string, int,
long, double, float and bool. strings are for text variables, and their values need to be
surrounded by quotation marks, like so:
string s = “Hi, I am a string.”;
strings can contain all sorts of characters, including spaces and special characters,
as well as some stranger things we will not go into.
We have already discussed ints to sufficient detail, but in the rare event that you
need larger numbers than can be contained in a normal int, use a long. Next up is
double, which is a decimal number, unlike an int, which is always a whole number.
Next up is a float, which for our purposes is the same as a double, but you have to
write an ‘f’ after the number, like so:
float example_float = 4.2f;
We will discuss bools in a separate section.
Casting/converting types
Say we wanted to increase the damage of an item based on its knockback. The
intuitive thing to do would be to write the following:
item.damage += item.knockBack;
However, this will not compile, as item.damage is an int value, and item.knockback is
a float value. To fix this, we will do something known as type casting (also sometimes
referred to as parsing, which is a slightly broader term), which is done like so:
Item.damage += (int)(item.knockBack);
Doing this will convert the float value to an int value, allowing us to add it to the
damage. This will also round down the value to a whole number. For other rounding
mechanisms, see
https://msdn.microsoft.com/en-us/library/system.math(v=vs.110).aspx/ or ask a more
experienced modder for help.
List of terms:
Variables
Defining a variable
Types
Null and NullReference Exception
Initializing a variable
Primitive Types: string, int, double and float
Type Casting
CLASS STRUCTURE
Let’s examine a very simple class:
http://pastebin.com/LbwaVN4B/
A class consists of, at a minimum, a namespace* and a class definition*. They can
also have an optional constructor*, which will be explored later in this section and the
ones following.
Namespace*
Namespaces are similar to file addresses in windows. Namespaces follow the name
of a folder, but unlike in windows, the folders are separated by dots rather than
slashes, for example: System.Exception
Class definition*
This is the name of the class. It is often preceded by an access keyword, such as
public or private, but for the purposes of general modding, you will use the public
keyword.
Class Instantiation
As was mentioned in the variables and primitive types section, classes are a form of
types. Their behaviour is somewhat different from primitive types though, as will be
explored shortly. Sometimes, a primitive type simply is not sufficient for our purposes
when creating variables. Let’s use the Item class as an example:
public Item i;
Like in the first code example, this is a variable definition. Unlike in the first code
example, this variable uses a class as a type. Whenever an item is dropped, crafted,
gathered or generated, a variable of the type Item is created. This is called
instantiating, or making an instance of the class. The player inventory is a long array
(more on that later) of Item-variables. Variables that are instances of a class are
called objects, and you should by now have an idea why this is extremely useful.
This brings us to the next section, but first:
List of terms:
Class
Class definition
Instantiation
Keyword: public
OBJECTS AND METHODS
Objects are at the core of C#. So much, in fact, that C# is called an object-oriented
programming language. The biggest difference between objects and primitive
variables is that objects can contain their own variables, including other objects.
Following along with the public Item i; example, we could do this:
int dmg = i.damage;
Here, we reference the variable damage, which is local to objects of the type Item.
This will give us another NullReference Exception, though, because like variables,
an empty definition like public Item i; will point to null.
Constructors
So how do we initialize an object, you might wonder. We do this by using the
constructor mentioned in the class structure section. Starting from the example class
from there, which I have linked for your convenience, we could do something like this
public ClassTutorial example_object = new ClassTutorial();
So what is a constructor exactly? To answer that, we will have to first explore the
topic of methods: In a C# program, all the code happens within methods. In fact,
there’s a single parent Method from which all the code starts, known as the Main()
method. This is unimportant for Terraria modding, but if you want to do a standalone
program, refer to https://msdn.microsoft.com/en-us/library/ms228506(v=vs.90).aspx/
Methods play two main roles in code: They are defined and called. Methods are
essentially a procedure that is defined and can be repeated at will without typing the
whole thing again.
Example method definition: http://pastebin.com/R9VAnWvL/
You can see the method definition as a sort of recipe describing a procedure that
happens every time the method is called. Method definitions happen at compilation,
so you can place a method at the very end of a class, even if it is called earlier.
A method has a list of keywords, (in the example, public and static), a return type*, a
method name (ExampleMethod), a list of arguments, and a method body*.
The content of the brackets on line 1 is method input, known as arguments. These
arguments are labelled like variables and can be referenced in the method body.
They cannot be changed, however, unless preceded by the keyword ref in both the
method definition and call.
You will also notice a new keyword, static. Variables and methods can be either
static or nonstatic. If they are nonstatic, you need to reference or call it from an
object, like so:
i.damage;
You can think of these as local variables, because each object has their own copy of
that variable and can have different values assigned to it. In some cases, this is not
desired. For those situations, we use the static keyword, which will be covered later.
“Local variable” means something else in C# though. A local variable is one which is
defined inside a method, which means you cannot reference or change it outside of
the same method. By contrast, variables declared directly in the class structure are
called fields.
Example method call: http://pastebin.com/XXC7wKsT/
Here the method is called, meaning that all the code in the method body is run using
the arguments (input) enclosed in the brackets. This saves you the trouble of
rewriting the entire process.
Now we will examine the concept known as return types. In the example method
definition, the return type is string. Return types allow us to reference the method like
a variable, and assign variables to the return.
Now let us return to the constructor example.
public ClassTutorial example_object = new ClassTutorial();
Despite being methods, constructors are not actually named. “ClassTutorial” is in fact
the return type rather than a name. This is why we can use a constructor to initialize
an object, because the constructor actually creates a new object and returns it when
called. Since the constructor has no name, it uses a distinct keyword: new
This example constructor takes no arguments and does not actually do anything, but
if we’d like, we could add some code to the method body. Constructors are
commonly used to initialize variables, so we will add a variable.
http://pastebin.com/XRcCdhG5/
You will notice the object referenced as this, referring to the object being created.
You do not actually need to put this, as it is implicitly understood, except when
there’s an argument of the same name as the local variable, like in the example.
Now that we have a local variable that gets initialized, we can reference it using any
object of the type ClassTutorial, like so:
int index = new ClassTutorial(14).index;
However, having not stored the object in a variable, all we’ve done is invented a
more complicated way to initialize an int, so next we’ll invent a way to retrieve the
ClassTutorial object from its index, using something known as arrays.
List of terms:
Object
Keywords: this, new
Method
Constructor
Method call
Method arguments
Local variables
fields
ARRAYS AND STATIC FIELDS
http://pastebin.com/4aePfqFm/
Arrays were mentioned in the context of the player’s inventory earlier. An array is a
long list of variables with a single name and an index. In the example code, you can
find an array definition and how to reference the value of an array. Arrays are not
limited to primitive types, however. The player’s inventory, for example, is an array of
Items, defined something along the lines of:
public Item[] inventory = new Item[50];
This allows us to reference an item in the player’s inventory by using
Item item = Main.player[Main.myPlayer].inventory[0];
You might have noticed another array being referenced here: Main.player[], whose
index we get by referencing an int variable, myPlayer, in the Main class.
Let us do something similar in our example class.
http://pastebin.com/FH2R9bup/
The constructor now adds the object being created to an array, allowing us to
retrieve the ClassTutorial object by writing
ClassTutorial object = ClassTutorial.objects[index];
You will notice that ClassTutorial.objects[] is a static array. This means it will be
stored directly in the class rather than in the object, and that no object reference is
needed to reference the array. Objects, methods, primitive variables can all be static,
as can classes, but that is beyond the aim of this guide.
Now let us make use of a static variable. Returning to the array example, we might
run into a problem if we use the same index twice, because only the second object
will take the place of the first. To prevent that problem from occurring, we might do
something like this:
http://pastebin.com/gs4Y1GVJ/
We have now removed the index as an argument and instead retrieved the index by
a value that increments itself every time. There are still some issues with
IndexOutOfBound Exceptions, which will occur the 101st you call the constructor, but
by now you should have already gotten a basic understanding of arrays, as well as
the static keyword.
List of terms
Array
Index
Keyword: static
IndexOutOfBound Exceptions
TMODLOADER AND CLASS EXTENSIONS
At the time of writing, tModLoader is the most prevalent modding API for Terraria,
and if you want to understand Terraria modding, you will need to know how
tModLoader works. To achieve this, you will need to know about a concept called
class extension.
Class Extension
Say you wanted to make a class that had the same basic functionality as
ClassTutorial, but with something added on top. The best way of doing this would be
to extend the class, looking something like this:
http://pastebin.com/s50A24Hc/
You will notice the colon in the class declaration. This makes the class an extension
of whatever is written after the colon. However, there’s not much point to this class
extension yet, as it doesn’t actually do anything differently from its parent class, so
let us change that up a bit:
http://pastebin.com/5rcUxJxG/
This is what an extended constructor looks like. The base() is actually calling the
original constructor, and any arguments need to go in the brackets. This means that
both the original constructor and its extension are executed.
override methods
This is how tModLoader ties into all of this. Your Mod class is actually an extension
of Terraria.ModLoader.Mod
http://pastebin.com/U0v2ZJQt/
In this example, we override the method known as AddRecipes() in order to make
Meowmere craftable out of thin air. To override a method, we need to put override as
a keyword. When you have typed the keyword, Visual Studio will make a popup with
available methods to override. Unlike extending a constructor, overriding a method
stops the original from being called. If you want to call it, you will have to manually do
it, like so:
base.AddRecipes();
However, in the case of AddRecipes(), this is redundant, because the base method
does not actually do anything at all. The hooks in tModLoader generally don’t do
anything except sometimes returning a boolean value, which will be covered in the
following section, but first I will link the tModLoader documentation:
https://github.com/bluemagic123/tModLoader/wiki/Mod/
And when it comes to documentation, MSDN is extremely useful:
https://msdn.microsoft.com/en-us/library/618ayhy6.aspx/
For a more professional and in-depth guide on class extension:
https://msdn.microsoft.com/en-us/library/k6sa6h87.aspx/
List of terms
Class extension
Keywords: base, override
Overriding methods
MISCELLANEOUS HELPFUL KNOWLEDGE
Namespaces pt. 2 / Importing libraries
During the tModLoader and class extensions section, you might have wondered
about encountering “Terraria.ModLoader.Mod”, when you are likely used to just
writing “Mod”, because that is indeed much simpler, but it requires you to write the
following above your namespace:
using Terraria.ModLoader;
Terraria.ModLoader is the namespace of tModLoader, and all its classes are located
within that namespace.
Statements
The term “statement” refers to many things, including declaring a variable, assigning
it a value, calling a method, etc., but not if this is done as an argument surrounded
by round brackets.
All statements should be followed by a semicolon, and it is general practise to put
separate statements on separate lines.
Booleans
bools were mentioned in the start of the guide. They can have the values true and
false. Conditional statements like if() and while() take a boolean input. You may have
seen structures like
if (i > 0) { … }
That does not look like a boolean input…? Well, actually it is. “>” is called a
comparative operator and it returns a boolean value, true if i > 0, false otherwise.
This means that the following is actually valid code:
bool b = i > 0;
if (b) { /*do something here*/ }
Other operators you should be familiar with are && and || These are called boolean
operators because they take boolean input and return a boolean value. The first is a
boolean AND, which returns true if both inputs are true. The latter is a boolean OR,
which returns false if both inputs are false.
A boolean value can be negated by writing ! in front of it like so: !true, which rather
unsurprisingly evaluates to false.
For a full list of operators, see
https://msdn.microsoft.com/en-us/library/6a71f45d.aspx/
Loops
Loops play a similar function to methods, in that they allow you to repeat similar code
without having to type it all over again. When the program encounters a loop, it will
be immediately run on repeat until some condition is no longer fulfilled, that is, until a
conditional statement evaluates to false.
http://pastebin.com/WaVkw3YN/
For loops have a structure as seen in the pastebin. First, the statement known as the
initializer is executed, mostly used to declare and assign a variable. Then the
condition is checked, and if the condition is true, the statement body is executed, and
afterwards, the iterator. If the condition is false, the loop is terminated.
Another way to terminate a loop is by the statement:
break;
For a possibly outdated list of loops in C#, refer to
https://msdn.microsoft.com/en-us/library/f0e10e56(v=vs.90).aspx/
List of terms:
Statements
Primitive type: bool
Comparative operators
Boolean operators && and ||
For loops
TERMS YOU SHOULD BE FAMILIAR WITH
Variables and Primitive Types:
Variable
Variable definition
Type
Null and NullReference Exception
Variable initialization
Primitive Types: string, int, double and float
Class Structure:
Class
Class definition
Instantiation
Objects and Methods:
Object
Keywords: this, new
Method
Constructor
Method call
Method arguments
Local variables
Fields
Arrays and static fields:
Array
Index
Keyword: static
IndexOutOfBound Exceptions
tModLoader and class extensions:
Class extension
Keywords: base, override
Overriding methods
Miscellaneous Helpful Knowledge:
Statements
Primitive type: bool
Comparative operators
Boolean operators && and ||
For loops
Afterword
If you have made it this far in the guide and are familiar with all the above listed
terminology, then you are off to a very good start when it comes to Terraria modding,
and you will likely soon find others asking you for advice. In this event, I encourage
you to assist them as it will help develop your own skills, and I would appreciate if
you direct them towards this guide.
You may find at first that you will keep needing the assistance of others to solve
problems in your code, but with a bit of routine, this will change. When you are
helped by experienced modders, it is crucial that you make an effort to understand
the purpose of their advice and the reasoning behind it, as this will help you
anticipate and avert problems when writing code.
This concludes the guide, thanks for your interest.
TO ADD:
Clean code, including:
Proper usage of methods for clean code.
Constants
Section on mathematics
Section on visual studio
References for common errors in compilation, including syntax errors, null pointers,
etc., as well as a guide on how to debug
Download