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 = TRUEBut 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 TYPEThat'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
PAUSESee 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 = 23. 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
LOOPLook 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
PAUSEYou 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
LOOPThis 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!)
PAUSEThis 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 SUBImportant: 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 SUBThis 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 TYPEQuick 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!
Create a
Cartype withx,y,speed,color, andhonking(true/false) fields. Make a simple driving demo.Create a
Cardtype for a card game withsuit(string) andvalue(integer). Make an array of 52 cards and "shuffle" them.Expand the enemy system above to include a player that can shoot bullets at the enemies.
Create a
Particletype and make a simple particle explosion effect when you click.Design a
MenuItemtype for a game menu system withtext,selected, andactionfields.
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!