diff --git a/src/assets/sprites/building/icons/confirm.png b/src/assets/sprites/building/icons/confirm.png new file mode 100644 index 00000000..1f1f7ebd Binary files /dev/null and b/src/assets/sprites/building/icons/confirm.png differ diff --git a/src/assets/sprites/building/icons/confirm_disabled.png b/src/assets/sprites/building/icons/confirm_disabled.png new file mode 100644 index 00000000..71b87251 Binary files /dev/null and b/src/assets/sprites/building/icons/confirm_disabled.png differ diff --git a/src/assets/sprites/building/icons/decline.png b/src/assets/sprites/building/icons/decline.png new file mode 100644 index 00000000..07a1bcae Binary files /dev/null and b/src/assets/sprites/building/icons/decline.png differ diff --git a/src/assets/sprites/building/icons/placeholder.png b/src/assets/sprites/building/icons/placeholder.png index 124c1bcd..b349c697 100644 Binary files a/src/assets/sprites/building/icons/placeholder.png and b/src/assets/sprites/building/icons/placeholder.png differ diff --git a/src/const/world/world.ts b/src/const/world/world.ts index 0b55f1e1..4054e060 100644 --- a/src/const/world/world.ts +++ b/src/const/world/world.ts @@ -1,5 +1,5 @@ export const WORLD_COLLIDE_SPEED_FACTOR = 0.002; -export const WORLD_DEPTH_DEBUG = 9999; +export const WORLD_DEPTH_GRAPHIC = 9999; export const WORLD_DEPTH_EFFECT = 9998; diff --git a/src/game/scenes/world/builder.ts b/src/game/scenes/world/builder.ts index 13ff74fc..1bab08f9 100644 --- a/src/game/scenes/world/builder.ts +++ b/src/game/scenes/world/builder.ts @@ -2,7 +2,7 @@ import EventEmitter from 'events'; import Phaser from 'phaser'; -import { WORLD_DEPTH_EFFECT } from '~const/world'; +import { WORLD_DEPTH_GRAPHIC } from '~const/world'; import { DIFFICULTY } from '~const/world/difficulty'; import { BUILDINGS } from '~const/world/entities/buildings'; import { LEVEL_TILE_SIZE } from '~const/world/level'; @@ -32,9 +32,11 @@ export class Builder extends EventEmitter implements IBuilder { private buildArea: Nullable = null; - private buildingPreview: Nullable = null; + private buildPreview: Nullable = null; - private buildingPlaceholder: Nullable = null; + private buildPlaceholder: Nullable = null; + + private buildControls: Nullable = null; private buildings: Partial> = {}; @@ -72,9 +74,9 @@ export class Builder extends EventEmitter implements IBuilder { public update() { if (this.isCanBuild()) { if (this.isBuild) { - this.updateAssumedPosition(); + this.updateSupposedPosition(); this.updateBuildAreaPosition(); - this.updateBuildingPreview(); + this.updateBuildInstance(); } else { this.open(); } @@ -106,8 +108,8 @@ export class Builder extends EventEmitter implements IBuilder { this.variant = variant; - if (this.buildingPreview) { - this.buildingPreview.setTexture(BuildingInstance.Texture); + if (this.buildPreview) { + this.buildPreview.setTexture(BuildingInstance.Texture); } } @@ -217,11 +219,18 @@ export class Builder extends EventEmitter implements IBuilder { return; } - this.createBuildArea(); - this.createBuildingPreview(); - this.isBuild = true; + if (!this.scene.game.device.os.desktop) { + this.supposedPosition = this.scene.level.getFreeAdjacentTile({ + ...this.scene.player.positionAtMatrix, + z: 1, + }) ?? this.scene.player.positionAtMatrix; + } + + this.createBuildArea(); + this.createBuildInstance(); + this.emit(BuilderEvents.BUILD_START); } @@ -230,7 +239,7 @@ export class Builder extends EventEmitter implements IBuilder { return; } - this.destroyBuildingPreview(); + this.destroyBuildInstance(); this.destroyBuildArea(); this.isBuild = false; @@ -341,7 +350,14 @@ export class Builder extends EventEmitter implements IBuilder { this.scene.sound.play(BuildingAudio.BUILD); if (!this.scene.game.device.os.desktop) { - this.unsetBuildingVariant(true); + const adjacentPosition = this.scene.level.getFreeAdjacentTile({ + ...this.supposedPosition, + z: 1, + }); + + if (adjacentPosition) { + this.supposedPosition = adjacentPosition; + } } } @@ -410,7 +426,7 @@ export class Builder extends EventEmitter implements IBuilder { this.radius * 2 * LEVEL_TILE_SIZE.persperctive, ); this.buildArea.updateDisplayOrigin(); - this.buildArea.setDepth(WORLD_DEPTH_EFFECT); + this.buildArea.setDepth(WORLD_DEPTH_GRAPHIC); } private updateBuildAreaPosition() { @@ -432,24 +448,54 @@ export class Builder extends EventEmitter implements IBuilder { this.buildArea = null; } - private createBuildingPreview() { + private createBuildPreview() { if (!this.variant) { return; } const BuildingInstance = BUILDINGS[this.variant]; - this.buildingPreview = this.scene.add.image(0, 0, BuildingInstance.Texture); - this.buildingPreview.setOrigin(0.5, LEVEL_TILE_SIZE.origin); + this.buildPreview = this.scene.add.image(0, 0, BuildingInstance.Texture); + this.buildPreview.setOrigin(0.5, LEVEL_TILE_SIZE.origin); + } + + private createBuildPlaceholder() { + this.buildPlaceholder = this.scene.add.image(0, 0, BuildingIcon.PLACEHOLDER); + } + + private createBuildControls() { + this.buildControls = this.scene.add.container(0, 0); + + const confirm = this.scene.add.image(-16, 0, BuildingIcon.CONFIRM); + + confirm.setInteractive(); + confirm.on(Phaser.Input.Events.POINTER_DOWN, (pointer: Phaser.Input.Pointer) => { + pointer.reset(); + this.build(); + }); + + const decline = this.scene.add.image(16, 0, BuildingIcon.DECLINE); + + decline.setInteractive(); + decline.on(Phaser.Input.Events.POINTER_DOWN, () => { + this.unsetBuildingVariant(); + }); + + this.buildControls.add([confirm, decline]); + } + + private createBuildInstance() { + this.createBuildPreview(); + this.createBuildPlaceholder(); if (!this.scene.game.device.os.desktop) { - this.buildingPlaceholder = this.scene.add.image(0, 0, BuildingIcon.PLACEHOLDER); + this.createBuildControls(); } - this.updateBuildingPreview(); + this.updateBuildInstance(); } - private updateBuildingPreview() { + private updateBuildInstance() { if (!this.supposedPosition) { return; } @@ -459,28 +505,41 @@ export class Builder extends EventEmitter implements IBuilder { const depth = Level.GetTileDepth(positionAtWorld.y, tilePosition.z) + 1; const isAllow = this.isAllowBuild(); - if (this.buildingPreview) { - this.buildingPreview.setPosition(positionAtWorld.x, positionAtWorld.y); - this.buildingPreview.setDepth(depth); - this.buildingPreview.setAlpha(isAllow ? 1.0 : 0.25); + if (this.buildPreview) { + this.buildPreview.setPosition(positionAtWorld.x, positionAtWorld.y); + this.buildPreview.setDepth(depth); + this.buildPreview.setAlpha(isAllow ? 1.0 : 0.25); + } + + if (this.buildPlaceholder) { + this.buildPlaceholder.setPosition(positionAtWorld.x, positionAtWorld.y + LEVEL_TILE_SIZE.height * 0.5); + this.buildPlaceholder.setDepth(depth); + this.buildPlaceholder.setAlpha(isAllow ? 0.75 : 0.25); } - if (this.buildingPlaceholder) { - this.buildingPlaceholder.setPosition(positionAtWorld.x, positionAtWorld.y + LEVEL_TILE_SIZE.height * 0.5); - this.buildingPlaceholder.setDepth(depth); - this.buildingPlaceholder.setAlpha(isAllow ? 0.75 : 0.25); + if (this.buildControls) { + const confirmBtton = this.buildControls.getAt(0); + + this.buildControls.setPosition(positionAtWorld.x, positionAtWorld.y + LEVEL_TILE_SIZE.height); + this.buildControls.setDepth(WORLD_DEPTH_GRAPHIC); + confirmBtton.setTexture(isAllow ? BuildingIcon.CONFIRM : BuildingIcon.CONFIRM_DISABLED); } } - private destroyBuildingPreview() { - if (this.buildingPreview) { - this.buildingPreview.destroy(); - this.buildingPreview = null; + private destroyBuildInstance() { + if (this.buildPreview) { + this.buildPreview.destroy(); + this.buildPreview = null; } - if (this.buildingPlaceholder) { - this.buildingPlaceholder.destroy(); - this.buildingPlaceholder = null; + if (this.buildPlaceholder) { + this.buildPlaceholder.destroy(); + this.buildPlaceholder = null; + } + + if (this.buildControls) { + this.buildControls.destroy(); + this.buildControls = null; } } @@ -492,7 +551,7 @@ export class Builder extends EventEmitter implements IBuilder { : this.scene.input.pointer1; } - private updateAssumedPosition() { + private updateSupposedPosition() { let position: Vector2D; if (this.scene.game.device.os.desktop) { @@ -503,17 +562,15 @@ export class Builder extends EventEmitter implements IBuilder { } else { const pointer = this.getCurrentPointer(); - if (!pointer.active) { + if (!pointer.active || pointer.event.target !== this.scene.sys.canvas) { return; } - // Using instead of pointer.worldXY - // for get actual position in camera moving state - const worldPosition = this.scene.cameras.main.getWorldPoint(pointer.x, pointer.y); + pointer.updateWorldPoint(this.scene.cameras.main); position = { - x: worldPosition.x, - y: worldPosition.y - LEVEL_TILE_SIZE.height / this.scene.cameras.main.zoom, + x: pointer.worldX, + y: pointer.worldY - LEVEL_TILE_SIZE.height / this.scene.cameras.main.zoom, }; } @@ -529,19 +586,19 @@ export class Builder extends EventEmitter implements IBuilder { } private handlePointer() { + if (!this.scene.game.device.os.desktop) { + return; + } + this.scene.input.on(Phaser.Input.Events.POINTER_UP, (pointer: Phaser.Input.Pointer) => { if (!this.isBuild) { return; } - if (this.scene.game.device.os.desktop) { - if (pointer.button === 0) { - this.build(); - } else if (pointer.button === 2) { - this.unsetBuildingVariant(); - } - } else if (pointer === this.getCurrentPointer()) { + if (pointer.button === 0) { this.build(); + } else if (pointer.button === 2) { + this.unsetBuildingVariant(); } }); } diff --git a/src/game/scenes/world/entities/npc/npc.ts b/src/game/scenes/world/entities/npc/npc.ts index 6fd0016b..4b1cd046 100644 --- a/src/game/scenes/world/entities/npc/npc.ts +++ b/src/game/scenes/world/entities/npc/npc.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import { DEBUG_MODS } from '~const/game'; -import { WORLD_DEPTH_DEBUG } from '~const/world'; +import { WORLD_DEPTH_GRAPHIC } from '~const/world'; import { NPC_PATH_FIND_RATE } from '~const/world/entities/npc'; import { Sprite } from '~entity/sprite'; import { equalPositions } from '~lib/utils'; @@ -265,7 +265,7 @@ export class NPC extends Sprite implements INPC { } this.pathDebug = this.scene.add.graphics(); - this.pathDebug.setDepth(WORLD_DEPTH_DEBUG); + this.pathDebug.setDepth(WORLD_DEPTH_GRAPHIC); this.on(Phaser.GameObjects.Events.DESTROY, () => { this.pathDebug?.destroy(); diff --git a/src/game/scenes/world/entities/sprite.ts b/src/game/scenes/world/entities/sprite.ts index 802bdf58..bad8f71a 100644 --- a/src/game/scenes/world/entities/sprite.ts +++ b/src/game/scenes/world/entities/sprite.ts @@ -1,7 +1,7 @@ import Phaser from 'phaser'; import { DEBUG_MODS } from '~const/game'; -import { WORLD_COLLIDE_SPEED_FACTOR, WORLD_DEPTH_DEBUG } from '~const/world'; +import { WORLD_COLLIDE_SPEED_FACTOR, WORLD_DEPTH_GRAPHIC } from '~const/world'; import { Live } from '~lib/live'; import { equalPositions } from '~lib/utils'; import { Particles } from '~scene/world/effects'; @@ -260,7 +260,7 @@ export class Sprite extends Phaser.Physics.Arcade.Sprite implements ISprite { } this.positionDebug = this.scene.add.graphics(); - this.positionDebug.setDepth(WORLD_DEPTH_DEBUG); + this.positionDebug.setDepth(WORLD_DEPTH_GRAPHIC); this.on(Phaser.GameObjects.Events.DESTROY, () => { this.positionDebug?.destroy(); diff --git a/src/game/scenes/world/level/level.ts b/src/game/scenes/world/level/level.ts index 50c66be4..6c26a8e8 100644 --- a/src/game/scenes/world/level/level.ts +++ b/src/game/scenes/world/level/level.ts @@ -134,6 +134,24 @@ export class Level extends TileMatrix implements ILevel { return LEVEL_PLANETS[this.planet].BIOMES.find((biome) => (biome.data.type === type))?.data ?? null; } + public getFreeAdjacentTile(position: Vector3D) { + const positions: Vector2D[] = [ + { x: position.x + 1, y: position.y }, + { x: position.x, y: position.y + 1 }, + { x: position.x - 1, y: position.y }, + { x: position.x, y: position.y - 1 }, + { x: position.x + 1, y: position.y - 1 }, + { x: position.x + 1, y: position.y + 1 }, + { x: position.x - 1, y: position.y + 1 }, + { x: position.x - 1, y: position.y - 1 }, + ]; + + return positions.find((p) => this.isFreePoint({ + ...p, + z: position.z, + })) ?? null; + } + private addTilemap() { const data = new Phaser.Tilemaps.MapData({ width: LEVEL_MAP_SIZE, diff --git a/src/types/world/entities/building.ts b/src/types/world/entities/building.ts index 4c63649d..181fe0c7 100644 --- a/src/types/world/entities/building.ts +++ b/src/types/world/entities/building.ts @@ -176,6 +176,9 @@ export enum BuildingTexture { } export enum BuildingIcon { + CONFIRM = 'building/icons/confirm', + CONFIRM_DISABLED = 'building/icons/confirm_disabled', + DECLINE = 'building/icons/decline', ALERT = 'building/icons/alert', PLACEHOLDER = 'building/icons/placeholder', UPGRADE = 'building/icons/upgrade', diff --git a/src/types/world/level/level.ts b/src/types/world/level/level.ts index 1cd546f2..37fde5df 100644 --- a/src/types/world/level/level.ts +++ b/src/types/world/level/level.ts @@ -68,6 +68,12 @@ export interface ILevel extends ITileMatrix { */ getBiome(type: BiomeType): Nullable + /** + * Get free adjacent tile around source position. + * @param position - Source position + */ + getFreeAdjacentTile(position: Vector3D): Nullable + /** * Get data for saving. */