a kaplay map is done

This commit is contained in:
MeowcaTheoRange 2024-06-01 13:36:00 -05:00
parent 30ac08e50f
commit 530256b476
7 changed files with 448 additions and 144 deletions

BIN
assets/images/grid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9 KiB

BIN
assets/images/minus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 B

BIN
assets/images/plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

View file

@ -0,0 +1,372 @@
export class KaplayMap {
kp;
opts = {
minZoomLevel: 0,
maxZoomLevel: 3,
dblClickDuration: 0.2, // s
dblClickForgiveness: 30, // px
};
uiLayer;
#zoomLevel = 0;
#scaleTween = null;
startMousePos = null;
startCamPos = null;
startZoomLevel = null;
prevCamPos = null;
moveVelocity = null;
moveFriction = 0.25;
moveDead = 1;
#moveTween = null;
lastReleaseTime;
lastReleasePos;
lastPressTime;
lastPressPos;
mouseMode = "drag";
fingers = new Map([]);
startFingerDiff = null;
startFingerCenterPos = null;
startFingerZoomLevel = null;
constructor(kp, opts) {
this.kp = kp;
this.opts = {
...this.opts,
...opts,
};
this.uiLayer = this.initUI();
this.kp.add(this.uiLayer);
this.kp.onScroll((delta) => {
const scrollDist = -delta.y / 120;
this.zoomTo(this.zoomLevel + scrollDist, this.kp.mousePos());
});
// Dragging
this.kp.onTouchStart((pos, touch) => {
this.fingers.set(touch.identifier, pos);
const fingerArr = Array.from(this.fingers.values());
this.startCamPos = this.kp.camPos();
this.startFingerCenterPos = fingerArr[0];
if (this.fingers.size > 1) {
this.clearMouseMode();
this.startFingerDiff = fingerArr[0].dist(fingerArr[1]);
const fingerDifference = fingerArr[0].sub(fingerArr[1]);
this.startFingerCenterPos = fingerArr[1].add(
fingerDifference.scale(0.5)
);
this.startFingerZoomLevel = this.zoomLevel;
this.mouseMode = "pinch";
} else if (this.fingers.size < 2) {
this.mouseMode = "drag";
}
});
this.kp.onTouchMove((pos, touch) => {
this.fingers.set(touch.identifier, pos);
});
this.kp.onTouchEnd((pos, touch) => {
if (this.fingers.size > 1) {
this.clearMouseMode();
}
this.fingers.delete(touch.identifier);
if (this.fingers.size < 2) {
const fingerArr = Array.from(this.fingers.values());
this.startFingerCenterPos = fingerArr[0];
this.startCamPos = this.kp.camPos();
this.startFingerDiff = null;
}
});
this.kp.onUpdate(() => {
const camScale = 1 / this.kp.camScale().y;
const fingerArr = Array.from(this.fingers.values());
if (this.fingers.size > 1) {
const curFingerDiff = fingerArr[0].dist(fingerArr[1]);
// Get centerpoint
const fingerDifference = fingerArr[0].sub(fingerArr[1]);
const fingerCenter = fingerArr[1].add(fingerDifference.scale(0.5));
this.zoomToNoTweenAbs(
this.startFingerZoomLevel +
Math.log2(curFingerDiff / this.startFingerDiff),
this.startCamPos.sub(
fingerCenter.sub(this.startFingerCenterPos).scale(camScale)
)
);
} else if (this.fingers.size == 1) {
if (this.mouseMode != "zoom") {
this.kp.camPos(
this.startCamPos.sub(
fingerArr[0].sub(this.startFingerCenterPos).scale(camScale)
)
);
}
}
});
this.kp.onUpdate(() => {
const curCamPos = this.kp.camPos();
const camScale = 1 / this.kp.camScale().y;
// ||| Completely unintelligible to the average person |||
// vvv Sorry y'all xwx vvv
if (this.kp.isMousePressed()) {
// ignore if no double click
if (this.lastPressTime != null && this.lastPressPos != null) {
const clickDifference = this.kp.time() - this.lastPressTime;
const clickDistance = this.kp.mousePos().dist(this.lastPressPos);
// Double-click!
if (
clickDifference <= this.opts.dblClickDuration &&
clickDistance <= this.opts.dblClickForgiveness
) {
this.mouseMode = "zoom";
}
}
if (this.mouseMode == "drag") {
this.startMousePos = this.kp.mousePos();
this.startCamPos = this.kp.camPos();
} else if (this.mouseMode == "zoom") {
this.startMousePos = this.kp.mousePos();
this.startZoomLevel = this.zoomLevel;
}
// Set variables
this.lastPressTime = this.kp.time();
this.lastPressPos = this.kp.mousePos();
} else if (this.kp.isMouseReleased()) {
if (this.mouseMode == "pinch") return;
this.mouseMode = "drag";
// ignore if no double click
if (this.lastReleaseTime != null && this.lastReleasePos != null) {
const clickDifference = this.kp.time() - this.lastReleaseTime;
const clickDistance = this.kp.mousePos().dist(this.lastReleasePos);
// Double-click!
if (
clickDifference <= this.opts.dblClickDuration &&
clickDistance <= this.opts.dblClickForgiveness
) {
this.zoomTo(this.zoomLevel + 1, this.kp.mousePos());
}
}
// Set variables
this.lastReleaseTime = this.kp.time();
this.lastReleasePos = this.kp.mousePos();
// if (this.prevCamPos != null)
// this.moveVelocity = this.prevCamPos
// .sub(curCamPos)
// .scale(1 / camScale);
}
if (this.kp.isMouseDown()) {
if (this.mouseMode == "drag") {
this.prevCamPos = this.kp.camPos();
this.kp.camPos(
this.startCamPos.sub(
this.kp.mousePos().sub(this.startMousePos).scale(camScale)
)
);
} else if (this.mouseMode == "zoom") {
this.zoomToNoTween(
this.startZoomLevel +
-this.kp.mousePos().sub(this.startMousePos).y /
(this.kp.height() / 2),
this.startMousePos
);
}
}
if (
this.moveVelocity != null &&
this.moveVelocity?.x != 0 &&
this.moveVelocity?.y != 0
)
this.kp.camPos(curCamPos.sub(this.moveVelocity?.scale(camScale) ?? 0));
this.kp.camPos(
Math.round(this.kp.camPos().x * this.kp.camScale().y) /
this.kp.camScale().y,
Math.round(this.kp.camPos().y * this.kp.camScale().y) /
this.kp.camScale().y
);
// ^^^ Completely unintelligible to the average person ^^^
// ||| Sorry y'all xwx |||
if (this.moveVelocity == null) return;
if (
this.moveVelocity.x <= this.moveDead &&
this.moveVelocity.x >= -this.moveDead
)
this.moveVelocity.x = 0;
if (
this.moveVelocity.y <= this.moveDead &&
this.moveVelocity.y >= -this.moveDead
)
this.moveVelocity.y = 0;
this.moveVelocity = this.moveVelocity.scale(1 - this.moveFriction);
});
}
initUI() {
const uiLayer = this.kp.make([
this.kp.pos(0),
this.kp.fixed(),
this.kp.stay(),
this.kp.z(1000),
]);
return uiLayer;
}
initGrid() {
const grid = this.kp.loadSprite(null, "/assets/images/grid.png");
this.kp.onDraw(() => {
this.kp.drawSprite({
sprite: grid,
tiled: true,
opacity: 0.25,
width: this.kp.width() + 100,
height: this.kp.height() + 100,
pos: this.kp.vec2(
Math.floor(this.kp.camPos().x / 100) * 100 - this.kp.width() * 0.5,
Math.floor(this.kp.camPos().y / 100) * 100 - this.kp.height() * 0.5
),
});
});
return grid;
}
clearMouseMode() {
this.lastReleaseTime = null;
this.lastReleasePos = null;
this.lastPressTime = null;
this.lastPressPos = null;
this.prevCamPos = null;
}
get zoomLevelLimit() {
if (this.#zoomLevel == this.opts.minZoomLevel) return -1;
if (this.#zoomLevel == this.opts.maxZoomLevel) return 1;
return 0;
}
get zoomLevel() {
return this.#zoomLevel;
}
set zoomLevel(newZoom) {
const cameraZoom = this.kp.camScale().y;
let addLinear = newZoom;
if (addLinear < this.opts.minZoomLevel) addLinear = this.opts.minZoomLevel;
if (addLinear > this.opts.maxZoomLevel) addLinear = this.opts.maxZoomLevel;
let linearToLog = Math.pow(2, addLinear);
this.#zoomLevel = addLinear;
if (this.#scaleTween != null) {
this.#scaleTween.finish();
}
this.#scaleTween = this.kp.tween(
cameraZoom,
linearToLog,
0.25,
this.kp.camScale,
this.kp.easings.easeOutQuad
);
this.#scaleTween.then(() => {
this.#scaleTween = null;
});
}
set zoomLevel_NoTween(newZoom) {
let addLinear = newZoom;
if (addLinear < this.opts.minZoomLevel) addLinear = this.opts.minZoomLevel;
if (addLinear > this.opts.maxZoomLevel) addLinear = this.opts.maxZoomLevel;
let linearToLog = Math.pow(2, addLinear);
this.#zoomLevel = addLinear;
this.kp.camScale(linearToLog);
}
zoomTo(newZoom, position) {
const curCamPos = this.kp.camPos();
const camScl = 1 / this.kp.camScale().y;
const diff = this.kp.center().sub(position).scale(camScl);
this.zoomLevel = newZoom;
let newZoomLog = 1 / Math.pow(2, newZoom);
const newDiff = this.kp.center().sub(position).scale(newZoomLog);
if (newZoom <= this.opts.minZoomLevel) return;
if (newZoom >= this.opts.maxZoomLevel) return;
if (this.#moveTween != null) {
this.#moveTween.finish();
}
this.#moveTween = this.kp.tween(
curCamPos,
curCamPos.sub(diff).add(newDiff),
0.25,
this.kp.camPos,
this.kp.easings.easeOutQuad
);
this.#moveTween.then(() => {
this.#moveTween = null;
});
}
zoomToNoTween(newZoom, position) {
const curCamPos = this.kp.camPos();
const camScl = 1 / this.kp.camScale().y;
const diff = this.kp.center().sub(position).scale(camScl);
this.zoomLevel_NoTween = newZoom;
let newZoomLog = 1 / Math.pow(2, newZoom);
const newDiff = this.kp.center().sub(position).scale(newZoomLog);
if (newZoom <= this.opts.minZoomLevel) return;
if (newZoom >= this.opts.maxZoomLevel) return;
this.kp.camPos(curCamPos.sub(diff).add(newDiff));
}
zoomToNoTweenAbs(newZoom, position) {
this.zoomLevel_NoTween = newZoom;
this.kp.camPos(position);
}
}

