Chapter 12: Building Your Own Blueprints

Last chapter, we learned about arrays—storing multiple values of the same type. But here's a question: what if you need to store DIFFERENT types of information that all belong together?

Think about a player in a game. A player has:

  • A name (text)
  • Health points (number)
  • X position (number)
  • Y position (number)
  • Is alive? (true/false)

You COULD use separate variables:

playerName$ = "Hero"
playerHealth = 100
playerX = 160
playerY = 100
playerAlive = TRUE

But what happens when you have TWO players? Or TEN enemies? Suddenly you need player1Name$, player2Name$, enemy1Name$, enemy2Name$... it gets messy FAST.

What if you could create your OWN type of variable that holds all this information together?

You can. They're called User-Defined Types (or UDTs for short), and they're like blueprints for your data!


Your First Custom Type

Let's create a blueprint for a "Point"—something that has an X and Y position:

TYPE Point
    x AS INTEGER
    y AS INTEGER
END TYPE

That's it! You've just created a new type called Point. It's not a variable yet—it's a BLUEPRINT that says "any Point has an x and a y."

Now let's USE that blueprint:

TYPE Point
    x AS INTEGER
    y AS INTEGER
END TYPE

' Create a variable using our blueprint
DIM playerPos AS Point

' Set the values using dot notation
playerPos.x = 160
playerPos.y = 100

PRINT "Player is at: "; playerPos.x; ", "; playerPos.y

PAUSE

See that dot notation? playerPos.x means "the x part of playerPos." It's like reaching into a container and grabbing a specific piece.


Why Custom Types Are Amazing

1. Organization

Instead of remembering which variables go together:

' MESSY: Which variables belong to the player?
playerX = 100
playerY = 50
playerHealth = 100
playerName$ = "Hero"
enemyX = 200     ' Wait, is this related to player or enemy?

Everything is neatly packaged:

' CLEAN: Everything about the player is in one place
DIM player AS Player
player.x = 100
player.y = 50
player.health = 100
player.name = "Hero"

2. Arrays of Complex Objects

Remember arrays? Now you can have an array of ANYTHING:

TYPE Enemy
    x AS INTEGER
    y AS INTEGER
    health AS INTEGER
    speed AS INTEGER
END TYPE

' An array of 10 enemies!
DIM enemies(10) AS Enemy

' Set up the first enemy
enemies(1).x = 50
enemies(1).y = 100
enemies(1).health = 30
enemies(1).speed = 2

3. Pass Entire Objects to Functions

DECLARE SUB DrawEnemy(e AS Enemy)

' One line passes ALL the enemy's data!
CALL DrawEnemy(enemies(1))

Building a Player Type

Let's create a real game player type:

SCREEN 13

' Define our Player blueprint
TYPE Player
    x AS INTEGER
    y AS INTEGER
    width AS INTEGER
    height AS INTEGER
    speed AS INTEGER
    health AS INTEGER
    maxHealth AS INTEGER
    score AS INTEGER
    name AS STRING
END TYPE

' Create our player
DIM hero AS Player

' Initialize the player
hero.name = "Crash-tan"
hero.x = 160
hero.y = 100
hero.width = 20
hero.height = 20
hero.speed = 4
hero.health = 100
hero.maxHealth = 100
hero.score = 0

' Game loop
DO
    ' Input
    IF KEYDOWN("LEFT") AND hero.x > 0 THEN
        hero.x = hero.x - hero.speed
    END IF
    IF KEYDOWN("RIGHT") AND hero.x < 320 - hero.width THEN
        hero.x = hero.x + hero.speed
    END IF
    IF KEYDOWN("UP") AND hero.y > 0 THEN
        hero.y = hero.y - hero.speed
    END IF
    IF KEYDOWN("DOWN") AND hero.y < 200 - hero.height THEN
        hero.y = hero.y + hero.speed
    END IF

    ' Render
    BEGIN FRAME
    CLS

    ' Draw player as a rectangle
    LINE (hero.x, hero.y)-(hero.x + hero.width, hero.y + hero.height), 14, BF

    ' Draw UI
    LOCATE 1, 1
    PRINT hero.name; " - Health: "; hero.health; "/"; hero.maxHealth
    LOCATE 2, 1
    PRINT "Score: "; hero.score

    END FRAME
    DELAY 16
LOOP

Look how clean that is! All the player's data travels together, and the code is easy to read.


Types Inside Types (Nesting)

Here's where it gets REALLY powerful. A type can contain OTHER types!

' A position has x and y
TYPE Position
    x AS INTEGER
    y AS INTEGER
END TYPE

' A rectangle has a position AND a size
TYPE Rectangle
    pos AS Position
    width AS INTEGER
    height AS INTEGER
END TYPE

' A player has a rectangle (for collision) and other stats
TYPE Player
    bounds AS Rectangle
    health AS INTEGER
    name AS STRING
END TYPE

' Create a player
DIM hero AS Player

' Set nested values with multiple dots
hero.bounds.pos.x = 100
hero.bounds.pos.y = 50
hero.bounds.width = 20
hero.bounds.height = 30
hero.health = 100
hero.name = "Hero"

