A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Santos
Party member
Posts: 383
Joined: Sat Oct 22, 2011 7:37 am

A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by Santos » Thu Oct 20, 2016 12:28 pm

A while ago I was thinking about what the least amount of stuff you could have in a game engine is and still be able to make some simple games with it.

I made something in C which uses Lua 5.3 and SDL2, SDL_image, SDL_ttf, and SDL_mixer. It's around 600 lines of code.

It's written badly and I have no idea how to use the Lua and SDL APIs or program in C, and it probably has lots of bugs (for example I don't think loop works), and the API would be better if (among other things) it was all in a table instead of global, and I don't recommend anyone actually use it, but because there's not that much code I thought it might be interesting to read for someone out there, maybe.

Usage

engine.exe game.lua

Callbacks

Code: Select all

frame() -- Called 60 times per second, I think
down(string) -- When a key or mouse button is pressed
up(string) -- When a key or mouse button is released
Window settings

Code: Select all

width = number
height = number
scale = number -- Window and graphics scale up by this number
Input

Code: Select all

boolean = isDown(string) -- True if given key or mouse button is down
number = mouseX()
number = mouseY()
Drawing

Code: Select all

image(path, x, y, angle, sx, sy, ox, oy, r, g, b, a)
line(x1, y1, x2, y2, r, g, b, a)
rectangle(x, y, width, height, r, g, b, a)
text(message, fontPath, fontSize, x, y, r, g, b, a)
Sound

Code: Select all

sound(path)
loop(path)
stopLoop()
Example

Code: Select all

width = 400
height = 300
scale = 2

player = {
    x = 0,
    y = 0,
}

function frame()
    if isDown('right') then
        player.x = player.x + 1
    end
    if isDown('mouseLeft') then
        player.y = player.y + 1
    end
    image('player.png', player.x, player.y)
end

function down(input)
    if input == 'space' then
        player.x = 0
    end
end
example.c

Code: Select all

#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_mixer.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

SDL_Texture *images[1000];
char *image_filenames[1000];
int image_count = 0;

TTF_Font *fonts[1000];
char *font_filenames[1000];
int font_sizes[1000];
int font_count = 0;

Mix_Chunk *sounds[1000];
char *sound_filenames[1000];
int sound_count = 0;

Mix_Music *musics[1000];
char *music_filenames[1000];
int music_count = 0;

int width;
int height;
int scale;

SDL_Window *window;
SDL_Renderer *renderer;