View file

@ -0,0 +1,60 @@
export function addZoomButtons(map) {
// Zoom buttons
const plus = map.kp.loadSprite(null, "/assets/images/plus.png", {
sliceX: 2,
});
const minus = map.kp.loadSprite(null, "/assets/images/minus.png", {
sliceX: 2,
});
const zoomIn = map.kp.make([
map.kp.sprite(plus),
map.kp.pos(16),
map.kp.scale(2),
map.kp.area(),
map.kp.opacity(1),
"ui",
]);
const zoomOut = map.kp.make([
map.kp.sprite(minus),
map.kp.pos(16, 42),
map.kp.scale(2),
map.kp.area(),
map.kp.opacity(1),
"ui",
]);
let ziw;
zoomIn.onClick(() => {
map.mouseMode = "ui";
map.zoomLevel += 1;
zoomIn.frame = 1;
if (ziw?.finish) ziw.finish();
ziw = map.kp.wait(0.25, () => (zoomIn.frame = 0));
map.clearMouseMode();
});
zoomIn.onUpdate(() => {
zoomIn.opacity = map.zoomLevelLimit > 0 ? 0.25 : 1;
});
let zow;
zoomOut.onClick(() => {
map.mouseMode = "ui";
map.zoomLevel -= 1;
zoomOut.frame = 1;
if (zow?.finish) zow.finish();
zow = map.kp.wait(0.25, () => (zoomOut.frame = 0));
map.clearMouseMode();
});
zoomOut.onUpdate(() => {
zoomOut.opacity = map.zoomLevelLimit < 0 ? 0.25 : 1;
});
map.uiLayer.add(zoomIn);
map.uiLayer.add(zoomOut);
}

