当前位置:网站首页>Remember a compose version of Huarong Road, you deserve it!
Remember a compose version of Huarong Road, you deserve it!
2022-06-23 22:31:00 【User 9239674】
The basic idea
The logic of the game is relatively simple , So I didn't use MVI A framework like that , But the whole is still data-driven UI Design idea :
- Define the state of the game
- State based UI draw
- User input triggers a state change
1. Define the game state
The state of the game is very simple , That is, the current pieces (Chees) Where are you going , So you can put the of a chess piece List As a carrier State Data structure of
1.1 Chess piece definition
Let's first look at the definition of a single piece
data class Chess(
val name: String, // Character name
val drawable: Int // Character picture
val w: Int, // The width of the piece
val h: Int, // The length of the pieces
val offset: IntOffset = IntOffset(0, 0) // Offset
) adopt w,h You can determine the shape of the chess piece ,offset Determine the current position in chess and cards
1.2 The chess pieces are placed at the beginning of the game
Next, we define the pieces of each character , And put these pieces according to the opening state
val zhang = Chess(" Zhang Fei ", R.drawable.zhangfei, 1, 2)
val cao = Chess(" Cao Cao ", R.drawable.caocao, 2, 2)
val huang = Chess(" Huang Zhong ", R.drawable.huangzhong, 1, 2)
val zhao = Chess(" zhaoyun ", R.drawable.zhaoyun, 1, 2)
val ma = Chess(" d ", R.drawable.machao, 1, 2)
val guan = Chess(" Guan yu ", R.drawable.guanyu, 2, 1)
val zu = buildList { repeat(4) { add(Chess(" pawn $it", R.drawable.zu, 1, 1)) } }Define the shape of the chess pieces in the definition of each role , such as “ Zhang Fei ” The aspect ratio is 2:1,“ Cao Cao ” The aspect ratio is 2:2.
Next, define a game start :
val gameOpening: List<Triple<Chess, Int, Int>> = buildList {
add(Triple(zhang, 0, 0)); add(Triple(cao, 1, 0))
add(Triple(zhao, 3, 0)); add(Triple(huang, 0, 2))
add(Triple(ma, 3, 2)); add(Triple(guan, 1, 2))
add(Triple(zu[0], 0, 4)); add(Triple(zu[1], 1, 3))
add(Triple(zu[2], 2, 3)); add(Triple(zu[3], 3, 4))}Triple The three members of represent the piece and its offset in the chessboard , for example Triple(cao, 1, 0) It means that Cao Cao started at (1,0) coordinate .
Finally, through the following code , take gameOpening Into what we need State, That is, a List<Chess>:
const val boardGridPx = 200 // Chess piece unit size
fun ChessOpening.toList() =
map { (chess, x, y) ->
chess.moveBy(IntOffset(x * boardGridPx, y * boardGridPx))
}2. UI Rendering , Draw a chess game
With List<Chess> after , Draw the pieces in turn , So as to complete the drawing of the whole chess game .
@Composable
fun ChessBoard (chessList: List<Chess>) {
Box(
Modifier
.width(boardWidth.toDp())
.height(boardHeight.toDp())
) {
chessList.forEach { chess ->
Image( // Picture of chess pieces
Modifier
.offset { chess.offset } // Offset position
.width(chess.width.toDp()) // The width of the piece
.height(chess.height.toDp())) // Chess piece height
painter = painterResource(id = chess.drawable),
contentDescription = chess.name
)
}
}
}Box Determine the range of the chessboard ,Image Draw chess pieces , And pass Modifier.offset{ } Put it in the right place .
Only this and nothing more , We use Compose Draw a static start , The next step is to move the chess pieces with your fingers , This involves Compose Gesture Used
3. Drag chess pieces , Trigger state change
Compose Event handling is also through Modifier Set up , for example Modifier.draggable(), Modifier.swipeable() You can use it out of the box . In the game scene of Huarong Road , have access to draggable Monitor drag
3.1 Monitor gestures
1) Use draggable Monitor gestures
Chess pieces can x Axis 、y Drag the axis in both directions , So we set two draggable :
@Composable
fun ChessBoard (
chessList: List<Chess>,
onMove: (chess: String, x: Int, y: Int) -> Unit
) {
Image(
modifier = Modifier
...
.draggable(// Monitor horizontal drag
orientation = Orientation.Horizontal,
state = rememberDraggableState(onDelta = {
onMove(chess.name, it.roundToInt(), 0)
})
)
.draggable(// Monitor vertical drag
orientation = Orientation.Vertical,
state = rememberDraggableState(onDelta = {
onMove(chess.name, 0, it.roundToInt())
})
),
...
)
}orientation Gesture used to specify which direction to listen for : Horizontal or vertical .rememberDraggableState Save drag state ,onDelta Specifies the callback of the gesture . We use custom onMove Throw out the displacement information of the drag gesture .
Now someone will ask ,draggable Can only monitor or drag horizontally or vertically , What if you want to monitor dragging in any direction , You can use detectDragGestures
2) Use pointerInput Monitor gestures
draggable , swipeable etc. , Its internal is through the call Modifier.pointerInput() Realized , be based on pointerInput You can implement more complex custom gestures :
fun Modifier.pointerInput(
key1: Any?,
block: suspend PointerInputScope.() -> Unit
) : Modifier = composed (...) {
...
}pointerInput Provides PointerInputScope, You can use suspend Function to monitor various gestures . for example , have access to detectDragGestures Monitor dragging in any direction :
suspend fun PointerInputScope.detectDragGestures(
onDragStart: (Offset) -> Unit = { },
onDragEnd: () -> Unit = { },
onDragCancel: () -> Unit = { },
onDrag: (change: PointerInputChange, dragAmount: Offset) -> Unit
)detectDragGestures It also provides a level 、 Vertical versions are available , So in the scene of Huarong Road , The following methods can also be used for horizontal and vertical monitoring :
@Composable
fun ChessBoard (
chessList: List<Chess>,
onMove: (chess: String, x: Int, y: Int) -> Unit
) {
Image(
modifier = Modifier
...
.pointerInput(Unit) {
scope.launch {// Monitor horizontal drag
detectHorizontalDragGestures { change, dragAmount ->
change.consumeAllChanges()
onMove(chess.name, 0, dragAmount.roundToInt())
}
}
scope.launch {// Monitor vertical drag
detectVerticalDragGestures { change, dragAmount ->
change.consumeAllChanges()
onMove(chess.name, 0, dragAmount.roundToInt())
}
}
},
...
)
} We need to pay attention to detectHorizontalDragGestures and detectVerticalDragGestures It's a suspend function , Therefore, it is necessary to start the cooperation process separately for monitoring , It can be compared to multiple flow Of collect.
3.2 Collision detection of chess pieces
After obtaining the displacement information of pawn dragging , You can update the chess game status and finally refresh UI. However, before updating the status, it is necessary to detect the collision of chess pieces , The dragging of chess pieces has boundaries .
The principle of collision detection is simple : The piece cannot cross other pieces in the current moving direction .
1) Relative position determination
First , You need to determine the relative position between the pieces . You can use the following methods , Judge the pieces A On the chess pieces B On top of :
val Chess.left get() = offset.x
val Chess.right get() = left + width
infix fun Chess.isAboveOf(other: Chess) =
(bottom <= other.top) && ((left until right) intersect (other.left until other.right)).isNotEmpty() Disassemble the above conditional expression , namely pieces A The lower boundary of is located on the chess piece B Above the upper boundary And In the horizontal direction, the pieces A And chess pieces B There is an intersection of areas :
For example, in the chess game above , The following judgment results can be obtained :
Cao Caobe locatedGuan yuaboveGuan yube locatedpawn 1Huang Zhongabovepawn 1be locatedpawn 2pawn 3above
Although the position Guan yu be located pawn 2 On top of , But from the perspective of collision detection , Guan yu and pawn 2 stay x There is no intersection in the axis direction , therefore Guan yu stay y Movement in the axial direction will not collide with pawn 2,
guan.isAboveOf(zu1) == false
Empathy , Several other positional relationships are as follows :
infix fun Chess.isToRightOf(other: Chess) =
(left >= other.right) && ((top until bottom) intersect (other.top until other.bottom)).isNotEmpty()
infix fun Chess.isToLeftOf(other: Chess) =
(right <= other.left) && ((top until bottom) intersect (other.top until other.bottom)).isNotEmpty()
infix fun Chess.isBelowOf(other: Chess) =
(top >= other.bottom) && ((left until right) intersect (other.left until other.right)).isNotEmpty()2) Cross border detection
Next , Judge whether the chess piece crosses the boundary when moving , That is, whether it has crossed other pieces in its moving direction or out of bounds
for example , Where are the pieces x Check whether it is out of range during the movement in the axis direction :
// X Axial movement
fun Chess.moveByX(x: Int) = moveBy(IntOffset(x, 0))
// Detect collisions and move fun Chess.checkAndMoveX(x: Int, others: List<Chess>): Chess {
others.filter { it.name != name }.forEach { other ->
if (x > 0 && this isToLeftOf other && right + x >= other.left)
return moveByX(other.left - right)
else if (x < 0 && this isToRightOf other && left + x <= other.right)
return moveByX(other.right - left)
}
return if (x > 0) moveByX(min(x, boardWidth - right)) else moveByX(max(x, 0 - left))
}The above logic is clear : When the pieces are x When the axis is moving positively , If it collides with the chess piece on its right, it stops moving ; Otherwise continue to move , Until it collides with the boundary of the chessboard , Other directions are the same .
3.3 Update chess game status
Sum up , After obtaining the gesture displacement information , Detect the collision and move to the correct position , Last update status , Refresh UI:
val chessList: List<Chess> by remember {
mutableStateOf(opening.toList())
}
ChessBoard(chessList = chessState) { cur, x, y -> // onMove Callback
chessState = chessState.map { //it: Chess
if (it.name == cur) {
if (x != 0) it.checkAndMoveX(x, chessState)
else it.checkAndMoveY(y, chessState)
} else { it }
}
}4. Topic switching , Game skin change
Last , Let's see how to realize multiple sets of skin for the game , What is used is Compose Of Theme.
Compose Of Theme The configuration of is simple and intuitive , This is because it is based on CompositionLocal Realized . You can put CompositionLocal See it as a Composable Parent container of , It has two characteristics :
- His son Composable Can be Shared CompositionLocal Data in , Avoid layer by layer parameter transfer .
- When
CompositionLocalWhen the data of , Son Composable Will automatically reorganize to get the latest data .
adopt CompositionLocal Characteristics , We can do that Compose Dynamic skin changing :
4.1 Define skin
First , We define multiple sets of skin , That is, multiple sets of picture resources of chess pieces
object DarkChess : ChessAssets {
override val huangzhong = R.drawable.huangzhong
override val caocao = R.drawable.caocao
override val zhaoyun = R.drawable.zhaoyun
override val zhangfei = R.drawable.zhangfei
override val guanyu = R.drawable.guanyu
override val machao = R.drawable.machao
override val zu = R.drawable.zu
}
object LightChess : ChessAssets {
//... ditto , A little
}
object WoodChess : ChessAssets {
//... ditto , A little
}4.2 establish CompositionLocal
Then create the skin CompositionLocal, We use compositionLocalOf Method creation
internal var LocalChessAssets = compositionLocalOf<ChessAssets> {
DarkChess
} Here DarkChess Is the default value , But it is not usually used directly , Generally we will pass CompositionLocalProvider by CompositionLocal establish Composable Containers , At the same time, set the current value :
CompositionLocalProvider(LocalChessAssets provides chess) {
//...
}Its inner son Composable Share the value of the current setting .
4.3 Follow Theme Change switch skin
In this game , We hope to add the skin of chess pieces to the whole game theme , And follow Theme Change and switch :
@Composable
fun ComposehuarongdaoTheme(
theme: Int = 0,
content: @Composable() () -> Unit
) {
val (colors, chess) = when (theme) {
0 -> DarkColorPalette to DarkChess
1 -> LightColorPalette to LightChess
2 -> WoodColorPalette to WoodChess
else -> error("")
}
CompositionLocalProvider(LocalChessAssets provides chess) {
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
} Definition theme The enumerated values , Get different values according to enumeration colors as well as ChessAssets, take MaterialTheme in LocalChessAssets Inside ,MaterialTheme In all of the Composalbe Can be Shared MaterialTheme and LocalChessAssets Value .
Last , by LocalChessAssets Order one MaterialTheme The extension function of ,
val MaterialTheme.chessAssets
@Composable
@ReadOnlyComposable
get() = LocalChessAssets.current Can be like visiting MaterialTheme Other properties of , visit ChessAssets.
Last
This article mainly introduces how to use Compose Of Gesture, Theme And other features to quickly complete a Huarong Road game , more API Implementation principle of , You can refer to the following article :
In depth understanding of MaterialTheme And CompositionLocal
Use Jetpack Compose Complete custom gesture processing
Code address :https://github.com/vitaviva/compose-huarongdao
At the end of the article What's your opinion on the article , Or any technical problems , Welcome to leave a message and discuss in the comment area !
边栏推荐
- Go language core 36 lectures (go language practice and application 26) -- learning notes
- What are the application flow restrictions of API gateway framework?
- Targeted, real-time audio and video optimization in remote control
- How to select Poe, poe+, and poe++ switches? One article will show you!
- Start learning simple JS
- Learn about reentrantlock
- December 14, 2021: rebuild the queue according to height. Suppose there's a bunch of people out of order
- New high-speed random graph API interface, the first sci-fi graph API interface
- TMUX support, file transfer tool Trz / Tsz (trzsz) similar to RZ / SZ
- What happened when the fortress remote login server was blocked? What can be done to solve it?
猜你喜欢

游戏安全丨喊话CALL分析-写代码
Performance optimization of database 5- database, table and data migration

SLSA: 成功SBOM的促进剂

Pourquoi une seule valeur apparaît - elle sur votre carte de données?

Ten thousand words! Understand the inheritedwidget local refresh mechanism

openGauss Developer Day 2022正式开启,与开发者共建开源数据库根社区

为什么你的数据图谱分析图上只显示一个值?

Beauty of script │ VBS introduction interactive practice

Application practice | Apache Doris integrates iceberg + Flink CDC to build a real-time federated query and analysis architecture integrating lake and warehouse

Slsa: accelerator for successful SBOM
随机推荐
Interviewer: the difference between uselayouteffect and useeffect
How to configure Nessus vulnerability scanning policy?
Targeted, real-time audio and video optimization in remote control
Impala port
2021-12-18: find all letter ectopic words in the string. Given two characters
Tcapulusdb Jun · industry news collection
Using the provider to transform the shit like code, the amount of code is reduced by 2/3!
Detailed explanation of lkadoc interface tool
How does the fortress machine view the account assigned by the server? What are the specific steps?
Why is only one value displayed on your data graph?
How to build a website after registering a domain name
TMUX support, file transfer tool Trz / Tsz (trzsz) similar to RZ / SZ
Role of API service gateway benefits of independent API gateway
Service API version design and Practice
Talking about using email to attack social engineering
CMU doctoral thesis | meta reinforcement learning through memory, 118 Pages pdf
Intelligent storage | high speed HD media processing capability
openGauss Developer Day 2022正式开启,与开发者共建开源数据库根社区
How many times can the server address and fortress address be entered before the connection is successful? Why did the connection fail?
Performance optimization of database 5- database, table and data migration