PRINT "Player "; hero.name; " is at "; hero.bounds.pos.x; ", "; hero.bounds.pos.y

PAUSE

You can chain as many dots as you need: hero.bounds.pos.x reaches into hero, then into bounds, then into pos, and finally gets x.


Arrays of Custom Types

This is where the magic really happens. Combine arrays with custom types to manage lots of game objects easily:

SCREEN 13

TYPE Bullet
    x AS INTEGER
    y AS INTEGER
    active AS INTEGER    ' 1 = flying, 0 = inactive
    speed AS INTEGER
END TYPE

' Array of 20 bullets
DIM bullets(20) AS Bullet
DIM bulletCount AS INTEGER
bulletCount = 0

' Initialize all bullets as inactive
FOR i = 1 TO 20
    bullets(i).active = 0
    bullets(i).speed = 8
NEXT i

playerX = 160

DO
    ' Shoot with SPACE
    IF KEYDOWN("SPACE") THEN
        ' Find an inactive bullet to fire
        FOR i = 1 TO 20
            IF bullets(i).active = 0 THEN
                bullets(i).x = playerX
                bullets(i).y = 180
                bullets(i).active = 1
                EXIT FOR    ' Only fire one bullet!
            END IF
        NEXT i
    END IF

    ' Move left/right
    IF KEYDOWN("LEFT") AND playerX > 10 THEN playerX = playerX - 4
    IF KEYDOWN("RIGHT") AND playerX < 310 THEN playerX = playerX + 4

    ' Update all active bullets
    FOR i = 1 TO 20
        IF bullets(i).active = 1 THEN
            bullets(i).y = bullets(i).y - bullets(i).speed

            ' Deactivate if off screen
            IF bullets(i).y < 0 THEN
                bullets(i).active = 0
            END IF
        END IF
    NEXT i

    ' Render
    BEGIN FRAME
    CLS

    ' Draw player
    CIRCLE (playerX, 190), 10, 14, 14

    ' Draw all active bullets
    FOR i = 1 TO 20
        IF bullets(i).active = 1 THEN
            LINE (bullets(i).x - 2, bullets(i).y)-(bullets(i).x + 2, bullets(i).y + 6), 12, BF
        END IF
    NEXT i

    LOCATE 1, 1
    PRINT "SPACE to shoot!"

    END FRAME
    DELAY 16
LOOP

This is called an object pool—you create all your objects up front, and just activate/deactivate them as needed. It's how real games manage bullets, particles, and enemies!


Copying Types

When you assign one type variable to another, it copies ALL the values:

TYPE Point
    x AS INTEGER
    y AS INTEGER
END TYPE

DIM p1 AS Point
DIM p2 AS Point

p1.x = 100
p1.y = 200

' Copy p1 to p2
p2 = p1

PRINT "p2: "; p2.x; ", "; p2.y    ' Prints: 100, 200

' Changing p1 does NOT change p2 (they're separate copies)
p1.x = 999

PRINT "p1: "; p1.x    ' Prints: 999
PRINT "p2: "; p2.x    ' Prints: 100 (unchanged!)

PAUSE

This is called copy by value. Each variable has its own independent copy of the data.


Passing Types to Subroutines

You can pass entire type variables to SUBs and FUNCTIONs:

SCREEN 13

TYPE Enemy
    x AS INTEGER
    y AS INTEGER
    health AS INTEGER
    enemyColor AS INTEGER
END TYPE

DECLARE SUB DrawEnemy(e AS Enemy)
DECLARE SUB DamageEnemy(e AS Enemy, amount AS INTEGER)

' Create an enemy
DIM badGuy AS Enemy
badGuy.x = 160
badGuy.y = 100
badGuy.health = 50
badGuy.enemyColor = 4    ' Red

' Draw it
CALL DrawEnemy(badGuy)

PRINT "Health before: "; badGuy.health
PAUSE

' Note: Changes in SUB don't affect original (passed by value)
CALL DamageEnemy(badGuy, 20)
PRINT "Health after: "; badGuy.health    ' Still 50!

PAUSE

SUB DrawEnemy(e AS Enemy)
    CIRCLE (e.x, e.y), 15, e.enemyColor, e.enemyColor
    LOCATE 1, 1
    PRINT "Enemy HP: "; e.health
END SUB

SUB DamageEnemy(e AS Enemy, amount AS INTEGER)
    e.health = e.health - amount
    ' This only changes the LOCAL copy!
END SUB

Important: When you pass a type to a SUB, it gets a COPY. Changes inside the SUB don't affect the original. If you need to modify the original, you'll need to return a value or use a global variable.


Real Example: A Complete Enemy System

Let's put it all together with a full enemy management system:

SCREEN 13

' ================================================
' TYPE DEFINITIONS
' ================================================

TYPE Enemy
    x AS INTEGER
    y AS INTEGER
    width AS INTEGER
    height AS INTEGER
    speedX AS INTEGER
    speedY AS INTEGER
    health AS INTEGER
    active AS INTEGER
    enemyColor AS INTEGER
