All times are UTC


Dear guest, welcome to YouTD!



Post new topic This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 
Author Message
 Post subject: vJASS Tutorial
PostPosted: Fri Jun 12, 2009 11:57 am 
Offline
Site Admin
Site Admin
User avatar

Joined: Fri Jul 21, 2006 11:31 pm
Posts: 4713
Location: Munich, Germany / Sydney, Australia
YouTD Score: 270
vJASS Beginner's Tutorial

This is no complete tutorial for vJASS, it is rather a small introduction that teaches only the stuff you need to know for YouTD.
It is meant for people that already know JASS, which will not be explained here.
In addition, it tries to communicate the concept of Object Oriented Programming (since this is the core of vJASS additions). If you are familiar to OOP, you can skip some of the chapters.



1. Global Declarations
As you might have seen, there are such blocks inside of some triggers:
globals
   integer i
   string s
endglobals

These declare global variables. That means you never have to open that variable GUI dialog again and create variables there. This is much easier and faster. Note that YouTD doesn't export global variables defined in the GUI dialog. So if you need any globlas, define them like that. And the best is that you get rid of that "udg_" prefix :).



2. Structs and the OOP Concept


2.1 A short introduction to Object Oriented Programming

The core idea behind vJASS structs is to apply the concept of Object Oriented Programming (OOP) to JASS.
Normal JASS uses the concept of Procedural Programming. That means the core thing about it are functions.

In OOP, the core concept are classes of objects (Structs).
The key idea is that you don't have just a big heap of functions with no structure but different types of objects. Each of those object classes brings its own functions, called methods, which can be applied onto them.
The key advantage about that is that the code gets structured and much more readable. Other advantages like information hiding are not discussed here, as they are not important for YouTD.


2.2 Defining a struct

Even if you don't have to define your own structs to create content for YouTD, I will start with this chapter to give you a hint about what lies behind the structures used inside the engine.

A struct (short for structure) is a class of objects. To make things easier I will use an object that you know from real life to make things clearer. This object is the apple tree. I hope you know apple trees, they stand
around and provide nice things called apples ;).

Insertion:
Naming convention: Just like in the engine, I will start every new defined struct type with a capital letter and all other things (function names, global / local variable names) with a lower case letter.

Next insertion:
Whenever I want to abbreviate something in my trigger code, because it is not important, I will use three dots (...). Of course, you have to replace these dots with something reasonable if you want to use the code.

In vJass we would define this class of objects like this:
struct AppleTree

   //Define the methods and other stuff that can be done with an apple tree here

endstruct


As soon as you define the apple tree like this. "AppleTree" becomes a new data type, just like integer, string and unit you know from normal jass.
This means we can use AppleTree whereever we could use an other type. For example we could create global and local variables of type AppleTree:
globals
   integer i
   string s = "sdfljdsajf"
   AppleTree at
endglobals


We have just defined our own data type. No we want to create instances of this data type. For old JASS data types we used functions like CreateUnit(...) CreateItem(...) CreateGroup(...) and so on for creating new instances. For our apple tree, no such function exists of course.
Instead, structs are created like this:
AppleTree.create()

which we could use to assign our global apple tree variable "at" with a newly made apply tree:
set at = AppleTree.create()

Create is the first (static) method that we learn. Even if we haven't defined it, every struct type has this method implicitly. It just creates an instance of the type that is written before the dot.

However, we can redefine that method to take additional parameters. I will not tell you how to redefine it, but lets say we have redefined it that it takes an integer paremeter: The number of apples the tree initially has. Then we would call
set at = AppleTree.create(23)

to create an apply tree that has 23 apples hanging on it.

To get back to the YouTD engine we have for example the struct BuffType, BuffTypes are also created using that method, however it takes some more parameters.
local BuffType bt = BuffType.create(...,...,...)


If we want to get rid of our apple tree, we can use the .destroy() method. This is the second method that we learn. We also do not have to define it ourselves, it exists implicitly for every struct we define.
So, this could be a lifecycle of our apple tree (this time we use a local variable to store it):
local AppleTree at = AppleTree.create() //Hooray, a new apple tree is born!
... //Do something with at
... //Do more stuff with at
call at.destroy() //That's it, our apple tree just died :( (that's life)


Here, you see the first difference from static methods (like create) and normal methods (like destroy). Static methods are prefixed with the struct type, (AppleTree. ...). Normal methods are prefixed with an instance of that type (at. ...). I will explain the difference later. The only thing you should remember here is the syntax difference.

