Add basic fog of war feature.
This commit is contained in:
parent
ae2c3da582
commit
3d5f29c18c
6 changed files with 171 additions and 20 deletions
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M720,760Q703,760 691.5,748.5Q680,737 680,720Q680,703 691.5,691.5Q703,680 720,680Q737,680 748.5,691.5Q760,703 760,720Q760,737 748.5,748.5Q737,760 720,760ZM280,880Q263,880 251.5,868.5Q240,857 240,840Q240,823 251.5,811.5Q263,800 280,800Q297,800 308.5,811.5Q320,823 320,840Q320,857 308.5,868.5Q297,880 280,880ZM240,760Q223,760 211.5,748.5Q200,737 200,720Q200,703 211.5,691.5Q223,680 240,680L600,680Q617,680 628.5,691.5Q640,703 640,720Q640,737 628.5,748.5Q617,760 600,760L240,760ZM400,880Q383,880 371.5,868.5Q360,857 360,840Q360,823 371.5,811.5Q383,800 400,800L680,800Q697,800 708.5,811.5Q720,823 720,840Q720,857 708.5,868.5Q697,880 680,880L400,880ZM300,640Q209,640 144.5,575.5Q80,511 80,420Q80,337 135,275Q190,213 271,202Q303,145 358.5,112.5Q414,80 480,80Q570,80 636.5,137.5Q703,195 717,281Q786,287 833,338Q880,389 880,460Q880,535 827.5,587.5Q775,640 700,640L300,640ZM300,560L700,560Q742,560 771,531Q800,502 800,460Q800,418 771,389Q742,360 700,360L640,360L640,320Q640,254 593,207Q546,160 480,160Q432,160 392.5,186Q353,212 333,256L323,280L298,280Q241,282 200.5,322.5Q160,363 160,420Q160,478 201,519Q242,560 300,560ZM480,360L480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z" />
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#000000"
|
||||
android:pathData="M720,760Q703,760 691.5,748.5Q680,737 680,720Q680,703 691.5,691.5Q703,680 720,680Q737,680 748.5,691.5Q760,703 760,720Q760,737 748.5,748.5Q737,760 720,760ZM280,880Q263,880 251.5,868.5Q240,857 240,840Q240,823 251.5,811.5Q263,800 280,800Q297,800 308.5,811.5Q320,823 320,840Q320,857 308.5,868.5Q297,880 280,880ZM240,760Q223,760 211.5,748.5Q200,737 200,720Q200,703 211.5,691.5Q223,680 240,680L600,680Q617,680 628.5,691.5Q640,703 640,720Q640,737 628.5,748.5Q617,760 600,760L240,760ZM400,880Q383,880 371.5,868.5Q360,857 360,840Q360,823 371.5,811.5Q383,800 400,800L680,800Q697,800 708.5,811.5Q720,823 720,840Q720,857 708.5,868.5Q697,880 680,880L400,880ZM300,640Q209,640 144.5,575.5Q80,511 80,420Q80,337 135,275Q190,213 271,202Q303,145 358.5,112.5Q414,80 480,80Q570,80 636.5,137.5Q703,195 717,281Q786,287 833,338Q880,389 880,460Q880,535 827.5,587.5Q775,640 700,640L300,640Z" />
|
||||
</vector>
|
||||
|
|
@ -1,20 +1,22 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_center_focus_weak_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_foggy_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_foggy_filled_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_visibility_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_in_map_24dp
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_zoom_out_map_24dp
|
||||
|
|
@ -29,6 +31,7 @@ fun MapScene(
|
|||
) {
|
||||
val campaign = LocalCampaignLayoutScope.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val fogOfWarEdit = remember { mutableStateOf(true) }
|
||||
|
||||
val map = rememberLayoutFromResource(
|
||||
name = "Dahomé",
|
||||
|
|
@ -68,7 +71,7 @@ fun MapScene(
|
|||
initialZoom = 1f,
|
||||
initialOffset = IntOffset(x = -150, y = -120),
|
||||
),
|
||||
fogOfWar = FogOfWar.NONE,
|
||||
fogOfWar = FogOfWar(),
|
||||
layers = listOf(
|
||||
map,
|
||||
mapRegionOverlay,
|
||||
|
|
@ -81,14 +84,21 @@ fun MapScene(
|
|||
)
|
||||
}
|
||||
Scene(
|
||||
modifier = modifier,
|
||||
modifier = modifier.onFogOfWarControl(
|
||||
scope = scope,
|
||||
enable = fogOfWarEdit.value,
|
||||
fogOfWar = scene.fogOfWar,
|
||||
camera = scene.camera,
|
||||
),
|
||||
scene = scene,
|
||||
) {
|
||||
Column(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.BottomEnd)
|
||||
.padding(end = campaign.rightPanel.value.width)
|
||||
.padding(all = 8.dp)
|
||||
.align(alignment = Alignment.BottomStart)
|
||||
.padding(
|
||||
start = campaign.leftPanel.value.width,
|
||||
bottom = campaign.chatOverlay.value.height,
|
||||
)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
|
|
@ -149,6 +159,19 @@ fun MapScene(
|
|||
contentDescription = null
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = {
|
||||
fogOfWarEdit.value = fogOfWarEdit.value.not()
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
painter = when (fogOfWarEdit.value) {
|
||||
true -> painterResource(Res.drawable.ic_foggy_filled_24dp)
|
||||
else -> painterResource(Res.drawable.ic_foggy_24dp)
|
||||
},
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,78 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
import androidx.compose.ui.input.pointer.isPrimaryPressed
|
||||
import androidx.compose.ui.input.pointer.onPointerEvent
|
||||
import com.pixelized.desktop.lwa.ui.composable.scene.utils.global
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class FogOfWar(
|
||||
val color: Color = Color.Black.copy(alpha = 0.0f),
|
||||
class FogOfWar(
|
||||
val color: Color = Color.Black.copy(alpha = 0.5f),
|
||||
) {
|
||||
val path = Path()
|
||||
|
||||
fun moveTo(position: Offset) {
|
||||
path.moveTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
fun lineTo(position: Offset) {
|
||||
path.lineTo(x = position.x, y = position.y)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val NONE = FogOfWar(color = Color.Transparent)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalComposeUiApi::class)
|
||||
fun Modifier.onFogOfWarControl(
|
||||
scope: CoroutineScope,
|
||||
enable: Boolean,
|
||||
camera: Camera,
|
||||
fogOfWar: FogOfWar,
|
||||
) = if (!enable) {
|
||||
this
|
||||
} else {
|
||||
var lastEventTime = System.currentTimeMillis()
|
||||
var previousEvent: PointerEvent? = null
|
||||
|
||||
this
|
||||
.onPointerEvent(PointerEventType.Release) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
println("PointerEventType.Release")
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
previousEvent = null
|
||||
}
|
||||
}
|
||||
.onPointerEvent(PointerEventType.Move) { event: PointerEvent ->
|
||||
scope.launch {
|
||||
val pointer = event.changes.firstOrNull()
|
||||
val time = pointer?.uptimeMillis ?: 0L
|
||||
|
||||
if (time - lastEventTime > 10L && event.buttons.isPrimaryPressed) {
|
||||
if (previousEvent?.buttons?.isPrimaryPressed == true) {
|
||||
println("PointerEventType.LineTo")
|
||||
pointer?.position
|
||||
?.global(camera = camera)
|
||||
?.let(fogOfWar::lineTo)
|
||||
} else {
|
||||
println("PointerEventType.MoveTo")
|
||||
pointer?.position
|
||||
?.global(camera = camera)
|
||||
?.let(fogOfWar::moveTo)
|
||||
}
|
||||
lastEventTime = System.currentTimeMillis()
|
||||
previousEvent = event
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,8 +15,14 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.BlendMode
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.CompositingStrategy
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.StrokeJoin
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.scale
|
||||
import androidx.compose.ui.graphics.drawscope.translate
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.PointerEvent
|
||||
import androidx.compose.ui.input.pointer.PointerEventType
|
||||
|
|
@ -55,7 +61,6 @@ data class Scene(
|
|||
}
|
||||
|
||||
|
||||
|
||||
@Composable
|
||||
fun Scene(
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -253,9 +258,38 @@ fun Modifier.drawElements(
|
|||
|
||||
fun Modifier.drawFogOfWar(
|
||||
scene: Scene,
|
||||
): Modifier = this.drawWithCache {
|
||||
): Modifier = this
|
||||
.graphicsLayer(
|
||||
compositingStrategy = CompositingStrategy.Offscreen
|
||||
)
|
||||
.drawWithCache {
|
||||
val stroke = Stroke(
|
||||
width = 32f,
|
||||
cap = StrokeCap.Round,
|
||||
join = StrokeJoin.Round
|
||||
)
|
||||
val fog = Color.Black.copy(alpha = 0.5f)
|
||||
val color = Color.Transparent
|
||||
onDrawBehind {
|
||||
drawRect(color = scene.fogOfWar.color)
|
||||
drawRect(
|
||||
color = fog,
|
||||
)
|
||||
scale(
|
||||
scale = 1 / scene.camera.zoom,
|
||||
pivot = Offset.Zero,
|
||||
) {
|
||||
translate(
|
||||
left = -scene.camera.offset.x.toFloat(),
|
||||
top = -scene.camera.offset.y.toFloat(),
|
||||
) {
|
||||
drawPath(
|
||||
path = scene.fogOfWar.path,
|
||||
style = stroke,
|
||||
color = color,
|
||||
blendMode = BlendMode.Clear,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
package com.pixelized.desktop.lwa.ui.composable.scene
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
|
@ -31,7 +33,9 @@ fun SceneDebugPanel(
|
|||
scene: Scene,
|
||||
) {
|
||||
Card(
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.then(other = modifier),
|
||||
backgroundColor = MaterialTheme.lwa.colorScheme.elevated.base4dp,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -39,12 +43,15 @@ fun SceneDebugPanel(
|
|||
verticalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
) {
|
||||
SceneDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
scene = scene,
|
||||
)
|
||||
SceneCursorDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
cursor = cursor,
|
||||
)
|
||||
SceneCameraDebug(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
camera = scene.camera,
|
||||
)
|
||||
Column {
|
||||
|
|
@ -54,7 +61,9 @@ fun SceneDebugPanel(
|
|||
)
|
||||
scene.layers.forEach { layer ->
|
||||
SceneLayerDebug(
|
||||
modifier = Modifier.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
isOpen = false,
|
||||
layer = layer,
|
||||
)
|
||||
|
|
@ -67,7 +76,9 @@ fun SceneDebugPanel(
|
|||
)
|
||||
scene.elements.forEach { element ->
|
||||
SceneElementDebug(
|
||||
modifier = Modifier.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.padding(start = MaterialTheme.lwa.dimen.debug.offset),
|
||||
isOpen = false,
|
||||
element = element,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue