Game: World Editor Beginnings

In this post, I’ll document some of the blow-by-blow of a (hopefully!) rapid bring up of a world editor. Rapid means different things to different people… I’m hoping for a couple of weeks here.

Wednesday, 2015-03-25

Haven’t looked at the codebase in about a week and half, was busy getting ready for the first deployment of my Video Feedback Toy at a private event at Chabot Science Center. Went well. Is built on MeLib just like Metareal will be.

Spent day at Cafe Pergolesi and the downtown Santa Cruz Public Library, created fresh project, and added some MeLib features for specifying viewport and scissoring for each MeRenderworld step. End of day:

MeEd01

A content area, & an info area. Pretty special!

Thursday, 2015-03-26

Tragically, the current state of my libraries is still that bringing stuff up has a painful-feeling up-front cost. Spent a couple of hours bringing forward some text console code, and managing an array of items. The process of bringing up this editor will imply creating the World Model, too. Gets and sets, here we come!

But for now, just a shallow visual presence.

MeEd02 MeEd02

 

Friday, 2015-03-27

Short workday today. Worked on some serialization. I have a “parameterizable thing manager”, which can instantiate subclasses of MeThing by class name. Today, brought in some XML code from another project to easily save the complete state of some things to a file.

There are smart people who seem to hate XML, but I think it’s just fine.

Anyway, the code to render to XML is quite easy (if you have enough good stuff behind it):

    std::string toXml()
    {
        StringXmlWriter *sx = new StringXmlWriter();
        OmXmlWriter *w = new OmXmlWriter(sx);
        w->beginElement("level");
        for(auto thing : this->things)
        {
            MeThingKind *kind = thing->getKind();
            w->beginElement("thing");
            w->addAttribute("kind", kind->getName().c_str());
            w->addAttribute("name", thing->getName().c_str());
            for(auto pd : kind->getParameterDescriptions())
            {
                w->beginElement("parameter");
                w->addAttribute("name", pd.name.c_str());
                w->addAttribute("value", thing->getValueString(pd.name).c_str());
                w->endElement();
            }
            w->endElement();
        }
        
        w->endElement();
        
        std::string result = sx->s;
        
        delete w;
        delete sx;
        
        return result;
    }

And produces a nice little text:

 <level>
  <thing kind="Thing1" name="">
   <parameter name="x" value="1e+20"/>
   <parameter name="y" value="0"/>
   <parameter name="i" value="0"/>
   <parameter name="color" value="1 , 0 , 0.5 , 1"/>
  </thing>
  <thing kind="Thing2" name="item 1">
   <parameter name="x" value="111.11"/>
   <parameter name="y" value="0"/>
   <parameter name="j" value="0"/>
  </thing>
  <thing kind="Thing1" name="item 2">
   <parameter name="x" value="-1e-20"/>
   <parameter name="y" value="0"/>
   <parameter name="i" value="0"/>
   <parameter name="color" value="1 , 0 , 0.5 , 1"/>
  </thing>
  <thing kind="Thing1" name="item 3">
   <parameter name="x" value="0"/>
   <parameter name="y" value="0"/>
   <parameter name="i" value="0"/>
   <parameter name="color" value="23 , 42 , 86 , 99"/>
  </thing>
 </level>

Saturday, 2015-03-28

They don’t work for money any more, or to earn a place in heaven (which was a big motivating factor once upon a time, believe you me). They’re working and inventing because they like it. Linda, Larry, there’s no concept of weekends any more! — Spaulding Grey in True Stories

This “no corporate employment” changes how time works. On the other hand, weekends are when my friends are around, so spent a few hours today helping one move. I always take the Truck Arranger role, for best stacking.

Implemented a first pass at XML loading for the editor. I have a c++ class built around expat2, so you can receive individual elements on two callbacks, like so:

    void beginElement(std::string elementName, std::map<std::string, std::string> &attributes)
    {
        if(elementName == "thing")
        {
            std::string thingKind = attributes["kind"];
            std::string thingName = attributes["name"];
            MeThing *thing = this->tm->newInstance(thingKind);
            thing->setName(thingName);
            this->things.push_back(thing);
            this->xmlCurrentThing = thing;
        }
        else if(elementName == "parameter")
        {
            if(this->xmlCurrentThing)
            {
                std::string name = attributes["name"];
                std::string value = attributes["value"];
                this->xmlCurrentThing->setValueString(name, value);
            }
        }
    }
    void endElement(S elementName)
    {
        std::string e = elementName;
        if(e == "thing")
            this->xmlCurrentThing = NULL;
    }

Two points of magic to note. As alluded to earlier, this->tm is a MeThingManager. It has the ability to instantiate different implementations of MeThing by name. The other is thing->setValueString(), which does a best-effort interpretation of text for the particular type of the named parameter.

So far, these thing-lists are just abstract bags of parameters. Next up, defining some thing kinds with a geometrical, visual presence. Probably cubes.

Sunday, 2015-03-29

Development shortcut: All UI is performed with the existing keyboard space navigation (6-axis fly-around using esdf/ijkl), and a text console.

Library has only one kind of Object in it, a cube. The cube only has position. Can be added & moved around, and saved & restored to XML. Can tab to edit different Objects, and page up/down keys to select different parameters to edit.

MeEd03

 

It’s inscrutable if you’re not me (but I am, so that’s ok for now), and it’s just barely enough.

Next up, managing rooms & walls, as well; these may be handled differently than the live objects.

Next up after that, letting the level “run”.

Tuesday, 2015-03-31

Life of a full-stack developer. To implement mouse hit-testing, needed to add an alternate shader that operates on the same geometry. To keep the shaders in sync, had to implement #include for the shader file loader. So had to flesh out my OmFile:: utility file library. (With benefit of well-defined swap point for platform porting.)

editorHitTest

Tuesday, 2015-04-07

And I think I’ll wrap up this post now. In the last week, many low-glamor changes such as keyboard bindings for numeric editing individual params, snapping to multiples, and the like. Mouse click selection throughout. A Material Manager class, so instantiated objects can reference their required shaders by name.

End result: can instantiate several different types of objects, edit & delete them, save & recall to disk.

2015-04-07

Onwards! A long slow path.

Engine: MeRenderWorld

And now, a few notes about the Metareal Engine architecture. As mentioned earlier, of course it’s silly to write my own, let’s move on.

The library presently consists of two major components: Rendering and Volume Management. The Rendering side deals with lists of triangles, groupings of parts, geometry lists, and shader graphs. The Volume Management side deals with detecting intersections of volumes, and notifying listeners. Physics is built on top of volume management, as a special case of responding to intersections.

In this post, I’ll talk about one aspect of Rendering.

MeRenderWorld

The class MeRenderWorld is presently the highest level container. An application may use several, certainly, but this is the top container on the library side.

An MeRenderWorld manages:

  • Lists of Parts (each part is a geometry list)
  • Cached geometry lists, grouping parts by material, so each material uses a single glDraw
  • References to materials (shader programs, for an OpenGL implementation)
  • Backing buffers for rendering post processing
  • List of render operations

The MeRenderWorld itself knows nothing about OpenGL; rather it deals with MeIMaterials and MeIPartRenderers and MeITextures and so on. For now there are only two implementations of each, one for OpenGL and one for FakeFL, which just records actions suitable for unit testing the library.

Often, one wants to describe a process by means of a document, perhaps in XML or similar. Describing the graph of draws and post-processing for a final scene frame is a perfect example of this. However, I have a strict policy of implementing code first, document second. Here I’ll show the code mechanism for building up a render graph.

The render graph is described as a strict, linear sequence of actions. It has to become one anyway (at least until glNext), so that’s the primitive. Here is an example setup:

    MeRenderWorld *rw = this->renderWorld;
    rw->addStepClear("", 0x123456);
    rw->addStepDraw(this->materialSky, "");
    rw->addStepDraw(this->materialOpaque, "");

Here we see that each frame will be created with three actions: a clear-to-color, and two material draws. Today this is, in fact, exactly two glDraw() calls, but that could change.

The empty strings are the names of frame buffers. The unnamed buffer, “”, is the default output buffer. This might be the screen or window, or be another buffer, depending later on the argument to rw->draw().

Here’s a more complex render sequence:

    rw->addStepClear("b0", this->bgColor);
    rw->addStepDraw(this->materialSky, "b0");
    rw->addStepDraw(this->material, "b0");
    addCopyingStep(rw, "b0", "b1");

    rw->addStepSetTextureColor("b1", this->materialTransparent, "texture1");
    rw->addStepDraw(this->materialTransparent, "b0");

    addCopyingStep(rw, "b0", "");

In this sequence, the background is cleared and then drawn with a skybox. Then, the opaque parts of the geometry are drawn onto a buffer named “b0”. This buffer is created on-demand by MeRenderWorld, you just have to name it, and it exists.

That first render is provided as a texture argument to the transparent material, in a uniform named “texture1”, which is then drawn onto “b0” also.

Lastly, “b0” is copied to the output. (addCopyingStep() is a macro which instantiates a screen-sized rectangle textured with one buffer, drawing to the other).

Here’s a frame of the output:

translucent

And there’s a peek into the Metareal Engine’s rendering component.

Looking Forward

2015-03-02_hitTesting

As I’m coding, managing lists of millions of vertices, swapping buffers, keeping track of state and building optimization caches (in time and space, as we say), it is wonderfully overwhelming. There is so very much more to do. Collada imports, managing interactive object types, state saving and recalling, and many fine details that can’t yet be revealed.

To say nothing of the joy of actually crafting the puzzles, and the graphics and the music.

I am reminded of an excellent SF book by Greg Egan called “Permutation City”, which takes place mostly in a simulated world. (The world has become detached from reality, because the computer that generated it was turned off, so they are free-floating.)

One of the persons inside it has found a way to cope with immortality; from time to time he is reborn with a new obsession. After he has cataloged the last of the several billion species of butterfly and moths in some imaginary world, he wonders who he will become next.

The room goes fuzzy, and he is disoriented… he realizes he is in a workshop with acres of bins of wooden dowels… all waiting to be lathed into table and chair legs.

He is in heaven.

Greetings!

Hello, to the future! And now, my contentful first post. These won’t be public for a while, so if you are reading this, it’s because you scrolled back in time.

A normal person writing a game, here in the year 2015, would get a nice framework & IDE, like Unity, and get to work. I read others’ dev blogs and they say things like, “Wow, wasted 3 weeks on a game concept before I decided it wasn’t that interesting.”

Not me. I fire up Xcode and start typing C++ code talking to OpenGL. Why? Indeed. The shortest answer is, because I like writing code. Also, 3 weeks to me is inconsequential. I’ve been assembling my libraries for close to six months now. And they are not bad.

Also, I know exactly what the game concept will be, and have 100% confidence in its viability. This is somewhat mitigated by the fact that I don’t care if, ultimately, anyone else likes it.

And also, having not written any video games in 30 years — literally, a lifetime ago — I was curious how it’s done. I wanted to truly feel the shaders, the collision testing, the lists of millions of triangles. All that.

Also, if you’re reading this, it’s because I’ve chosen to make it public. Hello, to the future!