forked from noxious/client
Best undo/redo function across all map editor elements
This commit is contained in:
parent
ca307d4de3
commit
9459639497
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<PlacedMapObjects ref="mapObjects" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<PlacedMapObjects ref="mapObjects" @update="updateMapObjects" @updateAndCommit="updateAndCommit" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
|
<MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -10,12 +10,13 @@ import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEven
|
|||||||
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||||
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { cloneArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
import { cloneArray, createTileArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
||||||
import { TileStorage } from '@/storage/storages'
|
import { TileStorage } from '@/storage/storages'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
|
|
||||||
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
||||||
import type { MapEventTile, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
|
import type { PlacedMapObject as PlacedMapObjectT, Map as MapT, MapEventTile } from '@/application/types'
|
||||||
|
import { useManualRefHistory, useRefHistory } from '@vueuse/core'
|
||||||
|
|
||||||
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||||
@ -28,18 +29,20 @@ const mapObjects = useTemplateRef('mapObjects')
|
|||||||
const eventTiles = useTemplateRef('eventTiles')
|
const eventTiles = useTemplateRef('eventTiles')
|
||||||
|
|
||||||
//Record of commands
|
//Record of commands
|
||||||
let commandStack: EditorCommand[] = []
|
let commandStack: (EditorCommand | number) [] = []
|
||||||
let commandIndex = ref(0)
|
let commandIndex = ref(0)
|
||||||
|
|
||||||
let originTiles: string[][] = []
|
let originTiles: string[][] = []
|
||||||
let originEventTiles: MapEventTile[] = []
|
let originEventTiles: MapEventTile[] = []
|
||||||
let originObjects: PlacedMapObjectT[] = []
|
let originObjects = ref<PlacedMapObjectT[]>(mapEditor.currentMap.value.placedMapObjects)
|
||||||
|
|
||||||
|
const {undo, redo, commit, reset, history, canUndo, canRedo} = useRefHistory(originObjects, {clone:true, deep:true, capacity:9})
|
||||||
|
|
||||||
//Command Pattern basic interface, extended to store what elements have been changed by each edit
|
//Command Pattern basic interface, extended to store what elements have been changed by each edit
|
||||||
export interface EditorCommand {
|
export interface EditorCommand {
|
||||||
apply: (elements: any[]) => any[]
|
apply: (elements: any[]) => any[]
|
||||||
type: 'tile' | 'map_object' | 'event_tile'
|
type: 'tile' | 'map_object' | 'event_tile'
|
||||||
operation: 'draw' | 'erase' | 'place' | 'move' | 'delete' | 'rotate'
|
operation: 'draw' | 'erase' | 'clear'
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
|
function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
|
||||||
@ -50,57 +53,95 @@ function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
|
|||||||
return tileVersion
|
return tileVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(() => commandIndex.value!, (val) => {
|
watch(
|
||||||
if (val !== undefined) {
|
() => mapEditor.shouldClearTiles.value,
|
||||||
update(commandStack.slice(0, val))
|
(shouldClear) => {
|
||||||
|
if (shouldClear && mapEditor.currentMap.value) {
|
||||||
|
mapTiles.value!.clearTiles()
|
||||||
|
eventTiles.value!.clearTiles()
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = []
|
||||||
|
updateAndCommit(mapEditor.currentMap.value)
|
||||||
|
mapEditor.resetClearTilesFlag()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
function update(commands: EditorCommand[]) {
|
function update(commands: (EditorCommand | number)[]) {
|
||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
const tileCommands = commands.filter((command) => command.type === 'tile')
|
if (commandStack.length >= 9) {
|
||||||
const eventTileCommands = commands.filter((command) => command.type === 'event_tile')
|
if (typeof commandStack[0] !== 'number') {
|
||||||
const objectCommands = commands.filter((command) => command.type === 'map_object')
|
const base = commandStack.shift() as EditorCommand
|
||||||
|
if (base.operation !== 'clear') {
|
||||||
|
switch (base.type) {
|
||||||
|
case 'tile':
|
||||||
|
originTiles = base.apply(originTiles) as string[][]
|
||||||
|
break
|
||||||
|
case 'event_tile':
|
||||||
|
originEventTiles = base.apply(originEventTiles) as MapEventTile[]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
commandStack.shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof commandStack[0] === 'number') {
|
||||||
|
commandStack.shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tileCommands = commands.filter((item) => typeof item !== 'number' && item.type === 'tile') as EditorCommand[]
|
||||||
|
let eventTileCommands = commands.filter((item) => typeof item !== 'number' && item.type === 'event_tile') as EditorCommand[]
|
||||||
|
|
||||||
let modifiedTiles = applyCommands(originTiles, ...tileCommands)
|
let modifiedTiles = applyCommands(originTiles, ...tileCommands)
|
||||||
placeTiles(tileMap.value!, tileMapLayer.value!, modifiedTiles)
|
placeTiles(tileMap.value!, tileMapLayer.value!, modifiedTiles)
|
||||||
|
|
||||||
|
let eventTiles = applyCommands(originEventTiles, ...eventTileCommands)
|
||||||
|
|
||||||
mapEditor.currentMap.value.tiles = modifiedTiles
|
mapEditor.currentMap.value.tiles = modifiedTiles
|
||||||
mapEditor.currentMap.value.mapEventTiles = applyCommands(originEventTiles, ...eventTileCommands)
|
mapEditor.currentMap.value.mapEventTiles = eventTiles
|
||||||
mapEditor.currentMap.value.placedMapObjects = applyCommands(originObjects, ...objectCommands)
|
}
|
||||||
|
|
||||||
|
function updateMapObjects(map: MapT) {
|
||||||
|
originObjects.value = map.placedMapObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAndCommit(map?: MapT) {
|
||||||
|
commandStack = commandStack.slice(0, commandIndex.value)
|
||||||
|
if (map) updateMapObjects(map)
|
||||||
|
commit()
|
||||||
|
commandStack.push(0)
|
||||||
|
commandIndex.value = commandStack.length
|
||||||
|
|
||||||
|
console.log(history.value)
|
||||||
|
console.log(commandStack)
|
||||||
|
console.log(commandIndex.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCommand(command: EditorCommand) {
|
function addCommand(command: EditorCommand) {
|
||||||
commandStack = commandStack.slice(0, commandIndex.value)
|
commandStack = commandStack.slice(0, commandIndex.value)
|
||||||
commandStack.push(command)
|
commandStack.push(command)
|
||||||
|
|
||||||
if (commandStack.length >= 9) {
|
|
||||||
switch (commandStack[0].type) {
|
|
||||||
case 'tile':
|
|
||||||
originTiles = commandStack.shift()?.apply(originTiles) as string[][]
|
|
||||||
break
|
|
||||||
case 'map_object':
|
|
||||||
originObjects = commandStack.shift()?.apply(originObjects) as PlacedMapObjectT[]
|
|
||||||
break
|
|
||||||
case 'event_tile':
|
|
||||||
originEventTiles = commandStack.shift()?.apply(originEventTiles) as MapEventTile[]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
commandIndex.value = commandStack.length
|
commandIndex.value = commandStack.length
|
||||||
}
|
}
|
||||||
|
|
||||||
function undo() {
|
function undoEdit() {
|
||||||
if (commandIndex.value > 0) {
|
if (commandIndex.value > 0) {
|
||||||
commandIndex.value--
|
if (typeof(commandStack[--commandIndex.value]) === 'number' && canUndo) {
|
||||||
|
undo()
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = originObjects.value
|
||||||
|
}
|
||||||
|
update(commandStack.slice(0, commandIndex.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function redo() {
|
function redoEdit() {
|
||||||
if (commandIndex.value <= 9 && commandIndex.value <= commandStack.length) {
|
if (commandIndex.value <= 9 && commandIndex.value < commandStack.length) {
|
||||||
commandIndex.value++
|
if (typeof(commandStack[commandIndex.value++]) === 'number' && canRedo) {
|
||||||
|
redo()
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = originObjects.value
|
||||||
|
}
|
||||||
|
update(commandStack.slice(0, commandIndex.value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,12 +174,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
|||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
//CTRL+Y
|
//CTRL+Y
|
||||||
if (event.key === 'y' && event.ctrlKey) {
|
if (event.key === 'y' && event.ctrlKey) {
|
||||||
redo()
|
redoEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
//CTRL+Z
|
//CTRL+Z
|
||||||
if (event.key === 'z' && event.ctrlKey) {
|
if (event.key === 'z' && event.ctrlKey) {
|
||||||
undo()
|
undoEdit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +195,7 @@ function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
|||||||
mapTiles.value!.finalizeCommand()
|
mapTiles.value!.finalizeCommand()
|
||||||
break
|
break
|
||||||
case 'map_object':
|
case 'map_object':
|
||||||
mapObjects.value!.finalizeCommand()
|
updateAndCommit()
|
||||||
break
|
break
|
||||||
case 'teleport':
|
case 'teleport':
|
||||||
eventTiles.value!.finalizeCommand()
|
eventTiles.value!.finalizeCommand()
|
||||||
@ -171,9 +212,10 @@ onMounted(async () => {
|
|||||||
|
|
||||||
//Clone
|
//Clone
|
||||||
originTiles = cloneArray(mapValue.tiles)
|
originTiles = cloneArray(mapValue.tiles)
|
||||||
originObjects = cloneArray(mapValue.placedMapObjects)
|
|
||||||
originEventTiles = cloneArray(mapValue.mapEventTiles)
|
originEventTiles = cloneArray(mapValue.mapEventTiles)
|
||||||
|
|
||||||
|
commit()
|
||||||
|
|
||||||
const tileStorage = new TileStorage()
|
const tileStorage = new TileStorage()
|
||||||
const allTiles = await tileStorage.getAll()
|
const allTiles = await tileStorage.getAll()
|
||||||
const allTileIds = allTiles.map((tile) => tile.id)
|
const allTileIds = allTiles.map((tile) => tile.id)
|
||||||
|
@ -13,7 +13,7 @@ import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
|||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer, finalizeCommand })
|
defineExpose({ handlePointer, finalizeCommand, clearTiles})
|
||||||
|
|
||||||
const emit = defineEmits(['createCommand'])
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ const props = defineProps<{
|
|||||||
let currentCommand: EventTileCommand | null = null
|
let currentCommand: EventTileCommand | null = null
|
||||||
|
|
||||||
class EventTileCommand implements EditorCommand {
|
class EventTileCommand implements EditorCommand {
|
||||||
public operation: 'draw' | 'erase' = 'draw'
|
public operation: 'draw' | 'erase' | 'clear' = 'draw'
|
||||||
public type: 'event_tile' = 'event_tile'
|
public type: 'event_tile' = 'event_tile'
|
||||||
public affectedTiles: MapEventTile[] = []
|
public affectedTiles: MapEventTile[] = []
|
||||||
|
|
||||||
@ -39,15 +39,18 @@ class EventTileCommand implements EditorCommand {
|
|||||||
else if (this.operation === 'erase') {
|
else if (this.operation === 'erase') {
|
||||||
tileVersion = tileVersion.filter((v) => !this.affectedTiles.includes(v))
|
tileVersion = tileVersion.filter((v) => !this.affectedTiles.includes(v))
|
||||||
}
|
}
|
||||||
|
else if (this.operation === 'clear') {
|
||||||
|
tileVersion = []
|
||||||
|
}
|
||||||
return tileVersion
|
return tileVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(operation: 'draw' | 'erase') {
|
constructor(operation: 'draw' | 'erase' | 'clear') {
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCommandUpdate(tile: MapEventTile, operation: 'draw' | 'erase') {
|
function createCommandUpdate(tile?: MapEventTile, operation: 'draw' | 'erase' | 'clear') {
|
||||||
if (!currentCommand) {
|
if (!currentCommand) {
|
||||||
currentCommand = new EventTileCommand(operation)
|
currentCommand = new EventTileCommand(operation)
|
||||||
}
|
}
|
||||||
@ -149,4 +152,11 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearTiles() {
|
||||||
|
if (mapEditor.currentMap.value.mapEventTiles.length === 0) return
|
||||||
|
createCommandUpdate(null, 'clear')
|
||||||
|
finalizeCommand()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@ import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
|||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer, finalizeCommand })
|
defineExpose({ handlePointer, finalizeCommand, clearTiles })
|
||||||
|
|
||||||
const emit = defineEmits(['createCommand'])
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
@ -20,32 +20,37 @@ const props = defineProps<{
|
|||||||
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
||||||
// *** COMMAND STATE ***
|
// *** COMMAND STATE ***
|
||||||
|
|
||||||
let currentCommand: TileCommand | null = null
|
let currentCommand: TileCommand | null = null
|
||||||
|
|
||||||
class TileCommand implements EditorCommand {
|
class TileCommand implements EditorCommand {
|
||||||
public operation: 'draw' | 'erase' = 'draw'
|
public operation: 'draw' | 'erase' | 'clear' = 'draw'
|
||||||
public type: 'tile' = 'tile'
|
public type: 'tile' = 'tile'
|
||||||
public tileName: string = 'blank_tile'
|
public tileName: string = 'blank_tile'
|
||||||
public affectedTiles: number[][] = []
|
public affectedTiles: number[][] = []
|
||||||
|
|
||||||
apply(elements: string[][]) {
|
apply(elements: string[][]) {
|
||||||
let tileVersion = cloneArray(elements) as string[][]
|
let tileVersion
|
||||||
|
if (this.operation === 'clear') {
|
||||||
|
tileVersion = createTileArray(props.tileMapLayer.width, props.tileMapLayer.height, 'blank_tile')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
tileVersion = cloneArray(elements) as string[][]
|
||||||
for (const position of this.affectedTiles) {
|
for (const position of this.affectedTiles) {
|
||||||
tileVersion[position[1]][position[0]] = this.tileName
|
tileVersion[position[1]][position[0]] = this.tileName
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return tileVersion
|
return tileVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(operation: 'draw' | 'erase', tileName: string) {
|
constructor(operation: 'draw' | 'erase' | 'clear', tileName: string) {
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
this.tileName = tileName
|
this.tileName = tileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
|
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase' | 'clear') {
|
||||||
if (!currentCommand) {
|
if (!currentCommand) {
|
||||||
currentCommand = new TileCommand(operation, tileName)
|
currentCommand = new TileCommand(operation, tileName)
|
||||||
}
|
}
|
||||||
@ -137,21 +142,14 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// *** LIFECYCLE ***
|
// *** LIFECYCLE ***
|
||||||
|
|
||||||
|
function clearTiles() {
|
||||||
watch(
|
const tileArray = createTileArray(props.tileMap.width, props.tileMap.height, 'blank_tile')
|
||||||
() => mapEditor.shouldClearTiles.value,
|
placeTiles(props.tileMap, props.tileMapLayer, tileArray)
|
||||||
(shouldClear) => {
|
createCommandUpdate(0,0,"blank_tile",'clear')
|
||||||
if (shouldClear && mapEditor.currentMap.value) {
|
finalizeCommand()
|
||||||
const blankTiles = createTileArray(props.tileMapLayer.width, props.tileMapLayer.height, 'blank_tile')
|
}
|
||||||
placeTiles(props.tileMap, props.tileMapLayer, blankTiles)
|
|
||||||
mapEditor.currentMap.value.tiles = blankTiles
|
|
||||||
mapEditor.resetClearTilesFlag()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :map :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
|
<SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :map="mapEditor.currentMap.value!" :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
|
||||||
<PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
|
<PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -7,101 +7,38 @@
|
|||||||
import type {
|
import type {
|
||||||
MapObject,
|
MapObject,
|
||||||
Map as MapT,
|
Map as MapT,
|
||||||
PlacedMapObject as PlacedMapObjectT,
|
PlacedMapObject as PlacedMapObjectT
|
||||||
UUID,
|
|
||||||
MapEventTile
|
|
||||||
} from '@/application/types'
|
} from '@/application/types'
|
||||||
import { uuidv4 } from '@/application/utilities'
|
import { uuidv4 } from '@/application/utilities'
|
||||||
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
|
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
|
||||||
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { cloneArray, getTile } from '@/services/mapService'
|
import { getTile } from '@/services/mapService'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
|
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||||
import { computed } from 'vue'
|
|
||||||
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
|
||||||
import Vector2 = Phaser.Math.Vector2
|
|
||||||
import Tile = Phaser.Tilemaps.Tile
|
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
const map = computed(() => mapEditor.currentMap.value!)
|
|
||||||
|
|
||||||
defineExpose({ handlePointer, finalizeCommand })
|
const emit = defineEmits<{(e: 'update', map: MapT): void, (e: 'updateAndCommit', map: MapT): void}>()
|
||||||
|
|
||||||
const emit = defineEmits(['createCommand'])
|
defineExpose({ handlePointer })
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Tilemap
|
tileMap: Tilemap
|
||||||
tileMapLayer: TilemapLayer
|
tileMapLayer: TilemapLayer
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
|
||||||
// *** COMMAND STATE ***
|
|
||||||
|
|
||||||
let currentCommand: MapObjectCommand | null = null
|
|
||||||
|
|
||||||
class MapObjectCommand implements EditorCommand {
|
|
||||||
public operation: 'place' | 'move' | 'delete' | 'rotate' = 'place'
|
|
||||||
public type: 'map_object' = 'map_object'
|
|
||||||
public affectedTiles: PlacedMapObjectT[] = []
|
|
||||||
public targetPosition?: Phaser.Math.Vector2
|
|
||||||
|
|
||||||
apply(elements: PlacedMapObjectT[]) {
|
|
||||||
let tileVersion = cloneArray(elements) as PlacedMapObjectT[]
|
|
||||||
if (this.operation === 'place') {
|
|
||||||
tileVersion = tileVersion.concat(this.affectedTiles)
|
|
||||||
}
|
|
||||||
else if (this.operation === 'delete') {
|
|
||||||
tileVersion = tileVersion.filter((v) => !this.affectedTiles.includes(v))
|
|
||||||
}
|
|
||||||
else if (this.operation === 'move') {
|
|
||||||
const targetObject = tileVersion.find((v) => this.affectedTiles[0].id === v.id)
|
|
||||||
if (targetObject) {
|
|
||||||
targetObject.positionX = this.targetPosition!.x
|
|
||||||
targetObject.positionY = this.targetPosition!.y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tileVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(operation: 'place' | 'move' | 'delete' | 'rotate') {
|
|
||||||
this.operation = operation
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createCommandUpdate(object: PlacedMapObjectT, operation: 'place' | 'move' | 'delete' | 'rotate', targetPosition?: Phaser.Math.Vector2) {
|
|
||||||
if (!currentCommand) {
|
|
||||||
currentCommand = new MapObjectCommand(operation)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (targetPosition) {
|
|
||||||
currentCommand.targetPosition = targetPosition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
currentCommand.affectedTiles.push(object)
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalizeCommand() {
|
|
||||||
if (!currentCommand) return
|
|
||||||
emit('createCommand', currentCommand)
|
|
||||||
currentCommand = null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// *** HANDLERS ***
|
// *** HANDLERS ***
|
||||||
|
|
||||||
|
|
||||||
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
// Check if object already exists on position
|
// Check if object already exists on position
|
||||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
const existingPlacedMapObject = findObjectByPointer(pointer, mapEditor.currentMap.value!)
|
||||||
if (existingPlacedMapObject) return
|
if (existingPlacedMapObject) return
|
||||||
|
|
||||||
if (!mapEditor.selectedMapObject.value) return
|
if (!mapEditor.selectedMapObject.value) return
|
||||||
@ -115,11 +52,10 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add new object to mapObjects
|
// Add new object to mapObjects
|
||||||
|
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
||||||
map.placedMapObjects.push(newPlacedMapObject)
|
map.placedMapObjects.push(newPlacedMapObject)
|
||||||
|
|
||||||
createCommandUpdate(newPlacedMapObject, 'place')
|
emit('update', map)
|
||||||
|
|
||||||
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||||
@ -127,10 +63,10 @@ function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
||||||
if (!existingPlacedMapObject) return
|
if (!existingPlacedMapObject) return
|
||||||
|
|
||||||
createCommandUpdate(existingPlacedMapObject, 'delete')
|
|
||||||
|
|
||||||
// Remove existing object
|
// Remove existing object
|
||||||
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
||||||
|
|
||||||
|
emit('update', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findObjectByPointer(pointer: Phaser.Input.Pointer, map: MapT): PlacedMapObjectT | undefined {
|
function findObjectByPointer(pointer: Phaser.Input.Pointer, map: MapT): PlacedMapObjectT | undefined {
|
||||||
@ -152,48 +88,51 @@ function objectPicker(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
function moveMapObject(id: string, map: MapT) {
|
function moveMapObject(id: string, map: MapT) {
|
||||||
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
||||||
|
|
||||||
let t: Tile
|
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
if (!mapEditor.movingPlacedObject.value) return
|
if (!mapEditor.movingPlacedObject.value) return
|
||||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
t = tile
|
|
||||||
|
|
||||||
mapEditor.movingPlacedObject.value.positionX = tile.x
|
mapEditor.movingPlacedObject.value.positionX = tile.x
|
||||||
mapEditor.movingPlacedObject.value.positionY = tile.y
|
mapEditor.movingPlacedObject.value.positionY = tile.y
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
|
|
||||||
function handlePointerUp() {
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
mapEditor.movingPlacedObject.value = null
|
|
||||||
|
|
||||||
createCommandUpdate(mapEditor.movingPlacedObject.value!, 'move', new Vector2(t.x, t.y))
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
finalizeCommand()
|
if (!tile) return
|
||||||
|
|
||||||
|
map.placedMapObjects.map((placed) => {
|
||||||
|
if (placed.id === id) {
|
||||||
|
placed.positionX = tile.x
|
||||||
|
placed.positionY = tile.y
|
||||||
|
}})
|
||||||
|
|
||||||
|
mapEditor.movingPlacedObject.value = null
|
||||||
}
|
}
|
||||||
|
emit('updateAndCommit', map)
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
function rotatePlacedMapObject(id: string, map: MapT) {
|
function rotatePlacedMapObject(id: string, map: MapT) {
|
||||||
const matchingObject = map.placedMapObjects.find((placedMapObject) => placedMapObject.id === id)
|
|
||||||
matchingObject!.isRotated = !matchingObject!.isRotated
|
map.placedMapObjects.map((placed) => {
|
||||||
|
if (placed.id === id) {
|
||||||
|
console.log(placed.id)
|
||||||
|
placed.isRotated = !placed.isRotated
|
||||||
|
}})
|
||||||
|
|
||||||
|
emit('updateAndCommit', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletePlacedMapObject(id: string, map: MapT) {
|
function deletePlacedMapObject(id: string, map: MapT) {
|
||||||
let mapE = mapEditor.currentMap.value!
|
map.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
||||||
|
|
||||||
const foundObject = mapE.placedMapObjects.find((obj) => obj.id === id)
|
|
||||||
if (!foundObject) return
|
|
||||||
|
|
||||||
createCommandUpdate(foundObject, 'delete')
|
|
||||||
finalizeCommand()
|
|
||||||
|
|
||||||
mapE.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
|
||||||
mapEditor.selectedPlacedObject.value = null
|
mapEditor.selectedPlacedObject.value = null
|
||||||
|
emit('updateAndCommit', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
|
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
|
||||||
|
@ -113,7 +113,6 @@ function clear() {
|
|||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
// Clear placed objects, event tiles and tiles
|
// Clear placed objects, event tiles and tiles
|
||||||
mapEditor.clearMap()
|
|
||||||
mapEditor.triggerClearTiles()
|
mapEditor.triggerClearTiles()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -36,12 +36,6 @@ export function useMapEditorComposable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMap = () => {
|
|
||||||
if (!currentMap.value) return
|
|
||||||
currentMap.value.placedMapObjects = []
|
|
||||||
currentMap.value.mapEventTiles = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleActive = () => {
|
const toggleActive = () => {
|
||||||
if (active.value) reset()
|
if (active.value) reset()
|
||||||
active.value = !active.value
|
active.value = !active.value
|
||||||
@ -105,7 +99,6 @@ export function useMapEditorComposable() {
|
|||||||
// Methods
|
// Methods
|
||||||
loadMap,
|
loadMap,
|
||||||
updateProperty,
|
updateProperty,
|
||||||
clearMap,
|
|
||||||
toggleActive,
|
toggleActive,
|
||||||
setTool,
|
setTool,
|
||||||
setDrawMode,
|
setDrawMode,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user