This commit is contained in:
John Alanbrook 2023-05-02 01:58:10 +00:00
parent bea5b326cf
commit d3cb9278a9
9 changed files with 194 additions and 79 deletions

View file

@ -32,6 +32,24 @@ associated script file can access.
While playing ... While playing ...
* F7 Stop * 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 .Scripting
Levels and objects have certain functions you can use that will be Levels and objects have certain functions you can use that will be

View file

@ -41,8 +41,8 @@ void set_cat_mask(int cat, unsigned int mask)
void color2float(struct color color, float *fcolor) void color2float(struct color color, float *fcolor)
{ {
fcolor[0] = (float)color.r/255; fcolor[0] = (float)color.r/255;
fcolor[1] = (float)color.b/255; fcolor[1] = (float)color.g/255;
fcolor[2] = (float)color.g/255; fcolor[2] = (float)color.b/255;
} }
struct color float2color(float *fcolor) struct color float2color(float *fcolor)

View file

@ -65,6 +65,11 @@ double js2number(JSValue v)
return g; return g;
} }
int js2bool(JSValue v)
{
return JS_ToBool(js,v);
}
JSValue float2js(double g) JSValue float2js(double g)
{ {
return JS_NewFloat64(js,g); return JS_NewFloat64(js,g);
@ -931,6 +936,23 @@ JSValue duk_cmd(JSContext *js, JSValueConst this, int argc, JSValueConst *argv)
case 93: case 93:
ret = int2js(logLevel); 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) if (str)
@ -1237,7 +1259,6 @@ JSValue duk_make_sprite(JSContext *js, JSValueConst this, int argc, JSValueConst
JS_FreeCString(js,path); JS_FreeCString(js,path);
return JS_NewInt64(js, sprite); return JS_NewInt64(js, sprite);
} }
/* Make anim from texture */ /* Make anim from texture */

View file

@ -189,6 +189,7 @@ void joystick_cb(int jid, int event)
JSValue jsgamepadstr[15]; JSValue jsgamepadstr[15];
JSValue jsaxesstr[4]; JSValue jsaxesstr[4];
JSValue jsaxis;
void input_init() void input_init()
{ {
@ -205,10 +206,11 @@ void input_init()
for (int b = 0; b < 15; b++) for (int b = 0; b < 15; b++)
jsgamepadstr[b] = str2js(gamepad2str(b)); jsgamepadstr[b] = str2js(gamepad2str(b));
jsaxesstr[0] = str2js("axis_ljoy"); jsaxesstr[0] = str2js("ljoy");
jsaxesstr[1] = str2js("axis_rjoy"); jsaxesstr[1] = str2js("rjoy");
jsaxesstr[2] = str2js("axis_ltrigger"); jsaxesstr[2] = str2js("ltrigger");
jsaxesstr[3] = str2js("axis_rtrigger"); jsaxesstr[3] = str2js("rtrigger");
jsaxis = str2js("axis");
/* Grab all joysticks initially present */ /* Grab all joysticks initially present */
for (int i = 0; i < 16; i++) for (int i = 0; i < 16; i++)
@ -397,7 +399,7 @@ void input_poll(double wait)
GLFWgamepadstate state; GLFWgamepadstate state;
if (!glfwGetGamepadState(joysticks[i].id, &state)) continue; if (!glfwGetGamepadState(joysticks[i].id, &state)) continue;
JSValue argv[3]; JSValue argv[4];
argv[0] = num_cache[joysticks[i].id]; argv[0] = num_cache[joysticks[i].id];
for (int b = 0; b < 15; b++) { for (int b = 0; b < 15; b++) {
argv[1] = jsgamepadstr[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]; argv[1] = jsaxesstr[0];
cpVect v; cpVect v;
v.x = state.axes[0]; v.x = state.axes[0];
v.y = -state.axes[1]; v.y = -state.axes[1];
argv[2] = vec2js(v); argv[3] = vec2js(v);
script_callee(gamepad_callee,3,argv); script_callee(gamepad_callee,4,argv);
JS_FreeValue(js, argv[2]); JS_FreeValue(js, argv[3]);
argv[1] = jsaxesstr[1]; argv[1] = jsaxesstr[1];
v.x = state.axes[2]; v.x = state.axes[2];
v.y = -state.axes[3]; v.y = -state.axes[3];
argv[2] = vec2js(v); argv[3] = vec2js(v);
script_callee(gamepad_callee,3,argv); script_callee(gamepad_callee,4,argv);
JS_FreeValue(js, argv[2]); JS_FreeValue(js, argv[3]);
argv[1] = jsaxesstr[2]; argv[1] = jsaxesstr[2];
argv[2] = num2js((state.axes[4]+1)/2); argv[3] = num2js((state.axes[4]+1)/2);
script_callee(gamepad_callee,3,argv); script_callee(gamepad_callee,4,argv);
JS_FreeValue(js, argv[2]); JS_FreeValue(js, argv[3]);
argv[1] = jsaxesstr[3]; argv[1] = jsaxesstr[3];
argv[2] = num2js((state.axes[5]+1)/2); argv[3] = num2js((state.axes[5]+1)/2);
script_callee(gamepad_callee,3,argv); script_callee(gamepad_callee,4,argv);
JS_FreeValue(js, argv[2]); JS_FreeValue(js, argv[3]);
joysticks[i].state = state; joysticks[i].state = state;
} }

View file

@ -122,7 +122,7 @@ void sprite_initialize()
glEnableVertexAttribArray(0); 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 model[16] = { 0.f };
mfloat_t r_model[16] = { 0.f }; mfloat_t r_model[16] = { 0.f };
mfloat_t s_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); mat4_translate_vec2(model, pos);
float white[3] = { 1.f, 1.f, 1.f };
shader_setmat4(spriteShader, "model", model); shader_setmat4(spriteShader, "model", model);
shader_setvec3(spriteShader, "spriteColor", white); shader_setvec3(spriteShader, "spriteColor", color);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->id); glBindTexture(GL_TEXTURE_2D, tex->id);
@ -173,7 +172,7 @@ void sprite_draw(struct sprite *sprite)
cpVect cpos = cpBodyGetPosition(go->body); cpVect cpos = cpBodyGetPosition(go->body);
float pos[2] = {cpos.x, cpos.y}; float pos[2] = {cpos.x, cpos.y};
float size[2] = { sprite->size[0] * go->scale * go->flipx, sprite->size[1] * go->scale * go->flipy }; 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 pos[2] = {x, y};
float size[2] = {1.f, 1.f}; float size[2] = {1.f, 1.f};
float offset[2] = { 0.f, 0.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) void sprite_setframe(struct sprite *sprite, struct glrect *frame)

View file

@ -34,24 +34,11 @@ struct Texture *texture_pullfromfile(const char *path)
YughInfo("Loading texture %s.", path); YughInfo("Loading texture %s.", path);
struct Texture *tex = calloc(1, sizeof(*tex)); struct Texture *tex = calloc(1, sizeof(*tex));
tex->opts.sprite = 1;
/* Find texture's asset; otherwise load default asset */ tex->opts.mips = 0;
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.gamma = 0; tex->opts.gamma = 0;
tex->anim.ms = 2;
tex->anim.tex = tex;
texanim_fromframes(&tex->anim, 2);
tex->anim.loop = 1;
int n; int n;
unsigned char *data = stbi_load(path, &tex->width, &tex->height, &n, 4); unsigned char *data = stbi_load(path, &tex->width, &tex->height, &n, 4);
if (data == NULL) { if (data == NULL) {
@ -89,11 +76,21 @@ struct Texture *texture_pullfromfile(const char *path)
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
if (tex->opts.sprite) { if (tex->opts.sprite) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); if (tex->opts.mips) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 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 { } else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); if (tex->opts.mips) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); 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_S, GL_REPEAT);
@ -107,6 +104,36 @@ struct Texture *texture_pullfromfile(const char *path)
return tex; 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) { char *tex_get_path(struct Texture *tex) {
for (int i = 0; i < shlen(texhash); i++) { for (int i = 0; i < shlen(texhash); i++) {
if (tex == texhash[i].value) { if (tex == texhash[i].value) {

View file

@ -68,6 +68,7 @@ struct Texture {
struct Texture *texture_pullfromfile(const char *path); // Create texture from image struct Texture *texture_pullfromfile(const char *path); // Create texture from image
struct Texture *texture_loadfromfile(const char *path); // Create texture & load to gpu struct Texture *texture_loadfromfile(const char *path); // Create texture & load to gpu
void texture_sync(const char *path);
struct Texture *str2tex(const char *path); struct Texture *str2tex(const char *path);
void tex_gpu_reload(struct Texture *tex); // gpu_free then gpu_load void tex_gpu_reload(struct Texture *tex); // gpu_free then gpu_load
void tex_gpu_free(struct Texture *tex); // Remove texture data from gpu void tex_gpu_free(struct Texture *tex); // Remove texture data from gpu

View file

@ -105,6 +105,10 @@ var sprite = clone(component, {
cmd(37, this.id, this.pos); cmd(37, this.id, this.pos);
}, },
set color(x) {
cmd(96, this.id, x);
},
load_img(img) { load_img(img) {
cmd(12, this.id, img, this.rect); cmd(12, this.id, img, this.rect);
}, },
@ -679,3 +683,35 @@ var circle2d = clone(collider2d, {
this.coll_sync(); 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];
},
};

View file

@ -420,7 +420,7 @@ var Action = {
var Player = { var Player = {
players: [], players: [],
input(fn, ...args) { input(fn, ...args) {
this.pawns.forEach(x => { if (fn in x) x[fn](...args); }); this.pawns.forEach(x => x[fn]?.(...args));
}, },
control(pawn) { control(pawn) {
@ -440,6 +440,7 @@ for (var i = 0; i < 4; i++) {
} }
function state2str(state) { function state2str(state) {
if (typeof state === 'string') return state;
switch (state) { switch (state) {
case 0: case 0:
return "down"; return "down";
@ -723,6 +724,7 @@ var Game = {
}, },
}; };
var Level = { var Level = {
levels: [], levels: [],
objects: [], objects: [],
@ -922,6 +924,8 @@ var Level = {
newobj.defn('level', this); newobj.defn('level', this);
this.objects.push(newobj); this.objects.push(newobj);
Game.register_obj(newobj); Game.register_obj(newobj);
newobj.setup?.();
newobj.start?.();
return newobj; return newobj;
}, },
@ -1185,9 +1189,11 @@ var Level = {
get left() { get left() {
return [-1,0].rotate(Math.deg2rad(this.angle)); return [-1,0].rotate(Math.deg2rad(this.angle));
}, },
}; };
var World = Level.create();
World.name = "World";
var gameobjects = {}; var gameobjects = {};
/* Returns the index of the smallest element in array, defined by a function that returns a number */ /* 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 = { var gameobject = {
get scale() { return this._scale; }, 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, _scale: 1.0,
save: true, save: true,
@ -1310,6 +1322,26 @@ var gameobject = {
body: -1, body: -1,
controlled: false, 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) { set_center(pos) {
var change = pos.sub(this.pos); var change = pos.sub(this.pos);
this.pos = pos; this.pos = pos;
@ -1515,6 +1547,7 @@ var gameobject = {
this2world(pos) { return cmd(71, this.body,pos); }, this2world(pos) { return cmd(71, this.body,pos); },
make(props, level) { make(props, level) {
level ??= World;
var obj = Object.create(this); var obj = Object.create(this);
this.instances.push(obj); this.instances.push(obj);
obj.toString = function() { 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) { locks.forEach(function(x) {
Object.defineProperty(gameobject, x, {enumerable:false}); 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 */ /* Load configs */
function load_configs(file) { function load_configs(file) {
var configs = JSON.parse(IO.slurp(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"); }; function save_gameobjects_as_prototypes() { slurpwrite(JSON.stringify(gameobjects,null,2), "proto.json"); };
Resources = {};