Skip to content

Graphics and Multimedia

github-actions[bot] edited this page Jun 19, 2026 · 5 revisions

Graphics and Multimedia

Language Reference | Next: Raycast Game Engine


All graphics functions are available in the full build (Windows GDI or Linux/X11). They are absent in the console build, which targets minimal environments.

The coordinate system has its origin at the top-left corner of the working window client area, with X increasing to the right and Y increasing downward. All coordinates are in pixels.

Colors

Colors are packed RGB integers in GDI byte order (BGR stored in the low three bytes), so the layout is &hBBGGRR:

Color nuBASIC constant
Red &h0000FF
Green &h00FF00
Blue &hFF0000
Yellow &h00FFFF
Cyan &hFFFF00
Magenta &hFF00FF
White &hFFFFFF
Black &h000000

The Rgb(r, g, b) function computes a color from separate 0–255 components:

skyBlue%   = Rgb(135, 206, 235)
sunYellow% = Rgb(255, 220, 50)

Drawing Primitives

Lines and Shapes

Line x1, y1, x2, y2, color%          ' straight line
Rect x1, y1, x2, y2, color%          ' rectangle outline
FillRect x1, y1, x2, y2, color%      ' filled rectangle
Ellipse x1, y1, x2, y2, color%       ' ellipse outline (bounding box)
FillEllipse x1, y1, x2, y2, color%   ' filled ellipse

Pixels

SetPixel x%, y%, color%       ' write a pixel
c% = GetPixel(x%, y%)         ' read a pixel's color

Text on Screen

TextOut draws a text string at a pixel coordinate. Unlike Print, it does not move the text cursor or scroll the buffer.

TextOut x%, y%, text$, color%

TextOut 10,  10, "Score: " + Str$(score%), &hFFFFFF
TextOut 200, 240, "GAME OVER", Rgb(255, 50, 50)

Bitmap Images

Two APIs are available. Use the cached one whenever the same image is drawn more than once (sprites, tiles, animation frames): the file is decoded only on the first call and subsequent draws hit a GDI/GPU handle.

Cached bitmap API (preferred)

Function Returns Description
BitmapLoad(path$) Integer Load a BMP/PNG/JPEG and return an integer handle (0 on failure)
BitmapDraw(id, x, y) Integer Draw a cached bitmap at the given top-left pixel
BitmapFree(id) Integer Release the cached bitmap
Dim sprite As Integer
sprite = BitmapLoad("hero.bmp")
For frame% = 1 To 240
   Cls
   BitmapDraw sprite, hero_x%, hero_y%
   Refresh
Next frame%
BitmapFree sprite

Backed by GDI+ on Windows and stb_image on Linux (R/B channels are pre-swapped at load time so the per-frame cost matches Windows).

Single-shot draw (PlotImage)

PlotImage reloads the file from disk on every call. Fine for one-off splashes, wasteful inside animation loops.

PlotImage "background.bmp", 0, 0
PlotImage "sprite.bmp", hero_x%, hero_y%

Complete Scene Example

Cls
MoveWindow GetWindowX(), GetWindowY(), 640, 480
FillRect 0, 0, 640, 480, Rgb(30, 30, 60)          ' dark night sky
FillEllipse 520, 20, 600, 100, Rgb(255, 255, 200)  ' moon
FillRect 0, 360, 640, 480, Rgb(0, 80, 0)           ' green ground
For i% = 0 To 5
   cx% = 80 + i% * 100
   FillEllipse cx%, 200, cx%+60, 360, Rgb(20, 120, 20)  ' trees
   FillRect cx%+25, 330, cx%+35, 365, Rgb(80, 50, 20)   ' trunks
Next i%
TextOut 10, 10, "nuBASIC Graphics Demo", &hFFFFFF

Screen Mode (SCREEN command)

The Screen statement switches the console/graphics mode at runtime, similar to GW-BASIC's SCREEN command.

Mode Instruction Behaviour
0 Screen 0 Text / hybrid mode — text I/O via the real Windows console (stdout/stdin); all GDI drawing commands are silent no-ops. No GDI window is required. Ideal for scripts, CI pipelines and regression tests.
1 Screen 1 GDI console mode (default) — text I/O and graphics go through the custom GDI window, as in previous nuBASIC versions.

The CLI flag -t / --text-mode pre-sets Screen 0 before the interpreter starts, so programs that never call Screen 1 can run fully headless:

nubasic.exe -t -e tests/test_math.bas

Example — switching modes at runtime:

Screen 0          ' text mode: Print/Input use the real console
Print "Hello from text mode"
Screen 1          ' back to GDI: drawing commands are active again
Line 0, 0, 200, 200, RGB(255, 0, 0)

Tip for test files: add Screen 0 as the very first statement so the test runner can capture Print output without a GUI window.


Flicker-free Rendering