Don't get me wrong! An apply tree is no unit you will see on the battle field. A struct is just an abstract data construct (just like you don't see an integer on the battlefield), however, we can fill it with things that you really see.


2.3 Struct members

Right now, our apple tree can't do anything. All we can do is create one and destroy it.

The next thing we want, is storing some data in it. The name structure already implies that it "structures" something, namely data from different types.
Variables that are attached to an object are called "members" or "attributes" of that object.

So let's attach some data to our tree! We use an integer to count the apples on the tree and a string to specify the apple color this tree has.
We modify the definition of the apple tree like this:
struct AppleTree
   integer numApples
   string appleColor
endstruct

As you see we just declare those members like we did with global variables inside of a globals..endglobals block.
These variables are now something different of course. Each apple tree has its own. If we create two apple trees we can set the number and color for each one.
So even if these definitions look like global ones they are something else!

We access (get/set) those members with the dot syntax again:
local AppleTree at = AppleTree.create() //Hooray, a new apple tree is born!
set at.appleColor = "green" //The apple tree carries green apples
set at.numApples = 100 //It carries 100 apples
set at.numApples = 2 * at.numApples //We just doubled the number of apples on our tree!

So as you see, you just set those members like normal variables with the set syntax.
We can also use them like normal variables in calculations (like you see at the last line).
To formulate this as a sentence:
set at.appleColor = "green"

means "Set the color of apples for the AppleTree 'at' to 'green'".

Now we can store and read arbitrary data inside a struct.


2.4 Struct methods

You know know how to get and set member variables. But if you check the API of YouTD, only a few structs have these member variables. The most stuff that the YouTD structs have are methods, i.e. functions that can be used only on these objects.

You already know two functions. The .destroy() method and the .create() method which is static. Here, we will talk about non static, normal methods. However, these methods are implicit, thus you don't have to define them, all other methods must be defined by you.

You call such a method like this (you already saw that):
call at.destroy()

So the general syntax is:
call OBJECT.METHODNAME()

To be said in a sentence, this means "apply the method <METHODNAME> onto the object <OBJECT>".
That is basically all you need to know about calling such methods. If you keep the above sentence in mind, such a call is a very easy things.

I will now explain you how to define a method. This is just for learning purposes. For a YouTD tower, you don't have to define methods. You rather have to call methods from predefined engine objects like Unit, Tower, Creep, Buff... .

I hope you know how to define a function, in JASS. A method is exactly defined like a function. The only difference is that we replace "function endfunction" with "method endmethod" and we have to define it inside a struct (of course, otherwise the compiler would have to guess to which struct this method belongs).

Let's give our apple tree a method .dropApple(). It reduces the apple count by one and outputs a debug message. If no more apples are on the tree a warning is displayed instead.

Let's start with how we you would write this function in normal JASS:
function dropApple takes AppleTree a returns nothing
   if a.numApples 1 then
      call BJDebugMsg("No more apples on this tree, unable to drop one!")
   else
      set a.numApples = a.numApples - 1
      call BJDebugMsg("Dropped an apple! Enjoy your meal!")
   endif
endfunction

As you see, we have to hand the apple tree as a parameter. Now, let's transform it into a method:
struct AppleTree
   integer numApples
   string appleColor
   
   method dropApple takes nothing returns nothing
      if this.numApples 1 then
         call BJDebugMsg("No more apples on this tree, unable to drop one!")
      else
         set this.numApples = this.numApples - 1
         call BJDebugMsg("Dropped an apple! Enjoy your meal!")
      endif
   endmethod
   
endstruct

The first thing you should notice is that the function is now called method and is inside of our struct definition.
The next thing is that the parameter "AppleTree a" has vanished. Instead, where we formerly used a. we now use this..
Since we call such methods with OBJECT.METHODNAME(), we don't need that parameter anymore. We know which object is meant (the one before the dot). However, we have to tell the compiler that we mean exactly this object. This is done by 'this'. As the name alreay implies "this" means somthing like "this object" or "the object we are curently dealing with" or "the object that was stated before the dot in the method call".

Note that we can omit the word this, so instead of
set this.numApples = this.numApples - 1

we can just write
set .numApples = .numApples - 1

(what I did in the engine).
So whenever you see just a dot followed by something, you know that the current object is meant with it.


2.5 Static members and methods

You already know one static method. The .create() method. As I already mentioned the syntax difference to normal methods (and members!) is that static methods/members are not prefixed with an instance of a struct but with the struct name itself.

This means, that inside of static functions, you cannot use 'this', because they are not called with a specific instance of that struct.
This makes clear that the .create() method MUST be static. Because with which instance should you call it? The instance will be created in the method itself, so you cannot call it with another instance.

The same goes for static member variables: They do not belong to a specific instance of that struct. They are rather a global variable for that struct. So where is the difference between a normal global variable and a static struct variable. The answer is: There is none (besides from information hiding)! However, the advantage is that the stuff is structured again. Let's get back to our apple tree.
Let's assume that we want a global counter that counts the number of apple trees. We could just use a global variable, however, we will use a static variable:
struct AppleTree

   static integer numTrees = 0  //Here we save the number of apple trees

   integer numApples
   string appleColor
   
   static method create takes nothing returns AppleTree
      set AppleTree.numTrees = AppleTree.numTrees + 1 //important
      return AppleTree.allocate()
   endmethod
   
   method onDestroy takes nothing returns nothing
      set AppleTree.numTrees = AppleTree.numTrees - 1 //important
   endmethod
   
endstruct

As you can see we defined a static member variable 'numTrees' by just adding the keyword 'static' before its declaration.

This method will count the number of apple trees currently alive.
Next thing is, that we write our own create method. The normal create method is no longer sufficient since we want to increase the number of apple trees whenever an instance is created.
As you see we define such a static function like create by just adding the keyword 'static' before its definition (just like for members).
In this function we increment the number of apple trees.
The rest that is done in the create method is allocating space for that apple tree. However, that is not interesting for us, keep the focus on the apple tree counter!
The next thing is that we define an onDestroy method. All you need to know is that this method is automatically called whenever we destroy an apple tree. In this function, we decrement the number of apple trees.

So now we have an automatical counter for our apple trees. Of course instead of using the static member variable 'numTrees' we could have used a global variable. But here comes the structure again:
With this solution, we know that 'numTrees' is associated with apple trees. Calling AppleTree.numTrees is much more readable than creating a global variable named 'numAppleTrees'.
By using statics we create a bond between the struct and the apple tree counter.

However, keep in mind that these variables are almost the same as global variables. So, there won't be one for each instance of the struct but only one for the struct type itself.


2.6 Reading the 'dot salad'

If you have understood everything until here, you know all the stuff about structs you need to know for YouTD. This chapter is just an addition to make things clearer.

In the engine, you often see long chains of variables/methods with many dots.
For example there could be
call buff.getCaster().getOwner().giveGold(buff.getBuffedUnit().getSpawnLevel(),null,false,false)

Let's assume buff is an instance of the struct Buff (capital B is the struct lower case b is the variable).

For a programmer who is used to OOP, this line is no problem, it is excellently readable, more or less like a normal english sentence. However, if you are used to procedural programming, that line may scare you.


But using the english sentence rule, applied from left to right, this line should be no problem for you.
  • We get the Unit that has cast the buff (buff.getCaster())
  • Since this method returns a Unit, which is again a struct type, we can call a method for that unit again:
    So we get the owning Playor of that unit: (.getOwner())
  • This is again a struct type, so we call the .giveGold method for it (which grants gold to a player).
  • As first parameter, this method needs the amount of gold to be given to the player. We calculate it from the creep level of the buffed unit.
  • buff.getBuffedUnit() gets the buffed unit.
  • .getSpawnLevel() gets us the level of that unit. The other parameters are not important here.

So here is the english sentence for this line (every experienced OOP programmer will have this sentence in mind as soon as he reads this line):
Get me the owner of caster of the buff and give him as much gold as the creeplevel of the creep that is buffed by the buff is.


2.7 Struct Inheritance

For normal real life objects, inheritance is like that:
An apple tree is kind of a tree. So we say "the type apple tree extends the type tree".
The same can be done for structs with the extends keyword.
In this case the type tree is called "super type" and the type apple tree (the extending type) is called "sub type"

Let's start with an example, it is very easy then. We will now model the inheritance for AppleTree and Tree:
struct Tree
   
   integer size
   
   method chopDown takes nothing returns nothing
      call this.destroy()
   endmethod   

endstruct

struct AppleTree extends Tree

   integer numApples
   string appleColor

   method dropApple takes nothing returns nothing
      if this.numApples 1 then
         call BJDebugMsg("No more apples on this tree, unable to drop one!")
      else
         set this.numApples = this.numApples - 1
         call BJDebugMsg("Dropped an apple! Enjoy your meal!")
      endif
   endmethod

endstruct

The struct tree has a member size (the size of the tree) and a method chopDown which just destroys that tree. Apple tree now extends Tree (check the definition of apple tree with the extends keyword).

What does this mean? To cut things short: This means we can use every member and method of Tree also for an apple tree. This makes sense: Just because a tree is an apple tree, it still has a size and you can still chop it down.
In addition, we can hand an apple tree to each other function that takes a normal tree as parameter and we can assign a variable of type tree with an apple tree. This makes sense again. If a function takes a tree as parameter or a variable stores a tree, then there is no reason why it shouldn't be able to store also an apple tree or a pine tree or what ever other kind of tree might exist.

So here would be valid code for these definitions:

local AppleTree at = AppleTree.create()
set at.size = 200
call at.chopDown()

So you see: Even if we create an apple tree, we can use the chopDown() method and the size attribute of the Tree struct.

Things are not possible vice versa:
local Tree t = Tree.create()
set t.numApples= 200 //error!
call t.dropApple() //error!

Of course, if we create a normal tree we cannot use AppleTree's members and methods. Since this tree is an ordinary tree or could be another sort of tree. A pine tree can neither drop apples nor can it have a number of apples on it.

I tell you all this stuff about inheritance because the YouTD engine uses it:
  • Tower and Creep extend Unit
  • Projectile extends DummyUnit
  • BuffType extends EventTypeList
  • Buff extends EventPlacer
  • .. and some others which are less important

So this means that if you have a variable of type Tower, you can also use all methods of the struct Unit onto it (like .addExp() or getOverallDamage()). Keep that in mind, you will need it a lot!

If you are sure that an instance of a super type belongs co a subtype you can make a cast onto the sub type to use it's methods.

Here is an example:
local Tree t = AppleTree.create()
call t.dropApple() //error!
call AppleTree(t).dropApple() //okay

As you see, we create an AppleTree but save it in a variable of type Tree (this is possible as mentioned above).
If we then call t.dropApple() we will get a compiler error. The compiler cannot know that inside of our variable 't' of type Tree is an apple tree. However, if we are sure that it is one (which we can be in this case), we can add a cast to the type AppleTree to make the stuff compile. Such a cast is just the name of the type to which should be cast, wrapped around the variable to be cast like a funciton call ( AppleTree(t) in this case).



3. Function Interfaces

If you can handle structs, you are preparred for 80% of the YouTD engine. The final 20% use function interfaces. These scare many people, however, they are ridiculously easy.


3.1 Problem description

I will present you again a real life example to fill that stuff with a bit of life.

Let's pretend you are a rich guy and have a butler. You can order the butler to mix a drink, to cook a meal, or to clean the house.
If we model the butler as a struct, it could look like this:
struct Butler

   method mixDrink takes nothing returns nothing
      call BJDebugMsg("The butler mixes a drink!")
   endmethod
   
   method cookMeal takes nothing returns nothing
      call BJDebugMsg("The butler cooks a meal!")
   endmethod
   
   method cleanHouse takes nothing returns nothing
      call BJDebugMsg("The butler cleans the house!")
   endmethod
endstruct


Now, for convenience, we want to add a method 'order' to the butler that just orders him one of these three jobs. This method must take some kind of parameter that specifies which job should be ordered.

The first idea is to use a string and an if block:
struct Butler

   method mixDrink takes nothing returns nothing
      call BJDebugMsg("The butler mixes a drink!")
   endmethod
   
   method cookMeal takes nothing returns nothing
      call BJDebugMsg("The butler cooks a meal!")
   endmethod
   
   method cleanHouse takes nothing returns nothing
      call BJDebugMsg("The butler cleans the house!")
   endmethod
   
   method order takes string orderId returns nothing
      if orderId == "cook" then
         call this.cookMeal()
      elseif orderId = "mix" then
         call this.mixDrink()
      elseif orderId = "clean" then
         call this.cleanHouse()
      endif
   endmethod
endstruct


This is a valid solution and will do the job! However, now imagine your butler has 100 different things you can order him. Do YOU want to write an if block with 100 ifs? This is neither fast, nor is it convenient to be written, it sucks indeed!
Or how do you handle if, during the time when you write the order method, you do not know which tasks the butler is able to do?
This is exactly the case for YouTD: When I wrote the engine, I did not know what event reaction functions you will write for your towers and which names you will give them.
So the above solution is impossible for YouTD.