int key_count = 84;
char *keys[] = {"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","1","2","3","4","5","6","7","8","9","0","return","backspace","tab","space","-","=","[","]","\\",";","'","`",",",".","/","capslock","f1","f2","f3","f4","f5","f6","f7","f8","f9","f10","f11","f12","scrolllock","pause","insert","home","pageup","delete","end","pagedown","right","left","down","up","lctrl","lshift","lalt","lgui","rctrl","rshift","ralt","rgui"};
SDL_Scancode scancodes[] = {SDL_SCANCODE_A,SDL_SCANCODE_B,SDL_SCANCODE_C,SDL_SCANCODE_D,SDL_SCANCODE_E,SDL_SCANCODE_F,SDL_SCANCODE_G,SDL_SCANCODE_H,SDL_SCANCODE_I,SDL_SCANCODE_J,SDL_SCANCODE_K,SDL_SCANCODE_L,SDL_SCANCODE_M,SDL_SCANCODE_N,SDL_SCANCODE_O,SDL_SCANCODE_P,SDL_SCANCODE_Q,SDL_SCANCODE_R,SDL_SCANCODE_S,SDL_SCANCODE_T,SDL_SCANCODE_U,SDL_SCANCODE_V,SDL_SCANCODE_W,SDL_SCANCODE_X,SDL_SCANCODE_Y,SDL_SCANCODE_Z,SDL_SCANCODE_1,SDL_SCANCODE_2,SDL_SCANCODE_3,SDL_SCANCODE_4,SDL_SCANCODE_5,SDL_SCANCODE_6,SDL_SCANCODE_7,SDL_SCANCODE_8,SDL_SCANCODE_9,SDL_SCANCODE_0,SDL_SCANCODE_RETURN,SDL_SCANCODE_BACKSPACE,SDL_SCANCODE_TAB,SDL_SCANCODE_SPACE,SDL_SCANCODE_MINUS,SDL_SCANCODE_EQUALS,SDL_SCANCODE_LEFTBRACKET,SDL_SCANCODE_RIGHTBRACKET,SDL_SCANCODE_BACKSLASH,SDL_SCANCODE_SEMICOLON,SDL_SCANCODE_APOSTROPHE,SDL_SCANCODE_GRAVE,SDL_SCANCODE_COMMA,SDL_SCANCODE_PERIOD,SDL_SCANCODE_SLASH,SDL_SCANCODE_CAPSLOCK,SDL_SCANCODE_F1,SDL_SCANCODE_F2,SDL_SCANCODE_F3,SDL_SCANCODE_F4,SDL_SCANCODE_F5,SDL_SCANCODE_F6,SDL_SCANCODE_F7,SDL_SCANCODE_F8,SDL_SCANCODE_F9,SDL_SCANCODE_F10,SDL_SCANCODE_F11,SDL_SCANCODE_F12,SDL_SCANCODE_SCROLLLOCK,SDL_SCANCODE_PAUSE,SDL_SCANCODE_INSERT,SDL_SCANCODE_HOME,SDL_SCANCODE_PAGEUP,SDL_SCANCODE_DELETE,SDL_SCANCODE_END,SDL_SCANCODE_PAGEDOWN,SDL_SCANCODE_RIGHT,SDL_SCANCODE_LEFT,SDL_SCANCODE_DOWN,SDL_SCANCODE_UP,SDL_SCANCODE_LCTRL,SDL_SCANCODE_LSHIFT,SDL_SCANCODE_LALT,SDL_SCANCODE_LGUI,SDL_SCANCODE_RCTRL,SDL_SCANCODE_RSHIFT,SDL_SCANCODE_RALT,SDL_SCANCODE_RGUI};

void error (const char *string)
{
    printf("Error: %s\n", string);
    exit(1);
}

int isDown (lua_State *L)
{
    const Uint8 *state = SDL_GetKeyboardState(NULL);

    if (!lua_isstring(L, 1)) {
        error("isDown: needs string");
    }

    const char *input = lua_tostring(L, 1);
    int boolean = 0;

    if (
        (strcmp(input, "mouseLeft") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_LEFT)) ||
        (strcmp(input, "mouseMiddle") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_MIDDLE)) ||
        (strcmp(input, "mouseRight") == 0 && SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT))
    ) {
        boolean = 1;
    }

    for (int i = 0; i < key_count; i++) {
        if (strcmp(input, keys[i]) == 0) {
            if (state[scancodes[i]]) {
                boolean = 1;
                break;
            }
        }
    }

    lua_pushboolean(L, boolean);
    return 1;
}

int mouseX (lua_State *L)
{
    int x;
    SDL_GetMouseState(&x, NULL);
    lua_pushnumber(L, x/scale);
    return 1;
}

int mouseY (lua_State *L)
{
    int y;
    SDL_GetMouseState(NULL, &y);
    lua_pushnumber(L, y/scale);
    return 1;
}

int get_number(lua_State *L, int x, int d)
{
    if (lua_isnoneornil(L, x)) {
        return d;
    } else {
        return lua_tonumber(L, x);
    }
}

