Reputation: 7078
I'm building a 2D game with a top down map using Vue3. I created a simple map editor, where you can drag each tile from a set to the map. This can get very cumbersome for a large map, thus I want to try another way. My idea is like a drawing tool, you select the "brush" and drag it over a "canvas". The only problem is, the map is built of tile div
arranged in a matrix. I tried the following, but for some reason the drawing is delayed and it generates "artifacts" on other tiles (I start dragging on tile 20:4 but also tile 15:2 is updated.
<div class="tiles">
<div v-for="(row, y) in tiles" class="row"
:key="'row_' + y"
:style="{width: mapSize.x + 'px'}">
<div v-for="(tile, x) in row" class="tile"
:class="tile.background"
:key="'tile_' + x + '_' + y"
@dragover.prevent
@dragenter="onDragEnter(x, y)">
</div>
</div>
</div>
I don't have a draggable element, maybe this is the problem, but what other event could I use for a clicked move?
Upvotes: 0
Views: 1169
Reputation: 35684
I would suggest that you use mousedown
, mousemove
, and mouseup
and possibly their touch semi-equivalents (if using touch devices).
Here is an example, with comments in the code.
const colors = ["powderblue","gold","orangered","deeppink", "deepskyblue","palegreen"];
const generateTiles = (wx, wy) => {
const ret = [];
for (let y = 0; y < wy; y++) {
const row = [];
ret.push(row)
for (let x = 0; x < wx; x++) {
row.push({x,y,background: colors[0]})
}
}
return ret;
}
Vue.createApp({
el: '#app',
setup() {
const tiles = Vue.reactive(generateTiles(10,10)); // setup tiles and make reactive
const color = Vue.ref(1); // helper for managing color(s)
let mouseDown = false; // no need to make reactive
// do some stuff to the tile here
const setTileBg = (tile) => {
tile.background=colors[color.value];
}
const handlers = {
// reuse handler, used both for parent and
onMouseDown: (tile) => {
mouseDown = true;
if(tile) setTileBg(tile);
},
onMouseUp: (e) => {
mouseDown = false;
},
onMouseOver: (tile) => {
if(mouseDown) setTileBg(tile);
}
}
// adding it to window (instead of an element) allows
// capturing mouseup event even when mouse is not within
// the dom element
window.addEventListener('mouseup', handlers.onMouseUp)
return {
tiles, color, colors, ...handlers
}
}
}).mount('#app')
.tiles,select{width:200px;margin:0 auto;display:block}.tile{display:inline-block;width:20px;height:20px;border:1px solid #000;box-sizing:border-box;font-size:10px;font-family:monospace;cursor:pointer;color:rgba(0,0,0,.4);text-align:center;padding:3px}.row{display:block;height:20px;width:200px;box-sizing:content-box;user-select:none}
<script src="https://unpkg.com/[email protected]/dist/vue.global.prod.js"></script>
<div id="app">
<div>
<select v-model="color">
<option :value="i" v-for="(c,i) in colors" :style="{'background-color':c}">{{c}}</option>
</select>
</div>
<div class="tiles">
<div v-for="(row, y) in tiles" class="row"
:key="'row_' + y"
>
<div v-for="(tile, x) in row" class="tile"
:style="{'background-color':tile.background}"
:key="'tile_' + x + '_' + y"
@mouseDown="onMouseDown(tile)"
@mouseOver="onMouseOver(tile)">
{{ y * row.length + x}}
</div>
</div>
</div>
</div>
Upvotes: 1