Jump to content

Geometry Overview

From OpenBOR

OpenBOR uses the Cartesian coordinate system to describe locations on the display screen and within the game world. The behavior of an axis depends on whether it describes a static location, velocity, dimensions, or another property. Axis behavior also varies depending on whether the coordinates apply to the screen or the game world.

The OpenBOR world axis layout.

Game Field

The game field is the “world”—a virtual three-dimensional space occupied by levels and entities during gameplay. It supports highly granular positioning and movement through floating-point decimal values.

For most purposes, creators do not need to account for this degree of precision. In fact, this precision is generally unavailable outside of scripts because almost all text commands operate in whole pixels. Whereas a level may span only a few hundred pixels, the game world is comparatively infinite, ranging from approximately −3.4E+38 to 3.4E+38 pixels in every direction.

Each level occupies a portion of this space, with its bottom-left origin initialized to position 0 on all axes. Objects can—and often do—exist outside the level’s boundaries.

  • X - Horizontal axis. Positions are measured from left (lower values) to right (higher values).
  • Y - Vertical axis. Positions are measured from bottom to top, beginning at the level’s default base.
  • Z - Lateral axis. Lower Z values move objects away from the “camera,” while higher Z values move them closer.
  • Base - A vertical-axis value that determines an entity’s Y position while it is not jumping, flying, or falling. For example, an entity standing in a normal stage area typically has a base of 0. If the entity jumps onto a platform 50 pixels high, its base becomes 50.

Movement may optionally be restricted to a single point on the Z axis. In this configuration, OpenBOR defaults to behavior appropriate for two-dimensional games, such as platformers. Ex: Pressing down activates the Duck animation, if available.

The world, level, and display hierarchy. Levels define a playable area and starting coordinates, but objects may exist anywhere you define them in the world.

Screen

The screen is the display area where objects from the game world, scripted sprites, and text are drawn. Screen dimensions are measured in whole pixels from top left. Any portion of an item outside the screen boundaries is not visible. The screen’s actual dimensions depend on the module’s resolution settings.

  • X - Horizontal axis. Positions are measured from 0 at the leftmost pixel, increasing rightward to the horizontal resolution.
  • Y - Vertical axis. Positions are measured from 0 at the topmost pixel, increasing downward to the vertical resolution.

Screens are flat by definition and have no Z axis, as is also true of most assets used in an OpenBOR game. To convey three-dimensional movement, OpenBOR uses a complex relationship between the Y and Z axes in conjunction with a drawing-layer hierarchy.

For example, as an object moves toward the “camera” (the player’s point of view) along the game-world Z axis, it also moves downward along the screen’s Y axis. The object’s drawing order increases as well, meaning it is drawn later and therefore appears in front of objects farther from the camera. This process is reversed as objects move away from the camera.

Both relationships depend on the item in question and are largely automatic. However, they are subject to various adjustments that provide direct control when needed. See the individual articles for more information.

Subpixel Movement

Although the screen displays objects at whole-pixel coordinates, as noted above OpenBOR stores game-world positions and velocities as floating-point values. This allows movement to accumulate in fractions of a pixel instead of being rounded after every update.

For example, an entity moving horizontally at 0.25 pixels per logic update might have the following internal X positions:

100.00
100.25
100.50
100.75
101.00

The entity cannot be drawn between physical screen pixels, so several updates may place it on the same screen pixel. Internally, however, none of its fractional movement is lost. OpenBOR preserves the remainder and projects the precise world position onto whole screen pixels during rendering. This produces consistent movement and prevents the rounding errors caused by converting every position update to an integer.

Movement Methods

For smooth movement, always use one of the following methods:

  • Native velocity control - Assign velocity and allow OpenBOR’s movement system to update the entity’s position. This is the preferred approach for normal entity movement.
  • Scripted subpixel movement - When implementing custom movement, store positions and velocities as floating-point values. Apply velocity to the precise position during each logic update and allow the renderer to convert the result to screen pixels.
float position_x = 100.0;
float velocity_x = 0.25;

// Preserve the fractional component.
position_x += velocity_x;

Do not round the working position after each update:

// Avoid this. The fractional movement is discarded every update.
position_x = round(position_x + velocity_x);

With a velocity of 0.25, repeatedly rounding the working position could prevent the object from moving at all. Even when movement occurs, discarding the fractional remainder produces inconsistent timing.

Animation Movement and Offsets

One of the most common mistakes made by new creators is attempting to produce continuous movement with frame Move commands - or worse, by modifying sprite offsets. Aside from their other detrimental side effects, these methods cannot replicate the engine’s smooth native movement because they operate exclusively in whole pixels. No combination of smaller increments or shorter frame delays can overcome this limitation.

Animation movement commands remain useful for deliberate frame-specific adjustments, such as a sudden step during an attack. Offsets remain useful for aligning differently sized sprites around a consistent entity origin. Neither should be used to implement general locomotion, scrolling, knockback, gravity, or other continuous motion. Use the engine’s native velocity controls instead. If you need to implement custom movement in script, maintain the position and velocity as floating-point values so fractional movement is preserved between updates.