int image (lua_State *L)
{
    if (!lua_isstring(L, 1)) error("image: filename is not a string");
    if (!lua_isnumber(L, 2) && !lua_isnoneornil(L, 2)) error("image: x is not a number");
    if (!lua_isnumber(L, 3) && !lua_isnoneornil(L, 3)) error("image: y is not a number");
    if (!lua_isnumber(L, 4) && !lua_isnoneornil(L, 4)) error("image: angle is not a number");
    if (!lua_isnumber(L, 5) && !lua_isnoneornil(L, 5)) error("image: sx is not a number");
    if (!lua_isnumber(L, 6) && !lua_isnoneornil(L, 6)) error("image: sy is not a number");
    if (!lua_isnumber(L, 7) && !lua_isnoneornil(L, 7)) error("image: ox is not a number");
    if (!lua_isnumber(L, 8) && !lua_isnoneornil(L, 8)) error("image: oy is not a number");
    if (!lua_isnumber(L, 9) && !lua_isnoneornil(L, 9)) error("image: r is not a number");
    if (!lua_isnumber(L, 10) && !lua_isnoneornil(L, 10)) error("image: g is not a number");
    if (!lua_isnumber(L, 11) && !lua_isnoneornil(L, 11)) error("image: b is not a number");
    if (!lua_isnumber(L, 12) && !lua_isnoneornil(L, 12)) error("image: a is not a number");
    
    const char *filename = lua_tostring(L, 1);
    int x = get_number(L, 2, 0);
    int y = get_number(L, 3, 0);
    double angle = get_number(L, 4, 0);
    double sx = get_number(L, 5, 1);
    double sy = get_number(L, 6, 1);
    int ox = get_number(L, 7, 0);
    int oy = get_number(L, 8, 0);
    int r = get_number(L, 9, 255);
    int g = get_number(L, 10, 255);
    int b = get_number(L, 11, 255);
    int a = get_number(L, 12, 255);

    SDL_Texture *texture = NULL;

    for (int i = 0; i < image_count; i++) {
        if (strcmp(filename, image_filenames[i]) == 0) {
            texture = images[i];
            break;
        }
    }

    if (!texture) {
        texture = IMG_LoadTexture(renderer, filename);
        if (!texture) {
            error(SDL_GetError());
        }
        images[image_count] = texture;
        image_filenames[image_count] = malloc(strlen(filename) + 1);
        strcpy(image_filenames[image_count], filename);
        image_count++;
    }

    SDL_Point center;
    center.x = ox;
    center.y = oy;

    SDL_Rect dst;
    dst.x = x;
    dst.y = y;

    SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h);
    dst.w *= sx;
    dst.h *= sy;

    SDL_SetTextureColorMod(texture, r, g, b);
    SDL_SetTextureAlphaMod(texture, a);

    SDL_RenderCopyEx(renderer, texture, NULL, &dst, angle, &center, SDL_FLIP_NONE);
}

int text (lua_State *L)
{
    const char *string = lua_tostring(L, 1);
    const char *filename = lua_tostring(L, 2);
    int size = lua_tonumber(L, 3);
    int x = lua_tonumber(L, 4);
    int y = lua_tonumber(L, 5);

    SDL_Color color;
    int a;
    if (!lua_isnumber(L, 6) || !lua_isnumber(L, 7) || !lua_isnumber(L, 8)) {
        color.r = 0;
        color.g = 0;
        color.b = 0;
        a = 255;
    } else {
        color.r = lua_tonumber(L, 6);
        color.g = lua_tonumber(L, 7);
        color.b = lua_tonumber(L, 8);
        if (!lua_isnumber(L, 9)) {
            a = 255;
        } else {
            a = lua_tonumber(L, 9);
        }
    }

    TTF_Font *font = NULL;
    for (int i = 0; i < font_count; i++) {
        if (strcmp(filename, font_filenames[i]) == 0 && font_sizes[i] == size) {
            font = fonts[i];
            break;
        }
    }

    if (!font) {
        font = TTF_OpenFont(filename, size);
        if (!font) {
            error("Couldn't open font");
        }
        fonts[font_count] = font;
        font_filenames[font_count] = malloc(strlen(filename) + 1);
        strcpy(font_filenames[font_count], filename);
        font_sizes[font_count] = size;
        font_count++;
    }

    SDL_Surface *surface = TTF_RenderText_Blended(font, string, color);
    SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);

    SDL_Rect dst;
    dst.x = x;
    dst.y = y;

    SDL_QueryTexture(texture, NULL, NULL, &dst.w, &dst.h);
    SDL_SetTextureAlphaMod(texture, a);
    SDL_RenderCopy(renderer, texture, NULL, &dst);
    SDL_FreeSurface(surface);
    SDL_DestroyTexture(texture);
}

