DaVince scripting tutorial/Objects

From Spheriki

Jump to: navigation, search

Contents

Objects

I've mentioned objects before, now we'll go into more detail about them. An object is one of the more complicated things in Sphere, but they will allow you to do very powerful things.


What is an object?

An object is a type of variable in which you can store many things - values, functions, and even more objects. However, an object is not like an array because of two main reasons:

  • Objects use names instead of numbers for your data. These "names" are attributes if you're storing data and methods if you're storing functions.
  • Objects can have different instances.


What is an instance?

Objects are very useful for one specific thing - defining the type of thing that you want to create (for example, an item, or a menu), and then being able to create as many of it as you like. Basically, it's a flexible way to create the exact collection of information and functionality you needed.

When you start making your own object, you start out by shaping the "original" object, which is called the prototype. This prototype is NOT the actual final deal, so it's not the exact item or menu as you envisioned it - instead, it's a so-called abstraction of the kind of thing you want to make (you want to have an item or a menu). From here on, your prototype will serve as the way to create actual, usable objects out of your prototype. These usable objects are what you would call an instance of that object. Instances let you have copies of the same object - with different values.

An example of different instances

The best example to start out with is with something familiar: let us look back at Sphere's own, internal objects. When you use LoadImage(), you actually create a new instance of the internal Sphere object called Image. For example:

var a = LoadImage("a.png");
var b = LoadImage("b.png");

Both a and b are now both different instances of the Image object. That is, they were both based on the Image object, and they both look like the Image object, but they both also contain different images. So a and b are alike, but not the same. In other words, instances.


Making your own prototype

Now, Sphere's internals use functions like LoadImage(), but this is not actually true object-oriented programming, and it is not how we will make our own object prototype. But in this first step, it will almost appear as if we are! Let's make a prototype that will function as an item.

function Item()
{
 
}

There you go, we just made an object prototype. What? It looks just like making a function? That's exactly right - all functions are actually objects, except we don't normally use them as such!

Note that this function is named a bit differently, though. Instead of describing an action (LoadImage(), FlipScreen(), StartCutscene()...) it's a noun. This is because we'll have many different instances of this object: we could have ten items, that makes sense, right? Having ten "feed rabbits" makes no sense.


Object attributes (or properties)

Let's take it a step further and see what's so special about objects, then. In this example, we'll be giving our items some information, like its name and its price. But note that this name and price will not be for any particular item - we'll have to define a default name and price that all items start out with. This is the purpose of a prototype: to define what kind of data the prototype's instances will actually hold, and what their default fallback values are.

function Item()
{
  this.name = "potion of uselessness";
  this.price = 10;
}

Objects have properties, also known as attributes. Inside attributes, you store values. It's kind of like using var, but instead we use this. The reason for this is so we can access the value from outside the function. Not only that, but we tell Sphere that this variable-type thing is a default value that the prototype will give to each instance. It can be changed individually per instance, but this is the default value.

In our example, we've given the prototype two attributes: the name and the price. Apparently any unmodified instance of this item will be a potion of uselessness that would cost us 10 bucks.


Object methods

Another very powerful attribute of objects is that object prototypes can also hold functions, and these functions will also be copied to each instance of your object. A function stored inside an object is called a method - it's a function that usually provides ways to do things for the object (hence the name).

function Item()
{
  this.name = "potion of uselessness";
  this.price = 10;
 
  this.doublePrice = function()  //Doubles the price!
  {
    this.price *= 2;
  }
}

The above method, doublePrice(), doubles the value of the object's price property (attribute). So every time it's called we pay an insane amount more for that useless potion.

But you know, there is no actual potion yet! There's just some prototype that defines what an "Item" actually is. With the above, Sphere now knows that there is such a thing as an Item, and it contains three attributes: a name, a price and a method (that can double the price). If we were to ever make a usable item based on this prototype, it would automatically be a potion of uselessness worth 10 bucks (of which we can double that price)! So now let's actually go and make that potion a reality: let's instantiate it.

Making an instance of the object

Let's get to it and make the actual item appear.

var thefirstitem = new Item();

Congratulations, you have now made an instance of the Item object and named it thefirstitem. And we can tell with certainty that it will contain a attribute called "name" with a value of "potion of uselessness", and that it has a attribute called "price" with a value of 10. Oh, and it has some method to double the price.

You can now safely access the attributes to actually get these values:

var some_potion_name = thefirstitem.name;    //So the variable will be "potion of uselessness"
var that_potions_price = thefirstitem.price; //And that variable will contain 10.

Changing the attribute of an instance

You can also change the attribute's values. For example, that item's instance that we made? It's not actually a potion of uselessness at all! It's a cane of Somaria worth a good 500 bucks!

thefirstitem.name = "Cane of Somaria";
thefirstitem.price = 500;

