Using arrays

For my little game I will be using arrays to represent game objects. I figured I want some sort of object oriented game where I could create a Game Object and use functions to manipulate it.

Since I’m looking to do a top down adventure game I want to build rooms and in each room I want to have objects like enemies, pickups, the player etc. So to do I want to easily be able to manage a bunch of different game objects.

I want something like this (pseudo code)

hGameObject = GameObject_Create()
GameObject_SetPosition( hGameObject, 0, 0 )
GameObject_Render( hGameObject )

Since LMS doesn’t have the concept of complex data structures I figured I could do something like that using arrays. So my principle is simple. A game object is an array of 16 bit values. Each value represent a handle to a component. A component can hold different kinds of data, depending on what the component is.

For example a component can be the world position of the game object, it can be a sprite to be rendered, it may be AI information, it could be an inventory etc. This type of component based game objects is common in the world of making games, and if you’re used to Unity you will immediately recognize what I’m going for. I’ve even borrowed the component names when it makes sense. I’m just that used to Unity. :)

So I created an array like so:

ARRAY( CREATE16, NUM_COMPONENTS, hGameObject )

And I created my first component, the Transform component, and added it to my game object like so:

HANDLE  hTransform
ARRAY( CREATE16, 2, hTransform )
ARRAY( WRITE_CONTENT, -1, hTransform, 0, 2, 0 )
ARRAY( WRITE_CONTENT, -1, hTransform, 1, 2, 0 )
ARRAY( WRITE_CONTENT, -1, hGameObject, 0, 2, hTransform )

This was all fine and dandy. So I added my second component, the sprite.

ARRAY( WRITE_CONTENT, -1, hGameObject, 1, 2, hSprite )

The code was spread out in different functions but all in all it looked (something like) this:

subcall GameObject_Create
{
  IN_16   hSprite       // The sprite that the game object should use for rendering
  OUT_16  hGameObject   // The handle this function will create

  // Create root array that will hold handles to all components
  ARRAY( CREATE16, NUM_COMPONENTS, hGameObject )

  // Add transform
  HANDLE  hTransform
  ARRAY( CREATE16, 2, hTransform )
  ARRAY( WRITE_CONTENT, -1, hTransform, 0, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hTransform, 1, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hGameObject, 0, 2, hTransform )

  // Add sprite
  ARRAY( WRITE_CONTENT, -1, hGameObject, 1, 2, hSprite )
}

But now the angst begun.

When I read the handle of the transform array I got strange results. I had written 4 to index 0, and when I read I got 772 back. At first I believed the problem was because I wrote 4 in one function and read it back in another function.

After some investigation it turned out that the index for READ_CONTENT and WRITE_CONTENT was the array offset in bytes, not the index based on the size of the array elements. I thought that if I asked to read from index 1 in an array with 16 bit elements the byte offset would be translated to 2, but alas it was not! And to my embarrassment it even say in the code that the index is in bytes, not in elements.

 *  - CMD = READ_CONTENT
 *    -   \param  (DATA16)  PRGID     - Program slot number (must be running) (see \ref prgid)\n
 *    -   \param  (HANDLER) HANDLE    - Array handle\n
 *    -   \param  (DATA32)  INDEX     - Index to first byte to read\n
 *    -   \param  (DATA32)  BYTES     - Number of bytes to read\n
 *    -   \return (DATA8)   ARRAY     - First byte of array to receive data\n
 *
 *\n
 *  - CMD = WRITE_CONTENT
 *    -   \param  (DATA16)  PRGID     - Program slot number (must be running) (see \ref prgid)\n
 *    -   \param  (HANDLER) HANDLE    - Array handle\n
 *    -   \param  (DATA32)  INDEX     - Index to first byte to write\n
 *    -   \param  (DATA32)  BYTES     - Number of bytes to write\n
 *    -   \param  (DATA8)   ARRAY     - First byte of array to deliver data\n

So to get it to work I had to write the code like this:

subcall GameObject_Create
{
  IN_16   hSprite       // The sprite that the game object should use for rendering
  OUT_16  hGameObject   // The handle this function will create

  // Create root array that will hold handles to all components
  ARRAY( CREATE16, NUM_COMPONENTS, hGameObject )

  // Add transform
  HANDLE  hTransform
  ARRAY( CREATE16, 2, hTransform )
  ARRAY( WRITE_CONTENT, -1, hTransform, 0, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hTransform, 2, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hGameObject, 0, 2, hTransform )

  // Add sprite
  ARRAY( WRITE_CONTENT, -1, hGameObject, 2, 2, hSprite )
}

That way it worked beautifully. On top of that I also have constants that define the read and write offset of transform. So the whole game object code currently look like this:

/**************************************************************************************************
*
*   Game object functions
*
* Game objects will have a component like structore
* A game object will basically be an array of arrays
* It will be decided what goes in each array
* For example, the first array may contain the transform information such as position
* The second array may contan sprite data, so a reference to a loaded sprite.
*   (Assuming multiple game object may reference the same sprite)
* The third array may contain animation data, such as current frame, time in current frame
* Each array will be defined individually
*
* So, we will create an array of 16 bit values.
* GAMEOBJECT = new DATA16[];
*
* The we will create each "component"
* GAMEOBJECT[ 0 ] = new DATA32[];
*
* The we can populate each component with data
* GAMEOBJECT[ 0 ][ 0 ] = position_x;
*
* The indices will have constants, to make it easier to remember the contents
* GAMEOBJECT[ Transform ][ world_x ] = position_x;
*
* If we would see the multidimensional array as a JSON it would look like this:
* GAMEOBJECT =
* {
*   Transform =     // index 0
*   {               //
*     world_x,      //   index 0
*     world_y       //   index 1
*   },              //
*   Sprite =        // index 1
*   {               //
*     Reference to  //
*     loaded sprite //
*   },
*   Animation =     // index 2
*   {               //
*     frame_time,   //    index 0
*     current_frame //    index 1
*   }
* }
*
**************************************************************************************************/
define COMPONENT_TRANSFORM          0
define COMPONENT_SPRITE             2
define COMPONENT_ANIMATION          4
define NUM_COMPONENTS               3

subcall GameObject_Create
{
  IN_16   hSprite       // The sprite that the game object should use for rendering
  OUT_16  hGameObject   // The handle this function will create

  // Create root array that will hold handles to all components
  ARRAY( CREATE16, NUM_COMPONENTS, hGameObject )

  // Create each component
  CALL( GameObject_AddComponent_Transform, hGameObject )
  CALL( GameObject_AddComponent_Sprite, hGameObject, hSprite )
}

subcall GameObject_AddComponent_Transform
{
  IN_16   hGameObject

  HANDLE  hTransform
  ARRAY( CREATE16, 2, hTransform )
  ARRAY( WRITE_CONTENT, -1, hTransform, TRANSFORM_X, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hTransform, TRANSFORM_Y, 2, 0 )
  ARRAY( WRITE_CONTENT, -1, hGameObject, COMPONENT_TRANSFORM, 2, hTransform )
}

subcall GameObject_AddComponent_Sprite
{
  IN_16   hGameObject
  IN_16   hSprite

  ARRAY( WRITE_CONTENT, -1, hGameObject, COMPONENT_SPRITE, 2, hSprite )
}

subcall GameObject_GetComponent
{
  IN_16   hGameObject
  IN_32   ComponentIndex
  OUT_16  hComponent

  ARRAY( READ_CONTENT, -1, hGameObject, ComponentIndex, 2, hComponent )
}