int line (lua_State *L)
{
    if (!lua_isnumber(L, 1)) error("line: x1 is not a number");
    if (!lua_isnumber(L, 2)) error("line: y1 is not a number");
    if (!lua_isnumber(L, 3)) error("line: x2 is not a number");
    if (!lua_isnumber(L, 4)) error("line: y2 is not a number");

    int x1 = lua_tonumber(L, 1);
    int y1 = lua_tonumber(L, 2);
    int x2 = lua_tonumber(L, 3);
    int y2 = lua_tonumber(L, 4);

    int r;
    int g;
    int b;
    int a;

    if (!lua_isnumber(L, 5) || !lua_isnumber(L, 6) || !lua_isnumber(L, 7)) {
        r = 0;
        g = 0;
        b = 0;
        a = 255;
    } else {
        r = lua_tonumber(L, 5);
        g = lua_tonumber(L, 6);
        b = lua_tonumber(L, 7);
        if (!lua_isnumber(L, 8)) {
            a = 255;
        } else {
            a = lua_tonumber(L, 8);
        }
    }

    SDL_SetRenderDrawColor(renderer, r, g, b, a);
    SDL_RenderDrawLine(renderer, x1, y1, x2, y2);
}

int rectangle (lua_State *L)
{
    if (!lua_isnumber(L, 1)) error("rectangle: x is not a number");
    if (!lua_isnumber(L, 2)) error("rectangle: y is not a number");
    if (!lua_isnumber(L, 3)) error("rectangle: width is not a number");
    if (!lua_isnumber(L, 4)) error("rectangle: height is not a number");

    int x = lua_tonumber(L, 1);
    int y = lua_tonumber(L, 2);
    int w = lua_tonumber(L, 3);
    int h = lua_tonumber(L, 4);

    int r;
    int g;
    int b;
    int a;

    if (!lua_isnumber(L, 5) || !lua_isnumber(L, 6) || !lua_isnumber(L, 7)) {
        r = 0;
        g = 0;
        b = 0;
        a = 255;
    } else {
        r = lua_tonumber(L, 5);
        g = lua_tonumber(L, 6);
        b = lua_tonumber(L, 7);
        if (lua_isnoneornil(L, 8)) {
            a = 255;
        } else {
            a = lua_tonumber(L, 8);
        }
    }

    SDL_Rect rect;
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;

    SDL_SetRenderDrawColor(renderer, r, g, b, a);
    SDL_RenderFillRect(renderer, &rect);
}

int sound (lua_State *L)
{
    const char *filename = lua_tostring(L, 1);
    Mix_Chunk *chunk = NULL;

    for (int i = 0; i < sound_count; i++) {
        if (strcmp(filename, sound_filenames[i]) == 0) {
            chunk = sounds[i];
            break;
        }
    }

    if (!chunk) {
        chunk = Mix_LoadWAV(filename);
        if (!chunk) {
            error("sound: Couldn't open sound file");
        }
        sounds[sound_count] = chunk;
        sound_filenames[sound_count] = malloc(strlen(filename) + 1);
        strcpy(sound_filenames[sound_count], filename);
        sound_count++;
    }

    Mix_PlayChannel(-1, chunk, 0);
}

int loop (lua_State *L)
{
    Mix_HaltMusic();

    const char *filename = lua_tostring(L, 1);
    Mix_Music *music = NULL;

    for (int i = 0; i < music_count; i++) {
        if (strcmp(filename, music_filenames[i]) == 0) {
            music = musics[i];
            break;
        }
    }

    if (!music) {
        music = Mix_LoadMUS(filename);
        if (!music) {
            error("loop: Couldn't open file");
        }
        musics[music_count] = music;
        music_filenames[music_count] = malloc(strlen(filename) + 1);
        strcpy(music_filenames[music_count], filename);
        music_count++;
    }

    Mix_PlayMusic(music, -1);
}

int stopLoop (lua_State *L)
{
    Mix_HaltMusic();
}

