Eternal blocks of the frantic mind

If you cannot think of a game, think of a spec. Think of rules. Implement logical interactions, checks and tests. The threaded, animating game view is one option. Debugging discrete state should take no less creativity: are they not data, calculable, derivable, pregnant of insight?

Inspiration comes in its time; I cannot rush it. All I can imagine is the requirements of a vague circumstance, and vivid scenes, and imagine at the play between. It is a gift of time to inherit the sum past in hand, collected experience in one field. Now, behold: a light brighter past night begs its due, and in that onslaught is the core expression of our vulnerable love.

Tools as the brushes we craft, for canvas to impress, once intangible ideas exercised in full manner and glory, constrained by time and pushed to fore by it, plenary purpose evoked from the growth of sable to handle: from the same fingers, compiling symbols; from the same hand, desperate art.

There is the most simplicity in the asynchronous process: I’ve only used it twice, while the world is lit by threads. First to divide jobs independent, and second to return immediately from a web service. It still unsettles me that an object in execution could share its data structure with multiple lightweight entities. It is my next file challenge.

ChatterServer

Brackeen’s sixth chapter is about multiplayer. I used Wireshark to sniff packet traffic and found the router forwards requests to the local machine-as-server. That meant the telnet into ChatterServer using the public-facing IP, on the same machine, sent packets to its own gateway. It gets confusing here, but there are a series of TCP retransmits before the server enters a looping WARN message:

java.io.IOException: An existing connection was forcibly closed by the remote host

The connection is established before this happens, but nothing gets echoed on the telnet screen. The solution was to use the LAN IP of the self-machine.

With port forwarding, I was able to connect from a foreign connection! It was similar to setting up a server in Minecraft.

I used Netbeans to build .jar versions of both client and server. Be sure to copy the lib\ directory when you transfer the client to the test machine, as there is a log4j dependency.

The trickiest part is figuring which handful of IPs is correct. Some hints:

  • Test on local machine using LAN IP. Use ipconfig and cygwin telnet.
  • The client will use the public IP.
  • Use the router IP to set up port forwarding.

The world scrolls with you

Earlier, I mentioned the tile map needs to be sized proportionally to the player sprite’s movement, or it will move off the screen. Actually, I was missing a crucial element: you draw the player sprite by the x_offset and y_offsets too. (gif)

By drawing the player at player.X + x_offset and player.Y + y_offset, the screen scrolls with the sprite. This is desirable: the screen into your world can access all portions of the map.

world-with-you

Now that we can define the world, what kind of world will we make?

Scrolling in 2d

The y_offset is similar to the one calculated for x_offset. (gif)

side-scroll-ii

I kept getting NullPointerException for a while. It’s because the rightmost ordinal represents the width of the map, but internally the tiles are stored in a 0-based array. My workaround was to guard the draw routine by max index:

for ...
  if (i <= 6 && j <= 6) {
    tileMap[i][j].x = tileToPx(i) + x_offset;
    tileMap[i][j].y = tileToPx(j) + y_offset;
    tileMap[i][j].draw(g);
  }

Flush with the ability to consume the world in chunks, there is one other thing: collision detection.

Sidescrolling III

Without bounding the sidescrolling coordinates, the tile ordinals will need constraints in the array loop to draw them – but that won’t prevent out-of-bound exceptions. Specifying limits on the offsets lets the player move around without scrolling off the map.

x_offset = screen_width - player.x;
x_offset = Math.min(x_offset, 0);
x_offset = Math.max(x_offset, screen_width - map_width_in_pixels);

Math.min() ensures that the first tile of the tile map is returned during the ordinal calculations if x_offset is positive or zero. This is true in the bad case where the player wanders too far left: player.x will be negative; flipped, it becomes positive; added to screen_width, x_offset becomes positive. Then,

int first_tile_ordinal = -x_offset / TILE_WIDTH; // negative index!

The right side is constrained with Math.max(): player.x effectively approaches map_width_in_pixels as the player moves right; beyond it, player.x > map_width_in_pixels, resulting in a more negative number. If this is passed in to the ordinal calculation, the last tile will be out of bounds. I have so far neglected to mention that calculation, but here it is:

lastTileOrdinal = first_tile_ordinal + toOrdinal(screen_width) + 1

If your first_tile_ordinal is off due to player too far right, the index window is shifted right too.

Game objects manage themselves

MVC is kind of weird with games. Typically, there is the view forwarding messages to the controller, which sends them to the model, and the view manages the effect of a return code or an output stream.

In games, the phenomenon of “one” model is multiple game object entities: each handles its own drawing; each – with the requisite parameter of a game object or the world bounds – manages its own collision logic.

There are plenty of exceptions. One such iffy is “a priori” collision: within the game update, calculate path intercepts, sort for the closest event, and advance all objects to that point; repeat while time is positive.

Hewing to the One True Model idea, I ended up with a Rules class that had its own update() and draw() methods. It drew and updated as the controller dictated, so it was kind of MVC; however, graphics and world state were combined. It felt vulgar.

The alternative is to accept the pluralistic one: each object holds a model of the world; each object manages its own existence.

Sidescrolling II

Sidescrolling is to define a mapping between tiles and the player sprite’s position. We can reduce the complexity scope by considering only left-right scrolling of tiles which fill the screen. Some further assumptions are

  • the screen fits a number of tiles comfortably
  • the map is wider than the screen
  • tiles are square, so width and height are equal
  • scrolling begins when player moves into right half of the screen
  • tiles are stored in an array starting from zero