END TYPE

' ================================================
' DECLARATIONS
' ================================================

DECLARE SUB InitEnemies()
DECLARE SUB SpawnEnemy()
DECLARE SUB UpdateEnemies()
DECLARE SUB DrawEnemies()

' ================================================
' CONSTANTS AND GLOBALS
' ================================================

CONST MAX_ENEMIES = 10

DIM enemies(MAX_ENEMIES) AS Enemy
DIM score AS INTEGER
DIM spawnTimer AS INTEGER

score = 0
spawnTimer = 0

' Initialize
CALL InitEnemies()

' ================================================
' MAIN GAME LOOP
' ================================================

DO
    ' Spawn enemies periodically
    spawnTimer = spawnTimer + 1
    IF spawnTimer > 60 THEN    ' Every ~1 second
        CALL SpawnEnemy()
        spawnTimer = 0
    END IF

    ' Update
    CALL UpdateEnemies()

    ' Render
    BEGIN FRAME
    CLS

    CALL DrawEnemies()

    LOCATE 1, 1
    PRINT "Score: "; score
    LOCATE 2, 1
    PRINT "Press ESC to quit"

    END FRAME
    DELAY 16

LOOP UNTIL KEYDOWN("ESCAPE")

END

' ================================================
' SUBROUTINES
' ================================================

SUB InitEnemies()
    FOR i = 1 TO MAX_ENEMIES
        enemies(i).active = 0
        enemies(i).width = 20
        enemies(i).height = 20
    NEXT i
END SUB

SUB SpawnEnemy()
    ' Find an inactive slot
    FOR i = 1 TO MAX_ENEMIES
        IF enemies(i).active = 0 THEN
            enemies(i).x = INT(RND * 280) + 20
            enemies(i).y = -20    ' Start above screen
            enemies(i).speedX = INT(RND * 3) - 1    ' -1, 0, or 1
            enemies(i).speedY = INT(RND * 2) + 1    ' 1 or 2
            enemies(i).health = 1
            enemies(i).active = 1
            enemies(i).enemyColor = INT(RND * 14) + 1    ' Random color

            EXIT FOR    ' Only spawn one
        END IF
    NEXT i
END SUB

SUB UpdateEnemies()
    FOR i = 1 TO MAX_ENEMIES
        IF enemies(i).active = 1 THEN
            ' Move
            enemies(i).x = enemies(i).x + enemies(i).speedX
            enemies(i).y = enemies(i).y + enemies(i).speedY

            ' Bounce off walls
            IF enemies(i).x < 0 OR enemies(i).x > 300 THEN
                enemies(i).speedX = -enemies(i).speedX
            END IF

            ' Deactivate if off bottom of screen
            IF enemies(i).y > 220 THEN
                enemies(i).active = 0
                score = score + 10
            END IF
        END IF
    NEXT i
END SUB

SUB DrawEnemies()
    FOR i = 1 TO MAX_ENEMIES
        IF enemies(i).active = 1 THEN
            LINE (enemies(i).x, enemies(i).y)-(enemies(i).x + enemies(i).width, enemies(i).y + enemies(i).height), enemies(i).enemyColor, BF
        END IF
    NEXT i
END SUB

This is how real games organize their code:

  • Types define what data each object has
  • Arrays hold all the objects
  • Subroutines handle spawn, update, and draw
  • The main loop just coordinates everything

Data Types Reference

When defining fields in your types, you can use these data types:

Type What It Stores Example
INTEGER Whole numbers x AS INTEGER
SINGLE Decimal numbers (less precision) speed AS SINGLE
DOUBLE Decimal numbers (more precision) angle AS DOUBLE
STRING Text name AS STRING

You can also nest other custom types:

TYPE Position
    x AS INTEGER
    y AS INTEGER
END TYPE

TYPE Player
    pos AS Position    ' A custom type inside another!
    name AS STRING
END TYPE

Quick Reference

' Define a type
TYPE TypeName
    fieldName AS DataType
    anotherField AS DataType
END TYPE

' Create a variable of that type
DIM varName AS TypeName

' Access fields with dot notation
varName.fieldName = value
PRINT varName.fieldName

' Create an array of a type
DIM arrayName(size) AS TypeName

' Access array element fields
arrayName(index).fieldName = value

' Copy a type variable
var2 = var1    ' Copies all fields

' Pass to a subroutine
DECLARE SUB MySub(param AS TypeName)
CALL MySub(varName)

Try It Yourself!

  1. Create a Car type with x, y, speed, color, and honking (true/false) fields. Make a simple driving demo.

  2. Create a Card type for a card game with suit (string) and value (integer). Make an array of 52 cards and "shuffle" them.

  3. Expand the enemy system above to include a player that can shoot bullets at the enemies.

  4. Create a Particle type and make a simple particle explosion effect when you click.

  5. Design a MenuItem type for a game menu system with text, selected, and action fields.

Custom types are the foundation of organized game code. Master them, and you can build ANYTHING!

See you in Chapter 13!


Next Chapter: Time to make some noise! We'll add sound effects and music to bring your games to life!