Learning BYOND, Day 5: Vital Misconceptions

Edit: This has once again been modified both because of correcting my own earlier ignorance and thanks to Zac’s helpful comments.

Yesterday was good practice to get familiar with coding in the environment. Today, I largely took my advice from yesterday and did a whole lot of RTFM. I went from chapter 7 to 14 in the DM guide (skipping the chapter on save files for now) taking hours to read and understand it. It has not been a fruitless endeavor, as I came away with quite a few nifty new commands and understanding.

Talk about “enough knowledge to be dangerous” — apparently much of my coding yesterday was done under massively false pretenses. Here’s a few things off the top of my head that I was doing very wrong:
  1. I didn’t really know how to declare new objects into existence.
    Instead, I was declaring my objects in the game world (dynamically at runtime as opposed to with the map editor) largely by cutting and pasting what I saw and getting lucky.

    This much the manual will tell you: New() is the universal constructor (translation: it’s the procedure that runs on the object immediately after it is created). I used it before to set up the default variables and whatnot on an object. The new() proc with a lowercase n is what actually bridges the gap from using var (to declare a variable reference to an object type) to actually creating the object.

    Without using new(), one tends to have their procedures crash a lot from attempting to do things with null-assigned reference variables: You might have declared a variable that points to that type of object, but you haven’t actually created the object yet!

    Of course, having dabbled with C++ and Java in the past, I’m no stranger to the idea of there being a difference between a prototype and the actual object. What stumped me were little things called “atoms” and why not passing a location to them stopped new() from working. Suddenly, new() was an alien command to me, it wasn’t working like new from other programming languages.

    It turns out that the trick is that atoms, being areas, turfs, mobs, and (in-game) objects, have a tendency to need a place in the game world to exist. Therefore, they take (as the first argument to new) a location in which they are placed. Without passing this location, BYOND’s dream maker tends to break.

    Likely this is because the involved New() constructor requires that the object be placed in a virtual world location to function right. If I try to use a mob’s Step_Dir() procedure and it’s not actually on the map, it’s not going to know what to do. If, in the New() constructor itself, I have some code that wants to check the mob’s location variable, and that location variable is set to null because it’s not actually placed anywhere, that’s going to cause problems.

    Thus the problem with new() and atoms is actually pretty understandable. The location is merely an important argument without which the atom objects largely don’t know what to do. However, it’s tough to grok for the first time BYOND user.

    Another tricky thing about new() is the format. It scans weirdly to the eyes of a code dabbler from elsewhere. In Java, declaring an object looks like this: “Point originOne = new Point(23, 94);” In BYOND, it’s a bit different. The following statements actually do the same thing:

    var/obj/robot/myRobot = new/obj/robot(loc)
    Implicitly declares a reference variable, then implicitly creates an object of the same type and assigns it to the reference. var/obj/robot/myRobot = new /obj/robot(loc)
    You can put a space after new if you want. This makes no difference to the compiler but clarifies for us (the dumb coders) that what we’re actually doing here is “new typedef(argument)”. (More on that in a moment)
    var/obj/robot/MyRobot = new(loc)
    Without the type and/or location specified, new() automatically adapts to the left side. The location is still needed to place it on the map.
    Actually, this does not do the same thing, at least not exactly. (An explanation follows.)

    That last one is particularly tricky (and in more than one way).

    In other coding languages, initializing an object without a variable is a bit like marking a part of your memory as a useless black hole with junk in it you can’t use. The compilers generally won’t allow it.

    It’s okay in BYOND because what we’re defining here is an atom, and it lives in the virtual world so long as we pass a location in which it is created. Because it exists in the virtual world, it can be referenced and (hopefully) eventually removed and garbage collected from the world, avoiding the useless black hole problem.

    The overall problem I have with most of these examples is the typedef argument. This is visible in examples 1, 2, and 4 above when we say “new/obj/robot().” Compare that to my Java example above and you can see we’ve suddenly got a whole bunch of junk between “new” and “robot()” I’m not used to. What we’re seeing is actually new/typedef(). Typedef is explicitly defines the type. As you can see in example 3, that’s not necessary, but an explicit definition at least settles the mind we’re creating the right kind of object.
  2. Having an unclear idea how objects were created, I also had no idea how to delete them.
    BYOND deletes objects in a rather novel, simplified way. In other languages, the deconstructor – which is what Del() is in BYOND – will take some arguments and so it looks just like New(). In BYOND, there is no argument.

    Consequently, you don’t call Del() with del(), you call Del() with simply del – without the parentheses.

    Want to get rid of that object in the world? del object – not del object(arguments).

    Trickier yet, Del() Is a finicky proc to override. According to the manual, section 7.2.2 says you need to add ..() at the end of the deconstructor to resume the parent process if you’re using it, otherwise, the item may not be deleted properly.

    (I’m not sure how true that still is. Section 7.2.2 of the manual also says that the contents of an object or world will be dumped to its location when it is deleted… I tested this and it did not. In the addendum page this is confirmed.)
  3. I was using the atom object Click() (and other events/verbs) the hard way when I could have been using their callers on the client object.The client object actually triggers these procedures on the other objects through their own versions. However, this necessitates having an additional arg for the Client: the object being manipulated.

    My mistake was defining the Click() directly on the tile or object every time. If you’ve been following this for awhile, you may have noticed I was wondering how I could go about passing waypoints. Through manipulation of the clientside.Click(), this is much easier.
Those are some of the more obvious blunders, I’m sure I’ll discover more in time: Such is learning.
I spent most of the day with my nose in the book. I’ll probably spent the rest of it and most of tomorrow trying to bring these new practices into fruition so that I’ll remember them.

However, first thing’s first. It’s time to draw up a proper design document. Such a document is vital for prototyping all the major objects in the game and how they will interact. I’ve found that the speed in which a project can be put together is increased a hundredfold with such a document.

It’s the difference between assembling a house with blueprint versus assembling a house by propping up poles and seeing which ones stay vertical long enough for you to tack on a ceiling. You might get there eventually anyway but a blueprint provides a clearer sense of purpose and a product which doesn’t look like it’s going to fall over if the wind blows too hard.

(As much as I’d like to give you Learning BYOND readers a respite from all these run-on entries, I probably won’t detail my master plan much: an aspiring game designer has to keep his cards close to his chest to surprise his future gamers.)


Popular Posts