1
0
forked from noxious/client

Worked on zone manager

This commit is contained in:
2024-06-11 01:10:22 +02:00
parent 399e994bbe
commit 8e174a063a
29 changed files with 658 additions and 379 deletions

View File

@ -0,0 +1,103 @@
<template>
<Teleport to="body">
<Modal v-if="isModalOpen" :isModalOpen="true" :modal-width="645" :modal-height="260">
<template #modalHeader>
<h3 class="modal-title">Tiles</h3>
</template>
<template #modalBody>
<canvas ref="canvas" :width="tileWidth" :height="tileHeight" style="display: none"></canvas>
<div class="tiles">
<img v-for="(tile, index) in tiles" :key="index" :src="tile" alt="Tile" @click="selectTile(index)" :class="{ selected: selectedTile === index }" />
</div>
</template>
</Modal>
</Teleport>
</template>
<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue'
import config from '@/config'
import Modal from '@/components/utilities/Modal.vue'
const tileWidth = config.tile_size.x
const tileHeight = config.tile_size.y
const tiles = ref<string[]>([])
const selectedTile = ref<number | null>(null)
const canvas = ref<HTMLCanvasElement | null>(null)
const isModalOpen = ref(false)
// Hardcoded image path
const imagePath = '/assets/tiles/default.png'
const loadImage = (src: string): Promise<HTMLImageElement> => {
return new Promise((resolve) => {
const img = new Image()
img.onload = () => resolve(img)
img.src = src
})
}
const splitTiles = (img: HTMLImageElement) => {
if (!canvas.value) {
console.error('Canvas not found')
return
}
const ctx = canvas.value.getContext('2d')
if (!ctx) {
console.error('Failed to get canvas context')
return
}
const tilesetWidth = img.width
const tilesetHeight = img.height
const columns = Math.floor(tilesetWidth / tileWidth)
const rows = Math.floor(tilesetHeight / tileHeight)
tiles.value = []
selectedTile.value = null
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
const x = col * tileWidth
const y = row * tileHeight
ctx.clearRect(0, 0, tileWidth, tileHeight)
ctx.drawImage(img, x, y, tileWidth, tileHeight, 0, 0, tileWidth, tileHeight)
const tileDataURL = canvas.value.toDataURL()
tiles.value.push(tileDataURL)
}
}
}
const selectTile = (index: number) => {
selectedTile.value = index
}
onMounted(async () => {
isModalOpen.value = true
const img = await loadImage(imagePath)
await nextTick()
splitTiles(img)
})
</script>
<style lang="scss">
.tiles {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tiles img {
width: 64px;
height: 32px;
cursor: pointer;
border: 2px solid transparent;
transition: border 0.3s ease;
}
.tiles img.selected {
border: 2px solid #ff0000;
}
</style>

View File

@ -0,0 +1,125 @@
<template>
<div class="wrapper">
<div class="toolbar">
<div class="tools">
<button :class="{ active: activeTool === 'move' }" @click="activeTool = 'move'">
<img src="/assets/icons/zoneEditor/move.svg" alt="Eraser tool" />
</button>
<div class="divider"></div>
<button :class="{ active: activeTool === 'tiles' }" @click="activeTool = 'tiles'">
<img src="/assets/icons/zoneEditor/tiles.svg" alt="Eraser tool" />
</button>
<div class="divider"></div>
<button :class="{ active: activeTool === 'eraser' }" @click="activeTool = 'eraser'">
<img src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser tool" />
</button>
</div>
<div class="buttons">
<button class="btn-cyan">Save</button>
<button class="btn-cyan">Load</button>
<button class="btn-cyan">Clear</button>
<button class="btn-cyan" @click="() => zoneEditorStore.toggleActive()">Exit</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref } from 'vue'
import { useScene } from 'phavuer'
import { getTile, tileToWorldXY } from '@/services/zone'
import config from '@/config'
import { useZoneStore } from '@/stores/zone'
import { useZoneEditorStore } from '@/stores/zoneEditor'
const zoneEditorStore = useZoneEditorStore()
const props = defineProps({
layer: Phaser.Tilemaps.TilemapLayer
})
const scene = useScene()
const activeTool = ref('move')
const emit = defineEmits(['erase', 'move', 'tile'])
function onPointerClick(pointer: Phaser.Input.Pointer) {
const px = scene.cameras.main.worldView.x + pointer.x
const py = scene.cameras.main.worldView.y + pointer.y
const pointer_tile = getTile(px, py, props.layer) as Phaser.Tilemaps.Tile
if (!pointer_tile) {
return
}
if (activeTool.value === 'eraser') {
emit('erase', pointer_tile)
}
}
scene.input.on(Phaser.Input.Events.POINTER_UP, onPointerClick)
onBeforeUnmount(() => {
scene.input.off(Phaser.Input.Events.POINTER_UP, onPointerClick)
})
</script>
<style scoped lang="scss">
@import '@/assets/scss/main';
.wrapper {
display: flex;
justify-content: center;
margin: 10px;
}
.toolbar {
position: fixed;
top: 20px;
border-radius: 5px;
opacity: 0.8;
display: flex;
background: $dark-gray;
border: 2px solid $cyan;
color: $light-gray;
padding: 5px;
min-width: 90%;
height: 40px;
.tools {
display: flex;
gap: 10px;
.divider {
width: 1px;
background: $cyan;
}
// vertical center
button {
display: flex;
justify-content: center;
align-items: center;
&.active {
border-bottom: 3px solid $light-cyan;
}
}
img {
filter: invert(1);
width: 20px;
height: 20px;
}
}
.buttons {
display: flex;
gap: 10px;
margin-left: auto;
button {
padding-left: 15px;
padding-right: 15px;
}
}
}
</style>

