Skip to content

Instantly share code, notes, and snippets.

@nikki93
Last active August 24, 2020 21:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nikki93/8cc5d99e45ad74e7f1053dbef2874e56 to your computer and use it in GitHub Desktop.
Save nikki93/8cc5d99e45ad74e7f1053dbef2874e56 to your computer and use it in GitHub Desktop.
#include "precomp.h"
#include "archive.h"
#include "edit.h"
#include "events.h"
#include "graphics.h"
#include "kernel.h"
#include "physics.h"
#include "platform.h"
#include "timing.h"
#include "ui.h"
//
// Types
//
// Basics
struct Position {
ngType(Position);
double x = 0;
ngField(x);
double y = 0;
ngField(y);
};
struct Sprite {
ngType(Sprite);
Graphics::Image image;
double scale = 0.25;
ngField(scale);
double depth = 0;
ngField(depth);
static auto add(Kernel &ker, const Entity ent, Archive::Reader &bp = Archive::empty) -> void {
auto &gfx = ker.ctx<Graphics>();
ker.add<Sprite>(
ent, gfx.createImage(Platform::getAssetPath(bp.str("imageName", "player.png"))));
}
ngMethod(add);
auto save(Archive::Writer &bp) -> void {
bp.str("imageName", std::filesystem::path(image.getPath()).filename().u8string());
}
ngMethod(save);
};
// Physics
struct Feet {
ngType(Feet);
Physics::Body body;
Physics::Shape shape;
static auto add(Kernel &ker, const Entity ent, Archive::Reader &bp = Archive::empty) -> void {
auto &phy = ker.ctx<Physics>();
const auto &pos = ker.get<Position>(ent);
auto body = phy.createStatic().setEntity(ent).setPosition({ pos.x, pos.y });
std::vector<Vec2> verts;
bp.arr("verts", [&]() {
auto size = bp.size();
for (auto i = 0; i + 1 < size; i += 2) {
verts.push_back({ bp.num(i), bp.num(i + 1) });
}
});
auto shape = verts.size() > 0 ? phy.createPoly(body, verts) : phy.createBox(body, 40, 40);
ker.add<Feet>(ent, std::move(body), std::move(shape));
}
ngMethod(add);
auto save(Archive::Writer &bp) -> void {
bp.arr("verts", [&]() {
for (auto nVerts = shape.getNumVertices(), i = 0; i < nVerts; ++i) {
auto [x, y] = shape.getVertex(i);
bp.num(x);
bp.num(y);
}
});
}
ngMethod(save);
};
struct Walk {
Physics::Body target;
Physics::Constraint constraint;
static auto add(Kernel &ker, const Entity ent) -> void {
auto &phy = ker.ctx<Physics>();
if (ker.has<Feet>(ent)) {
auto &feet = ker.get<Feet>(ent);
auto target = phy.createStatic().setPosition(feet.body.getPosition());
auto constraint = phy.createPivot(target, feet.body, { 0, 0 }, { 0, 0 })
.setMaxForce(3000)
.setMaxBias(200);
ker.add<Walk>(ent, std::move(target), std::move(constraint));
}
}
};
struct Friction {
ngType(Friction);
Physics::Constraint constraint;
static auto add(Kernel &ker, const Entity ent) -> void {
auto &phy = ker.ctx<Physics>();
if (ker.has<Feet>(ent)) {
auto &feet = ker.get<Feet>(ent);
auto constraint = phy.createPivot(phy.getBackground(), feet.body, { 0, 0 }, { 0, 0 })
.setMaxForce(800)
.setMaxBias(0);
ker.add<Friction>(ent, std::move(constraint));
}
}
};
// Tags
struct Player {
ngType(Player);
};
struct Prop {
ngType(Prop);
};
//
// Triggers
//
// Physics
struct PhysicsPre : Kernel::Trigger<> {};
struct PhysicsPost : Kernel::Trigger<> {};
// Draw
struct Draw : Kernel::Trigger<> {};
//
// Rules
//
// Sprite
ngRule(Draw, DrawSprites)(Kernel &ker) {
// Order sprites by depth and draw at positions
ker.isort<Sprite>([](const Sprite &a, const Sprite &b) {
return a.depth < b.depth;
});
auto &gfx = ker.ctx<Graphics>();
ker.view<Sprite, Position>().each([&](const Sprite &spr, const Position &pos) {
gfx.drawImage(spr.image, pos.x, pos.y, spr.scale);
});
};
// Player
ngRule(PhysicsPre, WalkPlayerToTouch)(Kernel &ker) {
auto &ev = ker.ctx<Events>();
auto &touches = ev.getTouches();
if (touches.size() > 0) {
// Touching: add walk, set target to first touch
ker.view<Player>().each([&](const Entity ent) {
auto &walk = ker.has<Walk>(ent) ? ker.get<Walk>(ent) : ker.add<Walk>(ent);
walk.target.setPosition({ touches[0].x, touches[0].y });
});
} else {
// Not touching: remove walk
ker.view<Player, Walk>().each([&](const Entity ent, Walk &) {
ker.remove<Walk>(ent);
});
}
};
ngRule(PhysicsPost, ReadPlayerPhysics)(Kernel &ker) {
// Read physics to position
ker.view<Player, Feet, Position>().each([&](const Feet &feet, Position &pos) {
auto [x, y] = feet.body.getPosition();
pos.x = x;
pos.y = y - 65;
});
};
ngRule(PhysicsPost, UpdatePlayerDepth)(Kernel &ker) {
// Set player depth behind objects that obscure it
auto &phy = ker.ctx<Physics>();
ker.view<Player, Feet, Sprite>().each([&](const Entity ent, const Feet &feet, Sprite &spr) {
spr.depth = 1000;
const auto query = [&](double x, double y) {
phy.segmentQuery({ x, y }, { x, y + 1e4 }, 1, [&](Physics::Shape &shape, Vec2, Vec2, double) {
auto other = shape.getBody().getEntity();
if (other != ent && other != Kernel::null && ker.has<Sprite>(other)) {
spr.depth = std::min(spr.depth, ker.get<Sprite>(other).depth - 0.2);
}
});
};
auto [x, y] = feet.body.getPosition();
query(x + 22, y);
query(x - 22, y);
query(x, y);
});
};
//
// Edit
//
ngRule(Edit::UpdateBoxes, EditBoxes)(Kernel &ker) {
ker.view<Position, Sprite>().each([&](const Entity ent, const Position &pos, const Sprite &spr) {
auto &box = ker.has<Edit::Box>(ent) ? ker.get<Edit::Box>(ent) : ker.add<Edit::Box>(ent);
box.x = pos.x;
box.y = pos.y;
auto [imgW, imgH] = spr.image.getSize();
box.width = spr.scale * imgW;
box.height = spr.scale * imgH;
});
};
ngRule(Edit::Input, EditShape)(Kernel &ker) {
auto &edit = ker.ctx<Edit>();
auto &mode = edit.getMode();
auto &ev = ker.ctx<Events>();
auto &touches = ev.getTouches();
if (mode == "shape") {
if (touches.size() == 1 && (touches[0].pressed || touches[0].released)) {
auto &phy = ker.ctx<Physics>();
ker.view<Edit::Select, Feet>().each([&](Feet &feet) {
std::vector<Vec2> verts;
for (auto nVerts = feet.shape.getNumVertices(), i = 0; i < nVerts; ++i) {
auto v = feet.shape.getVertex(i);
auto [wx, wy] = feet.body.toWorld(v);
if (!(abs(touches[0].x - wx) < 2 && abs(touches[0].y - wy) < 2)) {
verts.push_back(v);
}
}
if (touches[0].released || verts.size() == 0) {
verts.push_back(feet.body.toLocal({ touches[0].x, touches[0].y }));
}
feet.shape = phy.createPoly(feet.body, verts);
});
}
}
};
ngRule(Edit::Draw, EditOverlay)(Kernel &ker) {
auto &edit = ker.ctx<Edit>();
auto &mode = edit.getMode();
auto &gfx = ker.ctx<Graphics>();
if (mode == "shape") {
// Feet shape and vertices for selected only
gfx.setColor(0, 0, 0xff);
ker.view<Edit::Select, Feet>().each([&](const Feet &feet) {
for (auto nVerts = feet.shape.getNumVertices(), i = 0; i < nVerts; ++i) {
auto [wx, wy] = feet.body.toWorld(feet.shape.getVertex(i));
gfx.drawRectangleFill(wx, wy, 4, 4);
}
feet.body.draw(gfx);
});
}
if (mode == "select") {
// All feet shapes
gfx.scope([&]() {
gfx.setColor(0, 0, 0xff);
ker.view<Feet>().each([&](const Feet &feet) {
feet.body.draw(gfx);
});
});
}
};
auto Sprite_inspect(Sprite &spr, Kernel &ker, const Entity) -> void {
auto &ui = ker.ctx<UI>();
ui.elem("img")("preview")("src", spr.image.getBlobUrl());
ui.div()("info")([&]() {
ui.text("path: {}", spr.image.getPath());
});
ui.div()("info")([&]() {
auto [imgW, imgH] = spr.image.getSize();
ui.text("width: {}, height: {}", imgW, imgH);
});
}
ngMethod(Sprite, inspect);
auto Feet_inspect(Feet &feet, Kernel &ker, const Entity ent) -> void {
auto &ui = ker.ctx<UI>();
auto &edit = ker.ctx<Edit>();
ui.div()("info")([&]() {
ui.text("shape: {} vertices", feet.shape.getNumVertices());
if (!ker.has<Player>(ent)) {
ui.button()("shape")("selected", edit.getMode() == "shape")("click", [&](emscripten::val) {
edit.setEnabled(true);
edit.setMode(edit.getMode() == "shape" ? "select" : "shape");
});
}
});
}
ngMethod(Feet, inspect);
//
// Factory
//
namespace Factory {
auto createPlayer(Kernel &ker, Graphics &gfx, Physics &phy, double x, double y) -> void {
auto ent = ker.create();
ker.add<Player>(ent);
ker.add<Sprite>(ent, gfx.createImage(Platform::getAssetPath("player.png")), 0.25, 1000.0);
auto &pos = ker.add<Position>(ent, x, y);
auto body = phy.createDynamic(1, INFINITY).setEntity(ent).setPosition({ pos.x, pos.y });
constexpr auto radius = 8;
auto shape = phy.createBox(body, 45 - 2 * radius, 20 - 2 * radius, radius);
ker.add<Feet>(ent, std::move(body), std::move(shape));
ker.add<Friction>(ent);
}
auto saveScene(Kernel &ker) -> void {
Archive ar;
ar.save(ker, [&](const Entity ent) {
return !ker.has<Player>(ent);
});
#ifdef __EMSCRIPTEN__
emscripten::val::global("window").call<void>(
"prompt", std::string("Copy scene data..."), ar.toString());
#endif
}
auto loadScene(Kernel &ker, const std::string &path) -> void {
Archive::fromFile(path).load(ker);
}
auto loadImport(Kernel &ker, const std::string &path, double scale = 0.5) -> void {
auto &gfx = ker.ctx<Graphics>();
json::Document root;
std::ifstream ifs(path);
json::BasicIStreamWrapper isw(ifs);
root.ParseStream(isw);
for (auto &[sectionName, section] : root.GetObject()) {
if (sectionName == "objects") {
auto depth = 0.0;
for (auto &object : section.GetArray()) {
auto x = object["x"].GetDouble();
auto y = object["y"].GetDouble();
auto imagePath = Platform::getAssetPath(object["imageName"].GetString());
auto &type = object["type"];
if (type == "prop") {
auto ent = ker.create();
ker.add<Prop>(ent);
auto &spr = ker.add<Sprite>(ent, gfx.createImage(imagePath), scale, depth++);
auto [imgW, imgH] = spr.image.getSize();
ker.add<Position>(ent, scale * (x + 0.5 * imgW), scale * (y + 0.5 * imgH));
}
}
}
}
}
}
//
// main
//
auto main() -> int {
// Modules
Timing tim;
Graphics gfx("dream hotel");
UI ui(tim);
Events ev(gfx);
Physics phy(tim);
Kernel ker(tim);
Edit edit(ker, gfx, ui);
ker.ctx(gfx, ui, ev, phy, edit);
// Scene
Factory::loadScene(ker, Platform::getAssetPath("pool.scn"));
Factory::createPlayer(ker, gfx, phy, 120, 120);
// Loop
ev.loop([&]() {
// Timing
tim.frame();
// Unfocused?
if (!ev.isWindowFocused()) {
return;
}
// Logic
if (edit.getEnabled()) {
// Edit
edit.frame();
} else {
// Physics
ker.run<PhysicsPre>();
phy.frame();
ker.run<PhysicsPost>();
}
// Graphics
gfx.frame([&]() {
// Background color
gfx.clear(0xcc, 0xe4, 0xf5);
// Draw
ker.run<Draw>();
if (edit.getEnabled()) {
edit.draw();
}
});
// UI
ui.frame([&]() {
// Top
ui.panel("top", [&]() {
ui.div()("toolbar")([&]() {
ui.button()("edit")("selected", edit.getEnabled())("click", [&](emscripten::val) {
edit.setEnabled(!edit.getEnabled());
});
ui.div()("spacer");
if (edit.getEnabled()) {
ui.button()("zoom-in")("click", [&](emscripten::val) {
auto [vx, vy, vw, vh] = gfx.getView();
gfx.setView(vx, vy, 0.5 * vw, 0.5 * vh);
});
ui.button()("zoom-out")("click", [&](emscripten::val) {
auto [vx, vy, vw, vh] = gfx.getView();
gfx.setView(vx, vy, 2 * vw, 2 * vh);
});
ui.div()("small-gap");
ui.button()("save")("click", [&](emscripten::val) {
Factory::saveScene(ker);
});
}
});
});
// Bottom
ui.panel("bottom", [&]() {
ui.div()("status")([&]() {
ui.button()("reload")("click", [&](emscripten::val) {
emscripten::val::global("location").call<void>("reload");
});
ui.button()("profiler")("selected", tim.runningProf())("click", [&](emscripten::val) {
tim.startProf(5);
});
ui.div()([&]() {
ui.text("fps: {}", int(round(tim.getFPS())));
});
ui.div()("spacer");
if (edit.getEnabled()) {
ui.div()([&]() {
auto [vh, vy] = gfx.getViewSize();
ui.text("{}x", vh / 800);
});
ui.div()("small-gap");
ui.div()([&]() {
ui.text(edit.getMode());
});
}
});
});
// Side
ui.panel("side", [&]() {
edit.inspect();
});
});
});
return 0;
}
{
"entities": [{
"types": [{
"_type": "Position",
"x": 749.75,
"y": 616.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 26.0,
"imageName": "pool__layer-39__2.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 894.25,
"y": 340.5
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 25.0,
"imageName": "pool__layer-37__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 493.5,
"y": 617.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 24.0,
"imageName": "pool__from-selection__10.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 495.25,
"y": 508.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 23.0,
"imageName": "pool__from-selection__9.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 401.5,
"y": 402.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 22.0,
"imageName": "pool__from-selection__8.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 407.5,
"y": 261.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 21.0,
"imageName": "pool__layer-32__2.png"
}, {
"_type": "Feet",
"verts": [-52.5, 75.25, -30.5, 63.25, 14.5, 57.25, 43.5, 66.25, 16.5, 79.25, -23.5, 82.25]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 681.0,
"y": 193.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 20.0,
"imageName": "pool__layer-35-copy__1.png"
}, {
"_type": "Feet",
"verts": [-29.0, -42.0, -19.0, -44.0, 27.0, 75.0, 17.0, 79.0]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 476.75,
"y": 178.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 19.0,
"imageName": "pool__layer-35__4.png"
}, {
"_type": "Feet",
"verts": [-22.75, 71.25, 16.25, -36.75, 26.25, -38.75, -14.75, 73.25]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 199.25,
"y": 504.5
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 18.0,
"imageName": "pool__from-selection__7.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 659.5,
"y": 670.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 17.0,
"imageName": "pool__layer-33__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 845.75,
"y": 361.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 16.0,
"imageName": "pool__layer-35__3.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 309.25,
"y": 180.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 15.0,
"imageName": "pool__layer-35__2.png"
}, {
"_type": "Feet",
"verts": [-41.25, 38.25, -29.25, 11.25, 44.75, 16.25, 33.75, 40.25]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 862.75,
"y": 260.5
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 14.0,
"imageName": "pool__layer-35__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 362.5,
"y": 154.5
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 13.0,
"imageName": "pool__layer-38__4.png"
}, {
"_type": "Feet",
"verts": [-35.5, 25.5, -16.5, 1.5, 43.5, 6.5, 17.5, 29.5]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 830.25,
"y": 215.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 12.0,
"imageName": "pool__layer-38__3.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 819.0,
"y": 175.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 11.0,
"imageName": "pool__layer-38__2.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 99.0,
"y": 458.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 10.0,
"imageName": "pool__from-selection__6.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 156.5,
"y": 217.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 9.0,
"imageName": "pool__from-selection__5.png"
}, {
"_type": "Feet",
"verts": [-33.5, 204.25, -14.5, 191.25, 19.5, 190.25, 30.5, 199.25, 9.5, 213.25, -18.5, 211.25]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 662.0,
"y": 81.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 8.0,
"imageName": "pool__from-selection__4.png"
}, {
"_type": "Feet",
"verts": [-34.0, 63.0, -23.0, 47.0, 21.0, 47.0, 39.0, 54.0, 16.0, 67.0, -15.0, 69.0]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 519.25,
"y": 68.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 7.0,
"imageName": "pool__layer-38__1.png"
}, {
"_type": "Feet",
"verts": [-38.25, 62.25, -23.25, 50.25, 15.75, 48.25, 31.75, 53.25, 18.75, 69.25, -14.25, 70.25]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 477.25,
"y": 47.25
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 6.0,
"imageName": "pool__from-selection__3.png"
}, {
"_type": "Feet",
"verts": [-30.25, 35.75, -17.25, 23.75, 13.75, 19.75, 31.75, 27.75, 18.75, 38.75, -11.25, 43.75]
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 821.75,
"y": 66.75
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 5.0,
"imageName": "pool__from-selection__2.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 263.75,
"y": 86.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 4.0,
"imageName": "pool__from-selection__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 584.5,
"y": 205.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 3.0,
"imageName": "pool__layer-39__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 469.25,
"y": 375.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 2.0,
"imageName": "pool__layer-32__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 500.0,
"y": 375.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 1.0,
"imageName": "pool__layer-27__1.png"
}, {
"_type": "Prop"
}]
}, {
"types": [{
"_type": "Position",
"x": 500.0,
"y": 375.0
}, {
"_type": "Sprite",
"scale": 0.5,
"depth": 0.0,
"imageName": "pool__background__1.png"
}, {
"_type": "Prop"
}]
}]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment