[Editor] Enable drawing passage map on ObjectLayer

This commit is contained in:
2021-02-17 22:18:27 +01:00
parent c76ad76d37
commit 7f57514b18
10 changed files with 263 additions and 8 deletions

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.layer.Layer
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
@@ -48,6 +49,7 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
private fun dispatchLayerRender(gc: GraphicsContext, layer: Layer) {
when (layer) {
is TileLayer -> renderTileLayer(gc, layer)
is ObjectLayer -> renderObjectPassageMap(gc, layer)
}
}
@@ -70,12 +72,26 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
}
}
private fun renderGrid(gc: GraphicsContext) {
if(!editorStateVM.showGrid) {
private fun renderObjectPassageMap(gc: GraphicsContext, objectLayer: ObjectLayer) {
if (editorStateVM.selectedLayer !is ObjectLayer) {
return
}
for ((row, columns) in objectLayer.passageMap.withIndex()) {
for ((column, passage) in columns.withIndex()) {
PassageAbilitySymbol.render(gc, column * tileWidth, row * tileHeight, tileWidth, tileHeight, passage)
}
}
}
private fun renderGrid(gc: GraphicsContext) {
if (!editorStateVM.showGrid) {
return
}
val lineWidth = gc.lineWidth
gc.lineWidth = 1.5
gc.setLineDashes(0.7)
gc.strokeLine(0.0, 0.0, map.width, 0.0)
gc.strokeLine(0.0, 0.0, 0.0, map.height)
@@ -89,6 +105,8 @@ class MapCanvas(val map: GameMapVM, private val editorStateVM: EditorStateVM, pr
for (column in 0 until map.columns) {
gc.strokeLine(column * tileWidth, 0.0, column * tileWidth, map.height)
}
gc.lineWidth = lineWidth
}
companion object {

View File

@@ -1,5 +1,6 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
@@ -27,6 +28,7 @@ class MapPainter(
editorStateVM.selectedLayerProperty.addListener { _, _, layer ->
cursor = when (layer) {
is TileLayer -> TilePaintingCursor(tileWidth, tileHeight, editorStateVM, brushVM)
is ObjectLayer -> ObjectPaintingCursor(tileWidth, tileHeight, editorStateVM)
else -> null
}
}
@@ -56,6 +58,7 @@ class MapPainter(
if (event.button == MouseButton.PRIMARY && editorStateVM.selectedLayerIndex >= 0) {
currentTrace = when (editorStateVM.selectedLayer) {
is TileLayer -> TilePaintingTrace(mapVM, "Paint trace")
is ObjectLayer -> ObjectPaintingTrace(mapVM, "Toggle passage")
else -> null
}?.apply { beginTrace(editorStateVM, brushVM) }
}

View File

@@ -0,0 +1,34 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color
class ObjectPaintingCursor(
private val tileWidth: Double,
private val tileHeight: Double,
private val editorStateVM: EditorStateVM
) : PaintingCursor {
override fun render(gc: GraphicsContext) {
val alpha = gc.globalAlpha
val stroke = gc.stroke
val width = gc.lineWidth
gc.globalAlpha = 1.0
gc.stroke = Color.WHITE
gc.lineWidth = 3.0
val x = editorStateVM.cursorColumn * tileWidth
val y = editorStateVM.cursorRow * tileHeight
gc.strokeLine(x + tileWidth / 2, y + (1 - SIZE) * tileHeight, x + tileWidth / 2, y + SIZE * tileHeight)
gc.strokeLine(x + (1 - SIZE) * tileWidth, y + tileHeight / 2, x + SIZE * tileWidth, y + tileHeight / 2)
gc.globalAlpha = alpha
gc.stroke = stroke
gc.lineWidth = width
}
companion object {
private const val SIZE = 0.3
}
}

View File

@@ -0,0 +1,56 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
class ObjectPaintingTrace(val map: GameMapVM, override val commandName: String) : PaintingTrace {
private var layerIndex = 0
private var row = 0
private var column = 0
private lateinit var layer: ObjectLayer
private lateinit var formerPassageAbility: PassageAbility
private lateinit var passageAbility: PassageAbility
override fun beginTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) {
// Do nothing
}
override fun proceedTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) {
// Do nothing
}
override fun commitTrace(editorStateVM: EditorStateVM, brushVM: BrushVM) {
this.layerIndex = editorStateVM.selectedLayerIndex
this.row = editorStateVM.cursorRow
this.column = editorStateVM.cursorColumn
if (row >= map.rows || column >= map.columns || row < 0 || column < 0 || layerIndex < 0) {
return
}
this.layer = (map.layers[layerIndex] as ObjectLayer)
formerPassageAbility = layer.passageMap[row][column]
passageAbility = when (brushVM.mode) {
BrushMode.PAINTING_MODE -> PassageAbility.values()[(formerPassageAbility.ordinal + 1) % PassageAbility.values().size]
BrushMode.ERASING_MODE -> PassageAbility.ALLOW
else -> throw IllegalStateException("Unknown brush mode")
}
layer.passageMap[row][column] = passageAbility
}
override fun undo() {
layer.passageMap[row][column] = formerPassageAbility
}
override fun redo() {
layer.passageMap[row][column] = passageAbility
}
}

View File

@@ -0,0 +1,102 @@
package com.bartlomiejpluta.base.editor.map.canvas
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import javafx.scene.canvas.GraphicsContext
import javafx.scene.paint.Color
object PassageAbilitySymbol {
private const val SIZE = 0.6
fun render(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double, passageAbility: PassageAbility) {
val fill = gc.fill
val alpha = gc.globalAlpha
when (passageAbility) {
PassageAbility.ALLOW -> allow(gc, x, y, w, h)
PassageAbility.BLOCK -> block(gc, x, y, w, h)
PassageAbility.UP_ONLY -> up(gc, x, y, w, h)
PassageAbility.DOWN_ONLY -> down(gc, x, y, w, h)
PassageAbility.LEFT_ONLY -> left(gc, x, y, w, h)
PassageAbility.RIGHT_ONLY -> right(gc, x, y, w, h)
}
gc.fill = fill
gc.globalAlpha = alpha
}
fun allow(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.GREEN
gc.globalAlpha = 0.1
gc.fillRect(x, y, w, h)
}
fun block(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.RED
gc.globalAlpha = 0.4
gc.fillRect(x, y, w, h)
}
fun down(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.GREEN
gc.globalAlpha = 0.1
gc.fillRect(x, y, w, h)
gc.globalAlpha = 1.0
gc.fill = Color.WHITE
gc.beginPath()
gc.moveTo(x + (1 - SIZE) * w, y + (1 - SIZE) * h)
gc.lineTo(x + w * SIZE, y + (1 - SIZE) * h)
gc.lineTo(x + w / 2, y + h * SIZE)
gc.closePath()
gc.fill()
}
fun up(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.GREEN
gc.globalAlpha = 0.1
gc.fillRect(x, y, w, h)
gc.globalAlpha = 1.0
gc.fill = Color.WHITE
gc.beginPath()
gc.moveTo(x + (1 - SIZE) * w, y + h * SIZE)
gc.lineTo(x + w * SIZE, y + h * SIZE)
gc.lineTo(x + w / 2, y + (1 - SIZE) * h)
gc.closePath()
gc.fill()
}
fun left(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.GREEN
gc.globalAlpha = 0.1
gc.fillRect(x, y, w, h)
gc.globalAlpha = 1.0
gc.fill = Color.WHITE
gc.beginPath()
gc.moveTo(x + (1 - SIZE) * w, y + h / 2)
gc.lineTo(x + SIZE * w, y + (1 - SIZE) * h)
gc.lineTo(x + w * SIZE, y + h * SIZE)
gc.closePath()
gc.fill()
}
fun right(gc: GraphicsContext, x: Double, y: Double, w: Double, h: Double) {
gc.fill = Color.GREEN
gc.globalAlpha = 0.1
gc.fillRect(x, y, w, h)
gc.globalAlpha = 1.0
gc.fill = Color.WHITE
gc.beginPath()
gc.moveTo(x + SIZE * w, y + h / 2)
gc.lineTo(x + (1 - SIZE) * w, y + (1 - SIZE) * h)
gc.lineTo(x + w * (1 - SIZE), y + h * SIZE)
gc.closePath()
gc.fill()
}
}

View File

@@ -0,0 +1,10 @@
package com.bartlomiejpluta.base.editor.map.model.enumeration
enum class PassageAbility {
ALLOW,
BLOCK,
UP_ONLY,
DOWN_ONLY,
LEFT_ONLY,
RIGHT_ONLY
}

View File

@@ -1,14 +1,27 @@
package com.bartlomiejpluta.base.editor.map.model.layer
import com.bartlomiejpluta.base.editor.map.model.enumeration.PassageAbility
import javafx.beans.property.SimpleStringProperty
import tornadofx.getValue
import tornadofx.setValue
class ObjectLayer(name: String) : Layer {
class ObjectLayer(
name: String,
rows: Int,
columns: Int,
passageMap: Array<Array<PassageAbility>> = Array(rows) { Array(columns) { PassageAbility.ALLOW } }
) : Layer {
var passageMap = passageMap
private set
override val nameProperty = SimpleStringProperty(name)
override fun resize(rows: Int, columns: Int) {
// We essentially need to do nothing
passageMap = Array(rows) { row ->
Array(columns) { column ->
passageMap.getOrNull(row)?.getOrNull(column) ?: PassageAbility.ALLOW
}
}
}
override var name by nameProperty

View File

@@ -37,7 +37,7 @@ class ProtobufMapDeserializer : MapDeserializer {
private fun deserializeLayer(rows: Int, columns: Int, tileSet: TileSet, proto: GameMapProto.Layer): Layer {
return when {
proto.hasTileLayer() -> deserializeTileLayer(rows, columns, tileSet, proto)
proto.hasObjectLayer() -> deserializeObjectLayer(proto)
proto.hasObjectLayer() -> deserializeObjectLayer(rows, columns, proto)
proto.hasImageLayer() -> deserializeImageLayer(proto)
else -> throw IllegalStateException("Not supported layer type")
@@ -57,8 +57,8 @@ class ProtobufMapDeserializer : MapDeserializer {
return TileLayer(proto.name, rows, columns, layer)
}
private fun deserializeObjectLayer(proto: GameMapProto.Layer): Layer {
return ObjectLayer(proto.name)
private fun deserializeObjectLayer(rows: Int, columns: Int, proto: GameMapProto.Layer): Layer {
return ObjectLayer(proto.name, rows, columns)
}
private fun deserializeImageLayer(proto: GameMapProto.Layer): Layer {

View File

@@ -62,7 +62,7 @@ class MapLayersView : View() {
item("Object Layer", graphic = FontIcon("fa-cube")) {
action {
val layer = ObjectLayer("Layer ${mapVM.layers.size + 1}")
val layer = ObjectLayer("Layer ${mapVM.layers.size + 1}", mapVM.rows, mapVM.columns)
val command = CreateLayerCommand(mapVM.item, layer)
command.execute()
layersPane.selectionModel.select(mapVM.layers.size - 1)

View File

@@ -5,9 +5,12 @@ import com.bartlomiejpluta.base.editor.command.service.UndoRedoService
import com.bartlomiejpluta.base.editor.event.RedrawMapRequestEvent
import com.bartlomiejpluta.base.editor.map.controller.MapController
import com.bartlomiejpluta.base.editor.map.model.brush.BrushMode
import com.bartlomiejpluta.base.editor.map.model.layer.ObjectLayer
import com.bartlomiejpluta.base.editor.map.model.layer.TileLayer
import com.bartlomiejpluta.base.editor.map.viewmodel.BrushVM
import com.bartlomiejpluta.base.editor.map.viewmodel.EditorStateVM
import com.bartlomiejpluta.base.editor.map.viewmodel.GameMapVM
import javafx.beans.binding.Bindings
import javafx.scene.control.ToggleGroup
import org.kordamp.ikonli.javafx.FontIcon
import tornadofx.*
@@ -28,6 +31,16 @@ class MapToolbarView : View() {
}
}
private val isTileLayerSelected = Bindings.createBooleanBinding(
{ editorStateVM.selectedLayer is TileLayer },
editorStateVM.selectedLayerProperty
)
private val isObjectLayerSelected = Bindings.createBooleanBinding(
{ editorStateVM.selectedLayer is ObjectLayer },
editorStateVM.selectedLayerProperty
)
override val root = toolbar {
button(graphic = FontIcon("fa-floppy-o")) {
shortcut("Ctrl+S")
@@ -71,6 +84,8 @@ class MapToolbarView : View() {
togglebutton(value = BrushMode.PAINTING_MODE, group = brushMode) {
graphic = FontIcon("fa-paint-brush")
enableWhen(isTileLayerSelected.or(isObjectLayerSelected))
action {
brushVM.item = brushVM.withMode(BrushMode.PAINTING_MODE)
brushVM.commit()
@@ -80,6 +95,8 @@ class MapToolbarView : View() {
togglebutton(value = BrushMode.ERASING_MODE, group = brushMode) {
graphic = FontIcon("fa-eraser")
enableWhen(isTileLayerSelected.or(isObjectLayerSelected))
action {
brushVM.item = brushVM.withMode(BrushMode.ERASING_MODE)
brushVM.commit()
@@ -93,6 +110,8 @@ class MapToolbarView : View() {
isSnapToTicks = true
minorTickCount = 0
enableWhen(isTileLayerSelected)
valueProperty().addListener { _, _, newValue ->
brushVM.item = brushVM.withRange(newValue.toInt())
brushVM.commit()