diff --git a/docs/game.adoc b/docs/game.adoc index 40aabb5..eb7acdf 100644 --- a/docs/game.adoc +++ b/docs/game.adoc @@ -32,6 +32,24 @@ associated script file can access. While playing ... * F7 Stop +.Level model +The game world is made up of objects. Levels are collections of +objects. The topmost level is called "World". Objects are spawned into +a specific level. If none are explicitly given, objects are spawned +into World. Objects in turn are made up of components - sprites, +colliders, and so on. Accessing an object might go like this: + +World.level1.enemy1.sprite.path = "brick.png"; + +To set the image of enemy1 in level 1's sprite. + +.Textures & images +A sprite is a display of a specific texture in the game world. The +underlying texture has values associated with it, like how it should +be rendered: is it a sprite, is it a texture, does it have mipmaps, +etc. Textures are all file based, and are only accessed through the +explicit path to their associated image file. + .Scripting Levels and objects have certain functions you can use that will be diff --git a/source/engine/2dphysics.c b/source/engine/2dphysics.c index 329c477..a2a7a82 100644 --- a/source/engine/2dphysics.c +++ b/source/engine/2dphysics.c @@ -41,8 +41,8 @@ void set_cat_mask(int cat, unsigned int mask) void color2float(struct color color, float *fcolor) { fcolor[0] = (float)color.r/255; - fcolor[1] = (float)color.b/255; - fcolor[2] = (float)color.g/255; + fcolor[1] = (float)color.g/255; + fcolor[2] = (float)color.b/255; } struct color float2color(float *fcolor) diff --git a/source/engine/ffi.c b/source/engine/ffi.c index 5e82c66..6c4fe86 100644 --- a/source/engine/ffi.c +++ b/source/engine/ffi.c @@ -65,6 +65,11 @@ double js2number(JSValue v) return g; } +int js2bool(JSValue v) +{ + return JS_ToBool(js,v); +} + JSValue float2js(double g) { return JS_NewFloat64(js,g); @@ -931,6 +936,23 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv) case 93: ret = int2js(logLevel); + break; + + case 94: + str = JS_ToCString(js,argv[1]); + texture_pullfromfile(str)->opts.mips = js2bool(argv[2]); + texture_sync(str); + break; + + case 95: + str = JS_ToCString(js,argv[1]); + texture_pullfromfile(str)->opts.sprite = js2bool(argv[2]); + texture_sync(str); + break; + + case 96: + color2float(js2color(argv[2]), id2sprite(js2int(argv[1]))->color); + break; } if (str) @@ -1237,7 +1259,6 @@ JSValue duk_make_sprite(JSContext *js, JSValueConst this, int argc, JSValueConst JS_FreeCString(js,path); return JS_NewInt64(js, sprite); - } /* Make anim from texture */ diff --git a/source/engine/input.c b/source/engine/input.c index b916c4a..9d04a7c 100644 --- a/source/engine/input.c +++ b/source/engine/input.c @@ -189,6 +189,7 @@ void joystick_cb(int jid, int event) JSValue jsgamepadstr[15]; JSValue jsaxesstr[4]; +JSValue jsaxis; void input_init() { @@ -205,10 +206,11 @@ void input_init() for (int b = 0; b < 15; b++) jsgamepadstr[b] = str2js(gamepad2str(b)); - jsaxesstr[0] = str2js("axis_ljoy"); - jsaxesstr[1] = str2js("axis_rjoy"); - jsaxesstr[2] = str2js("axis_ltrigger"); - jsaxesstr[3] = str2js("axis_rtrigger"); + jsaxesstr[0] = str2js("ljoy"); + jsaxesstr[1] = str2js("rjoy"); + jsaxesstr[2] = str2js("ltrigger"); + jsaxesstr[3] = str2js("rtrigger"); + jsaxis = str2js("axis"); /* Grab all joysticks initially present */ for (int i = 0; i < 16; i++) @@ -397,7 +399,7 @@ void input_poll(double wait) GLFWgamepadstate state; if (!glfwGetGamepadState(joysticks[i].id, &state)) continue; - JSValue argv[3]; + JSValue argv[4]; argv[0] = num_cache[joysticks[i].id]; for (int b = 0; b < 15; b++) { argv[1] = jsgamepadstr[b]; @@ -417,30 +419,37 @@ void input_poll(double wait) } } + argv[2] = jsaxis; + + float deadzone = 0.05; + + for (int i = 0; i < 4; i++) + state.axes[i] = fabs(state.axes[i]) > deadzone ? state.axes[i] : 0; + argv[1] = jsaxesstr[0]; cpVect v; v.x = state.axes[0]; v.y = -state.axes[1]; - argv[2] = vec2js(v); - script_callee(gamepad_callee,3,argv); - JS_FreeValue(js, argv[2]); + argv[3] = vec2js(v); + script_callee(gamepad_callee,4,argv); + JS_FreeValue(js, argv[3]); argv[1] = jsaxesstr[1]; v.x = state.axes[2]; v.y = -state.axes[3]; - argv[2] = vec2js(v); - script_callee(gamepad_callee,3,argv); - JS_FreeValue(js, argv[2]); + argv[3] = vec2js(v); + script_callee(gamepad_callee,4,argv); + JS_FreeValue(js, argv[3]); argv[1] = jsaxesstr[2]; - argv[2] = num2js((state.axes[4]+1)/2); - script_callee(gamepad_callee,3,argv); - JS_FreeValue(js, argv[2]); + argv[3] = num2js((state.axes[4]+1)/2); + script_callee(gamepad_callee,4,argv); + JS_FreeValue(js, argv[3]); argv[1] = jsaxesstr[3]; - argv[2] = num2js((state.axes[5]+1)/2); - script_callee(gamepad_callee,3,argv); - JS_FreeValue(js, argv[2]); + argv[3] = num2js((state.axes[5]+1)/2); + script_callee(gamepad_callee,4,argv); + JS_FreeValue(js, argv[3]); joysticks[i].state = state; } diff --git a/source/engine/sprite.c b/source/engine/sprite.c index 01cec10..5cac0db 100644 --- a/source/engine/sprite.c +++ b/source/engine/sprite.c @@ -122,7 +122,7 @@ void sprite_initialize() glEnableVertexAttribArray(0); } -void tex_draw(struct Texture *tex, float pos[2], float angle, float size[2], float offset[2], struct glrect r) { +void tex_draw(struct Texture *tex, float pos[2], float angle, float size[2], float offset[2], struct glrect r, mfloat_t color[3]) { mfloat_t model[16] = { 0.f }; mfloat_t r_model[16] = { 0.f }; mfloat_t s_model[16] = { 0.f }; @@ -142,9 +142,8 @@ void tex_draw(struct Texture *tex, float pos[2], float angle, float size[2], flo mat4_translate_vec2(model, pos); - float white[3] = { 1.f, 1.f, 1.f }; shader_setmat4(spriteShader, "model", model); - shader_setvec3(spriteShader, "spriteColor", white); + shader_setvec3(spriteShader, "spriteColor", color); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, tex->id); @@ -173,7 +172,7 @@ void sprite_draw(struct sprite *sprite) cpVect cpos = cpBodyGetPosition(go->body); float pos[2] = {cpos.x, cpos.y}; float size[2] = { sprite->size[0] * go->scale * go->flipx, sprite->size[1] * go->scale * go->flipy }; - tex_draw(sprite->tex, pos, cpBodyGetAngle(go->body), size, sprite->pos, sprite->frame); + tex_draw(sprite->tex, pos, cpBodyGetAngle(go->body), size, sprite->pos, sprite->frame, sprite->color); } } @@ -190,7 +189,8 @@ void gui_draw_img(const char *img, float x, float y) { float pos[2] = {x, y}; float size[2] = {1.f, 1.f}; float offset[2] = { 0.f, 0.f }; - tex_draw(tex, pos, 0.f, size, offset, tex_get_rect(tex)); + float white[3] = {1.f,1.f,1.f}; + tex_draw(tex, pos, 0.f, size, offset, tex_get_rect(tex), white); } void sprite_setframe(struct sprite *sprite, struct glrect *frame) diff --git a/source/engine/texture.c b/source/engine/texture.c index f42fb95..d25390f 100644 --- a/source/engine/texture.c +++ b/source/engine/texture.c @@ -34,24 +34,11 @@ struct Texture *texture_pullfromfile(const char *path) YughInfo("Loading texture %s.", path); struct Texture *tex = calloc(1, sizeof(*tex)); - - /* Find texture's asset; otherwise load default asset */ - JSON_Value *rv = json_parse_file("texture.asset"); - JSON_Object *ro = json_value_get_object(rv); - tex->opts.sprite = json_object_get_boolean(ro, "sprite"); - tex->opts.mips = json_object_get_boolean(ro, "mips"); - json_value_free(rv); - + tex->opts.sprite = 1; + tex->opts.mips = 0; tex->opts.gamma = 0; - - - tex->anim.ms = 2; - tex->anim.tex = tex; - texanim_fromframes(&tex->anim, 2); - tex->anim.loop = 1; - + int n; - unsigned char *data = stbi_load(path, &tex->width, &tex->height, &n, 4); if (data == NULL) { @@ -89,11 +76,21 @@ struct Texture *texture_pullfromfile(const char *path) glGenerateMipmap(GL_TEXTURE_2D); if (tex->opts.sprite) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + if (tex->opts.mips) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (tex->opts.mips) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } } glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); @@ -107,6 +104,36 @@ struct Texture *texture_pullfromfile(const char *path) return tex; } +void texture_sync(const char *path) +{ + struct Texture *tex = texture_pullfromfile(path); + glBindTexture(GL_TEXTURE_2D, tex->id); + if (tex->opts.mips) + glGenerateMipmap(GL_TEXTURE_2D); + + if (tex->opts.sprite) { + if (tex->opts.mips) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST_MIPMAP_NEAREST); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + } else { + if (tex->opts.mips) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + } + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + +} + char *tex_get_path(struct Texture *tex) { for (int i = 0; i < shlen(texhash); i++) { if (tex == texhash[i].value) { diff --git a/source/engine/texture.h b/source/engine/texture.h index 54591c2..2d2c9a0 100644 --- a/source/engine/texture.h +++ b/source/engine/texture.h @@ -68,6 +68,7 @@ struct Texture { struct Texture *texture_pullfromfile(const char *path); // Create texture from image struct Texture *texture_loadfromfile(const char *path); // Create texture & load to gpu +void texture_sync(const char *path); struct Texture *str2tex(const char *path); void tex_gpu_reload(struct Texture *tex); // gpu_free then gpu_load void tex_gpu_free(struct Texture *tex); // Remove texture data from gpu diff --git a/source/scripts/components.js b/source/scripts/components.js index 912b0ec..3f7105e 100644 --- a/source/scripts/components.js +++ b/source/scripts/components.js @@ -105,6 +105,10 @@ var sprite = clone(component, { cmd(37, this.id, this.pos); }, + set color(x) { + cmd(96, this.id, x); + }, + load_img(img) { cmd(12, this.id, img, this.rect); }, @@ -679,3 +683,35 @@ var circle2d = clone(collider2d, { this.coll_sync(); }, }); + + +/* ASSETS */ + +var Texture = { + mipmaps(path, x) { + cmd(94, path, x); + }, + + sprite(path, x) { + cmd(95, path, x); + }, +}; + +var Resources = { + load(path) { + if (path in this) + return this[path]; + + var src = {}; + this[path] = src; + src.path = path; + + if (!IO.exists(`${path}.asset`)) + return this[path]; + + var data = JSON.parse(IO.slurp(`${path}.asset`)); + Object.assign(src,data); + return this[path]; + + }, +}; diff --git a/source/scripts/engine.js b/source/scripts/engine.js index 184e360..7acaf4c 100644 --- a/source/scripts/engine.js +++ b/source/scripts/engine.js @@ -420,7 +420,7 @@ var Action = { var Player = { players: [], input(fn, ...args) { - this.pawns.forEach(x => { if (fn in x) x[fn](...args); }); + this.pawns.forEach(x => x[fn]?.(...args)); }, control(pawn) { @@ -440,6 +440,7 @@ for (var i = 0; i < 4; i++) { } function state2str(state) { + if (typeof state === 'string') return state; switch (state) { case 0: return "down"; @@ -723,6 +724,7 @@ var Game = { }, }; + var Level = { levels: [], objects: [], @@ -922,6 +924,8 @@ var Level = { newobj.defn('level', this); this.objects.push(newobj); Game.register_obj(newobj); + newobj.setup?.(); + newobj.start?.(); return newobj; }, @@ -1185,9 +1189,11 @@ var Level = { get left() { return [-1,0].rotate(Math.deg2rad(this.angle)); }, - }; +var World = Level.create(); +World.name = "World"; + var gameobjects = {}; /* Returns the index of the smallest element in array, defined by a function that returns a number */ @@ -1211,7 +1217,13 @@ function grab_from_points(pos, points, slop) { var gameobject = { get scale() { return this._scale; }, - set scale(x) { this._scale = Math.max(0,x); if (this.body > -1) cmd(36, this.body, this._scale); this.sync(); }, + set scale(x) { + this._scale = Math.max(0,x); + if (this.body > -1) + cmd(36, this.body, this._scale); + + this.sync(); + }, _scale: 1.0, save: true, @@ -1309,6 +1321,26 @@ var gameobject = { body: -1, controlled: false, + + get properties() { + var keys = []; + for (var key of Object.keys(this)) { + if (key.startsWith("_")) + keys.push(key.substring(1)); + else + keys.push(key); + } + + return keys; + }, + + toJSON() { + var obj = {}; + for (var key of this.properties) + obj[key] = this[key]; + + return obj; + }, set_center(pos) { var change = pos.sub(this.pos); @@ -1515,6 +1547,7 @@ var gameobject = { this2world(pos) { return cmd(71, this.body,pos); }, make(props, level) { + level ??= World; var obj = Object.create(this); this.instances.push(obj); obj.toString = function() { @@ -1584,39 +1617,11 @@ var gameobject = { } -var locks = ['draw_layer', 'friction','elasticity', 'visible', 'body', 'flipx', 'flipy', 'scale', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name']; +var locks = ['draw_layer', 'friction','elasticity', 'visible', 'body', 'flipx', 'flipy', 'controlled', 'selectable', 'save', 'velocity', 'angularvelocity', 'alive', 'boundingbox', 'name', 'scale', 'angle', 'properties', 'moi', 'relpos', 'relangle', 'up', 'down', 'right', 'left', 'bodytype', 'gizmo', 'pos']; locks.forEach(function(x) { Object.defineProperty(gameobject, x, {enumerable:false}); }); -function private_non_enumerable(obj) { - for (var key in obj) { - if (key.startsWith('_')) - Object.defineProperty(obj, key, {enumerable:false}); - } -} - -function add_sync_prop(obj, prop, syncfn) { - var hidden = "_"+prop; - Log.info(hidden); - Object.defineProperty(obj, hidden, { - value: null, - writable: true, - }); - - Object.defineProperty(obj, prop, { - get: function() { return obj[hidden]; }, - set: function(x) { - obj[hidden] = x; - syncfn(obj[hidden]); - }, - enumerable: true, - }); - - return obj; - -}; - /* Load configs */ function load_configs(file) { var configs = JSON.parse(IO.slurp(file)); @@ -1761,5 +1766,3 @@ for (var key in prototypes) { } function save_gameobjects_as_prototypes() { slurpwrite(JSON.stringify(gameobjects,null,2), "proto.json"); }; - -Resources = {};