That is where function interfaces jump in! The idea is the following: Wouldn't it be just cool to hand the function that should be ordered DIRECTLY to the order function?
I.e. could we not just give a parameter of type function to the order function and just execute the handed function? I mean something like that:
   method order takes function orderFunc returns nothing
      call orderFunc()
   endmethod

As you see the method just takes a function parameter named 'orderFunc' and just executes this function. The order method would be only one line, no matter how many possible orders would exist! This would be an extremely elegant and fast solution!
We would just call it like that:
myButtler.order(mixDrink)

However, function parameters are not possible in JASS. So this very elegant solution collapses miserably!

Function interfaces however, do more or less exactly this! They define a new type of function variable that can be passed to other functions/methods and executed.


3.2 Defining and Calling Function Interfaces

We have to define that 'function' type first by defining the function interface. Here is the example for the butler task:
function interface ButlerJob takes nothing returns nothing

This line defines a new type of variable called 'ButlerJob'. At first, this line looks just like the beginning of a function, with the sole difference that the word 'interface' is added behind function.

Now we can define the order method, only one line, just like the perfect but impossible solution above:
   method order takes ButlerJob job returns nothing
      call job.evaluate()
   endmethod

As you can see, we just hand a variable of type ButlerJob to the method. The method calls this function by calling VARNAME.evaluate().
Now we can just call our function like that:
myButler.order(ButlerJob.mixDrink)

So we just write the method, prefixed with the name of the function interface to hand the variable to the order method

That was basically everything about function interfaces. Not too difficult, wasn't it?

A final small note: The example above does not compile because function interfaces don't work with methods, only with normal functions. However, since you will be writing normal functions for YouTD, this is no problem for you :).


3.3 Function Interfaces in YouTD

In YouTD you must hand some event reaction functions to methods of the engine to register these as event handlers (functions that are automatically called when the event appears).
So all you need to know for YouTD is how to hand such a function (simply prefix it with the function interface).
You don't have to create function interfaces yourself, just use the few predefined ones!

An example:
You want to creat a buff that does something to its target every second.
So you first create a BuffType, create a function that will be called every second and then add this function to the BuffType as periodic event handler. The code might look like this:
globals
   BuffType myBT
endglobals

//This is the function that should be called every second for every buffed unit
function doEverySecond takes Buff b returns nothing
   //Do something with the buffed unit
endfunction

function init takes nothing returns nothing
   set myBT = BuffType.create(...) //Create the BuffType
   call myBT.addPeriodicEvent(???) //Add our function 'doEverySecond' as periodic event
endfunction

There are is a gap in the code. What should be inserted into the addPeriodicEvent method.
Let's check its signature. It can be found in the struct EventTypeList. Since the struct BuffType extends EventTypeList it can also use this method. Here it is (from the library EventTypeList):
//Adds an event handler that is called every  seconds (period can not be smaller than 0.2)
method addPeriodicEvent takes EventHandler be, real period returns nothing   


As you see, this method takes a real parameter 'period' which states how often the function will be called. This parameter will be set to 1.0 since we want to call it every second.
The first variable is of the type 'EventHandler'. This type is defined at the top of the same library. Here is its definition:
//An event handler is a function that handles an arbitrary unit, item or buff event
//The integer i represents the thing which fired the event, so either the unit, item or event handler
function interface EventHandler takes integer i returns nothing

Here is the function interface again! As you see this interface takes an integer i. Our function takes a Buff. This is however totally valid. It would take too long to explain why it is valid, just take it as a fact.

So we use our learned knowledge and just insert the functions name, prefixed with the name of the function interface, as first parameter. So our final call will look like this:

call myBT.addPeriodicEvent(EventHandler.doEverySecond,1.0)


That's it. Now, the function will be executed every second for each creep that has the buff.

This was everything about vJASS you need to know for YouTD. There are many other constructs in vJASS, however the mentioned ones are the ones that you will use 90% of the time, so if you have understand everything, you know the most important parts of vJASS. If you want to learn more, read the vJASS Manual. Unfortunately it is not written too user friendly (it is a manual, no tutorial).

Thank you for reading and have fun with creating your towers!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic This topic is locked, you cannot edit posts or make further replies.  [ 1 post ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group