Last active
August 24, 2020 21:01
-
-
Save nikki93/8cc5d99e45ad74e7f1053dbef2874e56 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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