int read (lua_State *L)
{
    const char *filename = lua_tostring(L, 1);
    SDL_RWops *rw = SDL_RWFromFile(filename, "r");
    char string[SDL_RWsize(rw) + 1];

    if (!rw) {
        error(SDL_GetError());
    }

    SDL_RWread(rw, string, 1, SDL_RWsize(rw));
    string[SDL_RWsize(rw)] = '\0';
    SDL_RWclose(rw);
    lua_pushstring(L, string);

    return 1;
}

int write (lua_State *L)
{
    const char *filename = lua_tostring(L, 1);
    const char *string = lua_tostring(L, 2);

    SDL_RWops *rw = SDL_RWFromFile(filename, "w");
    SDL_RWwrite(rw, string, 1, strlen(string));
    SDL_RWclose(rw);
}

int traceback (lua_State *L) {
    const char *message = lua_tostring(L, 1);

    if (message) {
        luaL_traceback(L, L, message, 1);
    } else if (!lua_isnoneornil(L, 1)) {
        if (!luaL_callmeta(L, 1, "__tostring")) {
            lua_pushliteral(L, "(no error message)");
        }
    }

    return 1;
}

void call_mouse_function (lua_State *L, char *g, char *l) {
    lua_getglobal(L, g);
    lua_pushstring(L, l);
    if (lua_pcall(L, 1, 0, 0) != LUA_OK) {
        lua_pop(L, 1);
    }
}

int call_key_function (lua_State *L, SDL_Event event, char *g) {
    for (int i = 0; i < key_count; i++) {
        if (event.key.keysym.scancode == scancodes[i]) {
            lua_pushcfunction(L, traceback);
            lua_getglobal(L, g);
            if (!lua_isfunction(L, -1)) {
                lua_pop(L, 2);
            } else {
                lua_pushstring(L, keys[i]);
                if (lua_pcall(L, 1, 0, -3)) {
                    printf("%s", lua_tostring(L, -1));
                    lua_pop(L, 2);
                } else {
                    lua_pop(L, 1);
                }
            }
        }
    }
}

int get_global (lua_State *L, char *g, int default_) {
    lua_getglobal(L, g);
    int number = lua_tonumber(L, -1);
    lua_pop(L, 1);
    if (number == 0) {
        return default_;
    } else {
        return number;
    }
}