The Problem

When multiple drawing commands execute in sequence, each one immediately blits to the screen. The user sees each intermediate state — a cleared frame, a partial board, and so on — as visible flicker.

The Solution: Double Buffering

Instruction Effect
ScreenLock Suspend automatic screen refresh; all drawing goes to the back buffer
ScreenUnlock Present the back buffer to the screen in one blit; resume automatic refresh
Refresh Force an immediate blit; lock state is unchanged

Pattern 1 — Game Frame Loop

While Not(game_over%)
   ScreenLock
   FillRect 0, 0, 640, 480, &h000000   ' clear previous frame
   DrawBoard
   DrawPlayer player_x%, player_y%
   DrawEnemies
   DrawHUD score%, lives%
   ScreenUnlock
   MDelay 16                            ' pace to ~60 fps
Wend

Pattern 2 — Progressive Rendering (Mandelbrot)

For x0 = -2 To 2 Step 0.013
   ScreenLock
   For y0 = -1.5 To 1.5 Step 0.013
      ' ... compute c ...
      FillRect x0*d%+dx%, y0*d%+dy%, x0*d%+dx%+2, y0*d%+dy%+2, c%*16
   Next y0
   ScreenUnlock   ' one column appears per iteration
Next x0

Pattern 3 — Atomic Erase + Redraw

x_old% = x%  :  y_old% = y%
x% = x% + dx%  :  y% = y% + dy%

ScreenLock
FillEllipse x_old%*10, y_old%*10, x_old%*10+10, y_old%*10+10, 0        ' erase old
FillEllipse x%*10,     y%*10,     x%*10+10,     y%*10+10,     &hffffff ' draw new
ScreenUnlock

Pattern 4 — Dialog Before Blocking Input

ScreenLock
FillRect 150, 220, 490, 310, &hffff00
Rect     150, 220, 490, 310, &h000000
TextOut  180, 245, "Game over! Play again? (Y/N)", &h000000
ScreenUnlock
Refresh             ' ensure dialog is visible before blocking
key$ = Input$(1)

Mouse Input

Mouse input is polled — there is no event queue.

GetMouse() — preferred

GetMouse() captures the full pointer state in a single call and returns a Mouse struct:

Dim m As Mouse
m = GetMouse()
' m.x   — cursor X in pixels from left edge
' m.y   — cursor Y in pixels from top edge
' m.btn — 0=none, 1=left, 2=middle, 4=right

Legacy scalar functions (deprecated, removed in v2.0)

btn% = GetMouseBtn()   ' use GetMouse().btn instead
x%   = GetMouseX()     ' use GetMouse().x   instead
y%   = GetMouseY()     ' use GetMouse().y   instead

Hit-testing a Button Region

bx1% = 40  :  by1% = 60
bx2% = 200 :  by2% = 100

FillRect bx1%, by1%, bx2%, by2%, &hffff00
TextOut bx1%+20, by1%+15, "Click me", &h000000

Dim m As Mouse
While 1
   m = GetMouse()
   If m.btn = 1 And m.x >= bx1% And m.x <= bx2% And m.y >= by1% And m.y <= by2% Then
      Print "Button clicked!"
      MDelay 200
   End If
   MDelay 16
Wend

Drawing with the Mouse

Cls
FillRect 0, 0, 640, 480, &h000000

Dim m As Mouse
While 1
   key$ = InKey$()
   If key$ = "q" Or key$ = "Q" Then Exit While
   m = GetMouse()
   If m.btn = 1 Then
      SetPixel m.x, m.y, &hffffff
   End If
   MDelay 5
Wend

Sound and Window Management

Window Control

MoveWindow GetWindowX(), GetWindowY(), 800, 600

Print "Position: "; GetWindowX(); ", "; GetWindowY()
Print "Size:     "; GetWindowDx(); " x "; GetWindowDy()
Print "Canvas:   "; GetSWidth(); " x "; GetSHeight()

SetTopMost()   ' keep window above all others

Typical startup sequence:

Cls
MoveWindow 100, 100, 640, 480
FillRect 0, 0, 640, 480, &h000000

Sound

PlaySound "background.wav", 1   ' async: returns immediately
PlaySound "explosion.wav", 0    ' sync: waits for completion
Beep                            ' system beep

Message Boxes

result% = MsgBox("nuBASIC Demo", "Setup complete. Ready to play?")
If result% > 0 Then
   Print "User confirmed."
End If

Pseudo-3D Graphics

On Windows, nuBASIC 2.0 ships the integrated WinRayCast engine for rendering Wolfenstein 3D-style first-person scenes from BASIC. It builds on the GDI window and the flicker-free rendering primitives above. See Raycast Game Engine for the render loop and the full Ray… API reference.


Language Reference | Next: Raycast Game Engine

Clone this wiki locally