Added keyboard support

This commit is contained in:
2026-05-26 18:54:49 +10:00
parent 67dda3fa49
commit 1c1473339f
9 changed files with 267 additions and 121 deletions
+1 -1
Submodule Toy updated: b0387edeb0...bbb1e38649
+46 -4
View File
@@ -1,4 +1,4 @@
//debug testing
var counter = 0;
var randi: Int = 69420;
fn rand() {
@@ -12,6 +12,45 @@ fn wander(actor: Opaque) {
counter++;
}
//TODO: general purpose math functions
//TODO: optimize away single-line blocks in the AST
//player controlled character
var playerMaxMotion: Int = 5;
var playerMotionX: Int = 0;
var playerMotionY: Int = 0;
fn playerOnFrame(player: Opaque) {
//accept input
if (Keyboard.UP) {
playerMotionY -= 5;
}
if (Keyboard.DOWN) {
playerMotionY += 5;
}
if (Keyboard.LEFT) {
playerMotionX -= 5;
}
if (Keyboard.RIGHT) {
playerMotionX += 5;
}
// print playerMotionX;
// print playerMotionY;
// print playerMaxMotion;
// print -playerMaxMotion;
//cap the speed
if (playerMotionX > playerMaxMotion) playerMotionX = playerMaxMotion;
if (playerMotionX < -playerMaxMotion) playerMotionX = -playerMaxMotion;
if (playerMotionY > playerMaxMotion) playerMotionY = playerMaxMotion;
if (playerMotionY < -playerMaxMotion) playerMotionY = -playerMaxMotion;
//move the player
player.setX(player.x + playerMotionX);
player.setY(player.y + playerMotionY);
}
//when the game is ready, load the "zombie" sprite
fn onReady() {
loadSprite("zombie", "assets/parvati.png", 32, 32);
@@ -21,14 +60,17 @@ fn onReady() {
spawnActorAt("zombie", wander, 250, 500);
spawnActorAt("zombie", wander, 500, 250);
spawnActorAt("zombie", wander, 500, 500);
//spawn the player
loadSprite("player", "assets/parvati.png", 32, 32);
spawnActorAt("player", playerOnFrame, 300, 300);
}
fn onFrame() {
//debug
print counter;
// print Keyboard.A;
}
//example API for the game
initScreen(1280, 720, "Oh no, Zombies!");
initLoop(onReady, null, null);
initLoop(onReady, onFrame, null);
+2
View File
@@ -4,6 +4,8 @@
#LIBS+=-lm
#LDFLAGS+=
#TODO: add debug & release build options, at least for Toy
#directories
export VAMP_SOURCEDIR=source
export VAMP_OUTDIR=out
+4 -102
View File
@@ -13,7 +13,7 @@
static Toy_Table* spriteTable = NULL;
static Toy_Array* actorArray = NULL;
//callbacks
//API bindings
static void api_loadSprite(Toy_VM* vm, Toy_FunctionNative* self) {
//key, file, width, height -> null
(void)self;
@@ -165,6 +165,7 @@ static void api_spawnActorAt(Toy_VM* vm, Toy_FunctionNative* self) {
//finally, store the new actor's data
(*newActorPtr) = (ActorData){
.type = OPAQUE_ACTOR,
.sprite = (SpriteData*)(TOY_VALUE_AS_OPAQUE(spriteValue)),
.onStep = onStep,
.position = { TOY_VALUE_AS_INTEGER(xpos), TOY_VALUE_AS_INTEGER(ypos) },
@@ -309,100 +310,7 @@ void drawActors(Toy_VM* vm) {
}
}
//accessors & mutators
void loadSprite(Toy_Bucket** bucketHandle, Toy_Value key, const char* fname, int width, int height) {
//key, file, width, height -> null
if (!IsWindowReady()) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't load actor sprites before the window has been initialized" TOY_CC_RESET "\n");
return;
}
//check for initialization
if (spriteTable == NULL || actorArray == NULL) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Object pool for actor system hasn't been initialized" TOY_CC_RESET "\n");
return;
}
//check for overwriting the key
if ( TOY_VALUE_IS_NULL(Toy_lookupTable(&spriteTable, key)) != true ) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't overwrite existing actor sprite key" TOY_CC_RESET "\n");
Toy_freeValue(key);
return;
}
//create the sprite stored in the bucket
SpriteData* sprite = (SpriteData*)Toy_partitionBucket(bucketHandle, sizeof(SpriteData));
sprite->rect = (Rectangle){ 0, 0, width, height };
//load the texture from a file
sprite->texture = LoadTexture(fname);
//insert into the table as an opaque
Toy_insertTable(&spriteTable, Toy_copyValue(bucketHandle, key), TOY_OPAQUE_FROM_POINTER(sprite));
}
ActorData* spawnActorAt(Toy_Bucket** bucketHandle, Toy_Value key, Toy_Function* onStep, int xpos, int ypos) {
//sprite, onStep, x, y -> void
//check for initialization
if (spriteTable == NULL || actorArray == NULL) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Object pool for actor system hasn't been initialized" TOY_CC_RESET "\n");
return NULL;
}
//get the sprite
Toy_Value spriteValue = Toy_lookupTable(&spriteTable, key);
if (TOY_VALUE_IS_NULL(spriteValue)) {
Toy_String* string = Toy_stringifyValue(bucketHandle, key);
char* cstr = Toy_getStringRaw(string);
fprintf(stderr, TOY_CC_ERROR "ERROR: Can't spawn a actor with a non-existant sprite '%s'" TOY_CC_RESET "\n", cstr);
free(cstr);
Toy_freeString(string);
Toy_freeValue(key);
return NULL;
}
//expand the array if needed
if (actorArray->count == actorArray->capacity) {
actorArray = Toy_resizeArray(actorArray, actorArray->capacity * TOY_ARRAY_EXPANSION_RATE);
//set the new entries to null values
for (unsigned int i = actorArray->count; i < actorArray->capacity; i++) {
actorArray->data[i] = TOY_VALUE_FROM_NULL();
}
}
//find an existing spot for the new actor, overwriting a dead one if able
ActorData* newActorPtr = NULL;
for (unsigned int i = 0; i < actorArray->count; i++) {
ActorData* mData = (ActorData*)TOY_VALUE_AS_OPAQUE(actorArray->data[i]);
if (mData->enabled) { //if this actor is dead, steal the slot
newActorPtr = mData;
break;
}
}
//if no dead actors were found, make a new slot
if (newActorPtr == NULL) {
newActorPtr = (ActorData*)Toy_partitionBucket(bucketHandle, sizeof(ActorData));
actorArray->data[actorArray->count++] = TOY_OPAQUE_FROM_POINTER(newActorPtr);
}
//finally, store the new actor's data
(*newActorPtr) = (ActorData){
.sprite = (SpriteData*)(TOY_VALUE_AS_OPAQUE(spriteValue)),
.onStep = onStep,
.position = { xpos, ypos },
.enabled = true,
};
Toy_freeValue(spriteValue);
Toy_freeValue(key);
return newActorPtr;
}
//opaque handler
//opaque bindings
static void attr_actorSetX(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self;
@@ -440,12 +348,6 @@ Toy_Value handleActorAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attrib
return TOY_VALUE_FROM_NULL();
}
//check for correct types
if (!TOY_VALUE_IS_OPAQUE(compound) || !TOY_VALUE_IS_STRING(attribute) || TOY_VALUE_AS_STRING(attribute)->info.type != TOY_STRING_LEAF) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad parameters found in 'handleActorAttributes'" TOY_CC_RESET "\n");
return TOY_VALUE_FROM_NULL(); //do not free the params here
}
ActorData* actor = (ActorData*)TOY_VALUE_AS_OPAQUE(compound);
if (TOY_VALUE_AS_STRING(attribute)->info.length == 1 && strncmp(TOY_VALUE_AS_STRING(attribute)->leaf.data, "x", 1) == 0) {
@@ -465,7 +367,7 @@ Toy_Value handleActorAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attrib
else {
char buffer[256];
snprintf(buffer, 256, "Unknown attribute '%s' of type ActorData (an Opaque)", TOY_VALUE_AS_STRING(attribute)->leaf.data);
snprintf(buffer, 256, "Unknown attribute '%s' of 'ActorData'", TOY_VALUE_AS_STRING(attribute)->leaf.data);
Toy_error(buffer);
return TOY_VALUE_FROM_NULL();
}
+4 -5
View File
@@ -1,5 +1,6 @@
#pragma once
#include "opaque_type.h"
#include "toy_vm.h"
#include "toy_function.h"
#include "raylib.h"
@@ -13,21 +14,19 @@ typedef struct SpriteData {
//Actors loaded from scripts
typedef struct ActorData {
OpaqueType type;
SpriteData* sprite;
Toy_Function* onStep;
Vector2 position;
bool enabled;
} ActorData;
//object pool system
//opaque API
void initActorAPI(Toy_VM* vm);
void freeActorAPI(Toy_VM* vm);
void processActors(Toy_VM* vm);
void drawActors(Toy_VM* vm);
void loadSprite(Toy_Bucket** bucketHandle, Toy_Value key, const char* fname, int width, int height);
ActorData* spawnActorAt(Toy_Bucket** bucketHandle, Toy_Value key, Toy_Function* onStep, int xpos, int ypos);
//opaque hook
//opaque binding
Toy_Value handleActorAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
+140
View File
@@ -0,0 +1,140 @@
#include "keyboard.h"
#include "string.h"
KeyboardMap keyboardMap[] = {
{KEY_NULL, ""},
{KEY_APOSTROPHE, "APOSTROPHE"},
{KEY_COMMA, "COMMA"},
{KEY_MINUS, "MINUS"},
{KEY_PERIOD, "PERIOD"},
{KEY_SLASH, "SLASH"},
{KEY_ZERO, "ZERO"},
{KEY_ONE, "ONE"},
{KEY_TWO, "TWO"},
{KEY_THREE, "THREE"},
{KEY_FOUR, "FOUR"},
{KEY_FIVE, "FIVE"},
{KEY_SIX, "SIX"},
{KEY_SEVEN, "SEVEN"},
{KEY_EIGHT, "EIGHT"},
{KEY_NINE, "NINE"},
{KEY_SEMICOLON, "SEMICOLON"},
{KEY_EQUAL, "EQUAL"},
{KEY_A, "A"},
{KEY_B, "B"},
{KEY_C, "C"},
{KEY_D, "D"},
{KEY_E, "E"},
{KEY_F, "F"},
{KEY_G, "G"},
{KEY_H, "H"},
{KEY_I, "I"},
{KEY_J, "J"},
{KEY_K, "K"},
{KEY_L, "L"},
{KEY_M, "M"},
{KEY_N, "N"},
{KEY_O, "O"},
{KEY_P, "P"},
{KEY_Q, "Q"},
{KEY_R, "R"},
{KEY_S, "S"},
{KEY_T, "T"},
{KEY_U, "U"},
{KEY_V, "V"},
{KEY_W, "W"},
{KEY_X, "X"},
{KEY_Y, "Y"},
{KEY_Z, "Z"},
{KEY_LEFT_BRACKET, "LEFT_BRACKET"},
{KEY_BACKSLASH, "BACKSLASH"},
{KEY_RIGHT_BRACKET, "RIGHT_BRACKET"},
{KEY_GRAVE, "GRAVE"},
{KEY_SPACE, "SPACE"},
{KEY_ESCAPE, "ESCAPE"},
{KEY_ENTER, "ENTER"},
{KEY_TAB, "TAB"},
{KEY_BACKSPACE, "BACKSPACE"},
{KEY_INSERT, "INSERT"},
{KEY_DELETE, "DELETE"},
{KEY_RIGHT, "RIGHT"},
{KEY_LEFT, "LEFT"},
{KEY_DOWN, "DOWN"},
{KEY_UP, "UP"},
{KEY_PAGE_UP, "PAGE_UP"},
{KEY_PAGE_DOWN, "PAGE_DOWN"},
{KEY_HOME, "HOME"},
{KEY_END, "END"},
{KEY_CAPS_LOCK, "CAPS_LOCK"},
{KEY_SCROLL_LOCK, "SCROLL_LOCK"},
{KEY_NUM_LOCK, "NUM_LOCK"},
{KEY_PRINT_SCREEN, "PRINT_SCREEN"},
{KEY_PAUSE, "PAUSE"},
{KEY_F1, "F1"},
{KEY_F2, "F2"},
{KEY_F3, "F3"},
{KEY_F4, "F4"},
{KEY_F5, "F5"},
{KEY_F6, "F6"},
{KEY_F7, "F7"},
{KEY_F8, "F8"},
{KEY_F9, "F9"},
{KEY_F10, "F10"},
{KEY_F11, "F11"},
{KEY_F12, "F12"},
{KEY_LEFT_SHIFT, "LEFT_SHIFT"},
{KEY_LEFT_CONTROL, "LEFT_CONTROL"},
{KEY_LEFT_ALT, "LEFT_ALT"},
{KEY_LEFT_SUPER, "LEFT_SUPER"},
{KEY_RIGHT_SHIFT, "RIGHT_SHIFT"},
{KEY_RIGHT_CONTROL, "RIGHT_CONTROL"},
{KEY_RIGHT_ALT, "RIGHT_ALT"},
{KEY_RIGHT_SUPER, "RIGHT_SUPER"},
{KEY_KB_MENU, "KB_MENU"},
{KEY_KP_0, "KP_0"},
{KEY_KP_1, "KP_1"},
{KEY_KP_2, "KP_2"},
{KEY_KP_3, "KP_3"},
{KEY_KP_4, "KP_4"},
{KEY_KP_5, "KP_5"},
{KEY_KP_6, "KP_6"},
{KEY_KP_7, "KP_7"},
{KEY_KP_8, "KP_8"},
{KEY_KP_9, "KP_9"},
{KEY_KP_DECIMAL, "KP_DECIMAL"},
{KEY_KP_DIVIDE, "KP_DIVIDE"},
{KEY_KP_MULTIPLY, "KP_MULTIPLY"},
{KEY_KP_SUBTRACT, "KP_SUBTRACT"},
{KEY_KP_ADD, "KP_ADD"},
{KEY_KP_ENTER, "KP_ENTER"},
{KEY_KP_EQUAL, "KP_EQUAL"},
{KEY_BACK, "BACK"},
{KEY_MENU, "MENU"},
{KEY_VOLUME_UP, "VOLUME_UP"},
{KEY_VOLUME_DOWN, "VOLUME_DOWN"},
{0, NULL},
};
KeyboardData keyboardData = { //NOTE: it is just a dummy struct right now so the API looks nice
.type = OPAQUE_KEYBOARD,
};
Toy_Value handleKeyboardAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
(void)vm;
(void)compound;
Toy_String* string = TOY_VALUE_AS_STRING(attribute);
const char* cstr = string->leaf.data;
//find the mapped value, if available
for (KeyboardMap* ptr = keyboardMap; ptr->cstr != NULL; ptr++) {
if (strlen(ptr->cstr) == strlen(cstr) && strncmp(cstr, ptr->cstr, strlen(ptr->cstr)) == 0) {
bool result = IsKeyPressed(ptr->raykey);
return TOY_VALUE_FROM_BOOLEAN(result);
}
}
//unknown key
return TOY_VALUE_FROM_NULL();
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "opaque_type.h"
#include "toy_vm.h"
#include "raylib.h"
//wraps raylib's 'KeyboardKey' enum to a c-string
typedef struct KeyboardMap {
int raykey;
char* cstr;
} KeyboardMap;
extern KeyboardMap keyboardMap[];
//keyboard opaque
typedef struct KeyboardData {
OpaqueType type;
} KeyboardData;
extern KeyboardData keyboardData;
Toy_Value handleKeyboardAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
+37 -9
View File
@@ -8,12 +8,15 @@
#include "toy_vm.h"
#include "toy_attributes.h"
#include "keyboard.h"
#include "actor.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// #include "bytecode_inspector.h"
//utils
unsigned char* readFile(char* path, int* size) {
//open the file
@@ -43,7 +46,7 @@ unsigned char* readFile(char* path, int* size) {
return NULL;
}
buffer[(*size)++] = '\0';
buffer[(*size)] = '\0';
//clean up and return
fclose(file);
@@ -144,7 +147,31 @@ void api_initLoop(Toy_VM* vm, Toy_FunctionNative* self) {
}
}
//game API tools
//opaque dispatch
Toy_Value dispatchOpaqueAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
//check for correct types
if (!TOY_VALUE_IS_OPAQUE(compound) || !TOY_VALUE_IS_STRING(attribute) || TOY_VALUE_AS_STRING(attribute)->info.type != TOY_STRING_LEAF) {
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad parameters found in 'handleOpaqueAttributes'" TOY_CC_RESET "\n");
return TOY_VALUE_FROM_NULL(); //do not free the params here
}
//assume the first byte is the type
OpaqueType* type = (OpaqueType*)TOY_VALUE_AS_OPAQUE(compound);
switch(*type) {
case OPAQUE_KEYBOARD:
return handleKeyboardAttributes(vm, compound, attribute);
case OPAQUE_ACTOR:
return handleActorAttributes(vm, compound, attribute);
default:
fprintf(stderr, TOY_CC_ERROR "ERROR: Bad opaque type found in 'handleOpaqueAttributes'" TOY_CC_RESET "\n");
return TOY_VALUE_FROM_NULL(); //do not free the params here
}
}
//API tools
typedef struct CallbackPairs {
const char* name;
Toy_nativeCallback callback;
@@ -169,6 +196,11 @@ void initGameAPI(Toy_VM* vm) {
Toy_declareScope(vm->scope, key, TOY_VALUE_FUNCTION, TOY_VALUE_FROM_FUNCTION(fn), true);
Toy_freeString(key);
}
//declare keyboard opaque
Toy_String* keyboardString = Toy_toString(&vm->memoryBucket, "Keyboard");
Toy_declareScope(vm->scope, keyboardString, TOY_VALUE_OPAQUE, TOY_OPAQUE_FROM_POINTER(&keyboardData), true);
Toy_freeString(keyboardString);
}
//main file
@@ -191,7 +223,9 @@ int main() {
initGameAPI(&vm);
initActorAPI(&vm);
Toy_setOpaqueAttributeHandler(handleActorAttributes);
Toy_setOpaqueAttributeHandler(dispatchOpaqueAttributes);
// inspect_bytecode(entryCode);
Toy_runVM(&vm);
Toy_resetVM(&vm, false, false); //leave in a valid, but unset state
@@ -209,12 +243,6 @@ int main() {
}
while (!WindowShouldClose()) {
//TODO: player input
// if (IsKeyDown(KEY_UP)) player.position.y -= 5.0f;
// if (IsKeyDown(KEY_DOWN)) player.position.y += 5.0f;
// if (IsKeyDown(KEY_LEFT)) player.position.x -= 5.0f;
// if (IsKeyDown(KEY_RIGHT)) player.position.x += 5.0f;
//process the actors (if possible)
processActors(&vm);
+11
View File
@@ -0,0 +1,11 @@
#pragma once
//always first member of any opaques
typedef enum OpaqueType {
OPAQUE_KEYBOARD,
OPAQUE_ACTOR,
} OpaqueType;
typedef struct DummyOpaque {
OpaqueType type;
} DummyOpaque;