스테이지 시작 이펙트 동작 트리거 수정
스테이지 클리어 이펙트 최적화 클리어 이펙트 동작 시간 수정 -> 3.8초
This commit is contained in:
+1
-1
@@ -1,2 +1,2 @@
|
||||
#- File Locator -
|
||||
listingFile=../../../../outputs/apk/debug/output-metadata.json
|
||||
listingFile=../../../apk/debug/output-metadata.json
|
||||
|
||||
BIN
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,4 @@
|
||||
#Sun Oct 11 14:18:29 KST 2026
|
||||
#Sun Oct 11 14:34:13 KST 2026
|
||||
base.0=/home/gerd/Workspace/AndroidStudioProjects/wordpuzzles/app/build/intermediates/dex/debug/mergeExtDexDebug/classes.dex
|
||||
base.1=/home/gerd/Workspace/AndroidStudioProjects/wordpuzzles/app/build/intermediates/dex/debug/mergeProjectDexDebug/0/classes.dex
|
||||
base.10=/home/gerd/Workspace/AndroidStudioProjects/wordpuzzles/app/build/intermediates/dex/debug/mergeProjectDexDebug/6/classes.dex
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
+612
-611
File diff suppressed because it is too large
Load Diff
+1
@@ -71,6 +71,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:testOnly="true"
|
||||
android:theme="@style/Theme.WordPuzzle" >
|
||||
|
||||
<!-- AdMob Sample App ID for testing -->
|
||||
|
||||
+1
@@ -71,6 +71,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:testOnly="true"
|
||||
android:theme="@style/Theme.WordPuzzle" >
|
||||
|
||||
<!-- AdMob Sample App ID for testing -->
|
||||
|
||||
+1
@@ -71,6 +71,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:testOnly="true"
|
||||
android:theme="@style/Theme.WordPuzzle" >
|
||||
|
||||
<!-- AdMob Sample App ID for testing -->
|
||||
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
ε5ά&Α*Σ*φ'θ)ι)τ'
|
||||
Е5э&а*с*Ж'Х)И)Т'ж$с$В$
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
+1
-1
@@ -1 +1 @@
|
||||
钟邨䦶饏賱鹺閦觽鍌魧謱饗饗蹗陰
|
||||
钟邨䦶饏賱鹺閦觽鍌魧謱饗饗蹗陰韸閯茒
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -1,2 +1,2 @@
|
||||
80
|
||||
84
|
||||
0
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
+3
-3
@@ -59,7 +59,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="infoBox duration">
|
||||
<div class="counter">0.019s</div>
|
||||
<div class="counter">0.011s</div>
|
||||
<p>duration</p>
|
||||
</div>
|
||||
</td>
|
||||
@@ -95,7 +95,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="13">0.013s</td>
|
||||
<td data-sort-value="8">0.008s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -112,7 +112,7 @@
|
||||
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
|
||||
</label>
|
||||
</div>Generated by
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:17:48</p>
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:33:05</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="infoBox duration">
|
||||
<div class="counter">0.081s</div>
|
||||
<div class="counter">0.071s</div>
|
||||
<p>duration</p>
|
||||
</div>
|
||||
</td>
|
||||
@@ -95,7 +95,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="79">0.079s</td>
|
||||
<td data-sort-value="71">0.071s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -112,7 +112,7 @@
|
||||
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
|
||||
</label>
|
||||
</div>Generated by
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:17:48</p>
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:33:05</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+3
-3
@@ -59,7 +59,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="infoBox duration">
|
||||
<div class="counter">0.010s</div>
|
||||
<div class="counter">0.008s</div>
|
||||
<p>duration</p>
|
||||
</div>
|
||||
</td>
|
||||
@@ -95,7 +95,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="10">0.010s</td>
|
||||
<td data-sort-value="7">0.007s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -112,7 +112,7 @@
|
||||
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
|
||||
</label>
|
||||
</div>Generated by
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:17:48</p>
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:33:05</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+3
-3
@@ -95,7 +95,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="3">0.003s</td>
|
||||
<td data-sort-value="2">0.002s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -104,7 +104,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="0">0s</td>
|
||||
<td data-sort-value="1">0.001s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -139,7 +139,7 @@
|
||||
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
|
||||
</label>
|
||||
</div>Generated by
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:17:48</p>
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:33:05</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="infoBox duration">
|
||||
<div class="counter">0.480s</div>
|
||||
<div class="counter">0.391s</div>
|
||||
<p>duration</p>
|
||||
</div>
|
||||
</td>
|
||||
@@ -94,7 +94,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="19">0.019s</td>
|
||||
<td data-sort-value="11">0.011s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -105,7 +105,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="81">0.081s</td>
|
||||
<td data-sort-value="71">0.071s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -116,7 +116,7 @@
|
||||
<td>1</td>
|
||||
<td>0</td>
|
||||
<td>0</td>
|
||||
<td data-sort-value="10">0.010s</td>
|
||||
<td data-sort-value="8">0.008s</td>
|
||||
<td class="success" data-sort-value="100">100%</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -155,7 +155,7 @@
|
||||
<input id="line-wrapping-toggle" type="checkbox" autocomplete="off"/>
|
||||
</label>
|
||||
</div>Generated by
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:17:48</p>
|
||||
<a href="https://www.gradle.org">Gradle 9.6.0</a> at 2026. 10. 11. 오후 2:33:05</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.HangulHelperTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:17:48.302Z" hostname="vivoboxx" time="0.017">
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.HangulHelperTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:33:05.520Z" hostname="vivoboxx" time="0.01">
|
||||
<properties/>
|
||||
<testcase name="isHangulSyllable_validatesCorrectly" classname="com.wordpuzzle.hangul.domain.engine.HangulHelperTest" time="0.013"/>
|
||||
<testcase name="isHangulSyllable_validatesCorrectly" classname="com.wordpuzzle.hangul.domain.engine.HangulHelperTest" time="0.008"/>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.StageDataIntegrityTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:17:48.322Z" hostname="vivoboxx" time="0.08">
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.StageDataIntegrityTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:33:05.532Z" hostname="vivoboxx" time="0.071">
|
||||
<properties/>
|
||||
<testcase name="testAllStages_withinGridBounds_andNoIntersectionConflicts" classname="com.wordpuzzle.hangul.domain.engine.StageDataIntegrityTest" time="0.079"/>
|
||||
<testcase name="testAllStages_withinGridBounds_andNoIntersectionConflicts" classname="com.wordpuzzle.hangul.domain.engine.StageDataIntegrityTest" time="0.071"/>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.TilePoolGeneratorTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:17:48.404Z" hostname="vivoboxx" time="0.01">
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.engine.TilePoolGeneratorTest" tests="1" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:33:05.604Z" hostname="vivoboxx" time="0.007">
|
||||
<properties/>
|
||||
<testcase name="generateLayoutWithStarterLetters_respectsFiftyPercentLimitAndPlacesStarterLettersCorrectly" classname="com.wordpuzzle.hangul.domain.engine.TilePoolGeneratorTest" time="0.01"/>
|
||||
<testcase name="generateLayoutWithStarterLetters_respectsFiftyPercentLimitAndPlacesStarterLettersCorrectly" classname="com.wordpuzzle.hangul.domain.engine.TilePoolGeneratorTest" time="0.007"/>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
</testsuite>
|
||||
|
||||
+3
-3
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" tests="4" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:17:48.417Z" hostname="vivoboxx" time="0.004">
|
||||
<testsuite name="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" tests="4" skipped="0" failures="0" errors="0" timestamp="2026-10-11T05:33:05.614Z" hostname="vivoboxx" time="0.004">
|
||||
<properties/>
|
||||
<testcase name="removeCellChar_restoresTileToPool" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.001"/>
|
||||
<testcase name="applyHint_revealsOneCell" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.003"/>
|
||||
<testcase name="easyMode_tapTile_automaticallyInsertsIntoCorrectCell_andClears" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.0"/>
|
||||
<testcase name="applyHint_revealsOneCell" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.002"/>
|
||||
<testcase name="easyMode_tapTile_automaticallyInsertsIntoCorrectCell_andClears" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.001"/>
|
||||
<testcase name="normalMode_insertChar_advancesToNextCellInWord_orClosestCell()" classname="com.wordpuzzle.hangul.domain.usecase.SolveTileUseCaseTest" time="0.0"/>
|
||||
<system-out><![CDATA[]]></system-out>
|
||||
<system-err><![CDATA[]]></system-err>
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -1,5 +1,8 @@
|
||||
package com.wordpuzzle.hangul.ui.game
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.*
|
||||
@@ -7,23 +10,9 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlin.math.sin
|
||||
import kotlin.random.Random
|
||||
|
||||
data class ConfettiParticle(
|
||||
var x: Float,
|
||||
var y: Float,
|
||||
var vx: Float,
|
||||
var vy: Float,
|
||||
var size: Float,
|
||||
var color: Color,
|
||||
var rotation: Float,
|
||||
var vRot: Float,
|
||||
var isCircle: Boolean,
|
||||
var alpha: Float = 1.0f
|
||||
)
|
||||
|
||||
private val CONFETTI_COLORS = listOf(
|
||||
Color(0xFFFFD54F), // 황금 노랑
|
||||
Color(0xFFE57373), // 상큼 로즈 핑크
|
||||
@@ -34,97 +23,90 @@ private val CONFETTI_COLORS = listOf(
|
||||
Color(0xFF4DB6AC) // 세이지 틸
|
||||
)
|
||||
|
||||
// 메모리 가비지 컬렉션(GC) 부하 0%를 위한 플랫 원시 타입 파티클 시스템
|
||||
private class FastConfettiSystem(val count: Int) {
|
||||
val x = FloatArray(count)
|
||||
val y = FloatArray(count)
|
||||
val vx = FloatArray(count)
|
||||
val vy = FloatArray(count)
|
||||
val size = FloatArray(count)
|
||||
val colorIndex = IntArray(count)
|
||||
val flutterPhase = FloatArray(count)
|
||||
val isCircle = BooleanArray(count)
|
||||
|
||||
init {
|
||||
val rand = Random(42)
|
||||
for (i in 0 until count) {
|
||||
x[i] = rand.nextFloat() * 1000f
|
||||
y[i] = -30f - rand.nextFloat() * 350f
|
||||
vx[i] = (rand.nextFloat() - 0.5f) * 5f
|
||||
vy[i] = 3.5f + rand.nextFloat() * 7f
|
||||
size[i] = 12f + rand.nextFloat() * 16f
|
||||
colorIndex[i] = rand.nextInt(CONFETTI_COLORS.size)
|
||||
flutterPhase[i] = rand.nextFloat() * 6.28f
|
||||
isCircle[i] = rand.nextBoolean()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ConfettiParticleCanvas(
|
||||
modifier: Modifier = Modifier,
|
||||
particleCount: Int = 75,
|
||||
particleCount: Int = 45,
|
||||
onAnimationEnd: () -> Unit = {}
|
||||
) {
|
||||
var particles by remember { mutableStateOf<List<ConfettiParticle>>(emptyList()) }
|
||||
var animationTime by remember { mutableFloatStateOf(0f) }
|
||||
val system = remember { FastConfettiSystem(particleCount) }
|
||||
val animProgress = remember { Animatable(0f) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val rand = Random(System.currentTimeMillis())
|
||||
val list = mutableListOf<ConfettiParticle>()
|
||||
|
||||
for (i in 0 until particleCount) {
|
||||
val startX = rand.nextFloat() * 1000f
|
||||
val startY = -50f - rand.nextFloat() * 300f
|
||||
val vx = (rand.nextFloat() - 0.5f) * 12f
|
||||
val vy = 10f + rand.nextFloat() * 18f
|
||||
val size = 12f + rand.nextFloat() * 16f
|
||||
val color = CONFETTI_COLORS[rand.nextInt(CONFETTI_COLORS.size)]
|
||||
val rotation = rand.nextFloat() * 360f
|
||||
val vRot = (rand.nextFloat() - 0.5f) * 15f
|
||||
val isCircle = rand.nextBoolean()
|
||||
|
||||
list.add(
|
||||
ConfettiParticle(
|
||||
x = startX,
|
||||
y = startY,
|
||||
vx = vx,
|
||||
vy = vy,
|
||||
size = size,
|
||||
color = color,
|
||||
rotation = rotation,
|
||||
vRot = vRot,
|
||||
isCircle = isCircle
|
||||
)
|
||||
animProgress.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(
|
||||
durationMillis = 3800,
|
||||
easing = LinearEasing
|
||||
)
|
||||
}
|
||||
particles = list
|
||||
|
||||
val startTime = withFrameNanos { it }
|
||||
val durationNanos = 2_800_000_000L // 2.8초
|
||||
|
||||
while (isActive) {
|
||||
val currentNanos = withFrameNanos { it }
|
||||
val elapsedNanos = currentNanos - startTime
|
||||
val progress = (elapsedNanos.toFloat() / durationNanos.toFloat()).coerceIn(0f, 1f)
|
||||
animationTime = progress
|
||||
|
||||
for (p in particles) {
|
||||
p.x += p.vx
|
||||
p.y += p.vy
|
||||
p.vy += 0.35f // gravity
|
||||
p.vx *= 0.98f // air drag
|
||||
p.rotation += p.vRot
|
||||
if (progress > 0.7f) {
|
||||
p.alpha = ((1f - progress) / 0.3f).coerceIn(0f, 1f)
|
||||
}
|
||||
}
|
||||
|
||||
if (progress >= 1f) {
|
||||
onAnimationEnd()
|
||||
break
|
||||
}
|
||||
}
|
||||
)
|
||||
onAnimationEnd()
|
||||
}
|
||||
|
||||
val progress = animProgress.value
|
||||
|
||||
Canvas(modifier = modifier.fillMaxSize()) {
|
||||
val canvasWidth = size.width
|
||||
val canvasHeight = size.height
|
||||
val elapsedSec = progress * 3.8f
|
||||
|
||||
for (p in particles) {
|
||||
val px = (p.x / 1000f) * canvasWidth
|
||||
val py = (p.y / 1200f) * canvasHeight
|
||||
val globalAlpha = if (progress > 0.82f) {
|
||||
((1f - progress) / 0.18f).coerceIn(0f, 1f)
|
||||
} else {
|
||||
1f
|
||||
}
|
||||
|
||||
withTransform({
|
||||
rotate(degrees = p.rotation, pivot = Offset(px, py))
|
||||
}) {
|
||||
if (p.isCircle) {
|
||||
drawCircle(
|
||||
color = p.color.copy(alpha = p.color.alpha * p.alpha),
|
||||
radius = p.size / 2f,
|
||||
center = Offset(px, py)
|
||||
)
|
||||
} else {
|
||||
drawRect(
|
||||
color = p.color.copy(alpha = p.color.alpha * p.alpha),
|
||||
topLeft = Offset(px - p.size / 2f, py - p.size / 2f),
|
||||
size = Size(p.size, p.size * 0.6f)
|
||||
)
|
||||
}
|
||||
if (globalAlpha <= 0f) return@Canvas
|
||||
|
||||
for (i in 0 until system.count) {
|
||||
// 수학 공식으로 위치 계산 (매 프레임 객체 생성 0개)
|
||||
val flutterX = sin(elapsedSec * 3.5f + system.flutterPhase[i]) * 18f
|
||||
val px = ((system.x[i] + system.vx[i] * elapsedSec * 30f + flutterX) / 1000f) * canvasWidth
|
||||
val py = ((system.y[i] + system.vy[i] * elapsedSec * 35f + 0.5f * 9.8f * elapsedSec * elapsedSec * 15f) / 1000f) * canvasHeight
|
||||
|
||||
if (py < -20f || py > canvasHeight + 50f) continue
|
||||
|
||||
val pColor = CONFETTI_COLORS[system.colorIndex[i]].copy(alpha = globalAlpha)
|
||||
val pSize = system.size[i]
|
||||
|
||||
if (system.isCircle[i]) {
|
||||
drawCircle(
|
||||
color = pColor,
|
||||
radius = pSize / 2f,
|
||||
center = Offset(px, py)
|
||||
)
|
||||
} else {
|
||||
drawRect(
|
||||
color = pColor,
|
||||
topLeft = Offset(px - pSize / 2f, py - pSize / 2f),
|
||||
size = Size(pSize, pSize * 0.65f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.wordpuzzle.hangul.ui.theme.BackgroundLight
|
||||
import com.wordpuzzle.hangul.ui.theme.PrimaryIndigo
|
||||
import com.wordpuzzle.hangul.ui.theme.SecondaryOrange
|
||||
import com.wordpuzzle.hangul.util.SoundManager
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -68,9 +69,12 @@ fun GameScreen(
|
||||
}
|
||||
}
|
||||
|
||||
// 클리어 이벤트 수신: 누적 퍼즐 풀이 시간이 5분(300초) 이상이면 다음 스테이지 진입 전 전체화면 광고 노출 후 5분 카운트 리셋
|
||||
// 클리어 이벤트 수신: 참 잘했어요 도장과 폭죽 연출이 바닥까지 완전히 떨어지도록 3.8초 딜레이 후 전환
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.stageClearEvent.collect { event ->
|
||||
// 종이가 완전히 떨어지는 애니메이션을 위한 3.8초 딜레이
|
||||
delay(3800L)
|
||||
|
||||
if (event.isLastStage) {
|
||||
ToastUtils.showTopToast(context, "🎉 모든 스테이지를 클리어하셨습니다!")
|
||||
onNavigateBack()
|
||||
|
||||
Reference in New Issue
Block a user