diff --git a/src/Game/Camera/Camera.js b/src/Game/Camera/Camera.js index a00c33e..fe7467c 100644 --- a/src/Game/Camera/Camera.js +++ b/src/Game/Camera/Camera.js @@ -3,14 +3,14 @@ import { UICameraInfo } from "../UIPipes/UIPipes"; import { interpolate, interpolateCos } from "../Utils/Math.utils"; export function moveVertically(tick, keyCode) { - BC_CAMERA.position.y += (tick.deltaMS / 1000) * 800 * (keyCode === "KeyS" ? 1 : -1); + BC_CAMERA.position.y += (tick.deltaMS / 1000) * 1800 * (keyCode === "KeyS" ? 1 : -1); UICameraInfo.update((s) => { s.position.y = BC_CAMERA.position.y; }); } export function moveHorizontally(tick, keyCode) { - BC_CAMERA.position.x += (tick.deltaMS / 1000) * 800 * (keyCode === "KeyD" ? 1 : -1); + BC_CAMERA.position.x += (tick.deltaMS / 1000) * 1800 * (keyCode === "KeyD" ? 1 : -1); UICameraInfo.update((s) => { s.position.x = BC_CAMERA.position.x; }); diff --git a/src/Game/Game.js b/src/Game/Game.js index fde8456..ecc24ee 100644 --- a/src/Game/Game.js +++ b/src/Game/Game.js @@ -60,11 +60,17 @@ function setupInGameSelector() { sprite0.position.set(t.x, t.y); }; - // BC_VIEWPORT.onpointerdown = (e) => - // { - // let t = screenToWorldCoordinates(e.data.global.x, e.data.global.y); - // npccc.moveTo(new Point2D(getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getX(), getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getY())); - // } + BC_VIEWPORT.onpointerdown = (e) => + { + let t = screenToWorldCoordinates(e.data.global.x, e.data.global.y); + // npccc.moveTo(new Point2D(getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getX(), getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN).worldPosition.getY())); + let tile = getTileAt(t.x, t.y, ChunkStorageTypes.TYPE_TERRAIN); + if(tile.props.navigationCost > 10) return; + npccc.moveTo(new PointInt2D(tile.worldPosition.getX(), tile.worldPosition.getY()), + (cb)=>{ + console.log(cb); + }); + } } @@ -97,8 +103,8 @@ export async function initGame() { setBC_CURRENT_SCENE(new GameScene()); - BC_CAMERA.position.x = Math.floor(PRNG() * 3242 - 372); - BC_CAMERA.position.y = Math.floor(PRNG() * 1285 - 255); + // BC_CAMERA.position.x = Math.floor(PRNG() * 3242 - 372); + // BC_CAMERA.position.y = Math.floor(PRNG() * 1285 - 255); UICameraInfo.update((s)=>{ s.position.x = BC_CAMERA.position.x; @@ -136,18 +142,21 @@ function startGame() { createFirstWorldChunks(); - let testNPCController = new NPCController(); + let testNPCController = new NPCController(true); let testNPC = new NPCProto(true, testNPCController); testNPC.spriteSheetPath = "assets/images/characters/char0.png"; testNPC.frame = new PIXI.Rectangle(0, 0, 16, 16); testNPC.controller = testNPCController; + testNPC.worldPosition = new PointInt2D(0,0); testNPCController.controlledNPC = testNPC; addGameObjectToGameState(testNPCController); BC_CURRENT_SCENE.addObjectToSceneWithInitialization(testNPC); testNPC.drawObject.zIndex = 4; testNPC.drawObject.scale.set(BC_SPRITES_SETTINGS.scale, BC_SPRITES_SETTINGS.scale); npccc = testNPCController; - let pf = new PathFinder(); - let dest = pf.search(new PointInt2D(), new PointInt2D(10, 10)); - testNPCController.moveTo(new Point2D(dest.worldPosition.getX(), dest.worldPosition.getY())); + // let pf = new PathFinder(); + // setTimeout(()=>{ + // console.log(pf.search(new PointInt2D(), new PointInt2D(0, 0))); + // }, 1); + // testNPCController.moveTo(new Point2D(dest.worldPosition.getX(), dest.worldPosition.getY())); } diff --git a/src/Game/GlobalVariables/GlobalVariables.js b/src/Game/GlobalVariables/GlobalVariables.js index cb8123f..415b6d3 100644 --- a/src/Game/GlobalVariables/GlobalVariables.js +++ b/src/Game/GlobalVariables/GlobalVariables.js @@ -43,7 +43,8 @@ export function setBC_NPC_LAYER(npc_layer) { // export let BC_TERRAIN; export let BC_TERRAIN_SETTINGS = { tileSize: 16, - scale: 8.0 + scale: 8.0, + totalSize: 16*8.0 } // export let BC_TERRAIN_VAULT = {}; // export function setBC_TERRAIN(terrain) { diff --git a/src/Game/NPC/NPCController/NPCController.js b/src/Game/NPC/NPCController/NPCController.js index 8b6a63f..2d2c0ac 100644 --- a/src/Game/NPC/NPCController/NPCController.js +++ b/src/Game/NPC/NPCController/NPCController.js @@ -1,5 +1,6 @@ import { GameObject } from "../../GameObject/GameObject"; -import { Point2D } from "../../Utils/Math.utils"; +import { PointInt2D } from "../../Utils/Math.utils"; +import { NavigationPath, PathFinder } from "../../Utils/PathFinding.util"; import { NPCProto } from "../NPCProto/NPCProto"; /** * NPCController defines NPC behavior. Many NPC can have same NPCController for the same behavior. @@ -12,18 +13,87 @@ export class NPCController extends GameObject */ controlledNPC = null; - constructor(tickAble = false) + /** + * @type Array + */ + navigationPathQueue = new Array(); + navigationInProgress = false; + navigationFollowMidPoint = false; + navigationCallback = ()=>{}; + + constructor(tickAble = true) { super(tickAble); } /** * moves NPC to position - * @param {Point2D} position + * @param {PointInt2D} position + * @param {Function} callback */ - moveTo(position) + moveTo(position, callback) { - this.controlledNPC.currentPosition = position; - this.controlledNPC.currentPosition = position; + let pf = new PathFinder(); + let nPath = pf.search(new PointInt2D(this.controlledNPC.worldPosition.getX(), this.controlledNPC.worldPosition.getY()), position); + if(!nPath) + { + console.log("failed"); + callback("failed"); + return; + } + else if (nPath.path.length < 2) + { + console.log("success"); + callback("success"); + return; + } + for (let i = nPath.path.length-1; i > 0; i--) { + this.navigationPathQueue.push(nPath.path[i]); + } + this.navigationCallback = callback; + this.navigationInProgress = true; + } + + /** + * + * @param {NavigationPath} pathToFollow + * @param {Function} callback + */ + _moveByPath(pathToFollow, callback) + { + + } + + tick(ticker) + { + // console.log("object"); + if(this.navigationInProgress) + { + if(!this.navigationFollowMidPoint) + { + let target = this.navigationPathQueue.pop(); + if(!target) + { + this.navigationInProgress = false; + this.navigationCallback("success"); + console.log("success"); + } + else + { + this.controlledNPC.worldPosition = target; + this.navigationFollowMidPoint = true; + } + } + else + { + if( + Math.abs(this.controlledNPC.drawObject.x - this.controlledNPC.worldPosition.getX()) < 0.5 && + Math.abs(this.controlledNPC.drawObject.y - this.controlledNPC.worldPosition.getY()) < 0.5 + ) + { + this.navigationFollowMidPoint = false; + } + } + } } }; \ No newline at end of file diff --git a/src/Game/NPC/NPCProto/NPCProto.js b/src/Game/NPC/NPCProto/NPCProto.js index 7c1c933..7afe020 100644 --- a/src/Game/NPC/NPCProto/NPCProto.js +++ b/src/Game/NPC/NPCProto/NPCProto.js @@ -12,12 +12,7 @@ export class NPCProto extends SceneObject */ controller = null; - /** - * NPC position in world - */ - currentPosition = new Point2D(); - - _positionInterpolationSpeed = 10; + _positionInterpolationSpeed = 30; /** * path to NPC spritesheet @@ -54,7 +49,7 @@ export class NPCProto extends SceneObject _positionInterpolation(delta) { let {x, y} = this.drawObject.position; - this.drawObject.position.set(interpolate(x, this.currentPosition.getX(), delta), interpolate(y, this.currentPosition.getY(), delta)); + this.drawObject.position.set(interpolate(x, this.worldPosition.getX(), delta), interpolate(y, this.worldPosition.getY(), delta)); }; }; \ No newline at end of file diff --git a/src/Game/SceneObjects/SceneObject.js b/src/Game/SceneObjects/SceneObject.js index 068c305..d3790aa 100644 --- a/src/Game/SceneObjects/SceneObject.js +++ b/src/Game/SceneObjects/SceneObject.js @@ -1,6 +1,6 @@ import { GameObject } from "../GameObject/GameObject"; import { Container } from "../../pixi/pixi.mjs"; -import { Point2D } from "../Utils/Math.utils"; +import { Point2D, PointInt2D } from "../Utils/Math.utils"; export class SceneObject extends GameObject { /** @@ -17,7 +17,7 @@ export class SceneObject extends GameObject { /** * world position for scene object */ - worldPosition = new Point2D(); + worldPosition = new PointInt2D(); /** * diff --git a/src/Game/Utils/Math.utils.js b/src/Game/Utils/Math.utils.js index e4977a2..456c08e 100644 --- a/src/Game/Utils/Math.utils.js +++ b/src/Game/Utils/Math.utils.js @@ -73,6 +73,41 @@ export class PointInt2D this.#y = Math.floor(y); }; + /** + * + * @param {Number} x + */ + divideBy(x) + { + if(x > 0) + { + this.#x = Math.floor(this.#x / x); + this.#y = Math.floor(this.#y / x); + return true; + } + else if (x < 0) + { + this.#x = Math.ceil(this.#x / x); + this.#y = Math.ceil(this.#y / x); + return true; + } + return false; + }; + + multiplyBy(x) + { + if(x >= 0) + { + this.#x = Math.floor(this.#x * x); + this.#y = Math.floor(this.#y * x); + } + else if (x < 0) + { + this.#x = Math.ceil(this.#x * x); + this.#y = Math.ceil(this.#y * x); + } + } + /** * * @param {PointInt2D} a diff --git a/src/Game/Utils/PathFinding.util.js b/src/Game/Utils/PathFinding.util.js index 2d4d650..f81d6b7 100644 --- a/src/Game/Utils/PathFinding.util.js +++ b/src/Game/Utils/PathFinding.util.js @@ -2,71 +2,106 @@ import { BC_TERRAIN_SETTINGS } from "../GlobalVariables/GlobalVariables"; import { ChunkStorageTypes } from "../WorldGeneration/WorldChunk/WorldGenChunk"; import { getTileAt } from "../WorldGeneration/WorldGen"; import { PointInt2D } from "./Math.utils"; -import { TerrainTile } from "../WorldGeneration/WorldObjects/TerrainTile/TerrainTile"; +import { SceneObject } from "../SceneObjects/SceneObject"; -class PathFinderNode -{ +class PathFinderNode { position; - fScore; - neighbors; + fScore = 0; + gScore = 1e16; + dScore = 1; + // hScore = 1e16; + id; /** - * - * @param {PointInt2D} position - * @param {Number} fScore + * + * @param {PointInt2D} position + * @param {Number} gScore */ - constructor(position, fScore) - { + constructor(position, gScore = 1e16, fScore = 1e16, dScore) { this.position = position; + this.id = position.getX() + "_" + position.getY(); + this.gScore = gScore; this.fScore = fScore; + this.dScore = dScore; + } +} + +export class NavigationPath { + path; + + /** + * @param {Array} path + */ + constructor(path = []) + { + this.path = path; } }; -export class PathFinder -{ +export class PathFinder { /** * @type Array */ _openSet = new Array(); _closedSet = new Array(); + _goal = new PointInt2D(); + _start = new PointInt2D(); + /** - * - * @param {PointInt2D} start - * @param {PointInt2D} goal + * + * @param {PointInt2D} start + * @param {PointInt2D} goal */ - search(start, goal) - { - this._openSet.push(new PathFinderNode(start, 0)); + search(start, goal) { + start.divideBy(BC_TERRAIN_SETTINGS.totalSize); + start.multiplyBy(BC_TERRAIN_SETTINGS.totalSize); + this._start = start; + + goal.divideBy(BC_TERRAIN_SETTINGS.totalSize); + goal.multiplyBy(BC_TERRAIN_SETTINGS.totalSize); + this._goal = goal; + + this._openSet.push(new PathFinderNode(this._start, 0, this._heuristic(this._start, 0))); //code + /** + * @type PathFinderNode + */ + let currentNode; + // /** + // * @type Map + // */ + // let gScores = new Map(); + // /** + // * @type Map + // */ + // let fScores = new Map(); - let currentNode = new PathFinderNode(new PointInt2D(), 0); /** - * @type Map + * @type Map */ - let gScores = new Map(); - /** - * @type Map - */ - let fScores = new Map(); + let cameFrom = new Map(); let minFScoreNodeIndex = 0; //START LOOP WOOOOAAAW + // console.log(goal); while (this._openSet.length > 0) { //find node with min f score //better to rewrite it to priority queue for (let i = 0; i < this._openSet.length; i++) { - if(this._openSet[i] < this._openSet[minFScoreNodeIndex]) - minFScoreNodeIndex = i; + if(!this._openSet[i]){ + return undefined; + } + if (this._openSet[i].fScore < this._openSet[minFScoreNodeIndex].fScore) minFScoreNodeIndex = i; } //wow! node is found and set!! currentNode = this._openSet[minFScoreNodeIndex]; - if(PointInt2D.isEqual(currentNode.position, goal)) - { + if (PointInt2D.isEqual(currentNode.position, this._goal)) { //wow!!! we have found an end of the path!!! this is so cool!!! - return 1111; //return something weird stuff + // console.log(cameFrom); + return new NavigationPath(this._reconstructPath(cameFrom, currentNode)); //return something weird stuff } //and now we must delete this node... what a sad situation... @@ -75,21 +110,131 @@ export class PathFinder this._closedSet.push(currentNode); /** - * @type TerrainTile + * @type Array */ - let neighbors = this._getNeighbors(currentNode.position); - return neighbors; + let currentNeighbors = this._getNeighbors(currentNode.position); + if(!currentNeighbors) return undefined; + // console.log(currentNeighbors); + for (const neighbor of currentNeighbors) { + this._closedSet.push(neighbor); + let tentativeGScore = currentNode.gScore + this._manhattanDistance(currentNode.position, neighbor.position); + if (tentativeGScore < neighbor.gScore) { + // console.log(neighbor.id, currentNode.id); + cameFrom.set(neighbor.id, currentNode); + neighbor.gScore = tentativeGScore; + neighbor.fScore = tentativeGScore + this._heuristic(neighbor.position, neighbor.dScore); + + let neighborFoundInOpenSet = false; + for (const node of this._openSet) { + if (node.id === neighbor.id) { + neighborFoundInOpenSet = true; + break; + } + } + if (!neighborFoundInOpenSet) this._openSet.push(neighbor); + } + } } + + return undefined; } /** - * - * @param {PointInt2D} root + * + * @param {Map} cameFrom + * @param {PathFinderNode} current */ - _getNeighbors(root) - { - let neighborNorth = getTileAt(root.getX(), root.getY() - BC_TERRAIN_SETTINGS.tileSize*BC_TERRAIN_SETTINGS.scale, ChunkStorageTypes.TYPE_TERRAIN); - return neighborNorth; - //TODO + _reconstructPath(cameFrom, current) { + // console.log(cameFrom); + let totalPath = [current.position]; + let keys = [...cameFrom.keys()]; + while (keys.includes(current.id)) { + current = cameFrom.get(current.id); + totalPath.push(current.position); + } + return totalPath.reverse(); } -}; \ No newline at end of file + + /** + * + * @param {PointInt2D} root + */ + _getNeighbors(root) { + /** + * @type Array + */ + let neighbors = new Array(); + /** + * @type SceneObject + */ + let currentTile = getTileAt(root.getX(), root.getY() + BC_TERRAIN_SETTINGS.totalSize, ChunkStorageTypes.TYPE_TERRAIN); + if(!currentTile) return undefined; + // currentTile.drawObject.tint = 0xff0000; + let node = new PathFinderNode(new PointInt2D(currentTile.worldPosition.getX(), currentTile.worldPosition.getY()), 1e16, 1e16, currentTile.props.navigationCost); + //north + if (!this._existsInClosedSet(node)) { + neighbors.push(node); + } + //south + currentTile = getTileAt(root.getX(), root.getY() - BC_TERRAIN_SETTINGS.totalSize, ChunkStorageTypes.TYPE_TERRAIN); + if(!currentTile) return undefined; + // currentTile.drawObject.tint = 0xff0000; + node = new PathFinderNode(new PointInt2D(currentTile.worldPosition.getX(), currentTile.worldPosition.getY()), 1e16, 1e16, currentTile.props.navigationCost); + if (!this._existsInClosedSet(node)) { + neighbors.push(node); + } + //east + currentTile = getTileAt(root.getX() + BC_TERRAIN_SETTINGS.totalSize, root.getY(), ChunkStorageTypes.TYPE_TERRAIN); + if(!currentTile) return undefined; + // currentTile.drawObject.tint = 0xff0000; + node = new PathFinderNode(new PointInt2D(currentTile.worldPosition.getX(), currentTile.worldPosition.getY()), 1e16, 1e16, currentTile.props.navigationCost); + if (!this._existsInClosedSet(node)) { + neighbors.push(node); + } + //west + currentTile = getTileAt(root.getX() - BC_TERRAIN_SETTINGS.totalSize, root.getY(), ChunkStorageTypes.TYPE_TERRAIN); + if(!currentTile) return undefined; + // currentTile.drawObject.tint = 0xff0000; + node = new PathFinderNode(new PointInt2D(currentTile.worldPosition.getX(), currentTile.worldPosition.getY()), 1e16, 1e16, currentTile.props.navigationCost); + if (!this._existsInClosedSet(node)) { + neighbors.push(node); + } + + return neighbors; + } + + /** + * + * @param {PointInt2D} start + * @param {PointInt2D} goal + * @returns + */ + _manhattanDistance(start, goal) { + return Math.abs(start.getX() - goal.getX()) + Math.abs(start.getY() - goal.getY()); + } + + /** + * + * @param {PointInt2D} current + * @param {Number} d + */ + _heuristic(current, d) { + let x1 = current.getX() - this._goal.getX(); + let y1 = current.getY() - this._goal.getY(); + let x2 = this._start.getX() - this._goal.getX(); + let y2 = this._start.getY() - this._goal.getY(); + let cross = Math.abs(x1*y2 - x2*y1); + return this._manhattanDistance(current, this._goal) * d + cross*0.001; + } + + /** + * + * @param {PathFinderNode} node + */ + _existsInClosedSet(node) { + for (const i of this._closedSet) { + if (i.id === node.id) return true; + } + return false; + } +} diff --git a/src/Game/WorldGeneration/WorldGen.js b/src/Game/WorldGeneration/WorldGen.js index 3f11402..4eb28eb 100644 --- a/src/Game/WorldGeneration/WorldGen.js +++ b/src/Game/WorldGeneration/WorldGen.js @@ -150,7 +150,7 @@ let noise = new Noise(Math.floor(PRNG() * 188822321)); let noiseErosion = new Noise(Math.floor(PRNG() * 327749029)); let noiseBiomes = new Noise(Math.floor(PRNG() * 927472011)); -let terrainCue = new NumberCue([0, 1, 2, 3, 3], [0.0, 0.45, 0.5, 0.9, 1.0]); +let terrainCue = new NumberCue([0, 1, 2, 3, 3], [0.0, 0.2, 0.25, 0.9, 1.0]); let terrainTintCue = new NumberCue([0.9, 1, 1, 0.95, 0.9, 1, 0.93, 1], [0.0, 0.45, 0.45, 0.5, 0.5, 0.9, 0.9, 1.0]); /** @@ -223,8 +223,8 @@ export function fillWorldGenChunk(chunk, x, y) { export function createFirstWorldChunks() { let w = BC_CHUNKS_SETTINGS.width * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale; let h = BC_CHUNKS_SETTINGS.height * BC_TERRAIN_SETTINGS.tileSize * BC_TERRAIN_SETTINGS.scale; - for (let i = -1; i < 2; i++) { - for (let j = -1; j < 2; j++) { + for (let i = -2; i < 3; i++) { + for (let j = -2; j < 3; j++) { let chunkXCeiled = Math.floor((BC_CAMERA.position.x + w * i) / w); let chunkYCeiled = Math.floor((BC_CAMERA.position.y + h * j) / h); let chunkId = chunkXCeiled + "_" + chunkYCeiled; diff --git a/src/Test.js b/src/Test.js index 651a3b9..83fbbfa 100644 --- a/src/Test.js +++ b/src/Test.js @@ -109,6 +109,7 @@ export function search() { let neighbors = current.neighbors; for (let i = 0; i < neighbors.length; i++) { + // console.log(neighbors.length); let neighbor = neighbors[i]; if (!closedSet.includes(neighbor)) {