viperconsole.github.io

Viper Virtual Console official website


Project maintained by viperconsole Hosted on GitHub Pages — Theme by mattgraham

VIPER developer manual

Since November 2023, the Viper console has been undergoing a complete rewrite using the Godot Engine. This manual is a work in progress and may not fully reflect the current features of the console. However, the cheatsheet section does reflect the features of the current stable version.

If you’re consulting this from the viper console, an online version of this manual can be found at https://viperconsole.github.io

This is the 3.0.1a stable version manual (the one published at https://jice-nospam.itch.io/viper-console). You can browse the 3.0.2a development version at https://viperconsole.github.io/index_dev.html

Summary

1. Getting started

1.1. Presentation

Viper is a virtual console inspired by early 90s hardware such as NEC/PCEngine, Amiga 500, and Neo-Geo. While its primary focus is on 2D, pixel-art-based video games, it remains versatile enough to accommodate the development of 3D engines.

Although some 90s limitations have been lifted—like the use of 32-bit colors instead of indexed palettes—the project strives to maintain the nostalgic early 90s aesthetic. Notably, Viper avoids employing the alpha channel extensively, limiting its usage to layer compositing and parallax effects.

1.2. Specifications

Graphics :  
- 384x224 screen resolution
- 32 bits colors with alpha channel support
- Display based on a set of transparent layers
- Layers can be moved and resized
- Layer operations supported (mix, mul, add, sub, ...)
- Any number of offscreen spritesheets
- Row/colscroll parameters for smooth parallax effects
Sound :     
- 6 channels 44100Hz (native) or 16000Hz (web)
- Subtractive synthetizer
- Saw, triangle, square and noise generators
- Samples support (.wav)
- Midi controller input support
Input :
- Support mouse, keyboard and controller
Resources :
- Can load images (.png) and samples (.wav)
- Supports both local filesystem and web connectivity through HTTPS.
Code :
- Uses GDScript
- Full support of Godot API including coroutines

1.3. Hello world

From the console main menu, open the code editor and paste the code below :

var x = 10
func draw() :
   V.gfx.clear()
   V.gfx.print(V.gfx.FONT_8X8, "Hello world!", Vector2(x,10))

Then press the Start button from the main menu to run the code.

The draw() function is invoked every frame, and it’s the designated place to call all rendering methods from V.gfx. Keep in mind that the framerate is not guaranteed and may vary across different target systems.

The update() function is designed for updating time-dependent variables and is called at a fixed rate of 60 times per second. This consistent rate ensures uniform game speed across all systems. Additionally, it’s the ideal place to manage user input using the V.inp API.

func update() :
   x = 10 + V.elapsed()*50 # move at 50 pixels / second

There’s a third function available for setting up things before your game starts :

func init() :
   # do some initialization stuff
   pass

This is the designated place for loading images and samples. It’s recommended to perform these loading operations here to avoid pauses, particularly when fetching resources from HTTPS. This function can also call rendering functions, as it is executed during the rendering phase of the first frame.

1.4. Tutorials

Open the store from the main menu by clicking on and enter ‘tuto’ in the filter field. Here, you will discover a variety of commented tutorials that showcase the different features of the console.

1.5. Filesystem

The console features its own internal file system, allowing you to copy files through drag-and-drop directly onto the console window. For instance, you can drag an ‘spritesheet.png’ image onto the console window and then access it in your code simply as ‘spritesheet.png.’

This functionality also facilitates the effortless replacement of resources, be it a sample or an image, while running any game. By dropping a replacement file with the same name onto the window, the console seamlessly utilizes this file instead of the original resource.

You can also access files from outside the console using “file://<path>” syntax (only on native version of the console) or “https://<url>” to access online resources.

You don’t have to edit the code from the console editor, you can use an external editor and simply put the address of the file in the internal code editor :

Type file://C:/mygame.gd to load the code from the C:\mygame.gd file.

Type https://mysite.com/mygame.gd to load the code from the given URL using an HTTP request.

1.6. Command line / URL arguments

You can use those arguments to control the console’s behavior. For instance, to enable fullscreen at launch :

Use viper.exe --fullscreen in native mode.

Use https://html.itch.zone/html/9217920/index.html/?fullscreen=1 in web mode

Parameter Description
Bios  
cart=<path> Start a cartridge at launch. path = the .gd script to run
no_deepswitch Disable deepswitch menu (ctrl-shift-`)
no_docker Disable docker menu
Parameter Description
Graphics  
no_crt Disable all CRT effects
no_crt_warp Disable screen warping effect
no_crt_scan Disable scanline effect
no_crt_mask Disable shadow mask effect
fullscreen Start in fullscreen mode
no_splash Disable splash screen (only on native mode)
no_vsync Disable vertical sync (only on native mode)
Parameter Description
Audio  
no_audio Start with sound disabled
audio_rate=<rate> Change the audio sample rate (value in Hz)
Parameter Description
Input  
neutral_zone=<value> Change the controllers default neutral zone (value between 0.0 and 1.0)

2. Viper’s BIOS

2.1. Keyboard shortcuts

Key Command
Escape
Joypad 6*
Toggle docker menu
F12 Save a screenshot in %USERPROFILE%\Pictures\Viper
Ctrl-Shift-` Toggle deepswitch menu
X
LMB
Joypad 0*
Action1
C
RMB
Joypad 1*
Action2

* : Joypad buttons correspondance

Button Sony XBox Nintendo
0 cross A B
1 square X Y
6 start menu +

3. API Reference

3.1. Language

Viper uses GDScript as scripting language.

See https://docs.godotengine.org/en/stable/tutorials/scripting/gdscript/index.html for more information

3.1.1 Cartridge entry point

The following functions are called by the console :

This is where external resources should be loaded with

This is where you should update anything time dependant. Don’t call drawing methods in this function

Render one game frame. Framerate might vary from one computer to another. Use V.gfx.clear() to erase the previous frame.

3.1.2 access to the Viper API

All the Viper API can be found under the V singleton :

It also contains a few utilities :

This function returns the time elapsed since the game started in seconds.

3.2. Graphics (V.gfx)

3.2.1. Architecture

The viper screen size is 384x224 pixels. You can get those values with V.gfx.SCREEN_WIDTH and V.gfx.SCREEN_HEIGHT. The graphics engine can display on screen any number of transparent layers. Viper doesn’t use alpha blending, but a transparent key color that can be changed with V.gfx.set_transparent_color (default is pure black).

All colors are expressed with integer component between 0 and 255. All coordinates are floats but for a pixel perfect result, you should truncate them to integers. But smooth movement can be achieved using float coordinates.

Each layer can be resized with V.gfx.set_layer_size and moved with V.gfx.set_layer_offset. You can hide and show layers with V.gfx.show_layer and V.gfx.hide_layer. By default, only one layer (layer 0) is displayed.

The console renders the layers in increasing id order (first layer 0, then 1 on top of it and so on).

Layers can be used to overlay graphics on screen or store bitmap fonts/sprite sheets offscreen. You can copy an image from a layer to another with V.gfx.set_sprite_layer, V.gfx.set_active_layer and V.gfx.blit. You can load an image in the current active layer with V.gfx.load_img.

Each visible layer applies a color operation between its color and the underlying color :

Example :

V.gfx.set_layer_operation(1, V.gfx.LAYEROP_ADD)

You can draw on any layer by activating it with V.gfx.set_active_layer(id).

You can get the number of frames rendered during the last second with : V.gfx.fps()

3.2.2. Drawing

filepath is an URL :

TODO To convert an image into data url, simply drag and drop it on the console screen.

If resource_name is defined and not empty, this image can be overriden by the player by running the console with res parameters. This makes it easy for users to mod the game graphics by replacing a named resource with another image.

Example :

 local w,h = V.gfx.get_layer_size(0)

Example :

gfx.set_rowscroll(0,180,223,0,40)

Sets a rowscroll offset for rows 180 to 223. The offset ranges from 0 for row 180 to 40 for row 223

Example :

V.gfx.blit_pixels(0,0,1,{255}) would blit a single blue pixel at position 0,0 (255 = 0x0000FF => r=0,g=0,b=255)

Another way to write it :

V.gfx.blit_pixels(0,0,1,{V.gfx.to_rgb24(0,0,255)})

Note that this function is far slower than the V.gfx.blit function which is fully running on the GPU.

3.2.3. Font

Example : print hello at position 0,0 in white

V.gfx.print(V.gfx.FONT_8X8, "hello", 0,0)

3.2.4. Sprite

You select a spritesheet with set_sprite_layer (default is V.gfx.SYSTEM_LAYER). V.gfx.blit uses it as a source. The destination is the current active layer. Source and destination cannot be the same layer.

3.2.5. Spritesheets

You can define a spritesheet on any layer using the V.gfx.set_spritesheet function.

You can then blit a sprite from this layer using V.gfx.blit_sprite :

Example :

-- define layer 1 as a grid of 32x32 pixels sprites
spritesheet_id = V.gfx.set_spritesheet(1, 32, 32)
-- blit the top left sprite (#0) on the current layer at position 10,10
gfx.blit_sprite(spritesheet_id, 0, 10, 10)

3.3. Sound (V.snd)

The viper has 6 channels that each can play stereo sound at 48kHz with float32 samples.

3.3.1. Instrument

You can create any number of instruments that produce sound. Instrument can either use additive synthesis by using various oscillators or read samples from .wav files.

Oscillator instrument properties:

Sample instrument properties :

 var triangle_id = V.snd.new_instrument("INST OVERTONE 1.0 TRIANGLE 1.0 METALIZER 0.85 NAM triangle")
 var pulse_id = V.snd.new_instrument("INST OVERTONE 1.0 SQUARE 0.5 PULSE 0.5 TRIANGLE 1.0 METALIZER 1.0 OVERTONE_RATIO 0.5 NAM pulse")

For sample instruments, the description starts with the SAMPLEkeywords followed with a list of parameters. Example :

snare_id = V.snd.new_instrument("SAMPLE FILE musics/samples/snare.wav FREQ 17000")

3.3.2. Pattern

A pattern is a list of notes that plays at a specific rate.

The description starts with the PAT keyword followed by the notes duration (or speed at which the pattern plays the notes). The duration is divided by 120. For example :

Then it’s followed by a list of notes. Example :

new_pattern("PAT 02 F.2070 G.2070 D.3070 C.4070")

The note format is [note] [octave] [instrument_id] [volume] [fx] with :

You can add a silence by filling the note format with 6 dots : ...... Example :

new_pattern("PAT 16 C#3915 ...... ...... C#3915 D#3835")

3.3.3. Song

A song is an ordered list of patterns to play on one or several of the 6 available channels.

The description is a multi-line string.

Example :

MUSIC_TITLE = [[NAM title screen
PATLIST 56 57 58 59 60
SEQ 024... 134...]]
snd.new_music(MUSIC_TITLE)

Here the song is named “title screen”. It uses 5 patterns number 56, 57, 58, 59 and 60.

If you have more than 16 patterns, a single hexadecimal digit is not enough. You can then use SEQ2 instead of SEQ where each pattern index is a 2 digits hex number :

SEQ2 000204...... 010304......

For example with the previous song, which requires 3 channels, you can use the binary mask 111 = 7. This would result in the song using the channels 0,1,2.

3.3.4. Midi

TODO

3.4. Input (V.inp)

3.4.1. Keyboard

The value for the key scan codes are :

Arrow keys : (also triggered by numeric keypad if numlock is disabled)

Special keys :

3.4.2. Mouse

List of button values :

Functions :

You can use any button number or the predefined constants :

Example :

local mousex,mousey = V.inp.mouse_pos()

Note : you can show/hide the mouse cursor with the V.gfx API :

Example :

V.gfx.show_mouse_cursor(false)

3.4.3. Gamepad

in beta

In the following functions, player_num value is :

This makes it possible to use the same API whether the player is using the keyboard or the controller.

Functions :

You can use these constants for the num value :

Example : local x,y = V.inp.pad_ls(1)

3.4.4. Generic V.input

If you want to support both keyboard and controller at the same time in a single player game, you can use these generic functions instead :

The same functions with the _pressed suffix exist to check if the button was pressed since the last game tick. They all return a boolean.

Those last function will check controller #1 and keyboard if the player is not defined, else only the controller #player (0=keyboard, 1-8=controller)

4. Cheatsheet

4.1 Graphics - layer operations V.gfx
set_layer_operation(layer_id: int,op: int) set layer’s blending op :
LAYEROP_(SET\|ADD\|MULTIPLY\|SUBTRACT)
set_layer_size_v(layer_id: int, size: Vector2) set layer’s size in pixels
set_layer_size(layer_id: int, w: float, h: float) set layer’s size in pixels
set_layer_offset_v(layer_id, offset : Vector2) set layer’s offset in pixels
set_layer_offset(layer_id, x:float, y:float) set layer’s offset in pixels
set_active_layer(layer_id: int) set target layer for drawing functions
show_layer(layer_id: int) set a layer visible
hide_layer(layer_id: int) set a layer invisible
is_layer_visible(layer_id: int) -> bool check if layer is visible
set_layer_blit_col(layer_id: int, col: Color) force non transparent pixels color
unset_layer_blit_col(layer_id: int) disable forced color
set_rowscroll(first_row: int, last_row:int, start_offset: float, end_offset: float = start_offset) set per-row offset for current layer
set_colscroll(first_col: int, last_col:int, start_offset: float, end_offset: float = start_offset) set per-column offset for current layer
4.2 Graphics - drawing operations V.gfx
clear(col:Color = Color.TRANSPARENT) fill a layer with a color
line(p1: Vector2, p2: Vector2, col: Color) draw a line
rectangle(rect: Rect2,col:Color,fill=true) draw or fill a rectangle
triangle(p1: Vector2, p2: Vector2, p3: Vector2, col:Color, fill=true) draw or fill a triangle
disk(center: Vector2,radius_x: float,radius_y=0.0,col:Color=Color.WHITE) fill a circle
blit(dest_pos: Vector2 = Vector2.ZERO, source_pos: Vector2 = Vector2.ZERO, source_size : Vector2 = Vector2.ZERO, col: Color=Color.WHITE, _angle : float = 0.0, dest_size: Vector2 = Vector2.ZERO, hflip : bool = false, vflip : bool = false) blit a region of current spritesheet on current layer
blit_sprite(sprite_num: int, dest_pos: Vector2, col: Color = Color.WHITE, _angle: float = 0.0, dest_size: Vector2 = Vector2.ZERO, hflip: bool = false, vflip: bool = false) blit a sprite on current layer. See set_spritesheet_layout
print(font_id : int, text: String, pos:Vector2, col: Color = Color.WHITE) draw some text
4.3 Graphics - image operations V.gfx
load_img(filepath: String) -> int load an image, return its id
set_spritesheet(img_id: int) use a loaded image as source for blit operations
get_image_size(img_id: int) -> Vector2 get the size of a loaded image
set_spritesheet_layout(sprite_size: Vector2i, offset: Vector2i = Vector2i.ZERO, grid_width: int = 0) define sprites layout for current spritesheet
4.4 Graphics - font operations V.gfx
set_font(char_size: Vector2i, rect: Rect2i = Rect2i(0,0,0,0), charset: String="", spacing: Vector2i = Vector2i.ZERO, char_width: PackedByteArray = []) -> int define a custom font
4.5 Audio - general operations V.snd
enable() activate audio system
disable() deactivate audio system (mute everything)
play_sound(inst_id: int, freq:float, duration:float, lvolume:float = 1.0, rvolume:float=-1.0, channel: int =-1) play a sound
play_note(note:Note, duration:float, channel: int=-1) play a note
4.6 Audio - channel operations V.snd
get_available_channel(mask:int = 0) -> int get a free channel or -1
set_channel_volume(channel: int, lvolume: float, rvolume: float=-1) dynamically change a channel volume
set_channel_balance(channel: int, balance: float) dynamically change a channel balance between -1(left) and 1(right)
set_channel_freq(channel_id: int, freq: float) dynamically change the frequency of the current sound/note played on a channel
is_channel_silent(channel_id: int) -> bool wether a channel produces sound
get_channel_current_note(channel_id: int) -> Note current note played on a channel or null
mute_channel(channel:int) mute a channel
unmute_channel(channel:int) unmute a channel
solo_channel(channel:int) mute all channels but one
4.7 Audio - music operations V.snd
new_instrument(desc:Dictionary) -> int register a new instrument
new_pattern(desc:String) -> int register a new music or sfx pattern
play_pattern(pattern_id: int, channel_id: int=-1,loop=false) play a pattern
new_music(desc:Dictionary) -> int register a new music
stop_music() stop all channels playing music
play_music(music_id:int,channel_mask:int,loop:bool=true) play a music on specified channels
4.8 Input - generic operations V.inp
action1() -> bool wether action1 is currently pressed
action2() -> bool wether action2 is currently pressed
action1_pressed() -> bool wether action1 was pressed since last update
action2_pressed() -> bool wether action2 was pressed since last update
right(player=null) -> float amount of right movement between 0.0 and 1.0
left(player=null) -> float amount of left movement between 0.0 and 1.0
up(player=null) -> float amount of up movement between 0.0 and 1.0
down(player=null) -> float amount of down movement between 0.0 and 1.0
right_pressed(player=null) -> bool wether right was pressed since last update
left_pressed(player=null) -> bool wether left was pressed since last update
up_pressed(player=null) -> bool wether up was pressed since last update
down_pressed(player=null) -> bool wether down was pressed since last update
4.9 Input - mouse operations V.inp
mouse_button(button: int) -> bool wether a mouse button is currently pressed

5. FAQ

How can I generate 1D/2D/3D noise ?

See https://docs.godotengine.org/en/stable/classes/class_noise.html

Are there tweening utilities ?

You can use the Tween class. Example :

var value = Tween.interpolate_value(start_value, delta_value, t, duration, Tween.TRANS_CUBIC, Tween.EASE_IN_OUT)

See https://docs.godotengine.org/en/stable/classes/class_tween.html

How can I save data locally ?

See https://docs.godotengine.org/en/stable/tutorials/io/saving_games.html