int main (int argc, char **argv)
{
    lua_State *L = luaL_newstate();

    if (argc < 2) {
        printf("usage: engine.exe file.lua\n");
        return 1;
    }

    luaL_openlibs(L);
    lua_getglobal(L, "math");
    lua_getfield(L, -1, "randomseed");
    lua_pushnumber(L, time(NULL));
    lua_call(L, 1, 0);
    lua_getfield(L, -1, "random");
    lua_call(L, 0, 0);
    lua_getfield(L, -1, "random");
    lua_call(L, 0, 0);
    lua_pop(L, 1);

    lua_pushcfunction(L, isDown);
    lua_setglobal(L, "isDown");
    lua_pushcfunction(L, mouseX);
    lua_setglobal(L, "mouseX");
    lua_pushcfunction(L, mouseY);
    lua_setglobal(L, "mouseY");
    lua_pushcfunction(L, image);
    lua_setglobal(L, "image");
    lua_pushcfunction(L, text);
    lua_setglobal(L, "text");
    lua_pushcfunction(L, line);
    lua_setglobal(L, "line");
    lua_pushcfunction(L, rectangle);
    lua_setglobal(L, "rectangle");
    lua_pushcfunction(L, sound);
    lua_setglobal(L, "sound");
    lua_pushcfunction(L, loop);
    lua_setglobal(L, "loop");
    lua_pushcfunction(L, stopLoop);
    lua_setglobal(L, "stopLoop");
    lua_pushcfunction(L, read);
    lua_setglobal(L, "read");
    lua_pushcfunction(L, write);
    lua_setglobal(L, "write");

    SDL_assert(lua_gettop(L) == 0);

    lua_pushcfunction(L, traceback);

    if (luaL_loadfile(L, argv[1]) || lua_pcall(L, 0, 0, -2)) {
        printf("Cannot run script: %s", lua_tostring(L, -1));
        lua_pop(L, 2);
        return 1;
    }

    lua_pop(L, 1);
    SDL_assert(lua_gettop(L) == 0);

    lua_getglobal(L, "frame");
    if (!lua_isfunction(L, -1)) {
        return 1;
    }
    lua_pop(L, 1);

    width = get_global(L, "width", 500);
    height = get_global(L, "height", 500);
    scale = get_global(L, "scale", 1);

    SDL_Init(SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO);
    Mix_Init(MIX_INIT_MP3 | MIX_INIT_OGG);
    Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048);
    TTF_Init();

    window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width * scale, height * scale, SDL_WINDOW_SHOWN);
    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);

    SDL_RenderSetScale(renderer, scale, scale);
    SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);

    SDL_Event event;

    int start_time = SDL_GetTicks();
    int end_time;
    int delay;

    while (1) {
        while (SDL_PollEvent(&event)) {
            if (event.type == SDL_QUIT) {
                return 0;
            } else if (event.type == SDL_KEYDOWN && !event.key.repeat) {
                if (event.key.keysym.sym == SDLK_ESCAPE) {
                    return 0;
                }
                call_key_function(L, event, "down");
            } else if (event.type == SDL_KEYUP) {
                call_key_function(L, event, "up");
            } else if (event.type == SDL_MOUSEBUTTONDOWN) {
                if (event.button.button == SDL_BUTTON_LEFT) {
                    call_mouse_function(L, "down", "mouseLeft");
                } else if (event.button.button == SDL_BUTTON_MIDDLE) {
                    call_mouse_function(L, "down", "mouseMiddle");
                } else if (event.button.button == SDL_BUTTON_RIGHT) {
                    call_mouse_function(L, "down", "mouseRight");
                }
            } else if (event.type == SDL_MOUSEBUTTONUP) {
                if (event.button.button == SDL_BUTTON_LEFT) {
                    call_mouse_function(L, "up", "mouseLeft");
                } else if (event.button.button == SDL_BUTTON_MIDDLE) {
                    call_mouse_function(L, "up", "mouseMiddle");
                } else if (event.button.button == SDL_BUTTON_RIGHT) {
                    call_mouse_function(L, "up", "mouseRight");
                }
            } else if (event.type == SDL_MOUSEWHEEL) {
                if (event.wheel.y < 0) {
                    call_mouse_function(L, "down", "mouseWheelDown");
                } else if (event.wheel.y > 0) {
                    call_mouse_function(L, "down", "mouseWheelUp");
                }
            }
        }

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        SDL_RenderClear(renderer);
        SDL_assert(lua_gettop(L) == 0);
        lua_pushcfunction(L, traceback);
        lua_getglobal(L, "frame");
        if (lua_pcall(L, 0, 0, -2)) {
            printf("Cannot run script: %s", lua_tostring(L, -1));
            return 1;
        } else {
            lua_pop(L, 1);
            SDL_assert(lua_gettop(L) == 0);
            SDL_RenderPresent(renderer);
            end_time = SDL_GetTicks();
            delay = 33 - end_time + start_time;
            start_time = end_time;
            if (delay > 0) {
                SDL_Delay(delay);
            }
            SDL_assert(lua_gettop(L) == 0);
        }
    }

    SDL_assert(lua_gettop(L) == 0);
    return 0;
}
Last edited by Santos on Fri Oct 21, 2016 6:07 am, edited 4 times in total.

User avatar
ivan
Party member
Posts: 1524
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: A Lua game framework with 11 functions, 3 callbacks, and 3 settings

Post by ivan » Thu Oct 20, 2016 1:17 pm

I like the simplicity of it.
You could clean up the API a little bit,
lowering the number of arguments that are passed around, something like:

Code: Select all

color(r,g,b,a)
image(path,x,y,a,sx,sy,ox,oy)
rectangle(x,y,w,h)
line(x1,y1,x2,y2)
I don't see the point behind read/write since "Lua" has the "io" module.
Can you upload the executables so we can test it out?

Santos
Party member
Posts: 383
Joined: Sat Oct 22, 2011 7:37 am

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by Santos » Thu Oct 20, 2016 1:51 pm

I've attached the executable for Windows 64 bit.

I avoided "hidden state" which is why the color is passed to the functions.