/**************************************************************************************************
*
*   Transform functions
*
**************************************************************************************************/
define TRANSFORM_X      0
define TRANSFORM_Y      2
subcall Transform_SetPosition
{
  IN_16     hGameObject
  IN_16     PositionX
  IN_16     PositionY

  HANDLE  hTransform
  CALL( GameObject_GetComponent, hGameObject, COMPONENT_TRANSFORM, hTransform )
  ARRAY( WRITE_CONTENT, -1, hTransform, TRANSFORM_X, 2, PositionX )
  ARRAY( WRITE_CONTENT, -1, hTransform, TRANSFORM_Y, 2, PositionY )

  DATA16 temp
  ARRAY( READ_CONTENT, -1, hTransform, TRANSFORM_X, 2, temp )
  CALL( WriteLog16, 'org  pos x: ', PositionX )
  CALL( WriteLog16, 'read pos x: ', temp )
}

subcall Transform_GetPosition
{
  IN_16     hGameObject
  OUT_16    PositionX
  OUT_16    PositionY

  HANDLE  hTransform
  CALL( GameObject_GetComponent, hGameObject, COMPONENT_TRANSFORM, hTransform )
  ARRAY( READ_CONTENT, -1, hTransform, TRANSFORM_X, 2, PositionX )
  ARRAY( READ_CONTENT, -1, hTransform, TRANSFORM_Y, 2, PositionY )
}

And you can use the game object code like this:

  HANDLE hSprite
  CALL( Sprite_Create, 'spritetest.spr', hSprite )

  HANDLE hHero
  CALL( GameObject_Create, hSprite, hHero )
  CALL( Transform_SetPosition, hHero, 32760, 32761 )

(And here is the code to load and render sprites. The new and improved version)

/**************************************************************************************************
*
*   Graphics functions
*
**************************************************************************************************/
subcall Sprite_Create
{
  IN_S    FileName     128
  OUT_16  SpriteHandle

  HANDLE hFile
  DATA32 FileSize32

  FILE( OPEN_READ, FileName, hFile, FileSize32 )
  ARRAY( CREATE8, FileSize32, SpriteHandle )
  CALL( File_ReadToArray, hFile, SpriteHandle, FileSize32 )
  FILE( CLOSE, hFile )
}

subcall Sprite_Draw
{
  IN_16   SpriteDataHandle
  IN_16   ScreenX
  IN_16   ScreenY

  DATA8   SpriteWidth
  DATA8   SpriteHeight
  DATA32  ReadOfs
  DATA16  WriteX
  DATA16  WriteY
  DATA16  MaxX
  DATA16  MaxY
  DATA8   ReadPixel
  DATA8   WritePixel

  // Read sprite dimensions
  ARRAY( READ_CONTENT, -1, SpriteDataHandle, 0, 1, SpriteWidth )
  ARRAY( READ_CONTENT, -1, SpriteDataHandle, 1, 1, SpriteHeight )

  // Setup
  MOVE8_16( SpriteWidth, MaxX )
  ADD16( MaxX, ScreenX, MaxX )

  MOVE8_16( SpriteHeight, MaxY )
  ADD16( MaxY, ScreenY, MaxY )

  // The whole thing
  MOVE32_32( 2, ReadOfs )
  MOVE16_16( ScreenY, WriteY )

Loop_Y:
  MOVE16_16( ScreenX, WriteX )

Loop_X:
  ARRAY( READ_CONTENT, -1, SpriteDataHandle, ReadOfs, 1, ReadPixel )
  JR_EQ8( ReadPixel, 0, DoneDrawing )   // Don't do any drawing at all
  JR_EQ8( ReadPixel, 1, DrawBlack )
  MOVE8_8( BG_COLOR, WritePixel )       // Set WritePixel to White
  JR( Draw )

DrawBlack:
  MOVE8_8( FG_COLOR, WritePixel )       // Set WritePixel to Black

Draw:
  UI_DRAW( PIXEL, WritePixel, WriteX, WriteY )

DoneDrawing:
  ADD32( ReadOfs, 1, ReadOfs )
  ADD16( WriteX, 1, WriteX )
  JR_LT16( WriteX, MaxX, Loop_X )

  ADD16( WriteY, 1, WriteY )
  JR_LT16( WriteY, MaxY, Loop_Y )
}

So the lesson of the day is, when you are reading from and writing to arrays, the index is in bytes, not in elements.

Leave a Reply