From 530256b4769c215b6bbdd0f08e024c45e2e29ce6 Mon Sep 17 00:00:00 2001 From: MeowcaTheoRange Date: Sat, 1 Jun 2024 13:36:00 -0500 Subject: [PATCH] a kaplay map is done --- assets/images/grid.png | Bin 0 -> 9178 bytes assets/images/minus.png | Bin 0 -> 208 bytes assets/images/plus.png | Bin 0 -> 248 bytes assets/scripts/KaplayMap/index.js | 372 ++++++++++++++++++++++++++++++ assets/scripts/KaplayMap/zoom.js | 60 +++++ assets/scripts/script.js | 156 +------------ assets/styles/style.css | 4 + 7 files changed, 448 insertions(+), 144 deletions(-) create mode 100644 assets/images/grid.png create mode 100644 assets/images/minus.png create mode 100644 assets/images/plus.png create mode 100644 assets/scripts/KaplayMap/index.js create mode 100644 assets/scripts/KaplayMap/zoom.js diff --git a/assets/images/grid.png b/assets/images/grid.png new file mode 100644 index 0000000000000000000000000000000000000000..04da9eb2c7dff448d7c61b91dedb5d36bd3bd95a GIT binary patch literal 9178 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 Hr-UW|A~#B% literal 0 HcmV?d00001 diff --git a/assets/images/plus.png b/assets/images/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..af56f5bbe5651f90fed23f87315cd9eb0ab5ebde GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYe)P7GF#}JRsPa`#Brt?fehS5B@KFhE>i?}Go+Y~Fzngc5TMlZ zj47f^ShCqv`PUcbAV8 o)Hw1pH!HJfu(F%St48ZHY!J7sI?5oo0_YV6Pgg&ebxsLQ09MXZoB#j- literal 0 HcmV?d00001 diff --git a/assets/scripts/KaplayMap/index.js b/assets/scripts/KaplayMap/index.js new file mode 100644 index 0000000..4125027 --- /dev/null +++ b/assets/scripts/KaplayMap/index.js @@ -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); + } +} diff --git a/assets/scripts/KaplayMap/zoom.js b/assets/scripts/KaplayMap/zoom.js new file mode 100644 index 0000000..0b4404e --- /dev/null +++ b/assets/scripts/KaplayMap/zoom.js @@ -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); +} diff --git a/assets/scripts/script.js b/assets/scripts/script.js index f5e7510..23b594d 100644 --- a/assets/scripts/script.js +++ b/assets/scripts/script.js @@ -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())]); \ No newline at end of file diff --git a/assets/styles/style.css b/assets/styles/style.css index cd9fcef..3a4b14b 100644 --- a/assets/styles/style.css +++ b/assets/styles/style.css @@ -4,4 +4,8 @@ body, html { width: 100vw; box-sizing: border-box; overflow: hidden; +} + +#map { + cursor: grab; } \ No newline at end of file