Conceptually, the map slides left as we move right and vice-versa. The map’s leftward shift is negative, and we obtain it with

x_offset = screenwidth / 2 - player_x

Suppose the player is on the right side one pixel past the halfway mark. Then

player_x = screenwidth / 2 + 1 // effectively

x_offset = screenwidth / 2 - (screenwidth / 2 + 1)
         = -1

So we shift the tile map one pixel left. Scrolling!

We want to find the first tile position, which we decide is the leftmost tile, ordinally. Ordinals? Division lets us find ordinals:

first_tile_ordinal = -x_offset / ONE_TILE_SIZE

We flip the x_offset sign because negative indices are not possible. But our first tile is the 0th index, which is also the leftmost tile. Once we have moved the player at least a tile’s width farther,

first_tile_ordinal = -(screenwidth / 2 - player_x) / ONE_TILE_SIZE
= (-(screenwidth / 2 - screenwidth / 2 + ONE_TILE_SIZE) / 
  ONE_TILE_SIZE
= 1

Our leftmost tile is the second tile. We can convert to pixels by multiplying by ONE_TILE_SIZE. We draw the tile with the offset:

for i = first_tile_ordinal to last_tile_ordinal*
  drawTile(toPixels(first_tile_ordinal) + x_offset, 0) // y = 0

* last_tile_ordinal is determined by screenwidth / ONE_TILE_SIZE

The offset lets us draw tiles gradually scrolling. The offset lets us draw the fortieth tile as the leftmost tile on the screen because the world slides for us.

Concept synthesis

I’ve always been a poor student, so it is a sea change for my personal curriculum to digress sharply among books: rather than following one to completion, I jump from title to title, an eager host, interviewing each author in turn – a human moderator of paper conversations.

The two-thread idea was a wash: the animation was not very fluid; I didn’t know why; I got frustrated. Then “Killer Game Programming Java” showed up, and just in time: the second chapter went into measuring frames per second! As well as another example of a game loop (two if you count the nanosecond timer from Java3D utils).

Here’s a summary of the three books I’ve been using:

"Java Game Programming for Dummies" - Holder/Bell [JGPD]
"Developing Games in Java" - Brackeen [Br]
"Killer Game Programming in Java" - Davison [KGPJ]

concept          JGPD       Br      KGPJ
-------          ----       --      ----
vector           x          x       ? 
momentum         x          ?       ?
measure data     ?          x       x
threaded loop    x          x       x
windowed apps    x          -       x

x: yes
?: haven't looked far enough
-: do not think so

Maybe one does not finish books, but writes demonstrative programs and completes projects.

Width-limited scrolling

I’m excited to get into Brackeen’s version of 3d, but there’s a lot of 2d stuff I haven’t done yet. For example, side-scrolling (gif):

side-scroll

The caveat is the window size must be wide enough to stop scrolling. If the game window is not wide enough, the character will collide with the edge before the last tile is reached.

Brackeen’s double-buffering strategy is better; my updates still have tears and subtle flicker. His threading code enables a 0.5f velocity to move faster than my 5f dx change. Maybe it is due to using GraphicsDevice in the Screen class.

The scrolling itself is possible because the moving sprite’s x-position is absolute. So in Brackeen’s x-offset equation, the rest of the variables are constant:

x_offset = screenWidth/2 - player.X - ONE_TILE_WIDTH;

The second constant, ONE_TILE_WIDTH, is the tile width; launch Paint, resize the canvas, and make some nice 128×128 tiles.

The first constant determines when to start scrolling: begin scrolling once the sprite moves to the right half of the screen. This is because player.X > screenWidth/2 past the halfway mark, making the whole equation negative. As the sprite moves right, positive, positive, zero – then negative. That explains

x_offset = Math.min(x_offset, 0);

At the far right, scrolling stops because screenWidth-as-a-point minus the player’s current x-position is way far to the left, way, way over there, versus screenWidth minus mapWidth, which is less so (but still way out to the left).

                  +---------+---+ A
                  |         |   |
  D/2-C D-A       +---------+---+
   ^     ^           ^       (C)
   B     E           D

A is map width in pixels
B = (D/2 - C)
C is anywhere on the screen at the end of the map
D is screen width treated as point
E is a *constant*, large distance = (D - A)
I imaginary screen 

D/2 - C < D - A

As player.X (C) approaches mapWidth, the subtraction is such that the larger number is (screenWidth – mapWidth) instead of (screenWidth/2 – player.X – ONE_TILE_SIZE):

x_offset = Math.max(x_offset, screenWidth - mapWidthInPixels);

Twist: the offset is negated to determine the ordinal of the first tile to draw! x_offset is negative all this time, and then we flip the sign to determine which tile will be drawn first from the left of our screen. Yet the x-position of the tile is that number plus the raw (negative) x_offset.

This makes sense considering the first tile: if it is drawn at (0,0),

x_offset = Math.min(0, screenWidth/2 - player.X - ONE_TILE_WIDTH);
                       -----------------------------------------
x_offset = Math.max(screenWidth/2 - player.X - ONE_TILE_WIDTH,
                    -----------------------------------------
           screenWidth - mapWidthInPixels);

first_tile_ordinal = -x_offset / ONE_TILE_WIDTH;
first_tile_x = inPixels(first_tile_ordinal) + x_offset; // 0