3

I'm learning some graphics programming, and am in the midst of my first such project of any substance. But, I am really struggling at the moment with how to architect it cleanly. Let me explain.

To display complicated graphics in my current language of choice (JavaScript -- have you heard of it?), you have to draw graphical content onto a <canvas> element. And to do animation, you must clear the <canvas> after every frame (unless you want previous graphics to remain).

Thus, most canvas-related JavaScript demos I've seen have a function like this:

function render() {
   clearCanvas();
   // draw stuff here
   requestAnimationFrame(render);
}

render, as you may surmise, encapsulates the drawing of a single frame. What a single frame contains at a specific point in time, well... that is determined by the program state. So, in order for my program to do its thing, I just need to look at the state, and decide what to render. Right?

Right. But that is more complicated than it seems.

My program is called "Critter Clicker". In my program, you see several cute critters bouncing around the screen. Clicking on one of them agitates it, making it bounce around even more. There is also a start screen, which says "Click to start!" prior to the critters being displayed.

Here are a few of the objects I'm working with in my program:

StartScreenView  // represents the start screen
CritterTubView   // represents the area in which the critters live
CritterList      // a collection of all the critters
Critter          // a single critter model
CritterView      // view of a single critter

Nothing too egregious with this, I think. Yet, when I set out to flesh out my render function, I get stuck, because everything I write seems utterly ugly and reminiscent of a certain popular Italian dish. Here are a couple of approaches I've attempted, with my internal thought process included, and unrelated bits excluded for clarity.

Approach 1: "It's conditions all the way down"

// "I'll just write the program as I think it, one frame at a time."

if (assetsLoaded) {
  if (userClickedToStart) {
    if (critterTubDisplayed) {
      if (crittersDisplayed) {
        forEach(crittersList, function(c) {
          if (c.wasClickedRecently) {
             c.getAgitated();
          }
        });
      } else {
        displayCritters();
      }
    } else {
      displayCritterTub();
    }
  } else {
    displayStartScreen();
  }
}

That's a very much simplified example. Yet even with only a fraction of all the rendering conditions visible, render is already starting to get out of hand. So, I dispense with that and try another idea:

Approach 2: Under the Rug

// "Each view object shall be responsible for its own rendering.
// "I'll pass each object the program state, and each can render itself."

startScreen.render(state);
critterTub.render(state);
critterList.render(state);

In this setup, I've essentially just pushed those crazy nested conditions to a deeper level in the code, hiding them from view. In other words, startScreen.render would check state to see if it needed actually to be drawn or not, and take the correct action. But this seems more like it only solves a code-aesthetic problem.

The third and final approach I'm considering that I'll share is the idea that I could invent my own "wheel" to take care of this. I'm envisioning a function that takes a data structure that defines what should happen at any given point in the render call -- revealing the conditions and dependencies as a kind of tree.

Approach 3: Mad Scientist

renderTree({
  phases: ['startScreen', 'critterTub', 'endCredits'],
  dependencies: {
    startScreen: ['assetsLoaded'],
    critterTub: ['startScreenClicked'],
    critterList ['critterTubDisplayed']
    // etc.
  },
  exclusions: {
    startScreen: ['startScreenClicked'],
    // etc.
  }
});

That seems kind of cool. I'm not exactly sure how it would actually work, but I can see it being a rather nifty way to express things, especially if I flex some of JavaScript's events.

In any case, I'm a little bit stumped because I don't see an obvious way to do this. If you couldn't tell, I'm coming to this from the web development world, and finding that doing animation is a bit more exotic than arranging an MVC application for handling simple requests -> responses.


What is the clean, established solution to this common-I-would-think problem?

1
  • Try TDD. Once it's testable, it'll be "cleanly" implemented (or really darn close) If you can't test it, it's not "clean".
    – blesh
    Commented Nov 13, 2013 at 17:37

1 Answer 1

4

The concept of a "scene" might be helpful. A scene could be an initial menu, a level among many levels, or the credits at the end of your game. Each scene has its own behaviors, state, and render-able entities. Some state may be shared between scenes (scores, character upgrades, etc), but most state is specific to a scene. Abstracting by scene makes a game easier to think about and is also handy for keeping system resources to a minimum - you only load the resources you need for the current scene. In pseudo code:

currentScene = nothing

game-start:
    currentScene = load-start-menu()

game-loop:
    currentScene.update()
    currentScene.render()

menu-start-button-clicked:
    currentScene = load-level1()

Inside each scene, you have a collection of game entities. For simplicity, I'll show simple containers. In reality, you may want to split conceptual entities into a data-only model and a render-able view. Scenes may also delegate user interaction.

definition Critter:
    isAgitated = false
    x = get-appropriate-starting-x() 
    y = get-appropriate-starting-y()

    update:
        if isAgitated:
            x = some-random-move(x)
            y = some-random-move(y)

    render:
        draw(image or whatever, x, y)

definition Level1:
    entities = new collection()

    init:
        for 1 to some-number:
            entities.add(new Critter)
        entities.add(new CritterTub) // definition not shown

    update:
        for each entity in entities:
            entity.update()

    render:
        for each entity in entities:
            entity.render()

    mouse-click(x,y):
        entity = find-entity(entities, x, y)
        if entity is Critter:
            entity.isAgitated = true

There are many variations: Your game engine may allow entities to receive user actions directly. As mentioned earlier, your entities may only contain data and some other component is responsible for rendering. Your game may be more functional versus object-oriented, ex: render(entity) versus entity.render()

Once you've defined your scene, there are many interesting problems to solve: which entities are off-screen and shouldn't be rendered, in which order should entities be rendered, and how can I decide these things quickly? Take a look at scene graphs for more info. Gamerendering.com provides an overview of several techniques for scene management from simple to advanced.

For more information on the scene concept, the following offer high-level explanations:

1
  • Great answer, thank you! This totally got me unstuck. Commented Nov 15, 2013 at 2:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.