Good point about the file operations, I removed them from the "documentation", but they're still there in the code, just in case someone wants to see how they can be done in SDL.

But I forgot about the "text" function so it's only down to 10! :P
Attachments
engine.zip
(2.44 MiB) Downloaded 81 times

User avatar
ivan
Party member
Posts: 1524
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by ivan » Thu Oct 20, 2016 3:39 pm

Good point about the file operations, I removed them from the "documentation", but they're still there in the code
A few actions that Lua's io module cannot perform because they are platform specific:
1.create directory, 2.list items in directory, 3.get the path of the private "appdata" directory 4.get file attributes like modification time (keep in mind that these may contain utf8 on Windows)
I avoided "hidden state" which is why the color is passed to the functions.
Sure, but there are practical considerations too. "draw" will be called a lot of times compared to other functions.

Thanks for the zip, but unfortunately I'm on a 32-bit system right now so I have no other feedback. :(

User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by kikito » Thu Oct 20, 2016 3:59 pm

I like the idea. Not particularly enthused about having the function in the global space - I would create a table and put the functions inside it. Something short, like "eros" or "lo". Or "sex".

Independently of that: you could reduce the number of functions by one if you unified both mouse functions:

Code: Select all

local x,y = mouse()
When I write def I mean function.

User avatar
josefnpat
Inner party member
Posts: 955
Joined: Wed Oct 05, 2011 1:36 am
Location: your basement
Contact:

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by josefnpat » Fri Oct 21, 2016 5:52 am

I like the minimal approach here! Maybe make a manifesto (e.g. what the goal of the project is) and push it on GitHub so folks can contribute?
Missing Sentinel Software | Twitter

FORCIBLY IGNORED.
<leafo> when in doubt delete all of your code
<bartbes> git rm -r *
<bartbes> git commit -m "Fixed all bugs"
<bartbes> git push

Santos
Party member
Posts: 383
Joined: Sat Oct 22, 2011 7:37 am

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by Santos » Fri Oct 21, 2016 6:56 am

ivan, that's interesting to know what the io module can and can't do. Also the color defaults to "white" (i.e. no modulation) for drawing images and black for shapes (the background is white), maybe you assumed this but I didn't make it obvious.

kikito, thanks for the suggestions. :D I also think the functions should be inside a table. Good point about the mouse functions... actually, now that I think about it, the frame callbacks could have the mouse position as arguments, like frame(mouseX, mouseY).

If we wanted to get really crazy, we could even do away with the down and up callbacks, and pass the frame functions a sequence of keyboard and mouse state changes, like frame(mouseX, mouseY, changes), and changes is a table which looks something like:

Code: Select all

{
    input = 'space', change = 'down',
    input = 'space', change = 'up',
    input = 'leftMouse', change = 'down',
}
Actually you could even have the current input state as a table too, with the input types as keys and whether they are down or not (and the mouse position) as values, like frame(state, changes), and state could look like:

Code: Select all

{
    mouseX = 345,
    mouseY = 234,
    mouseLeft = true,
    mouseRight = false,
    a = false,
    b = false,
    c = false,
    -- etc.
}
Then you'd have 1 callback and no input functions.

I'm not sure if this makes things simpler though. :D

josefnpat, I would do that if I didn't think the project is useless. :D

