Welcome to basicFusion
The environment is compatible with QBasic/QuickBASIC syntax. You can write programs with line numbers (classic style) or without them, using text labels instead. The built-in graphics panel lets you draw shapes, animations, and demoscene effects.
How to Run a Program
Type your code in the editor and press Ctrl+F9 or click the Run button. The terminal and graphics canvas will appear on the right.
Text Mode
Default mode. PRINT writes to the terminal. INPUT reads data from the user.
Graphics Mode
Activated by SCREEN. An HTML5 canvas with full 2D support: lines, circles, fills, text, animation.
Program Structure
Line Numbers vs. Labels
You can use line numbers (classic style) or text labels (modern style). Both approaches work simultaneously.
' Classic style with numbers 10 PRINT "Hello!" 20 GOTO 10
' Modern style with labels START: PRINT "Hello!" GOTO START ' jumps to label START
Comments
REM This is a full-line comment PRINT "Code" ' This is also a comment (apostrophe)
Program Header => REM metadata (Program / Author / Version / Description)
You can put a small header of REM comments at the very top of your program. In the Build menu, App Settings lets you edit it, and the export dialog reads it to prefill its fields. Keys are case-insensitive and each line is optional.
REM Program: Franko REM Author: Terror REM Version: 1.0 REM Description: A retro action game made with basicFusion
The header must sit in the leading comment block (before the first line of real code). It is made of ordinary comments, so it has no effect on how the program runs. The apostrophe form works too, e.g. ' Author: Terror.
Multiple Statements on One Line
LET A = 5 : LET B = 10 : PRINT A + B
Ending the Program
END ' Stops program execution
Compiler Options => OPTION
The OPTION command is used to configure how the basicFusion interpreter behaves, acting like a compiler directive or pragma.
OPTION EXPLICIT
By default, variables in basicFusion do not need to be declared before use. If you misspell a variable name, the interpreter simply creates a new one with a default value of 0 or "". Using OPTION EXPLICIT forces you to explicitly declare all variables using DIM or LOCAL before assigning values to them. This helps prevent bugs caused by typos.
OPTION EXPLICIT DIM PLAYER_SPEED AS INTEGER PLAYER_SPEED = 5 PRINT PLAYER_SPEED
OPTION STRICT
While OPTION EXPLICIT forces variable declaration, OPTION STRICT goes a step further by enforcing strict data typing and preventing unsafe implicit type conversions. For example, it stops you from accidentally assigning a decimal number to an integer variable without an explicit conversion, or mixing strings and numbers.
OPTION STRICT DIM SCORE AS INTEGER SCORE = 10.5 ' Error: Implicit conversion not allowed SCORE = CINT(10.5) ' OK: Explicit conversion using CINT()
OPTION EXPLICIT and OPTION STRICT must be placed at the very top of your program, before any variables are used or assigned.OPTION STACKSIZE / OPTION VMSPEED
Two options let a program override the IDE Settings for itself: OPTION STACKSIZE n sets how deep recursion and GOSUB may nest before a Stack overflow error (raise it for deep recursion, lower it to catch runaway recursion early), and OPTION VMSPEED n sets how many instructions run per tick (higher = faster/less responsive, lower = smoother). If omitted, the values from Settings are used.
OPTION VMSPEED 500000 ' run faster than the default OPTION STACKSIZE 20000 ' allow deeper recursion
Variables and Data Types
Variable Types
BASIC distinguishes between numeric and text (string) variables. String variables end with the $ character.
| Type | Example name | Example value | Description |
|---|---|---|---|
| Numeric | A, X, SCORE, i | 42, 3.14, -7 | Integer or floating-point number |
| Text (String) | A$, NAME$, S$ | "Hello", "Jan" | Character sequence, name ends with dollar sign |
Assigning Values
The LET keyword is optional, you can omit it.
LET X = 10 ' with LET Y = 3.14 ' without LET (also works) NAME$ = "Anna" ' string variable RESULT = X + Y ' expression PRINT NAME$; " is "; X; " years old"
Compound Assignment => += -= *= /= ^=
Shorthand for updating a variable from its own value. X += 5 means X = X + (5). Works on array elements and object fields too. Since strings join with +, S$ += "!" appends text (there is no & operator).
SCORE = 0 SCORE += 100 ' 100 SCORE *= 2 ' 200 MSG$ = "Hi" MSG$ += " there" ' "Hi there"
INC / DEC => Increment & Decrement
INC x adds 1, DEC x subtracts 1. An optional second argument is a custom step. Works on array elements and object fields as well.
LIVES = 3 DEC LIVES ' 2 INC SCORE, 50 ' SCORE = SCORE + 50
SWAP => Swapping Values
A = 10 : B = 20 SWAP A, B PRINT A, B ' prints: 20 10
Constants => CONST
Constants are named values that are set once and never change during program execution. Using constants makes code more readable and eliminates "magic numbers".
Declaring Constants
Use CONST followed by the name, an equals sign, and the value. Constants can be numeric or string.
CONST MAX_LIVES = 3 CONST SCREEN_W = 640 CONST SCREEN_H = 480 CONST GRAVITY = 0.5 CONST TITLE$ = "My Game"
Using Constants
After declaration, use a constant just like any variable, but you cannot assign a new value to it.
CONST PLAYER_SPEED = 5 CONST BALL_R = 20 SCREEN 12 PX = 320 LOOP: PAINT "black" IF KEY("ARROWRIGHT") THEN PX = PX + PLAYER_SPEED IF KEY("ARROWLEFT") THEN PX = PX - PLAYER_SPEED CIRCLE (PX, 240), BALL_R, "lime" SYNC GOTO LOOP
MAX_LIVES = 5 after declaring it with CONST) will cause a runtime error. Declare all constants at the top of your program.Operators
Arithmetic
| Operator | Meaning | Example | Result |
|---|---|---|---|
| + | Addition | 3 + 4 | 7 |
| - | Subtraction | 10 - 3 | 7 |
| * | Multiplication | 4 * 5 | 20 |
| / | Division | 10 / 4 | 2.5 |
| ^ | Exponentiation | 2 ^ 8 | 256 |
| MOD | Remainder (modulo) | 17 MOD 5 | 2 |
Comparisons (return -1 = true, 0 = false)
| Operator | Meaning |
|---|---|
| = or == | Equal |
| <> | Not equal |
| < , > | Less than / greater than |
| <= , >= | Less than or equal / greater than or equal |
Logical
| Operator | Meaning | Example |
|---|---|---|
| AND | Logical AND (both must be true) | X > 0 AND X < 10 |
| OR | Logical OR (at least one must be true) | X < 0 OR X > 100 |
| NOT | Logical NOT (negation) | NOT (X = 5) |
String Concatenation
A$ = "Hello" + " World" B$ = "Age: " + STR$(25) PRINT A$ ' Hello World
PRINT and INPUT
PRINT => Output
PRINT "Hello!" ' newline after text PRINT "X ="; X ' semicolon = no space PRINT "A", "B", "C" ' comma = tab every 14 chars PRINT "No newline"; ' semicolon at end = no newline PRINT ' empty line PRINT TAB(20); "Indented text" ' TAB(N) moves cursor
INPUT => Getting Data from the User
INPUT X ' prints "?" and waits for a number INPUT "Enter name: "; NAME$ ' custom prompt, string INPUT "Age", AGE ' comma = prompt + "? " automatically
LOCATE => Cursor Position in Terminal
LOCATE 5, 10 ' row 5, column 10 PRINT "Here!"
TERMINAL => Resize the Text Grid
The TERMINAL command changes the size of the text terminal. The default is 80, 25, and the grid resets back to 80×25 every time a program starts (and when you call SCREEN 0).
TERMINAL 40, 10 ' 40 columns, 10 rows CLS PRINT "Small screen!"
BEEP => Audio Signal
BEEP ' short 800Hz beep for 0.2 seconds
Conditions => IF / ELSE / END IF
Single-line Form
IF X > 0 THEN PRINT "Positive" IF X > 0 THEN PRINT "Positive" ELSE PRINT "Non-positive"
Block Form (multi-line)
IF SCORE >= 90 THEN PRINT "Grade: A" ELSE IF SCORE >= 70 THEN PRINT "Grade: B" ELSE IF SCORE >= 50 THEN PRINT "Grade: C" ELSE PRINT "Grade: F" END IF
END IF at the end. The single-line form does not need END IF.Nested Conditions
IF A > 0 THEN IF B > 0 THEN PRINT "A and B are positive" END IF END IF
Loops => FOR / WHILE / DO
FOR is closed by NEXT, DO by LOOP, and WHILE by WEND.FOR ... NEXT => Counter Loop
FOR I = 1 TO 10 PRINT "Step:"; I NEXT I
WHILE ... WEND => Simple Condition
WHILE X < 10 X = X + 1 WEND
DO ... LOOP => Flexible Loop
DO INPUT "Again? (y/n)"; A$ LOOP UNTIL A$ = "n"
EXIT => Early Loop Termination
Use EXIT FOR or EXIT DO to immediately break out of a loop. Note that there is no "EXIT WHILE".
FOR I = 1 TO 100 IF KEY("ESCAPE") THEN EXIT FOR NEXT I
CONTINUE => Skip to Next Iteration
Use CONTINUE to jump straight to the next pass of the loop, skipping the rest of the body. It works in FOR, WHILE and DO loops (in FOR the counter is still advanced). Optionally name the loop type: CONTINUE FOR.
FOR I = 1 TO 6 IF I MOD 2 = 0 THEN CONTINUE PRINT I ' prints 1, 3, 5 NEXT I
GOTO, GOSUB and ON
GOTO => Unconditional Jump
GOTO DONE PRINT "This will not run" DONE: PRINT "End"
GOSUB / RETURN => Subroutines with Return
GOSUB DRAW_HEADER PRINT "Content" GOSUB DRAW_FOOTER END DRAW_HEADER: PRINT STRING$(40, "=") PRINT " PROGRAM TITLE" PRINT STRING$(40, "=") RETURN DRAW_FOOTER: PRINT STRING$(40, "-") RETURN
ON X GOTO / ON X GOSUB => Jump by Index
INPUT "Choose 1-3: "; CHOICE ON CHOICE GOTO OPC1, OPC2, OPC3 GOTO ERR OPC1: PRINT "You chose 1" : GOTO DONE OPC2: PRINT "You chose 2" : GOTO DONE OPC3: PRINT "You chose 3" : GOTO DONE ERR: PRINT "Invalid choice" DONE:
SELECT CASE
An elegant alternative to long IF/ELSE IF chains when checking a single variable.
INPUT "Day of week (1-7): "; D SELECT CASE D CASE 1 PRINT "Monday" CASE 2 PRINT "Tuesday" CASE 6, 7 PRINT "Weekend!" CASE ELSE PRINT "Midweek" END SELECT
Ranges => CASE lo TO hi
A single CASE can match a whole range of values with TO. The bounds can be numbers, variables, or expressions, and it works on strings too (compared alphabetically).
SELECT CASE SCORE CASE 90 TO 100 PRINT "A" CASE 80 TO 89 PRINT "B" CASE ELSE PRINT "lower" END SELECT ' String range - matches "A" through "M" SELECT CASE LETTER$ CASE "A" TO "M" PRINT "first half" CASE "N" TO "Z" PRINT "second half" END SELECT
Comparisons => CASE IS
Use CASE IS with a comparison operator (<, <=, >, >=, =, <>) to match everything above, below, or different from a value.
SELECT CASE N CASE IS < 0 PRINT "negative" CASE 0 PRINT "zero" CASE IS >= 100 PRINT "big" CASE ELSE PRINT "in between" END SELECT
You can mix all of these in one CASE, separated by commas:
CASE 1, 5 TO 9, IS > 100 ' 1, or 5..9, or anything over 100
Arrays => DIM
Arrays are variables that store multiple values indexed by a number. Indices start at 0.
One-dimensional Array
DIM SCORES(9) ' 10 elements: SCORES(0) to SCORES(9) FOR I = 0 TO 9 SCORES(I) = I * 10 NEXT I PRINT SCORES(5) ' prints: 50
Two-dimensional Array (Matrix)
DIM MAP(9, 9) ' 10x10 matrix FOR R = 0 TO 9 FOR C = 0 TO 9 MAP(R, C) = R * 10 + C NEXT C NEXT R
Multiple Arrays in One DIM
DIM X(99), Y(99), ACTIVE(99)
Custom Index Bounds => DIM with TO
Instead of always starting at 0, you can declare arrays with any lower bound using the TO keyword. This is very useful when indices represent real-world values like years, coordinates, or months.
' DIM name(lower TO upper) DIM YEAR_DATA(2000 TO 2030) ' index 2000..2030 (31 elements) DIM BOARD(1 TO 8, 1 TO 8) ' chess board, rows/cols 1..8 DIM GRID(-5 TO 5) ' negative lower bound is allowed YEAR_DATA(2024) = 999 PRINT YEAR_DATA(2024) ' 999 FOR R = 1 TO 8 FOR C = 1 TO 8 BOARD(R, C) = R * 10 + C NEXT C NEXT R
LBOUND / UBOUND => Query Array Bounds
LBOUND returns the lowest valid index and UBOUND returns the highest. They accept an optional second argument for the dimension (default: 1). This lets you write loops that work correctly regardless of how the array was declared.
| Function | Syntax | Description |
|---|---|---|
| LBOUND | LBOUND(array [, dim]) | Returns the lower bound of the given dimension (default dim=1) |
| UBOUND | UBOUND(array [, dim]) | Returns the upper bound of the given dimension (default dim=1) |
DIM PRICES(5 TO 15) PRINT LBOUND(PRICES) ' 5 PRINT UBOUND(PRICES) ' 15 FOR I = LBOUND(PRICES) TO UBOUND(PRICES) PRICES(I) = I * 2.5 NEXT I
' Multi-dimensional, pass dimension number as second arg DIM MAP(1 TO 10, 3 TO 7) PRINT LBOUND(MAP, 1) ' 1 PRINT UBOUND(MAP, 1) ' 10 PRINT LBOUND(MAP, 2) ' 3 PRINT UBOUND(MAP, 2) ' 7
LBOUND/UBOUND in your loops instead of hard-coded numbers. If you ever change the array size, your loops automatically adapt.REDIM => Resize an Array
REDIM changes the size of an array. Plain REDIM gives you a fresh array at the new size, so all elements are reset to 0 (or "" for strings). It also works on an array that was never DIM'd, and the bounds can be expressions.
DIM GRID(9) ' 10 elements ' ... later, need more room ... REDIM GRID(99) ' now 100 elements, all reset to 0 ' Size from a variable, and 2D works too N = 50 REDIM MAP(N, N)
REDIM clears the contents. REDIM PRESERVE (keeping the old values) is not supported yet, it raises a clear error for now. Inside a SUB, REDIM targets the local array, like DIM.ERASE => Free Arrays From Memory
ERASE removes one or more arrays from memory and frees the space they used. After erasing, the array no longer exists, using it again without a fresh DIM raises a "not an array" error. Use it to reclaim memory from large temporary arrays, or to rebuild an array at a different size.
' ERASE array [, array...] DIM BUFFER(9999) ' ... fill and use the buffer ... ERASE BUFFER ' frees the memory it held ' Re-DIM at a new size afterwards DIM BUFFER(99) ' fresh, smaller array
' Erase several arrays in one statement DIM X(99), Y(99), SCORES(50) ERASE X, Y, SCORES
ERASE takes bare array names, no parentheses or indices. Inside a SUB it erases the local array, leaving any global array of the same name untouched. After erasing you must DIM the array again before reading or writing its elements.Dynamic Expression Evaluation => EVAL
EVAL(string) takes a string and evaluates it as a BASIC expression at runtime, returning the result. This lets you build and execute expressions dynamically, useful for formula interpreters, calculators, scripted logic, and data-driven programs.
Basic Usage
EXPR$ = "3 + 4 * 2" PRINT EVAL(EXPR$) ' 11 X = 10 PRINT EVAL("X * X + 1") ' 101, can reference current variables
Example => Simple Formula Calculator
INPUT "Enter formula: "; F$ RESULT = EVAL(F$) PRINT "= "; RESULT
Example => Building Expressions from Data
FOR N = 1 TO 5 E$ = STR$(N) + " * " + STR$(N) + " + 1" PRINT N; "^2 + 1 = "; EVAL(E$) NEXT N
EVAL("SCORE * MULTIPLIER") work as expected.Custom Structures => TYPE / END TYPE
TYPE lets you group multiple related fields into a single named structure, similar to a record or struct. Instead of keeping separate arrays for each property, you store everything together under one variable.
Defining a TYPE
Declare the structure before the program starts. Fields use AS with a type: STRING * N (fixed-length string), INTEGER, SINGLE, or DOUBLE.
TYPE Player name AS STRING * 20 ' fixed 20-char string score AS INTEGER health AS SINGLE END TYPE
Creating Variables and Arrays of a TYPE
Use DIM with AS TypeName to create a single variable or an array of that type.
DIM p AS Player ' single record DIM team(4) AS Player ' array of 5 players (0..4)
Accessing Fields => Dot Notation
Access fields with a dot: variable.field
p.name = "Hero" p.score = 100 p.health = 99.5 PRINT p.name; ", score: "; p.score ' Hero, score: 100
Using an Array of Structures
Loop through records just like a normal array, use dot notation on each element.
TYPE Employee nm AS STRING * 40 role AS STRING * 40 salary AS SINGLE END TYPE DIM emp(4) AS Employee emp(0).nm = "Anna Kowalski" emp(0).role = "Developer" emp(0).salary = 8500 FOR i = 0 TO 4 PRINT RTRIM$(emp(i).nm); ", "; emp(i).salary NEXT i
RTRIM$() when displaying or comparing them to strip the trailing spaces.Object-Oriented Programming => OBJECT
OBJECT can encapsulate both fields (variables) and methods (subroutines or functions with executable code).
Methods can have their executable code written in two ways: directly inside the object block, or forward-declared and implemented outside.
1. Inline Methods (Code inside the Object)
You can write the complete executable code of a method directly inside the OBJECT block. Fields belonging to the object are implicitly available to these methods.
OBJECT IntStack DIM buf(16) AS INTEGER DIM sp AS INTEGER SUB Push(v) sp = sp + 1 buf(sp) = v END SUB FUNCTION Pop() AS INTEGER Pop = buf(sp) sp = sp - 1 END FUNCTION END OBJECT
2. Declared Methods (Code outside the Object)
Alternatively, you can keep the object definition clean by forward-declaring headers with DECLARE, and providing the executable code outside the block using dot notation (ClassName.MethodName).
OBJECT Vec DIM x AS SINGLE DIM y AS SINGLE DECLARE FUNCTION Dot(o) AS SINGLE END OBJECT FUNCTION Vec.Dot(o) Dot = x * o.x + y * o.y END FUNCTION
The THIS Keyword
While object fields are bound implicitly, you can use the special THIS keyword inside any method to explicitly reference the current object instance. This is required when a method needs to call another method on itself or pass its context along.
OBJECT Mob DIM hp AS INTEGER SUB Hurt(d) hp = hp - d IF hp < 0 THEN hp = 0 END SUB SUB HurtTwice(d) THIS.Hurt(d) ' Calls its own Hurt method twice THIS.Hurt(d) END SUB END OBJECT
DIM pos AS Vec inside a player class), the runtime engine recursively instantiates the sub-object safely in memory.Usage Example => Arrays of Objects
Objects can be instantiated into regular variables or stored inside standard arrays initialized via DIM.
DIM party(3) AS Mob FOR i = 1 TO 3 party(i).hp = i * 10 NEXT i party(2).HurtTwice(4) PRINT "Mob 2 HP: "; party(2).hp
Subroutines => SUB / CALL
Subroutines are code blocks with parameters. They can be called repeatedly using CALL.
DECLARE SUB GREET(NAME$) DECLARE SUB SQUARE(N) CALL GREET("Anna") CALL SQUARE(5) CALL SQUARE(12) END SUB GREET(NAME$) PRINT "Hello, "; NAME$; "!" END SUB SUB SQUARE(N) PRINT N; " squared = "; N ^ 2 END SUB
SUB ... END SUB definition can be placed at the end of the file.Variable Scope => GLOBAL / LOCAL / STATIC / SHARED
Inside a SUB or FUNCTION a variable can behave in several different ways. Choosing the right one prevents procedures from accidentally overwriting each other's data.
| Kind | How to declare | Lifetime & visibility |
|---|---|---|
| Global | just use the name | Shared by the whole program. A plain variable used in a SUB refers to the global of that name. |
| Local | LOCAL X | Private to the current call. Created fresh on entry, discarded on exit. Never touches a global of the same name. |
| Static | STATIC X | Private like LOCAL, but its value survives between calls. The procedure "remembers" it. |
| Shared | SHARED X / DIM SHARED X | Explicitly ties a name to the global of the same name, so a procedure reads and writes the shared copy. |
Global by Default
If you do not declare a variable, it is global. The parameters of a SUB/FUNCTION are always local, but any other plain name reaches out to the shared global space.
SCORE = 0 CALL ADD_POINT CALL ADD_POINT PRINT SCORE ' prints 2, the global was updated END SUB ADD_POINT() SCORE = SCORE + 1 ' no LOCAL → this is the global SCORE END SUB
LOCAL => Private and Fresh Each Call
Use LOCAL for a scratch variable that should not leak out of the procedure. It starts empty (0 or "") every time the procedure runs, even if a global of the same name exists.
N = 100 CALL DEMO PRINT N ' still 100, the global was untouched END SUB DEMO() LOCAL N = 5 ' a separate, private N N = N + 1 PRINT "inside: "; N ' inside: 6 END SUB
STATIC => Remembers Between Calls
A STATIC variable is local, but it keeps its value from one call to the next. The optional = value initializer runs only on the first call, perfect for counters and state.
CALL COUNTER CALL COUNTER CALL COUNTER ' prints 1, then 2, then 3 END SUB COUNTER() STATIC TIMES = 0 ' = 0 runs once, on the first call TIMES = TIMES + 1 PRINT TIMES END SUB
Local Arrays
To make an array local to a procedure, declare it with LOCAL DIM. A static array uses STATIC DIM and is built only on the first call.
SUB SCRATCH() LOCAL DIM BUFFER(10) ' private array, fresh each call BUFFER(0) = 42 END SUB
SHARED => Reaching the Global on Purpose
There are two ways to make a global explicitly available to procedures. Use DIM SHARED at the top level to declare a variable that every SUB and FUNCTION sees automatically:
DIM SHARED LIVES LIVES = 3 CALL HURT PRINT LIVES ' prints 2, the shared global changed END SUB HURT() LIVES = LIVES - 1 ' same LIVES as the main program END SUB
Or use SHARED inside a single procedure to tie one name to the global, just for that procedure:
TOTAL = 0 CALL ADD_TEN PRINT TOTAL ' prints 10 END SUB ADD_TEN() SHARED TOTAL ' TOTAL here means the global TOTAL TOTAL = TOTAL + 10 END SUB
DIM SHARED X at the top level shares X with every procedure at once. SHARED X written inside a single SUB shares it only within that SUB. Both make the procedure use the one global copy instead of a private one.LOCAL/STATIC of the same name are different variables. If a SUB declares LOCAL SCORE, changing it will not affect the global SCORE outside the SUB.Multi-line Functions => FUNCTION
FUNCTION block allows for complex, multi-line operations. You return a value from a function by assigning it directly to the function's name.
PRINT statement or variable assignment), not with the CALL keyword.PRINT "Result: "; ADD_AND_MULTIPLY(5, 10) FINAL_SCORE = ADD_AND_MULTIPLY(3, 4) PRINT "Final Score: "; FINAL_SCORE END FUNCTION ADD_AND_MULTIPLY(A, B) SUM = A + B ' Return value by assigning to the function name ADD_AND_MULTIPLY = SUM * 2 END FUNCTION
Example: Factorial (Recursion)
PRINT "Factorial of 5 is: "; FACTORIAL(5) END FUNCTION FACTORIAL(N) IF N <= 1 THEN FACTORIAL = 1 ELSE FACTORIAL = N * FACTORIAL(N - 1) END IF END FUNCTION
DATA and READ => Embedded Data
Allows you to embed data directly in the program code and read it sequentially.
DATA 10, 20, 30, 40, 50 DATA "Ala", "Bob", "Celina" READ A, B, C ' A=10, B=20, C=30 PRINT A, B, C FOR I = 1 TO 3 READ NAME$ PRINT "Name: "; NAME$ NEXT I RESTORE ' reset pointer to start of DATA READ FIRST ' 10 again
Math Functions
| Function | Description | Example | Result |
|---|---|---|---|
| SIN(x) | Sine (radians) | SIN(PI/2) | 1 |
| COS(x) | Cosine (radians) | COS(0) | 1 |
| TAN(x) | Tangent | TAN(PI/4) | 1 |
| ATN(x) | Arctangent | ATN(1)*4 | PI |
| SQR(x) | Square root | SQR(16) | 4 |
| ABS(x) | Absolute value | ABS(-7) | 7 |
| INT(x) | Round down (floor) | INT(3.9) | 3 |
| FIX(x) | Truncate fraction | FIX(-3.7) | -3 |
| CINT(x) | Round to nearest integer | CINT(3.6) | 4 |
| SGN(x) | Sign of number (-1, 0, 1) | SGN(-5) | -1 |
| LOG(x) | Natural logarithm | LOG(EXP(1)) | 1 |
| EXP(x) | e raised to the power x | EXP(1) | 2.718... |
| RND() | Random number 0.0, 1.0 | INT(RND()*6)+1 | 1-6 |
| CLAMP(v,lo,hi) | Constrains v to [lo, hi] | CLAMP(120, 0, 100) | 100 |
| LERP(a,b,t) | Linear blend: a+(b-a)*t | LERP(0, 100, 0.25) | 25 |
| PI | Constant π (3.14159...) | 2*PI*R | circumference |
' Random dice roll ROLL = INT(RND() * 6) + 1 PRINT "Rolled:"; ROLL
String Operations
| Function | Description | Example | Result |
|---|---|---|---|
| LEN(s$) | String length | LEN("Hello") | 5 |
| LEFT$(s$,n) | First n characters | LEFT$("BASIC",3) | "BAS" |
| RIGHT$(s$,n) | Last n characters | RIGHT$("BASIC",3) | "SIC" |
| MID$(s$,p,n) | Substring from position p, n characters | MID$("BASIC",2,3) | "ASI" |
| INSTR(s$,s2$) | Position of s2$ in s$ (0 = not found) | INSTR("Hello","ll") | 3 |
| UCASE$(s$) | Convert to uppercase | UCASE$("abc") | "ABC" |
| LCASE$(s$) | Convert to lowercase | LCASE$("ABC") | "abc" |
| LTRIM$(s$) | Remove leading spaces | LTRIM$(" ok") | "ok" |
| RTRIM$(s$) | Remove trailing spaces | RTRIM$("ok ") | "ok" |
| STR$(n) | Number to string | STR$(42) | " 42" |
| VAL(s$) | String to number | VAL("3.14") | 3.14 |
| CHR$(n) | Character from ASCII code | CHR$(65) | "A" |
| ASC(s$) | ASCII code of first character | ASC("A") | 65 |
| STRING$(n,s$) | Repeat character n times | STRING$(5,"*") | "*****" |
| SPACE$(n) | n spaces | SPACE$(3) | " " |
| HEX$(n) | Hexadecimal representation | HEX$(255) | "FF" |
| OCT$(n) | Octal representation | OCT$(8) | "10" |
8-bit Music and Sound
basicFusion has built-in simple sound generators, ideal for creating retro effects and 8-bit melodies (chiptune).
PLAY => Playing Notes
' PLAY "note", duration_ms, [wave_type], [volume] PLAY "C4", 200 PLAY "E4", 200, 1, 0.5
Notes: A, B, C, D, E, F, G (plus sharps # and flats b, e.g. Bb4). The digit sets the octave (default 4). This command does not pause code execution!
Wave Types (Sound Shape)
| Value | Type | Sound character |
|---|---|---|
| 0 | Sine | Smooth, flute or clean whistle |
| 1 | Square | Classic 8-bit "Nintendo" sound, default, best for melodies |
| 2 | Sawtooth | Sharp, buzzing, strings or techno bass |
| 3 | Triangle | Soft, sub-bass, NES style |
SOUND => Raw Frequency
' SOUND frequency_Hz, duration_ms, [wave_type], [volume] SOUND 440, 500 ' 440 Hz (note A) for 0.5 seconds SOUND 120, 1000, 2 ' Low buzz (Sawtooth) for 1 second
NOISE => Noise (Drums / Explosions)
' NOISE duration_ms, [volume] NOISE 50, 0.4 ' Short click, hi-hat drum NOISE 300, 0.8 ' Longer, louder noise, explosion
SLEEP => Blocking Pause
SLEEP ms pauses program execution for the given number of milliseconds. Use it to time notes when playing melodies.
DELAY => Non-blocking Audio Delay
DELAY ms advances the internal audio scheduler without pausing the program. Sounds play in the background, useful alongside animations.
PLAY "C4", 200 SLEEP 220 ' wait 220 ms (200 for note + 20 ms gap) PLAY "D4", 200 SLEEP 220 PLAY "E4", 400
Graphics Mode => SCREEN
The SCREEN command sets the screen mode. SCREEN 0 returns to text mode (the 80×25 terminal); the other modes open the graphics canvas at a fixed resolution.
| Mode | Resolution | Use case |
|---|---|---|
| SCREEN 0 | text | Text mode, back to the 80×25 terminal |
| SCREEN 1 | 320 × 200 | Classic CGA, retro effects, pixel art |
| SCREEN 2 | 640 × 200 | Horizontal text graphics |
| SCREEN 7 | 320 × 200 | EGA compatible |
| SCREEN 9 | 640 × 350 | EGA, more room |
| SCREEN 12 | 640 × 480 | VGA, full resolution, recommended |
SCREEN 12 ' 640x480, black background PAINT "#001133" ' set background color CIRCLE (320,240), 100, "white" END
PAINT "color" or BOX (0,0)-(640,480), "color". The CLS command clears both the terminal and the canvas.GRAPHICS => Custom Resolution
Instead of the predefined SCREEN modes, you can use the GRAPHICS command to create a canvas of any exact size.
' GRAPHICS width, height GRAPHICS 800, 600 ' Custom 800x600 resolution PAINT "#112233" END
Drawing Shapes
PSET => Pixel
PSET (100,150), "red"
POINT => Get Pixel Color
Pobiera kolor punktu z podanej pozycji płótna (canvas). Funkcja jest inteligentna i zwraca wynik w formacie zależnym od zmiennej, do której przypisujesz wartość:
- Zmienna z $ (String): Zwraca kolor jako tekstowy kod Hex (np.
#FF0000). - Zmienna bez $ (Liczba): Zwraca kolor jako liczbę całkowitą (Integer) w formacie RGB.
' Pobranie jako tekst (Hex) C$ = POINT(100, 150) PRINT "Kolor HEX: "; C$ ' Pobranie jako liczba (Integer) C = POINT(100, 150) PRINT "Wartosc liczbowa: "; C
LINE => Straight Line
LINE (10,10)-(300,200), "cyan"
LINE, BOX, RECT, GRADIENT) also accept all coordinates in one parenthesis, e.g. LINE (10, 10, 300, 200, "cyan"). Both styles are equivalent.BOX / BOXFRAME => Rectangle
BOX (50,50)-(200,150), "blue" ' filled BOXFRAME (50,50)-(200,150), "white" ' outline only
CIRCLE => Circle
CIRCLE (320,240), 80, "yellow" ' center (320,240), radius 80
ELLIPSE => Ellipse
ELLIPSE (320,240), 120, 60, "lime", 0 ' outline ELLIPSE (320,240), 120, 60, "lime", 1 ' filled
ARC => Arc
' ARC (cx,cy), radius, start_angle, end_angle, color ' Angles in radians: 0=right, PI/2=down, PI=left ARC (200,200), 80, 0, 3.14, "orange"
TRI => Filled Triangle
TRI (160,50)-(50,200)-(270,200), "red"
TRI also accepts all six coordinates in one parenthesis: TRI (160, 50, 50, 200, 270, 200, "red").LINEWIDTH => Line Thickness
LINEWIDTH 4 CIRCLE (200,200), 50, "white" LINEWIDTH 1 ' restore default
Transforms => TRANSLATE / ROTATE / SCALE
These change the coordinate system for everything drawn afterwards, the whole canvas. Great for rotozoomers, spinning sprites, screen shake, and zoom effects. TRANSLATE moves the origin, ROTATE turns it (radians, so PI = 180°), SCALE zooms it (one value = both axes; negatives flip). Wrap a shape in PUSHMATRIX / POPMATRIX to isolate its transform, and use RESETTRANSFORM to go back to normal. Transforms stay active until you reset them (a good habit is to reset or push/pop every frame). Note: they affect vector/image/sprite drawing, but not direct POKEBUF pixel writes.
' spin a square around its own centre PUSHMATRIX TRANSLATE 160, 100 ' move origin to centre ROTATE T ' T grows each frame (radians) SCALE 2 ' twice as big BOX (-20, -20, 20, 20, "cyan") ' centred on the origin POPMATRIX ' back to normal for the HUD, etc.
Glow & Fades => BLENDMODE / ALPHA / FADE
Three tiny commands unlock a lot of demoscene/intro flair. BLENDMODE chooses how new pixels combine with the screen: "add" brightens where shapes overlap (that classic neon glow), "screen" is a softer glow, "multiply" darkens, and "normal" restores. ALPHA a sets a global opacity (0-1) for everything you draw next. FADE amount lays a translucent black rectangle over the whole screen, do it a little every frame and moving shapes leave glowing trails (feedback); do FADE 1 to clear. All three stick until you change them (they reset when a new program starts).
' additive glow: two circles brighten where they overlap BLENDMODE "add" CIRCLE (140,100), 60, RGB(255,0,0) CIRCLE (190,100), 60, RGB(0,0,255) ' overlap -> bright magenta/white BLENDMODE "normal" ' motion trails: fade the screen a bit each frame instead of CLS DO FADE 0.08 ' darken by 8% (leaves trails) PSET (160 + 100*SIN(t), 100 + 60*COS(t*1.7)), "cyan" t = t + 0.05 SYNC LOOP
POKEBUF pixel writes. Combine BLENDMODE "add" with many translucent shapes for cheap bloom.Colors, Fills, and Gradients
Colors can be specified as CSS color names or hex values. The full set of CSS colors is supported.
Color Formats => String, Hex, CSS Functions
Wherever a color parameter is accepted (LINE, CIRCLE, BOX, TEXT, COLOR, PAINT, etc.) you can use any of these formats interchangeably:
' Named CSS color CIRCLE (100,100), 40, "red" ' Hex color CIRCLE (200,100), 40, "#ff6600" ' HSL, great for cycling through rainbow colors FOR I = 0 TO 359 HUE$ = "hsl(" + STR$(I) + ",100%,60%)" PSET (I,240), HUE$ NEXT I ' RGBA with transparency BOX (50,50)-(200,150), "rgba(255,0,0,0.5)"
Integer Colors => QBasic Palette (0-15)
For compatibility with classic QBasic code, you can pass a color as an integer from 0 to 15. Each number maps to a fixed color from the original CGA/EGA palette. Any graphics command that accepts a color string also accepts these integers.
| Value | Color | Value | Color |
|---|---|---|---|
| 0 | black | 8 | gray |
| 1 | blue | 9 | lightblue |
| 2 | green | 10 | lightgreen |
| 3 | cyan | 11 | lightcyan |
| 4 | red | 12 | pink (light red) |
| 5 | magenta | 13 | lightmagenta |
| 6 | brown | 14 | yellow |
| 7 | lightgray | 15 | white |
These constants are also available by name: BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN, LIGHTGRAY, GRAY, LIGHTBLUE, LIGHTGREEN, LIGHTCYAN, YELLOW, WHITE.
' Integer colors work anywhere a color string is expected LINE (0,0)-(640,480), 4 ' red (4) CIRCLE (320,240), 100, 14 ' yellow (14) ' Using named constants, same result LINE (0,0)-(640,480), RED CIRCLE (320,240), 100, YELLOW ' Cycling through palette colors (0..15) FOR I = 0 TO 15 BOX (I*40,50)-(I*40+38,100), I NEXT I
PAINT => Background
PAINT "#0a0a1a" ' fill entire canvas with color
FILL => Flood Fill
SCREEN 12 CIRCLE (200,200), 80, "white" FILL (200,200), "cyan"
GRADIENT => Linear Gradient
GRADIENT (0,0)-(640,480), "#001133", "#330011" GRADIENT (100,50)-(100,200), "red", "blue" ' vertical gradient
COLOR Command
The COLOR command sets the default foreground color for subsequent PRINT output in text mode, and optionally the background color of the terminal.
Syntax
' COLOR foreground [, background] COLOR "lime" ' set text color only COLOR "yellow", "#1a1a2e" ' set text + background color COLOR "white" ' reset to white
After calling COLOR, all following PRINT statements use that color until COLOR is called again. Any CSS color name or hex value is accepted.
Example => Colored Terminal Output
COLOR "cyan" PRINT "=== MENU ===" COLOR "white" PRINT "1. New Game" PRINT "2. Load Game" COLOR "red" PRINT "3. Quit" COLOR "white" ' always reset at the end INPUT "Your choice: "; CH
Example => Error and Success Messages
INPUT "Enter PIN: "; PIN$ IF PIN$ = "1234" THEN COLOR "lime" PRINT "Access granted!" ELSE COLOR "red" PRINT "Wrong PIN!" END IF COLOR "white"
COLOR "white" (or your preferred default) after colored output so subsequent PRINT statements aren't accidentally colored.COLOR affects only PRINT in text/terminal mode. To draw colored text on the graphics canvas use TEXT (x,y), "...", "color" instead.Text on the Graphics Canvas
TEXT => Drawing Text
' TEXT (x,y), "text", "color" SCREEN 12 TEXT (100,100), "Hello World!", "white" TEXT (100,130), "Score: " + STR$(SCORE), "yellow"
FONTSIZE => Font Size
FONTSIZE 36 TEXT (160,200), "GAME OVER", "red" FONTSIZE 16 TEXT (220,250), "Press space", "gray"
Font Handling (SETFONT)
By default, the TEXT command draws text using the Courier New font.
However, you can change this to any font installed on your operating system using the
SETFONT command (or its alias LOADFONT).
You control the text size separately using FONTSIZE. Both commands retain their
settings until you change them again or restart the program.
Example usage:
SCREEN 12 CLS ' Setting a large Impact font SETFONT "Impact" FONTSIZE 40 TEXT (50, 60), "HEADER IN IMPACT" ' Changing the font to Comic Sans SETFONT "Comic Sans MS" FONTSIZE 20 TEXT (50, 120), "And this is standard text in Comic Sans :)", 14 ' You can also use standard sans-serif fonts SETFONT "Arial" FONTSIZE 16 TEXT (50, 160), "Text written in Arial font.", 15
Custom Bitmap Fonts
TEXT together with SETFONT draws using whatever fonts are installed on the operating system, great for clean labels, but it never looks truly retro. When you want full control over every pixel (chunky demoscene headers, a game logo, a console font that looks identical on every machine) you can design your own bitmap font and draw it with PRINTFONT.
A custom font is just a grid of pixels per character. You declare the cell size once, paint each character row by row using "1" (solid) and "0" (empty), pick a color, then print any string with it.
DEFFONT => Create a Font
Reserves a custom font in memory under an id, with every character being w pixels wide and h pixels tall. An optional 4th argument names the font: DEFFONT id, w, h, "name" (the Font Editor fills this in).
' DEFFONT id, width, height [, "name"] DEFFONT 0, 5, 5
FONTDATA => Paint a Character
Fills in the pixels for a single character. charCode is the ASCII code of the character, use ASC("A") for readability, or pass the number directly. After it come h row strings, each exactly w characters long, where 1 is a solid pixel and 0 is empty.
' FONTDATA id, charCode, "row0", "row1", ... (h rows of w chars) ' Letter H FONTDATA 0, ASC("H"), "10001", "10001", "11111", "10001", "10001" ' Letter I FONTDATA 0, ASC("I"), "11111", "00100", "00100", "00100", "11111" ' Exclamation mark FONTDATA 0, ASC("!"), "00100", "00100", "00100", "00000", "00100"
FONTCOLOR => Set the Color
Sets the color used by every following PRINTFONT call. Accepts CSS color names, hex strings, or the 0-15 QBasic color numbers, exactly like the other drawing commands.
FONTCOLOR "lime" FONTCOLOR "#FF004D" FONTCOLOR 14 ' yellow
PRINTFONT => Draw the Text
Draws a string at (X, Y) using the bitmap font with the given id. Two optional parameters let you blow it up and space it out: scale multiplies every pixel (default 1), and spacing adds extra empty pixels between characters.
' PRINTFONT id, X, Y, "text" [, scale [, spacing]] PRINTFONT 0, 40, 40, "HI!" PRINTFONT 0, 40, 100, "HI!", 6, 2 ' 6x size, +2px spacing
Full Example
Defines a tiny 5×5 font, then prints the same word small and huge:
SCREEN 12 CLS ' --- Design a 5x5 bitmap font (id 0) --- DEFFONT 0, 5, 5 FONTDATA 0, ASC("H"), "10001", "10001", "11111", "10001", "10001" FONTDATA 0, ASC("I"), "11111", "00100", "00100", "00100", "11111" FONTDATA 0, ASC("!"), "00100", "00100", "00100", "00000", "00100" ' --- Draw it --- FONTCOLOR "lime" PRINTFONT 0, 40, 40, "HI!" FONTCOLOR "#FF004D" PRINTFONT 0, 40, 100, "HI!", 8, 2
FONTDATA must match the font's width, and you need exactly as many rows as the height you declared in DEFFONT. A 5×5 font means five strings of five characters each. Only the codes you define get drawn, any undefined character in a PRINTFONT string simply renders blank.DEFFONT 0 for the title font, DEFFONT 1 for the HUD) and switch between them just by changing the id you pass to PRINTFONT. Design them quickly in the IDE's sprite/font tooling instead of typing rows by hand.Direct Pixel Buffer (Demoscene Speed)
For extreme performance (e.g., when writing custom 3D engines, demoscene effects like plasma or fire), bypass standard drawing commands. Use FASTGRAPHICS and manipulate screen memory directly with POKEBUF and MEMSET.
To extract maximum speed, pass colors to the buffer as packed integers using the RGB(r, g, b) function.
' Hypnotic demoscene circles FASTGRAPHICS : GRAPHICS 320, 240 t = 0 DO FOR y = 0 TO 239 FOR x = 0 TO 319 ' Magic math for the pattern c = CINT((x * x + y * y) / 128 + t) MOD 256 ' POKEBUF writes directly to memory POKEBUF x, y, RGB(c, 50, 255 - c) NEXT x NEXT y SYNC t = t + 5 LOOP
POKEBUF x, y, color, Writes a pixel directly to RAM. Use only inFASTGRAPHICSmode.PEEKBUF(x, y), Returns the packed color value of a given pixel.MEMSET color, Fills the entire screen with one color in a fraction of a second (e.g.,MEMSET RGB(0,0,0)for a super-fast clear screen).
Animation and SYNC
SYNC pauses execution until the next browser render frame (requestAnimationFrame), guaranteeing smooth animations at ~60 FPS.
FASTGRAPHICS => Double Buffering
The FASTGRAPHICS command turns on double-buffering. All drawing commands are performed in background memory rather than directly on the screen, which completely eliminates flickering during complex frame rendering.
FASTGRAPHICS is enabled, the screen will NOT update automatically. You MUST use the SYNC command to swap the buffer to the screen. If you forget SYNC, your canvas will remain completely blank!FASTGRAPHICS can also be used in text mode for smooth screen double buffering. To swap the buffer to the screen, you can use either the SYNC or sleep command, although SYNC is the better solution.GRAPHICS 640, 480 FASTGRAPHICS ' Enable drawing in the background LOOP: CLS CIRCLE (MOUSEX, MOUSEY), 20, "lime" SYNC ' REQUIRED to actually display the frame! GOTO LOOP
Basic Game Loop
SCREEN 12 X = 0 LOOP: CLS CIRCLE (X,240), 20, "yellow" X = X + 3 IF X > 640 THEN X = 0 SYNC GOTO LOOP
Bouncing Ball
SCREEN 12 PX = 320 : PY = 240 VX = 4 : VY = 3 BOUNCE: PAINT "black" PX = PX + VX PY = PY + VY IF PX >= 620 OR PX <= 20 THEN VX = -VX IF PY >= 460 OR PY <= 20 THEN VY = -VY CIRCLE (PX,PY), 20, "orange" SYNC GOTO BOUNCE
Keyboard Input => KEY()
KEY("name") returns 1 when a key is currently held down, or 0 if not. Works in real time inside the animation loop.
| Key | Name in KEY() |
|---|---|
| Left arrow | KEY("ARROWLEFT") |
| Right arrow | KEY("ARROWRIGHT") |
| Arrow up | KEY("ARROWUP") |
| Arrow down | KEY("ARROWDOWN") |
| Space | KEY("SPACE") or KEY(" ") |
| Enter | KEY("ENTER") |
| Letters A-Z | KEY("A"), KEY("W"), KEY("S"), KEY("D") |
| Digits 0-9 | KEY("0") ... KEY("9") |
| Escape | KEY("ESCAPE") |
Example: Player Movement
SCREEN 12 PX = 320 : PY = 240 GAMELOOP: PAINT "#0a0a1a" IF KEY("ARROWLEFT") THEN PX = PX - 5 IF KEY("ARROWRIGHT") THEN PX = PX + 5 IF KEY("ARROWUP") THEN PY = PY - 5 IF KEY("ARROWDOWN") THEN PY = PY + 5 IF PX < 15 THEN PX = 15 IF PX > 625 THEN PX = 625 IF PY < 15 THEN PY = 15 IF PY > 465 THEN PY = 465 BOX (PX-15,PY-15)-(PX+15,PY+15), "lime" SYNC GOTO GAMELOOP
Keyboard Input => INKEY$
INKEY$ reads a single character from the keyboard buffer and returns it as a string. If no key has been typed, it returns "" (empty string). Unlike INPUT, it does not pause the program, the loop keeps running while waiting.
KEY() vs INKEY$
| Feature | KEY("name") | INKEY$ |
|---|---|---|
| Blocking? | No | No |
| Detects held keys? | Yes => returns 1 while held | No => fires once per press |
| Returns value? | 0 or 1 | Character string or "" |
| Best for | Game movement, held-down actions | Menus, text input, single-press confirm |
Basic Usage => Reading a Single Keypress
WaitKey: k$ = INKEY$ IF k$ = "" THEN GOTO WaitKey ' loop until a key is pressed PRINT "You pressed: "; k$
Example: Yes / No Confirmation
A classic pattern in text-mode programs, wait for a specific key without blocking.
PRINT "Delete record? [Y / N]" ConfirmLoop: c$ = UCASE$(INKEY$) IF c$ = "" THEN GOTO ConfirmLoop IF c$ = "N" THEN GOTO Cancelled IF c$ <> "Y" THEN GOTO ConfirmLoop ' ignore other keys PRINT "Deleted!" : GOTO Done Cancelled: PRINT "Cancelled." Done: END
Example: Build a String Character by Character
typed$ = "" PRINT "Type something (Enter to finish):" TypeLoop: ch$ = INKEY$ IF ch$ = "" THEN GOTO TypeLoop IF ASC(ch$) = 13 THEN GOTO Done ' Enter = ASCII 13 typed$ = typed$ + ch$ PRINT ch$; ' echo character GOTO TypeLoop Done: PRINT PRINT "You typed: "; typed$
SLEEP 80FlushLoop: IF INKEY$ <> "" THEN GOTO FlushLoopUCASE$() to handle both Y and y with one check.Mouse Input => MOUSEX, MOUSEY, MOUSECLICK, HIDEMOUSE, SHOWMOUSE
basicFusion allows you to read the mouse position and button clicks on the graphics canvas in real time.
| Function | Description |
|---|---|
| MOUSEX | Returns the current X coordinate of the mouse pointer relative to the canvas. |
| MOUSEY | Returns the current Y coordinate of the mouse pointer relative to the canvas. |
| MOUSECLICK | Returns 1 if the left button is clicked, 2 for the right button, and 0 if no button is pressed. |
| HIDEMOUSE | Hides the mouse cursor. |
| SHOWMOUSE [sprite_id_num] | Displays the default mouse cursor, or a custom one if a sprite ID is provided. |
Example: Simple Drawing App
SCREEN 12 PAINT "#111111" FONTSIZE 18 DRAW_LOOP: X = MOUSEX Y = MOUSEY BTN = MOUSECLICK IF BTN = 1 THEN CIRCLE (X,Y), 5, "lime" ELSE IF BTN = 2 THEN CIRCLE (X,Y), 5, "red" END IF ' UI Overlay BOX (0,0)-(200,30), "black" TEXT (10,20), "X:" + STR$(X) + " Y:" + STR$(Y), "white" SYNC GOTO DRAW_LOOP
Sprites
basicFusion supports pixel-art sprites defined directly in code using color grids. Sprites can have multiple animation frames and be drawn at any position, scale, angle, or stretched to a target size.
DEFSPRITE => Define a Sprite
' DEFSPRITE id, width, height [, "name"] DEFSPRITE 1, 4, 4 ' sprite ID 1, 4x4 pixels (optional name as 4th arg)
SPRITEDATA => Set a Pixel Row
Defines one row of pixels for a sprite frame. Colors are comma-separated CSS color strings. An empty string means transparent.
' SPRITEDATA id, frame, row, "col,col,col,..." DEFSPRITE 1, 4, 4 SPRITEDATA 1, 0, 0, ",red,red," SPRITEDATA 1, 0, 1, "red,red,red,red" SPRITEDATA 1, 0, 2, "red,red,red,red" SPRITEDATA 1, 0, 3, ",red,red,"
SPRITEDATA is the recommended form. The syntax is identical.",red,," draws only the second pixel red.DRAWSPRITE => Draw a Sprite
' DRAWSPRITE id, x, y [, frame [, scale [, angle_deg]]] DRAWSPRITE 1, 100, 100 ' frame 0, scale 1 DRAWSPRITE 1, 200, 100, 0, 8 ' scale x8 DRAWSPRITE 1, 300, 100, 0, 4, 45 ' rotated 45°
DRAWSPRITESTRETCHED => Draw Stretched to Size
' DRAWSPRITESTRETCHED id, x, y, dest_width, dest_height [, frame] DRAWSPRITESTRETCHED 1, 50, 50, 64, 64 DRAWSPRITESTRETCHED 1, 200, 50, 128, 32
DRAWSPRITETRI => Texture-mapped Triangle
' DRAWSPRITETRI id, x1,y1,u1,v1, x2,y2,u2,v2, x3,y3,u3,v3 [, frame] DRAWSPRITETRI 1, 160,50, 0,0, 320,50, 4,0, 240,200, 2,4
Full Example => Animated Sprite
SCREEN 12 DEFSPRITE 1, 4, 4 ' Frame 0 SPRITEROW 1,0,0, ",lime,lime," SPRITEROW 1,0,1, "lime,lime,lime,lime" SPRITEROW 1,0,2, "lime,,lime,lime" SPRITEROW 1,0,3, ",lime,,lime" ' Frame 1 SPRITEROW 1,1,0, ",cyan,cyan," SPRITEROW 1,1,1, "cyan,cyan,cyan,cyan" SPRITEROW 1,1,2, "cyan,,cyan,cyan" SPRITEROW 1,1,3, ",cyan,,cyan" F = 0 : TICK = 0 LOOP: PAINT "#0a0a1a" TICK = TICK + 1 IF TICK MOD 15 = 0 THEN F = 1 - F DRAWSPRITE 1, 300, 220, F, 10 SYNC GOTO LOOP
Sprite Collision => SPRITECOL()
Tests whether the bounding boxes of two sprites overlap. Returns -1 (True) if they collide, or 0 (False) if they don't.
Syntax
' SPRITECOL(id1, x1, y1, id2, x2, y2) ' id1, id2, sprite IDs (defined with DEFSPRITE) ' x1, y1 , current draw position of sprite 1 ' x2, y2 , current draw position of sprite 2 ' Returns -1 (True) if bounding boxes overlap, 0 otherwise IF SPRITECOL(1, PX, PY, 2, EX, EY) THEN PRINT "Collision!" END IF
Full Example => Player vs Enemy
SCREEN 12 ' Player sprite (ID 1), 4x4 DEFSPRITE 1, 4, 4 SPRITEROW 1,0,0, ",lime,lime," SPRITEROW 1,0,1, "lime,lime,lime,lime" SPRITEROW 1,0,2, "lime,lime,lime,lime" SPRITEROW 1,0,3, ",lime,lime," ' Enemy sprite (ID 2), 4x4 DEFSPRITE 2, 4, 4 SPRITEROW 2,0,0, ",red,red," SPRITEROW 2,0,1, "red,red,red,red" SPRITEROW 2,0,2, "red,red,red,red" SPRITEROW 2,0,3, ",red,red," PX = 100 : PY = 220 ' player position EX = 400 : EY = 220 ' enemy position EVX = -2 ' enemy moves left HIT = 0 GL: PAINT "#0a0a1a" ' Move player IF KEY("ARROWLEFT") THEN PX = PX - 4 IF KEY("ARROWRIGHT") THEN PX = PX + 4 IF KEY("ARROWUP") THEN PY = PY - 4 IF KEY("ARROWDOWN") THEN PY = PY + 4 ' Move enemy EX = EX + EVX IF EX < 0 THEN EX = 640 ' Draw sprites (scale 8) DRAWSPRITE 1, PX, PY, 0, 8 DRAWSPRITE 2, EX, EY, 0, 8 ' Collision check IF SPRITECOL(1, PX, PY, 2, EX, EY) THEN FONTSIZE 28 TEXT (220,50), "HIT!", "red" END IF FONTSIZE 16 TEXT (10,22), "Arrows = move | avoid the red enemy", "gray" SYNC GOTO GL
Tilemaps => DRAWTILEMAP & GETTILE
A tilemap is just a regular 2D array you draw as a grid of tiles. Each cell holds a frame index of a tileset sprite. This is exactly the array the built-in Map Editor produces, so you design a level visually, export it as a DATA_LEVEL_n block, load it with RESTORE/READ, and draw the whole thing with one DRAWTILEMAP. GETTILE reads the tile at a world position, which is all you need for map collision.
DRAWTILEMAP => Draw a Tile Grid
' DRAWTILEMAP map(), tilesetId, tileW [, tileH] ' map() , a 2D array (Y, X); each cell = tileset frame index ' tilesetId , a sprite (DEFSPRITE) whose frames are your tiles ' tileW/tileH, size each tile is drawn at (sprite is scaled to fit) DRAWTILEMAP Map(), 1, 32, 32
GETTILE => Read a Tile (Collision)
' GETTILE(map(), worldX, worldY, tileW [, tileH]) -> frame index, or -1 outside the map IF GETTILE(Map(), PX, PY, 32) > 0 THEN ' a solid tile is here -> block the move END IF
DATA_LEVEL_1: DATA 1, 15, 10, 1 ' levelId, width, height, layer DATA 0, 0, 0, ... ' one DATA line per rowLoad it once with
RESTORE DATA_LEVEL_1 + a READ loop into a DIM array, then draw it with DRAWTILEMAP. You can re-open the block in the editor and re-export any time, your loader and draw code never change.DATA output. Pass it to the commands as Map() or just Map; both hand over the whole array.0, so make frame 0 of your tileset transparent and start real tiles at frame 1. Then test solidity with GETTILE(...) > 0. (For maps you build by hand instead, you can use -1 for empty, DRAWTILEMAP skips it entirely.)Full Example => Load a Level from the Map Editor
The level lives in the DATA_LEVEL_1 block at the bottom, the exact format the Map Editor exports. The loader reads the header (levelId, width, height, layer), fills a DIM array, and the game loop just draws it and uses GETTILE for gravity and the wall. Edit the DATA visually in the editor and the rest still works untouched.
SCREEN 12 : FASTGRAPHICS TS = 32 ' Tileset (sprite 1): frame 0 = empty, 1 = grass, 2 = brick. ' The map editor uses 0 for empty cells, so frame 0 stays transparent. DEFSPRITE 1, 8, 8 FOR R = 0 TO 7 SPRITEROW 1, 0, R, ",,,,,,," SPRITEROW 1, 1, R, "green,limegreen,green,green,green,limegreen,green,green" SPRITEROW 1, 2, R, "firebrick,red,firebrick,red,firebrick,red,firebrick,red" NEXT R ' Player (sprite 2) DEFSPRITE 2, 8, 8 FOR R = 0 TO 7 SPRITEROW 2, 0, R, "yellow,gold,yellow,yellow,yellow,gold,yellow,yellow" NEXT R ' --- Load the level straight from the map editor's DATA block --- RESTORE DATA_LEVEL_1 READ LevelID : READ MapW : READ MapH : READ LayerNo DIM Map(MapH - 1, MapW - 1) FOR Y = 0 TO MapH - 1 FOR X = 0 TO MapW - 1 READ Tile Map(Y, X) = Tile NEXT X NEXT Y PX = 64 : PY = 100 DO PAINT "#101820" DX = 0 IF KEY("ARROWLEFT") THEN DX = -3 IF KEY("ARROWRIGHT") THEN DX = 3 ' move only if the target cell is not solid (0 = empty, -1 = off map) IF GETTILE(Map(), PX + DX + 16, PY + 16, TS) <= 0 THEN PX = PX + DX ' gravity + land on solid tiles PY = PY + 4 IF GETTILE(Map(), PX + 16, PY + TS, TS) > 0 THEN PY = INT((PY + TS) / TS) * TS - TS DRAWTILEMAP Map(), 1, TS, TS DRAWSPRITE 2, PX, PY, 0, 4 SYNC(60) IF KEY("ESCAPE") THEN END LOOP ' ============================================================ ' This block is produced by the Map Editor. You can re-open it ' there, edit visually, and re-export, the loader above stays ' the same. Header = levelId, width, height, layer. ' ============================================================ DATA_LEVEL_1: DATA 1, 15, 10, 1 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 DATA 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0 DATA 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
Camera => CAMERA, CAMERAZOOM & CAMERASHAKE
The camera sets a global world-to-screen offset. After CAMERA, everything you draw, sprites, images, shapes, text, tilemaps, is in world-space, so you never subtract the scroll position by hand again. CAMERA 0, 0 snaps back to screen-space for your HUD and background.
CAMERA => Set the World Offset
' CAMERA [x, y] , shifts the whole world by (-x, -y) CAMERA CamX, 0 ' world scrolls; draw at world coords CAMERA 0, 0 ' reset, screen-space again (HUD, background)
CAMERAZOOM & CAMERASHAKE
' CAMERAZOOM scale , 1 = normal, 2 = 2x zoom (anchored at world origin) CAMERAZOOM 2 CAMERAZOOM 1 ' back to normal ' CAMERASHAKE amount, adds shake "trauma" (0..1) that fades on its own CAMERASHAKE 0.6 ' call once on an explosion / hit
CAMERA 0, 0 → set CAMERA CamX, CamY → draw the world (tilemap, sprites) → CAMERA 0, 0 again → draw the HUD. PAINT and CLS always clear the full screen, so they're safe either way.CAMERASHAKE once on impact, don't call it every frame or it never settles. It works wherever the camera is (even at 0, 0), and the whole screen, HUD included, jitters briefly. The bigger the trauma, the bigger the shake (it grows with the square of the value, so 0.6 is much stronger than 0.3).Full Example => Side-scrolling Camera
The camera follows the player and is clamped to the level edges. The world tiles scroll; the "world X" label is drawn at CAMERA 0, 0 so it stays fixed on screen.
SCREEN 12 : FASTGRAPHICS TS = 32 MAPW = 60 DEFSPRITE 1, 8, 8 FOR R = 0 TO 7 SPRITEROW 1, 0, R, "steelblue,lightblue,steelblue,steelblue,steelblue,lightblue,steelblue,steelblue" NEXT R DEFSPRITE 2, 8, 8 FOR R = 0 TO 7 SPRITEROW 2, 0, R, "orange,gold,orange,orange,orange,gold,orange,orange" NEXT R DIM Map(9, MAPW) FOR Y = 0 TO 9 FOR X = 0 TO MAPW Map(Y, X) = -1 NEXT X NEXT Y FOR X = 0 TO MAPW Map(9, X) = 0 NEXT X PX = 64 DO PAINT "#0a0a1a" IF KEY("ARROWLEFT") THEN PX = PX - 4 IF KEY("ARROWRIGHT") THEN PX = PX + 4 CamX = PX - 320 IF CamX < 0 THEN CamX = 0 MaxCam = (MAPW + 1) * TS - 640 IF CamX > MaxCam THEN CamX = MaxCam CAMERA CamX, 0 DRAWTILEMAP Map(), 1, TS, TS DRAWSPRITE 2, PX, 240, 0, 4 CAMERA 0, 0 TEXT (10, 14), "world X = " + STR$(INT(PX)), "white" SYNC(60) IF KEY("ESCAPE") THEN END LOOP
FOR loops just to keep the camera demo short. That's fine for a quick test, but for a real game you shouldn't hand-build levels like this. Design them visually in the Map Editor, export a DATA_LEVEL_n block, and load it with RESTORE/READ (see the Tilemaps section). The loader is always the same:' --- Standard way: load a level designed in the Map Editor --- RESTORE DATA_LEVEL_1 READ LevelID : READ MapW : READ MapH : READ LayerNo ' header line DIM Map(MapH - 1, MapW - 1) FOR Y = 0 TO MapH - 1 FOR X = 0 TO MapW - 1 READ Tile Map(Y, X) = Tile NEXT X NEXT Y ' ...then in your game loop just draw it: ' CAMERA CamX, 0 ' DRAWTILEMAP Map(), 1, TS, TS
Saving Data => SAVEDATA & LOADDATA$
These two commands give you persistent storage that survives closing and reopening the program, perfect for high scores, settings, or save slots. The same code works in the browser, the desktop app, and the Android APK.
SAVEDATA & LOADDATA$
' SAVEDATA key$, value , stores a number or string under a key SAVEDATA "hiscore", 1500 SAVEDATA "player", "TERROR" ' LOADDATA$(key$) , returns the stored STRING, or "" if nothing was saved Name$ = LOADDATA$("player") ' for numbers, wrap it in VAL(): Best = VAL(LOADDATA$("hiscore"))
$). On the very first run nothing is saved yet, so it returns "", and VAL("") is 0, which makes a perfect starting high score.Full Example => Persistent High Score
Loads the best score on start, counts up while you hold SPACE, and saves a new record the moment you beat it. Close the program and run it again, your best is still there.
SCREEN 12 HiScore = VAL(LOADDATA$("demo_best")) Score = 0 DO PAINT "#101030" IF KEY("SPACE") THEN Score = Score + 1 IF Score > HiScore THEN HiScore = Score SAVEDATA "demo_best", HiScore END IF FONTSIZE 24 TEXT (40, 80), "Score: " + STR$(Score), "yellow" TEXT (40, 130), "Best: " + STR$(INT(HiScore)), "lime" FONTSIZE 16 TEXT (40, 200), "Hold SPACE to score. Best is saved across runs.", "gray" SYNC(30) IF KEY("ESCAPE") THEN END LOOP
Images => LOADIMAGE, PUTIMAGE, IMGSAVE & more
basicFusion lets you load external images (PNG, JPG, GIF, etc.) onto the canvas, transform them (stretch, rotate, mirror, slice spritesheets, map onto 3D quads), read individual pixels, and save the canvas as a PNG file. These commands require a running instance of the standalone basicFusion app, see the compatibility note below.
LOADIMAGE => Load an Image from URL or File Path
LOADIMAGE is a function, it takes a URL or local file path as a string and returns an image object. Assign the result to a variable and then draw it with PUTIMAGE.
' IMG = LOADIMAGE("url_or_path") IMG = LOADIMAGE("https://example.com/sprite.png") ' In standalone version, local file path IMG = LOADIMAGE("images/background.png")
PUTIMAGE => Draw an Image on the Canvas
PUTIMAGE (x,y), imageVar draws the loaded image at the given top-left coordinates.
SCREEN 12 BG = LOADIMAGE("background.png") HERO = LOADIMAGE("hero.png") GAMELOOP: PUTIMAGE (0,0), BG ' draw background PUTIMAGE (PX,PY), HERO ' draw player at (PX, PY) SYNC GOTO GAMELOOP
IMAGEALPHA => Set Image Opacity
IMAGEALPHA imgVar, alpha sets how transparent an image is, from 0.0 (invisible) to 1.0 (fully opaque). The value sticks to the image, so every PUTIMAGE (and the stretched / rotated / mirrored / part variants) draws it with that opacity until you change it again.
GHOST = LOADIMAGE("ghost.png") IMAGEALPHA GHOST, 0.5 ' half transparent LOOP: CLS PUTIMAGE (X,Y), GHOST SYNC GOTO LOOP
Set the alpha back to 1 to make the image solid again: IMAGEALPHA GHOST, 1. Stepping the value each frame fades an image in or out.
IMAGETINT => Color an Image
IMAGETINT imgVar, COLOR multiplies an image by a color, so a white sprite takes on that color and a colored one is shaded toward it. COLOR works like everywhere else, a named color, a variable, or RGB(r,g,b).
HERO = LOADIMAGE("hero.png") IMAGETINT HERO, RED ' damage flash IMAGETINT HERO, RGB(80,80,255) ' frozen / blue IMAGETINT HERO, -1 ' clear tint, back to normal
The tint is baked once when you call IMAGETINT, not every frame, so drawing a tinted image stays as fast as a normal one. Pass -1 as the color to remove the tint and draw the original pixels again.
SETTRANSPARENT => Color Key (make a color transparent)
SETTRANSPARENT imgVar, COLOR erases one color from an image, every pixel matching COLOR becomes fully transparent. This is the classic trick for images saved without an alpha channel: paint the background a flag color (often magenta) and key it out after loading. The optional third argument is a tolerance (0-255) that also removes near-matching shades, which helps with anti-aliased edges.
HERO = LOADIMAGE("hero.png") SETTRANSPARENT HERO, RGB(255,0,255) ' magenta -> transparent SETTRANSPARENT HERO, "black", 16 ' black + close shades PUTIMAGE (100, 80), HERO
PUTIMAGE calls are just as fast. The parenthesized form works too: SETTRANSPARENT(HERO, RGB(255,0,255)).IMGSAVE => Save the Canvas as a PNG
IMGSAVE saves the current graphics canvas as a PNG file. If you omit the filename, it defaults to canvas.png.
' IMGSAVE [filename] IMGSAVE "screenshot.png" ' save with a name IMGSAVE ' save as canvas.png
IMAGEWIDTH / IMAGEHEIGHT => Measure an Image
IMAGEWIDTH imgVar and IMAGEHEIGHT imgVar return the width and height of a loaded image object in pixels. Use them to center, scale, or tile images without hard-coding sizes.
IMG = LOADIMAGE("hero.png") W = IMAGEWIDTH IMG H = IMAGEHEIGHT IMG ' center the image on a 640x480 screen PUTIMAGE (320 - W / 2, 240 - H / 2), IMG
PUTIMAGESTRETCHED => Draw a Scaled Image
PUTIMAGESTRETCHED (x,y), W, H, imgVar draws a loaded image scaled to the width W and height H you specify, starting at the top-left corner (x,y).
IMG = LOADIMAGE("logo.png") ' draw it at double size in the corner PUTIMAGESTRETCHED (10,10), 256, 256, IMG ' stretch to fill the whole screen PUTIMAGESTRETCHED (0,0), 640, 480, IMG
PUTIMAGEROTATED => Draw a Rotated Image
PUTIMAGEROTATED (x,y), W, H, [angle,] imgVar draws a scaled image rotated by angle degrees around its center point. Increase the angle each frame to spin the image.
SCREEN 12 WHEEL = LOADIMAGE("wheel.png") A = 0 SPIN: PAINT "black" ' 128x128, rotated by A degrees around its center PUTIMAGEROTATED (320,240), 128, 128, A, WHEEL A = A + 3 SYNC GOTO SPIN
PUTIMAGEMIRRORED => Draw a Flipped Image
PUTIMAGEMIRRORED (x,y), flipH, flipV, imgVar draws an image, mirrored horizontally when flipH is 1 and/or vertically when flipV is 1. Use 0 for no flip. This is perfect for facing a sprite left or right.
HERO = LOADIMAGE("hero.png") ' flipH, flipV PUTIMAGEMIRRORED (100,100), 0, 0, HERO ' normal PUTIMAGEMIRRORED (200,100), 1, 0, HERO ' face the other way PUTIMAGEMIRRORED (300,100), 0, 1, HERO ' upside down
PUTIMAGEPART => Draw a Cutout from a Spritesheet
PUTIMAGEPART (x,y), srcX, srcY, W, H, imgVar draws only a rectangular region of an image, taken from (srcX, srcY) with size W×H. This is how you pull a single frame out of a spritesheet for animation.
SHEET = LOADIMAGE("explosion_sheet.png") ' each frame is 64x64, laid out in a row FRAME = 2 ' draw frame number FRAME at screen (100,100) PUTIMAGEPART (100,100), FRAME * 64, 0, 64, 64, SHEET
EXTRACTIMAGE => Cut a Region into a New Image
EXTRACTIMAGE (imgVar, x, y, W, H) is a function, it cuts a rectangular region out of an existing image and returns it as a brand-new, independent image object. Unlike PUTIMAGEPART (which only draws), this gives you a reusable image you can draw, transform, or save on its own.
SHEET = LOADIMAGE("tiles.png") ' grab the 32x32 tile at column 3, row 1 GRASS = EXTRACTIMAGE(SHEET, 96, 32, 32, 32) ' now reuse it like any other image PUTIMAGE (0,0), GRASS PUTIMAGESTRETCHED (64,0), 128, 128, GRASS
IMAGEFLIPH / IMAGEFLIPV => Lossless Flipped Copies
IMAGEFLIPH(imgVar) and IMAGEFLIPV(imgVar) are functions that return a brand-new image flipped horizontally or vertically. The flip is a pure pixel mirror, so there is no quality loss whatsoever, ideal for turning a right-facing sprite into a left-facing one without drawing or loading a second image.
HERO = LOADIMAGE("hero_right.png") ' build the mirrored copies ONCE, before the game loop HEROLEFT = IMAGEFLIPH(HERO) HEROFLIP = IMAGEFLIPV(HERO) ' draw whichever way the player faces IF DIR = -1 THEN PUTIMAGE (X,Y), HEROLEFT ELSE PUTIMAGE (X,Y), HERO
IMAGEALPHA or IMAGETINT from the source.IMAGEPIXEL => Read a Pixel from Image Memory
IMAGEPIXEL (imgVar, x, y) is a function that reads the color of a pixel directly from an image's memory and returns it as an integer RGB value. It is much faster than reading from the canvas, which makes it ideal for collision masks and per-pixel tests.
MASK = LOADIMAGE("level_mask.png") ' read the pixel under the player C = IMAGEPIXEL(MASK, PX, PY) ' black pixel = wall => block movement IF C = 0 THEN PRINT "Hit a wall!"
PUTIMAGEQUAD => Map onto a 4-Point Polygon
PUTIMAGEQUAD (x1,y1)-(x2,y2)-(x3,y3)-(x4,y4), img warps the image so its corners land on four arbitrary points. Give the points in order (top-left, top-right, bottom-right, bottom-left) to create Mode 7 / pseudo-3D floor and skew effects.
FLOOR = LOADIMAGE("floor.png") ' a trapezoid: narrow at the top, wide at the bottom PUTIMAGEQUAD (260,200)-(380,200)-(600,460)-(40,460), FLOOR
PUTIMAGEQUAD3D => Perspective-Correct 3D Quad
PUTIMAGEQUAD3D X1,Y1,Z1, X2,Y2,Z2, X3,Y3,Z3, X4,Y4,Z4, img maps an image onto a quad defined by four 3D points, applying perspective correction. Use it for textured 3D surfaces where the flat PUTIMAGEQUAD would look distorted.
TEX = LOADIMAGE("brick.png") ' X,Y,Z for each of the 4 corners (top-L, top-R, bottom-R, bottom-L) PUTIMAGEQUAD3D -1,1,2, 1,1,2, 1,-1,4, -1,-1,4, TEX
LOADIMAGE with local file paths and IMGSAVE work reliably only in the standalone basicFusion desktop application. In a browser, loading local images may be blocked by the browser's security policy (CORS). Remote URLs (https://...) may also be restricted depending on the server's CORS headers. If an image fails to load in the browser, run your program from the standalone app instead.LOADIMAGE before the game loop, not inside it. Loading an image on every frame is very slow, store the result in a variable and reuse it.3D Rendering
basicFusion features a built-in software 3D renderer that supports loading Wavefront .obj models, UV texture mapping, and automatic Z-Buffer depth sorting.
LOAD3DOBJECT & DRAW3DOBJECT => Solid Color Models
Used to load and draw basic 3D objects with a solid color, without texture mapping.
| Parameter | Description |
|---|---|
| obj | The 3D object loaded via LOAD3DOBJECT. |
| x, y | Screen position (center of the object). |
| z | Depth position (Z-axis offset). |
| scale | Size multiplier for the model. |
| rx, ry, rz | Rotation angles in degrees around the X, Y, and Z axes. |
| color | A CSS color string or integer (e.g., "lime", "#FF0000", 14). |
Example 1: Basic Solid Model
SCREEN 12 FASTGRAPHICS MODEL = LOAD3DOBJECT("https://basicfusion.org/cloud/assets/3d/skull.obj") A = 0 SOLID_LOOP: CLS DRAW3DOBJECT MODEL, 0, 0, 0, 3.5, 0, A * 1.5, 0, 2 A = A + 1 SYNC GOTO SOLID_LOOP
LOAD3DUVOBJECT, SET3DTEXTURE & DRAW3DUVOBJECT => Textured Models
Loads an .obj file containing UV mapping. SET3DTEXTURE assigns a loaded image to a specific material slot (starting from 0).
| Parameter | Description |
|---|---|
| obj | The 3D object loaded via LOAD3DUVOBJECT. |
| x, y | Screen position (center of the object). |
| z | Depth position (Z-axis offset). Normally 0. |
| scale | Size multiplier for the model. |
| rx, ry, rz | Rotation angles in degrees. |
Example 2: Textured 3D Object
SCREEN 12 FASTGRAPHICS BOX_OBJ = LOAD3DUVOBJECT("https://basicfusion.org/cloud/assets/3d/cube.obj") BOX_TEX = LOADIMAGE("https://basicfusion.org/cloud/assets/3d/texture.jpg") SET3DTEXTURE BOX_OBJ, 0, BOX_TEX A = 0 SPIN_LOOP: CLS DRAW3DUVOBJECT BOX_OBJ, 0, 0, 0, 2, A, A * 1.5, A * 0.5 A = A + 1 SYNC GOTO SPIN_LOOP
.obj models are exported with UV mapping enabled (the file must contain vt tags). Also, keep the polygon count reasonable (low-poly), as basicFusion uses a software renderer.CLS.File I/O => FOPEN, FREAD$, FWRITE, FCLOSE
Open a file (or a URL) with FOPEN, it returns a numeric handle, or 0 if it failed. A path starting with http:// / https:// is downloaded (read-only); any other name is a local file kept in the browser. The optional second argument is the mode: "r" read (default), "w" write (truncate), "a" append.
fp = FOPEN("save.txt", "w") IF fp = 0 THEN PRINT "cannot open" : END FWRITELINE(fp, "hello") FWRITE(fp, "score=") : FWRITE(fp, score) FCLOSE(fp) ' local files are saved on close
Read the whole thing with FREAD$(fp), or line by line with FREADLINE$(fp) until FEOF(fp). Also available: FSEEK(fp,pos), FTELL(fp), FSIZE(fp), FEXISTS(name$).
fp = FOPEN("https://example.com/data.txt") IF fp = 0 THEN PRINT "load error" : END WHILE FEOF(fp) = 0 line$ = FREADLINE$(fp) PRINT line$ WEND FCLOSE(fp)
$ (FREAD$, FREADLINE$); the rest return numbers. See the ready-made File / config parser example.Where do files live?
Three kinds of path are understood:
- URL (
http:///https://), downloaded, read-only. Great for loading levels or config from the web. - Local name (e.g.
"save.txt"), a small file kept inside the browser (persists between runs). Read and write. - Real disk path (e.g.
"c:/data.txt","/home/me/x.txt"), only works in the desktop app (basicFusion.exe); in the browser it returns 0 for security.
FDIALOG => Let the user pick a file
In the browser you cannot read arbitrary disk paths, so FDIALOG() opens the system "Open file" dialog, reads the chosen file as text and returns a read handle (0 if cancelled). Then use FREAD$ / FREADLINE$ as usual. An optional filter narrows the file types.
fp = FDIALOG(".txt") ' user picks a file IF fp = 0 THEN PRINT "cancelled" : END PRINT FREAD$(fp) FCLOSE(fp)
FDIALOG near the start of your program (or right after a keypress), not deep inside a loop.Example: a simple INI config parser
Reads a file with [sections], key = value lines and # comments, and prints it back:
cfg = FOPEN("https://example.com/config.txt") IF cfg = 0 THEN PRINT "load error" : END section$ = "(root)" WHILE FEOF(cfg) = 0 line$ = LTRIM$(RTRIM$(FREADLINE$(cfg))) IF line$ = "" THEN CONTINUE IF LEFT$(line$, 1) = "#" THEN CONTINUE IF LEFT$(line$, 1) = "[" THEN section$ = MID$(line$, 2, INSTR(line$, "]") - 2) PRINT "[" + section$ + "]" CONTINUE END IF eq = INSTR(line$, "=") IF eq > 0 THEN PRINT " " + RTRIM$(LEFT$(line$, eq - 1)) + " = " + LTRIM$(MID$(line$, eq + 1)) END IF WEND FCLOSE(cfg)
Sockets => SOCKETOPEN, SOCKETSEND, SOCKETRECV$ (WebSocket)
For a two-way, real-time connection (chat, multiplayer, live data) basicFusion speaks WebSocket. SOCKETOPEN connects and returns a handle once the socket is open (or 0 on failure); messages arrive asynchronously and queue up for SOCKETRECV$.
sock = SOCKETOPEN("wss://echo.websocket.events") IF sock = 0 THEN PRINT "connect failed" : END SOCKETSEND(sock, "hello") FOR T = 1 TO 30 WHILE SOCKETCOUNT(sock) > 0 PRINT "recv: " + SOCKETRECV$(sock) WEND SLEEP 100 NEXT T SOCKETCLOSE(sock)
Other calls: SOCKETSTATUS(sock) is 1 while the connection is open, SOCKETCOUNT(sock) tells you how many messages are waiting. SOCKETRECV$ is non-blocking, it returns "" when nothing is queued, so poll it inside your loop.
FOPEN with an http(s) URL and FREAD$ (see the Files section and the Mini HTML parser example). WebSockets are for live, bidirectional links; cross-site fetches may be limited by the server's CORS policy.Bluetooth LE => BTSCAN, BTWRITE, BTNOTIFY
basicFusion can talk to Bluetooth Low Energy devices (micro:bit, ESP32, sensors, wearables) using the browser's Web Bluetooth. BTSCAN opens the system device picker and connects; then read, write and subscribe to notifications on GATT characteristics.
SVC$ = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" ' Nordic UART service TX$ = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" ' notifications from device RX$ = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" ' write to device dev = BTSCAN(SVC$) IF dev = 0 THEN PRINT "no device" : END PRINT "connected: " + BTNAME$(dev) BTNOTIFY(dev, TX$) BTWRITE(dev, RX$, "hello" + CHR$(10)) FOR T = 1 TO 60 WHILE BTCOUNT(dev) > 0 PRINT "recv: " + BTRECV$(dev) WEND SLEEP 100 NEXT T BTCLOSE(dev)
USB Serial => SERIALOPEN, SERIALWRITE, SERIALRECVLINE$
Talk to an Arduino (or any USB serial device) over the cable. SERIALOPEN shows the port picker and opens it; SERIALWRITE sends, and SERIALRECVLINE$ reads one line at a time, which matches how Serial.println() sends data.
p = SERIALOPEN(9600) IF p = 0 THEN PRINT "no port" : END SERIALWRITE(p, "1" + CHR$(10)) ' e.g. turn an LED on FOR T = 1 TO 100 line$ = SERIALRECVLINE$(p) WHILE line$ <> "" PRINT "arduino: " + line$ line$ = SERIALRECVLINE$(p) WEND SLEEP 50 NEXT T SERIALCLOSE(p)
On the Arduino side it's just Serial.begin(9600) with Serial.println(...) / Serial.read(), the same as the Arduino IDE. Use SERIALRECV$ for raw buffered text, SERIALCOUNT to see how much is waiting.
SERIALCLOSE when done. On Android you'll need a USB-OTG adapter.Sound Files => LOADSOUND, PLAYSOUND, STOPSOUND, SOUNDVOLUME
basicFusion can load and play real audio files (MP3, WAV, OGG, etc.) from URLs using the LOADSOUND family of commands. This is separate from the synthesised 8-bit SOUND / PLAY / NOISE commands, use these when you need recorded music, voice, or sampled sound effects.
LOADIMAGE. Remote HTTPS URLs work best in the browser; local files are most reliable from the standalone basicFusion desktop app.LOADSOUND => Load an Audio File
LOADSOUND is a function, it takes a URL string and returns a sound object. Assign the result to a variable for later use.
' SND = LOADSOUND("url_or_path") MUSIC = LOADSOUND("https://example.com/theme.mp3") BOOM = LOADSOUND("sounds/explosion.wav")
PLAYSOUND => Play a Loaded Sound
Pass the sound variable. The optional second argument controls looping: 1 = loop continuously, 0 (default) = play once.
' play once PLAYSOUND BOOM ' loop continuously (background music) PLAYSOUND MUSIC, 1
PLAYSOUND also plays synthesised effects: pass a number instead of a loaded sound to play the DEFSOUND with that id (e.g. PLAYSOUND 0). See the SFX Editor / DEFSOUND section.STOPSOUND => Stop Playback
STOPSOUND MUSIC
SOUNDVOLUME => Set Volume
Volume ranges from 0.0 (silent) to 1.0 (full). You can call this at any time, before or after starting playback.
SOUNDVOLUME MUSIC, 0.5 ' 50% volume SOUNDVOLUME BOOM, 1.0 ' full volume
Full Example => Background Music + SFX
SCREEN 12 ' Load sounds once before the game loop MUSIC = LOADSOUND("https://example.com/bgm.mp3") BOOM = LOADSOUND("https://example.com/explosion.wav") PICKUP = LOADSOUND("https://example.com/coin.wav") SOUNDVOLUME MUSIC, 0.4 ' quiet background track PLAYSOUND MUSIC, 1 ' loop it PX = 100 : PY = 240 CX = INT(RND() * 580) + 30 ' coin X CY = INT(RND() * 420) + 30 ' coin Y SCORE = 0 GL: PAINT "#0a0a1a" IF KEY("ARROWLEFT") THEN PX = PX - 5 IF KEY("ARROWRIGHT") THEN PX = PX + 5 IF KEY("ARROWUP") THEN PY = PY - 5 IF KEY("ARROWDOWN") THEN PY = PY + 5 IF ABS(PX - CX) < 20 AND ABS(PY - CY) < 20 THEN PLAYSOUND PICKUP SCORE = SCORE + 1 CX = INT(RND() * 580) + 30 CY = INT(RND() * 420) + 30 END IF CIRCLE (PX,PY), 12, "lime" CIRCLE (CX,CY), 10, "gold" FONTSIZE 18 TEXT (10,22), "SCORE: " + STR$(SCORE), "white" SYNC GOTO GL
LOADSOUND before your game loop, not inside it. Loading audio on every frame is very slow. Store the result in a variable and reuse it.Custom Sound Effects => DEFSOUND
DEFSOUND lets you define PICO-8 style sound effects right inside your program: a pattern of up to 32 notes, each with its own pitch, waveform, volume and effect. Unlike LOADSOUND (which streams a recorded file), these are fully synthesised, no external files, and they live in your code as plain text.
DEFSOUND / SOUNDDATA lines for you and can re-load every sound it finds in your code.Defining a Sound
A sound is two lines that share the same id (a number or a constant): a DEFSOUND header and a SOUNDDATA note pattern.
' DEFSOUND id, speed [, loopStart, loopEnd] ' SOUNDDATA id, "pitch:wave:vol:fx, ..." (up to 32 notes) DEFSOUND 0, 8 SOUNDDATA 0, "24:3:6:0, 28:3:6:0, 31:3:5:0, 36:3:5:0" PLAYSOUND 0 ' play it by id
speed is how many ticks each note lasts (lower = faster). The id can be a constant, which keeps your code readable:
CONST SFX_LASER = 1 DEFSOUND SFX_LASER, 5 SOUNDDATA SFX_LASER, "60:2:5:3, 52:2:5:3, 44:2:4:3, 36:2:4:3" PLAYSOUND SFX_LASER
The Note Format => pitch:wave:vol:fx
Each note in SOUNDDATA is four numbers joined by colons, and notes are separated by commas. A note with vol = 0 is a rest (silence).
| Field | Range | Meaning |
|---|---|---|
| pitch | 0-63 | Semitone, low to high (0 ≈ C2). Each +12 is one octave up. |
| wave | 0-7 | Waveform / instrument (see table below). |
| vol | 0-7 | Volume. 0 = rest (no sound for that step). |
| fx | 0-7 | Per-note effect (see table below). |
Waveforms (wave)
| Value | Type | Character |
|---|---|---|
| 0 | Triangle | Soft, mellow, leads and bass |
| 1 | Tilted saw | Slightly buzzy, warmer than a full saw |
| 2 | Saw | Sharp, buzzy, basses and zaps |
| 3 | Square | Classic 8-bit melody tone |
| 4 | Pulse | Thin, nasal pulse wave |
| 5 | Organ | Layered harmonics, fuller tone |
| 6 | Noise | White noise, drums, explosions, hits |
| 7 | Phaser | Detuned, swirling saw |
Effects (fx)
| Value | Effect | What it does |
|---|---|---|
| 0 | None | Plain note |
| 1 | Slide | Glides in pitch from the previous note |
| 2 | Vibrato | Wobbles the pitch up and down |
| 3 | Drop | Pitch falls during the note |
| 4 | Fade in | Volume ramps up |
| 5 | Fade out | Volume ramps down |
| 6 | Arp fast | Fast arpeggio across the group of 4 notes |
| 7 | Arp slow | Slower arpeggio across the group of 4 notes |
Speed and Looping
The optional loopStart, loopEnd on DEFSOUND mark a region (by note index) that repeats. Omit them or use 0, 0 for a one-shot effect. Looping kicks in only when you ask for it with PLAYSOUND id, vol, 1.
' a 32-step pattern that loops steps 4..12 forever DEFSOUND 2, 10, 4, 12 SOUNDDATA 2, "24:3:6:0, 24:3:5:0, 31:3:5:0, 28:3:5:0, 24:3:6:2, 27:3:5:2, 31:3:5:2, 28:3:5:2" PLAYSOUND 2, 0.8, 1 ' vol 0.8, loop on
Full Example => Coin Pickup
SCREEN 12 ' two quick rising blips, a classic coin sound DEFSOUND 0, 7 SOUNDDATA 0, "43:0:6:0, 50:0:6:5" PX = 100 : PY = 240 CX = 400 : CY = 240 GL: PAINT "#0a0a1a" IF KEY("ARROWLEFT") THEN PX = PX - 5 IF KEY("ARROWRIGHT") THEN PX = PX + 5 IF ABS(PX - CX) < 20 THEN PLAYSOUND 0 ' play the DEFSOUND coin effect CX = INT(RND() * 580) + 30 END IF CIRCLE (PX,PY), 12, "lime" CIRCLE (CX,CY), 10, "gold" SYNC GOTO GL
DEFSOUND / SOUNDDATA register the pattern when they run, so put them above your game loop, PLAYSOUND id on an undefined id is simply silent.Music Tracker => DEFMUSIC
DEFMUSIC lets you compose multi-channel chiptune music directly in your program. Each pattern has up to 4 channels (lead, bass, chord, percussion) and any number of steps. Unlike DEFSOUND which works with raw pitch numbers, the tracker uses standard note names like C4, A#3, F#5, much easier to write and read.
DEFMUSIC / MUSICDATA lines automatically and can re-load any pattern it finds in your code.Defining a Pattern
A music pattern is one DEFMUSIC header line plus one MUSICDATA line per active channel, all sharing the same id.
' DEFMUSIC id, steps ' MUSICDATA id, channel, "step, step, ..." DEFMUSIC 0, 16 MUSICDATA 0, 0, "C4:1:80:150:0:0,E4:1:80:150:0:0,-,G4:1:80:150:0:0,-,-,A4:1:70:150:0:0,-,-,-,-,-,-,-,-,-" MUSICDATA 0, 1, "C3:2:60:300:0:0,-,-,-,C3:2:60:300:0:0,-,-,-,G2:2:60:300:0:0,-,-,-,-,-,-,-" PLAYMUSIC 0 ' start looping, plays in the background
steps sets the loop length (16 and 32 are most common). The id can be a constant to keep your code readable:
CONST MUS_THEME = 0 DEFMUSIC MUS_THEME, 16 MUSICDATA MUS_THEME, 0, "C4:1:80:150:0:0,E4:1:80:150:0:0,G4:1:80:150:0:0,-,..." PLAYMUSIC MUS_THEME ' later, in a game over screen: STOPMUSIC MUS_THEME
The Step Format => note:wave:vol:dur:fx:fxVal
Each step in MUSICDATA is six fields joined by colons. Use - (a single dash) for a rest (silent step).
| Field | Example | Meaning |
|---|---|---|
| note | C4, A#3, F#5 | Note name + octave. Standard notation: A-G, optional # for sharp. |
| wave | 0-7 | Waveform / instrument (0=sine, 1=square, 2=saw, 3=triangle, ...). |
| vol | 0-100 | Volume in percent. 0 is silent. |
| dur | 150 | Note duration in milliseconds. |
| fx | ARP, POR, VIB, 0 | Per-step effect name, or 0 for none. |
| fxVal | 7 | Numeric parameter for the effect (semitones for ARP, glide ms for POR, depth for VIB). |
Effects (fx)
| Name | What it does |
|---|---|
| 0 / NONE | Plain note, no effect. |
| ARP | Arpeggio, rapidly alternates between the base note and base + fxVal semitones. |
| POR | Portamento (glide), pitch slides up from fxVal semitones below to the target note over fxVal ms. |
| VIB | Vibrato, wobbles the pitch at ~8 Hz with a depth proportional to fxVal. |
PLAYMUSIC Options
PLAYMUSIC takes optional arguments for loop, tempo and volume. Multiple patterns can play simultaneously on separate slots.
PLAYMUSIC 0 ' loop=1 (default), bpm=120, vol=1.0 PLAYMUSIC 0, 1, 140 ' loop, 140 bpm PLAYMUSIC 0, 0 ' play once (no loop) PLAYMUSIC 0, 1, 120, 0.6 ' loop, 120 bpm, volume 60% STOPMUSIC 0 ' stop one pattern STOPMUSIC ' stop all patterns
Full Example => Background Music in a Game
SCREEN 12 ' --- DEFINED MUSIC ID: 0 (BPM: 130, STEPS: 16) --- CONST MUS_GAME = 0 DEFMUSIC MUS_GAME, 16 MUSICDATA MUS_GAME, 0, "C4:1:80:150:0:0,-,E4:1:80:150:0:0,-,G4:1:80:150:0:0,-,E4:1:70:150:0:0,-,D4:1:80:150:0:0,-,F4:1:80:150:0:0,-,E4:1:75:150:0:0,-,-,-,-,-" MUSICDATA MUS_GAME, 1, "C3:2:55:300:0:0,-,-,-,G2:2:55:300:0:0,-,-,-,F2:2:55:300:0:0,-,-,-,G2:2:55:300:0:0,-,-,-" PX = 320 : PY = 240 PLAYMUSIC MUS_GAME, 1, 130 ' start music, loop, 130 BPM GL: PAINT "#080820" IF KEY("ARROWLEFT") THEN PX = PX - 4 IF KEY("ARROWRIGHT") THEN PX = PX + 4 IF KEY("ARROWUP") THEN PY = PY - 4 IF KEY("ARROWDOWN") THEN PY = PY + 4 CIRCLE (PX,PY), 14, "cyan" FONTSIZE 14 TEXT (10,22), "arrows to move | music plays in background", "white" SYNC GOTO GL
DEFMUSIC / MUSICDATA lines before PLAYMUSIC, they register the pattern when they run, so calling PLAYMUSIC on an undefined id is silent. Also remember: STOPMUSIC without an argument stops all active patterns at once.IDE Keyboard Shortcuts
Running
Editor
Search
Help
Ready-made Examples
1. BMI Calculator
CLS COLOR "cyan" PRINT STRING$(40, "=") PRINT " BMI CALCULATOR" PRINT STRING$(40, "=") COLOR "white" INPUT "Weight (kg): "; WAGA INPUT "Height (m): "; WZROST BMI = WAGA / (WZROST * WZROST) PRINT "Your BMI:"; INT(BMI * 10) / 10 IF BMI < 18.5 THEN COLOR "yellow" : PRINT "Underweight" IF BMI >= 18.5 AND BMI < 25 THEN COLOR "lime" : PRINT "Normal weight" IF BMI >= 25 THEN COLOR "red" : PRINT "Overweight" COLOR "white"
2. Multiplication Table
FOR I = 1 TO 10 FOR J = 1 TO 10 PRINT RIGHT$(" " + STR$(I * J), 4); NEXT J PRINT NEXT I
3. Pythagorean Spiral
FASTGRAPHICS : SCREEN 12 CX = 320 : CY = 240 DO CLS FOR I = 1 TO 200 T = I * 0.2 + ANGLE R = I * 0.9 X = CX + COS(T) * R Y = CY + SIN(T) * R C = INT(I * 1.27) MOD 360 PSET (X, Y), "hsl(" + STR$(C) + ",100%,60%)" NEXT I ANGLE = ANGLE + 0.001 SYNC LOOP UNTIL INKEY$ <> "" END
4. Mini Game => Collecting Points
SCREEN 12 CONST SPD = 4 PX = 320 : PY = 400 GX = INT(RND() * 600) + 20 GY = INT(RND() * 400) + 20 SCORE = 0 GL: PAINT "#0a0010" IF KEY("ARROWLEFT") THEN PX = PX - SPD IF KEY("ARROWRIGHT") THEN PX = PX + SPD IF KEY("ARROWUP") THEN PY = PY - SPD IF KEY("ARROWDOWN") THEN PY = PY + SPD IF ABS(PX-GX) < 20 AND ABS(PY-GY) < 20 THEN SCORE = SCORE + 10 GX = INT(RND() * 600) + 20 GY = INT(RND() * 400) + 20 END IF BOX (PX-12,PY-12)-(PX+12,PY+12), "lime" CIRCLE (GX,GY), 10, "gold" FONTSIZE 18 TEXT (10,22), "SCORE: " + STR$(SCORE), "white" SYNC GOTO GL