View file

@ -1,160 +1,28 @@
import kaplay from "./modules/kaplay.js";
import { KaplayMap } from "./KaplayMap/index.js";
import { addZoomButtons } from "./KaplayMap/zoom.js";
const map = document.querySelector("map");
class KaplayMap {
kp;
opts = {
minZoomLevel: 0,
maxZoomLevel: 3,
};
#zoomLevel = 0;
#scaleTween = null;
startMousePos = null;
startCamPos = null;
prevCamPos = null;
moveVelocity = null;
moveFriction = 0.25;
moveDead = 1;
#moveTween = null;
constructor(kp, opts) {
this.kp = kp;
this.opts = {
...this.opts,
...opts,
};
// Zooming
this.kp.onScroll((delta) => {
const scrollDist = -delta.y / 120;
this.zoomTo(this.zoomLevel + scrollDist, this.kp.mousePos());
});
// Dragging
this.kp.onUpdate(() => {
const curCamPos = this.kp.camPos();
const camScale = 1 / this.kp.camScale().y;
// ||| Completely unintelligible to the average person |||
// vvv Sorry y'all xwx vvv
if (this.kp.isMousePressed()) {
this.startMousePos = this.kp.mousePos();
this.startCamPos = this.kp.camPos();
} else if (this.kp.isMouseReleased() && this.prevCamPos != null) {
this.moveVelocity = this.prevCamPos.sub(curCamPos).scale(1 / camScale);
}
if (this.kp.isMouseDown()) {
this.prevCamPos = this.kp.camPos();
this.kp.camPos(
this.startCamPos.sub(
this.kp.mousePos().sub(this.startMousePos).scale(camScale)
)
);
} else if (this.moveVelocity?.x != 0 && this.moveVelocity?.y != 0)
this.kp.camPos(curCamPos.sub(this.moveVelocity?.scale(camScale) ?? 0));
// ^^^ Completely unintelligible to the average person ^^^
// ||| Sorry y'all xwx |||
if (this.moveVelocity == null) return;
if (
this.moveVelocity.x <= this.moveDead &&
this.moveVelocity.x >= -this.moveDead
)
this.moveVelocity.x = 0;
if (
this.moveVelocity.y <= this.moveDead &&
this.moveVelocity.y >= -this.moveDead
)
this.moveVelocity.y = 0;
this.moveVelocity = this.moveVelocity.scale(1 - this.moveFriction);
});
}
get zoomLevel() {
return this.#zoomLevel;
}
set zoomLevel(newZoom) {
const cameraZoom = this.kp.camScale().y;
let addLinear = newZoom;
if (addLinear < this.opts.minZoomLevel) addLinear = this.opts.minZoomLevel;
if (addLinear > this.opts.maxZoomLevel) addLinear = this.opts.maxZoomLevel;
let linearToLog = Math.pow(2, addLinear);
this.#zoomLevel = addLinear;
if (this.#scaleTween != null) {
this.#scaleTween.finish();
}
this.#scaleTween = this.kp.tween(
cameraZoom,
linearToLog,
0.25,
this.kp.camScale,
this.kp.easings.easeOutQuad
);
this.#scaleTween.then(() => {
this.#scaleTween = null;
});
}
zoomTo(newZoom, position) {
const curCamPos = this.kp.camPos();
const camScale = 1 / this.kp.camScale().y;
const diff = this.kp.center().sub(position).scale(camScale);
this.zoomLevel = newZoom;
let newZoomLog = 1 / Math.pow(2, newZoom);
const newDiff = this.kp.center().sub(position).scale(newZoomLog);
if (newZoom < this.opts.minZoomLevel) return;
if (newZoom > this.opts.maxZoomLevel) return;
if (this.#moveTween != null) {
this.#moveTween.finish();
}
this.#moveTween = this.kp.tween(
curCamPos,
curCamPos.sub(diff).add(newDiff),
0.25,
this.kp.camPos,
this.kp.easings.easeOutQuad
);
this.#moveTween.then(() => {
this.#moveTween = null;
});
}
}
const map = document.querySelector("#map");
const kp = kaplay({
canvas: map,
focus: true,
loadingScreen: false,
crisp: false,
debug: false,
crisp: true,
// debug: false,
// touchToMouse: false,
global: false,
maxFPS: 120,
texFilter: "nearest",
background: "404040",
});
const kaplaymap = new KaplayMap(kp, {});
kaplaymap.initGrid();
addZoomButtons(kaplaymap);
const bean = kp.loadBean();
kp.add([kp.sprite("bean"), kp.pos(kp.center())]);
kp.add([kp.sprite("bean"), kp.pos(kp.center())]);

View file

@ -4,4 +4,8 @@ body, html {
width: 100vw;
box-sizing: border-box;
overflow: hidden;
}
#map {
cursor: grab;
}