View File

@ -1,99 +1,73 @@
<template>
<div>
<canvas ref="tileCanvas" :width="tileWidth" :height="tileHeight" style="display: none;"></canvas>
<div class="tiles">
<img
v-for="(tile, index) in tiles"
:key="index"
:src="tile"
alt="Tile"
@click="selectTile(index)"
:class="{ selected: selectedTile === index }"
/>
</div>
</div>
<TilemapLayerC :tilemap="tileMap" :tileset="zoneStore.tiles" ref="tilemapLayer" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
<Controls :layer="layer" />
<Toolbar :layer="layer" @erase="erase" @tile="tile" />
<Tiles />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import config from '@/config'
import Tileset = Phaser.Tilemaps.Tileset
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
import { Container, TilemapLayer as TilemapLayerC, useScene } from 'phavuer'
import { onBeforeMount, ref, type Ref, watch } from 'vue'
import Controls from '@/components/utilities/Controls.vue'
import { useSocketStore } from '@/stores/socket'
import { useZoneStore } from '@/stores/zone'
import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
import Tiles from '@/components/utilities/zoneEditor/Tiles.vue'
const tileWidth = config.tile_size.x;
const tileHeight = config.tile_size.y;
const tiles = ref([]);
const selectedTile = ref(null);
const tileCanvas = ref(null);
// Phavuer logic
let scene = useScene()
let tilemapLayer = ref()
let zoneData = new Phaser.Tilemaps.MapData({
width: 10, // @TODO : get this from the server
height: 10, // @TODO : get this from the server
tileWidth: config.tile_size.x,
tileHeight: config.tile_size.y,
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
format: Phaser.Tilemaps.Formats.ARRAY_2D
})
let tileMap = new Phaser.Tilemaps.Tilemap(scene, zoneData)
let tileset: Tileset = tileMap.addTilesetImage('default', 'tiles') as Tileset
let layer: TilemapLayer = tileMap.createBlankLayer('layer', tileset, 0, config.tile_size.y) as TilemapLayer
// Hardcoded image path
const imagePath = '/assets/tiles/default.png';
// center camera
const centerY = (tileMap.height * tileMap.tileHeight) / 2
const centerX = (tileMap.width * tileMap.tileWidth) / 2
scene.cameras.main.centerOn(centerX, centerY)
const loadImage = (src) => {
return new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve(img);
img.src = src;
});
};
// Multiplayer / server logics
const zoneStore = useZoneStore()
const socket = useSocketStore()
const splitTiles = (img) => {
const canvas = tileCanvas.value;
const ctx = canvas.getContext('2d');
zoneStore.setTiles([
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
])
zoneStore.tiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
const tilesetWidth = img.width;
const tilesetHeight = img.height;
const columns = Math.floor(tilesetWidth / tileWidth);
const rows = Math.floor(tilesetHeight / tileHeight);
// Watch for changes in the zoneStore and update the layer
watch(
() => zoneStore.tiles,
() => {
// @TODO : change to tiles for when loading other maps
zoneStore.tiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
},
{ deep: true }
)
tiles.value = [];
selectedTile.value = null;
function erase(tile: Phaser.Tilemaps.Tile) {
layer.removeTileAt(tile.x, tile.y)
}
for (let row = 0; row < rows; row++) {
for (let col = 0; col < columns; col++) {
const x = col * tileWidth;
const y = row * tileHeight;
ctx.clearRect(0, 0, tileWidth, tileHeight);
ctx.drawImage(img, x, y, tileWidth, tileHeight, 0, 0, tileWidth, tileHeight);
const tileDataURL = canvas.toDataURL();
tiles.value.push(tileDataURL);
}
}
};
const selectTile = (index) => {
selectedTile.value = index;
};
onMounted(async () => {
const img = await loadImage(imagePath);
splitTiles(img);
});
/**
* Resources:
* https://codepen.io/Xymota/pen/gOOyxWB
* https://www.dynetisgames.com/2022/06/09/update-how-to-manage-big-isometric-maps-with-phaser-3-5/
* https://stackoverflow.com/questions/11533606/javascript-splitting-a-tileset-image-to-be-stored-in-2d-image-array
*/
function tile() {}
</script>
<style lang="scss">
.tiles {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.tiles img {
width: 64px;
height: 32px;
cursor: pointer;
border: 2px solid transparent;
transition: border 0.3s ease;
}
.tiles img.selected {
border: 2px solid #ff0000;
}
</style>

View File

@ -1,81 +0,0 @@
<template>
<div class="toolbar">
<div class="tools">
<button :class="{ active: activeTool === 'tiles' }" @click="activeTool = 'tiles'">
<img src="/assets/icons/zoneEditor/tiles.svg" alt="Eraser tool" />
</button>
<div class="divider"></div>
<button :class="{ active: activeTool === 'eraser' }" @click="activeTool = 'eraser'">
<img src="/assets/icons/zoneEditor/eraser-tool.svg" alt="Eraser tool" />
</button>
</div>
<div class="buttons">
<button class="btn-cyan">Save</button>
<button class="btn-cyan">Load</button>
<button class="btn-cyan">Clear</button>
<button class="btn-cyan">Exit</button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
const activeTool = ref('tiles');
</script>
<style scoped lang="scss">
@import '@/assets/scss/main';
.toolbar {
position: absolute;
border-radius: 5px;
opacity: 0.8;
display: flex;
background: $dark-gray;
border: 2px solid $cyan;
color: $light-gray;
padding: 5px;
min-width: 100%;
height: 40px;
.tools {
display: flex;
gap: 10px;
.divider {
width: 1px;
background: $cyan;
}
// vertical center
button {
display: flex;
justify-content: center;
align-items: center;
&.active {
border-bottom: 3px solid $light-cyan;
border-radius: 5px;
}
}
img {
filter: invert(1);
width: 35px;
height: 35px;
}
}
.buttons {
display: flex;
gap: 10px;
margin-left: auto;
button {
padding-left: 10px;
padding-right: 10px;
}
}
}
</style>