Oh and that method doublePrice()? In this particular case, it also changes the item's name a bit.

thefirstitem.doublePrice = function()
{
  this.price *= 2;
  this.name += "!"; //Add an exclamation mark to the name every time the price doubles
}

Yup, you can totally replace functions by your own functions if you need to. Practical uses for this is if you have a generic Menu object but two different menus that render their contents differently - you could then replace the .renderMenu() (or whatever) method with that, while everything else about the Menu prototype stays usable for both.

Calling an instance's method

So yeah. Our item is now a nice cane. But uh, it's actually a thousand bucks now.

thefirstitem.doublePrice();

Boom. We called the instance's .doublePrice() method. Of course, that caused thefirstitem.price to now be 1000. Just what we needed.


Let's instantiate (eg. make an instance of) the generic Item object again.

var theseconditem = new Item();
Abort(theseconditem.name);  //Will return "potion of uselessness"

Yup, every new instance of Item will contain "potion of uselessness" and 10 as respectively the name and price. And the nice thing? thefirstitem is still also an Item and it still contains "Cane of Somaria" and 1000! So you can make many, many different items, all stemming from the same prototype item, containing the same basic properties (in this case the name and price) and the same functions (in this case, doubling the item's own price).

Filling in object attributes

Right, so we have our item prototype, and we know how to create actual items out of it, and we know how to change these items to our liking. But the "change these items to our liking" part still requires us to write two extra lines:

var thefirstitem = new Item();
thefirstitem.name = "Cane of Somaria";
thefirstitem.price = 500;

Now, remember functions? Of course you do. Remember function arguments? Values that you can pass to functions as you call them that the function can use, right? Wait a minute... We can use this!

Let's modify the Item prototype a bit:

function Item(itemname, theprice)
{
  this.name = itemname;
  this.price = theprice;
 
  this.doublePrice = function() //Doubles the price!
  {
    this.price *= 2;
  }
}

So yeah. Basically, you can do the same thing here as you can with regular functions: pass it arguments, and use those arguments inside the function itself. In this case, we ask you, the coder, to fill in two arguments (itemname and theprice) every time you instantiate this object. And then we fill in the values of these arguments (itemname, theprice (name them however you like)) as the values for the object's attributes (name, price).

Example time.

var thefirstitem = new Item("Cane of Somaria", 1000);
var theseconditem = new Item("potion of uselessness", 10);

There, much simpler! You can make entirely new items on a single line now. And you never asked for doublePrice() to be changed, so that will be keeping its default behaviour of doubling the price for you if you call it.


Taking care of both defaults and arguments

The tradeoff to the above is that those two object attributes name and price no longer have a default value: you either fill them in, or they become the JavaScript value undefined. And that's bad if these attributes need to be something specific. For example, you can't calculate anything with the price if it's undefined:

var wallet = 2000;
var myitem = new Item("bad egg");  //Instantiated without the "theprice" argument. Thus, the instance's name becomes "bad egg" and the price becomes undefined.
wallet -= myitem.price;  //Sphere aborts with an error because 2000 - undefined = ???

So if you want an argument to be optional, or if you want to prevent mistakes like these, you can either:

  • Choose to leave it out of the arguments altogether and just always set a default value;
  • Set a default value if the argument turned out to be undefined, and otherwise set the given value.

It depends entirely on the situation. In our case, I'll assume that we want to take advantage of a default name and price so we have to type less and they'll just assume a default value (like before).

function Item(itemname, theprice)
{
  if (itemname == undefined) this.name = "potion of nothingness"; //Oops, it was undefined? Let's set a default.
  else this.name = itemname;
 
  if (theprice == undefined) this.price = 10;
  else this.price = theprice;
 
  this.doublePrice = function() //Doubles the price!
  {
    this.price *= 2;
  }
}

That works, but now we suddenly have an extra line for every attribute. It's a bit messy. Luckily, there's a shorthand notation for if...else that fits on a single line. Compare:

if (condition)
{
  condition_was_true_action();
}
else
{
  condition_was_false_action();
}
(condition) ? condition_was_true_action() : condition_was_false_action();

These do exactly the same thing. Except that ?...: one is a lot shorter, isn't it? It's useful for exactly these kinds of situations. Let's apply it:

function Item(itemname, theprice)
{
  this.name = (itemname == undefined) ? "potion of nothingness" : itemname; //If name wasn't filled in we set a default.
  this.price = (theprice == undefined) ? 10 : theprice;
  //Example: value = (condition) ? this_value_if_condition_was_true : this_value_if_condition_was_false;
 
  this.doublePrice = function() //Doubles the price!
  {
    this.price *= 2;
  }
}

As you might see, this can confuse other readers, so always properly comment your code and use the ?...: statement in moderation.

Personal tools