My original goal of the project was to see if I could make something suitable for beginners, in that there are not many functions you would need to remember (there's something kinda nice about "being able to hold the whole thing in your head", maybe especially for beginners?), and you don't have to worry about hidden state, and you don't have to understand how dt works in love.update.

But, then I thought that it's way more important for beginners to have access to a debugger (like ZeroBrane Studio for LOVE), and maybe I could get debugging support for it, but even then, it's not that much simpler than LOVE (except for the "holding the whole thing in your head" part), and I think a lack of quality tutorials/examples/learning resources/expert guidance is probably the real "bottleneck" for beginners, and something like Scratch seems like way more fun for beginners, so I decided this project was useless.

I guess the goal of it now is just to be interesting to read if anyone wants to see the SDL and Lua APIs in action.

That said, the code is public domain as far as I'm concerned (I stole the traceback code from Lua though), so if anyone wants to put it up on GitHub or do anything else with it, go for it.

User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by kikito » Fri Oct 21, 2016 11:05 am

Actually you could even have the current input state as a table too, with the input types as keys and whether they are down or not (and the mouse position) as values.
I like that. I can think of several simplifications.

I would personally use two input tables, with just what is "active". Like this:

Code: Select all

function frame(mouse, keyboard)
The mouse table would look like this - it would contain only which buttons are pressed:

Code: Select all

{ x = 100, y = 100, left = true }
And the keyboard table would look like this - containing only which keys are pressed:

Code: Select all

{ a = true }
So, to see if a key is pressed:

Code: Select all

if keyboard[' '] then print("space is pressed") end
Since keyboard[' '] returns nil when space isn't pressed, you don't need to pass 'false' to it.

I think if you really want to go minimal, you don't need to provide the "key released" information. If someone wants to detect that, implementing their own state thing is quite easy. So you would not need a "changes" table.

I also think you should put this on github somewhere, even if you think it isn't worth it. I've lots of projects on github that I have basically abandoned. But putting them there makes sure that they don't disappear when my computer hard drive fails (or a switch to a new computer). People can see it easily, fork it, etc. Also, it's nice that you are ok with other people putting it on github, but if you do it you will appear as the original author, which is nice. If someone else uploads it, it can be seen as they trying to "appropiate" what you created. Once you do that, people can create their own forks, but github will say "this is a fork of this", pointing to yours, so there will be no possible mistake about who started it.
When I write def I mean function.

Santos
Party member
Posts: 383
Joined: Sat Oct 22, 2011 7:37 am

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by Santos » Wed Oct 26, 2016 12:57 am

I like the simplifications.

If a key/mouse button was pressed and released within a frame the "changes" information would still be needed. Similarly, if, for example, the left arrow then the right arrow was pressed within a frame, or the other way around with the right arrow then the left arrow, the order of these might be important for the game, but wouldn't be able to be detected.

But, I wonder how low the FPS would have to be for this to be possible.

You make good points about why I should put this on GitHub, but I don't think I'd be good at maintaining it, and plus I don't even know which contributions I would accept because I don't know what the project is about (It could be code to demonstrate how the SDL and Lua APIs work, or simple to use and suitable for beginners, or trying to have as little code as possible, or trying to have as little callbacks/functions in the API as possible, or something else, or a mix of the above).

I do get that people could feel awkward about potentially being seen as, like you say, "appropriating" it though. I guess I'll just say "please don't feel awkward about this, I really don't mind at all!" :D


I have an idea for the name of the main table: the capital letter "E", so E.frame, E.image, E.sound, etc.

"Why is the letter E in front of everything?"

"So you can program with ease."






:awesome:

User avatar
ivan
Party member
Posts: 1524
Joined: Fri Mar 07, 2008 1:39 pm
Contact:

Re: A Lua "game framework" with 10 functions, 3 callbacks, and 3 settings

Post by ivan » Wed Oct 26, 2016 11:40 am

I agree with kikito - you only really need the "down" and "up" callbacks in Lua,
and you can decide in Lua if you want to cache the state of the keyboard

Code: Select all

state = {}
function down(key)
  state[key] = true
end
function up(key)
  state[key] = false
end
or buffer it:

Code: Select all

buffer = {}
function down(key)
  buffer[#buffer + 1] = { k = key, s = 'down' }
end
function up(key)
  buffer[#buffer + 1] = { k = key, s = 'up' }
end
You can also pass the sdl scancodes directly instead of using strings. This would make the C code simpler - and will allow users to define their own strings to represent the scancodes in Lua (something like: "if isDown(scancodes.up) then").
Santos wrote:If a key/mouse button was pressed and released within a frame the "changes" information would still be needed. Similarly, if, for example, the left arrow then the right arrow was pressed within a frame, or the other way around with the right arrow then the left arrow, the order of these might be important for the game, but wouldn't be able to be detected.
In practice this is almost never an issue in games - but if you *really* want to preserve the order of events then buffering is the way to go.

Post Reply

Who is online

Users browsing this forum: No registered users and 16 guests