Achievements
System osiągnięć i gamifikacji
📖 Przegląd
Moduł Achievements umożliwia dodanie systemu osiągnięć i gamifikacji
do aplikacji. Wspiera różne typy osiągnięć: jednorazowe, czasowe, streaki i ilościowe.
// Definiowanie osiągnięcia
ADict.Achievements.define("first_purchase") {
title = "Pierwszy zakup!"
description = "Dokonaj pierwszego zakupu"
points = 100
type = AchievementType.ONE_TIME
}
// Odblokowanie
ADict.Achievements.unlock("first_purchase")
// Inkrementacja postępu
ADict.Achievements.increment("articles_read", 1)
📦 Typy osiągnięć
| Typ | Opis | Przykład |
|---|---|---|
ONE_TIME |
Jednorazowe - odblokuj raz i zostaje | Pierwszy zakup, Zarejestruj konto |
PROGRESSIVE |
Ilościowe - wymaga wykonania N czynności | Przeczytaj 100 artykułów |
STREAK |
Streak - wymaga ciągłości | 7 dni z rzędu |
TIMED |
Czasowe - dostępne tylko przez określony czas | Świąteczna promocja |
📝 Definiowanie osiągnięć
define(id: String, config: DefinitionBuilder.() -> Unit)
Zdefiniuj nowe osiągnięcie.
Właściwości DefinitionBuilder
| Właściwość | Typ | Opis |
|---|---|---|
title |
String | Tytuł osiągnięcia |
description |
String | Opis osiągnięcia |
icon |
Int? | ID zasobu ikony (@DrawableRes) |
points |
Int | Liczba punktów za osiągnięcie |
type |
AchievementType | Typ osiągnięcia |
targetCount |
Int | Cel dla PROGRESSIVE/STREAK |
hidden |
Boolean | Czy ukryte do momentu odblokowania |
expiresAt |
Long? | Timestamp wygaśnięcia (TIMED) |
availableFrom |
Long? | Timestamp dostępności (TIMED) |
category |
String? | Kategoria osiągnięcia |
tier |
Int | Poziom (1=bronze, 2=silver, 3=gold, 4=platinum) |
prerequisiteIds |
List<String> | Wymagane wcześniejsze osiągnięcia |
// Jednorazowe
ADict.Achievements.define("first_purchase") {
title = "Pierwszy zakup!"
description = "Dokonaj pierwszego zakupu w aplikacji"
icon = R.drawable.badge_purchase
points = 100
type = AchievementType.ONE_TIME
category = "shopping"
tier = 1 // Bronze
}
// Ilościowe (progressive)
ADict.Achievements.define("read_100_articles") {
title = "Czytelnik"
description = "Przeczytaj 100 artykułów"
points = 500
type = AchievementType.PROGRESSIVE
targetCount = 100
category = "reading"
tier = 3 // Gold
}
// Streak
ADict.Achievements.define("streak_7") {
title = "Tydzień z rzędu"
description = "Używaj aplikacji 7 dni z rzędu"
points = 200
type = AchievementType.STREAK
targetCount = 7
tier = 2 // Silver
}
// Czasowe (np. świąteczne)
val christmasStart = Calendar.getInstance().apply {
set(2024, Calendar.DECEMBER, 20)
}.timeInMillis
val christmasEnd = Calendar.getInstance().apply {
set(2024, Calendar.DECEMBER, 27)
}.timeInMillis
ADict.Achievements.define("christmas_shopper") {
title = "Świąteczny zakupoholik"
description = "Dokonaj zakupu w okresie świątecznym"
points = 300
type = AchievementType.TIMED
availableFrom = christmasStart
expiresAt = christmasEnd
hidden = true
}
// Z wymaganiami
ADict.Achievements.define("master_shopper") {
title = "Mistrz zakupów"
description = "Dokonaj 50 zakupów"
points = 1000
type = AchievementType.PROGRESSIVE
targetCount = 50
prerequisiteIds = listOf("first_purchase") // Wymaga first_purchase
tier = 4 // Platinum
}
🔓 Odblokowywanie
unlock(id: String): Boolean
Odblokuj osiągnięcie (dla ONE_TIME).
Zwraca: true jeśli odblokowano, false jeśli już było odblokowane lub nie spełniono wymagań
// Proste odblokowanie
if (ADict.Achievements.unlock("first_purchase")) {
showAchievementUnlockedDialog("first_purchase")
}
// W kontekście zakupu
fun onPurchaseCompleted(product: Product) {
// Sprawdź i odblokuj różne osiągnięcia
ADict.Achievements.unlock("first_purchase")
if (product.category == "premium") {
ADict.Achievements.unlock("premium_buyer")
}
// Inkrementuj purchase counter
ADict.Achievements.increment("total_purchases")
}
📈 Postęp i streaki
increment(id: String, amount: Int = 1): Boolean
Inkrementuj postęp (dla PROGRESSIVE).
Zwraca: true jeśli osiągnięcie zostało odblokowane
setProgress(id: String, count: Int): Boolean
Ustaw postęp bezpośrednio.
recordStreakDay(id: String): Boolean
Zapisz dzień streaka (dla STREAK).
resetStreak(id: String)
Zresetuj streak.
// Progressive - inkrementacja
fun onArticleRead(articleId: String) {
val unlocked = ADict.Achievements.increment("read_100_articles")
if (unlocked) {
showCongratulations("Przeczytałeś 100 artykułów!")
}
// Możesz też dodać więcej niż 1
ADict.Achievements.increment("total_words_read", article.wordCount)
}
// Streak - zapisz dzień
fun onAppOpened() {
val unlocked = ADict.Achievements.recordStreakDay("streak_7")
if (unlocked) {
showCongratulations("7 dni z rzędu!")
}
// Sprawdź aktualny streak
val state = ADict.Achievements.get("streak_7")?.state
Log.d("Achievements", "Current streak: ${state?.streakDays}")
}
// Ustaw postęp bezpośrednio (np. z serwera)
fun syncProgressFromServer(serverProgress: Int) {
ADict.Achievements.setProgress("read_100_articles", serverProgress)
}
🔍 Pobieranie osiągnięć
getAll(includeHidden: Boolean = false): List<Achievement>
Pobierz wszystkie osiągnięcia.
getUnlocked(): List<Achievement>
Pobierz odblokowane osiągnięcia.
getLocked(includeHidden: Boolean = false): List<Achievement>
Pobierz zablokowane osiągnięcia.
getByCategory(category: String): List<Achievement>
Pobierz osiągnięcia w kategorii.
get(id: String): Achievement?
Pobierz pojedyncze osiągnięcie.
isUnlocked(id: String): Boolean
Sprawdź czy osiągnięcie jest odblokowane.
getProgress(id: String): Float
Pobierz postęp (0.0 - 1.0).
getTotalPoints(): Int
Pobierz sumę punktów za odblokowane osiągnięcia.
getStats(): Stats
Pobierz statystyki osiągnięć.
// Lista wszystkich osiągnięć
val allAchievements = ADict.Achievements.getAll()
allAchievements.forEach { achievement ->
println("${achievement.title}: ${if (achievement.isUnlocked) "✅" else "🔒"}")
println(" Progress: ${(achievement.progress * 100).toInt()}%")
println(" Points: ${achievement.points}")
}
// Odblokowane
val unlockedCount = ADict.Achievements.getUnlocked().size
// Statystyki
val stats = ADict.Achievements.getStats()
println("Odblokowane: ${stats.unlocked}/${stats.total}")
println("Punkty: ${stats.totalPoints}")
println("Completion: ${(stats.completionRate * 100).toInt()}%")
// W RecyclerView
class AchievementsAdapter : RecyclerView.Adapter<...>() {
private var achievements = listOf()
fun setAchievements(list: List) {
achievements = list.sortedWith(
compareByDescending { it.isUnlocked }
.thenByDescending { it.definition.tier }
)
notifyDataSetChanged()
}
}
// Obserwacja zmian (Flow)
lifecycleScope.launch {
ADict.Achievements.achievements.collect { achievementsMap ->
updateUI(achievementsMap)
}
}
🔔 Callbacki
onUnlock(callback: (Achievement) -> Unit)
Callback wywoływany przy odblokowaniu osiągnięcia.
// Globalny callback
ADict.Achievements.onUnlock { achievement ->
// Pokaż UI
showAchievementToast(achievement)
// Analytics
ADict.Analytics.log("achievement_unlocked", mapOf(
"achievement_id" to achievement.id,
"achievement_name" to achievement.title,
"points" to achievement.points,
"tier" to achievement.definition.tier
))
// Może nagroda?
if (achievement.definition.tier >= 3) {
grantBonusCoins(achievement.points)
}
}
fun showAchievementToast(achievement: Achievement) {
Toast.makeText(
context,
"🏆 ${achievement.title} (+${achievement.points} pts)",
Toast.LENGTH_LONG
).show()
}
💡 Przykłady praktyczne
Pełna konfiguracja dla gry
class AchievementsSetup {
fun setup() {
// === ONBOARDING ===
ADict.Achievements.define("first_level") {
title = "Początek przygody"
description = "Ukończ pierwszy poziom"
points = 10
type = AchievementType.ONE_TIME
category = "onboarding"
tier = 1
}
ADict.Achievements.define("tutorial_complete") {
title = "Gotowy do gry"
description = "Ukończ tutorial"
points = 25
type = AchievementType.ONE_TIME
category = "onboarding"
tier = 1
}
// === POSTĘP ===
ADict.Achievements.define("levels_10") {
title = "Podróżnik"
description = "Ukończ 10 poziomów"
points = 100
type = AchievementType.PROGRESSIVE
targetCount = 10
category = "progress"
tier = 2
}
ADict.Achievements.define("levels_50") {
title = "Weteran"
description = "Ukończ 50 poziomów"
points = 500
type = AchievementType.PROGRESSIVE
targetCount = 50
category = "progress"
tier = 3
prerequisiteIds = listOf("levels_10")
}
ADict.Achievements.define("levels_100") {
title = "Legenda"
description = "Ukończ 100 poziomów"
points = 1000
type = AchievementType.PROGRESSIVE
targetCount = 100
category = "progress"
tier = 4
prerequisiteIds = listOf("levels_50")
}
// === STREAK ===
ADict.Achievements.define("daily_3") {
title = "Regularny gracz"
description = "Graj 3 dni z rzędu"
points = 50
type = AchievementType.STREAK
targetCount = 3
category = "engagement"
tier = 1
}
ADict.Achievements.define("daily_7") {
title = "Oddany gracz"
description = "Graj 7 dni z rzędu"
points = 150
type = AchievementType.STREAK
targetCount = 7
category = "engagement"
tier = 2
}
ADict.Achievements.define("daily_30") {
title = "Uzależniony"
description = "Graj 30 dni z rzędu"
points = 500
type = AchievementType.STREAK
targetCount = 30
category = "engagement"
tier = 4
}
// === KOLEKCJONOWANIE ===
ADict.Achievements.define("collect_all_characters") {
title = "Kolekcjoner"
description = "Odblokuj wszystkie postacie"
points = 300
type = AchievementType.ONE_TIME
category = "collection"
tier = 3
hidden = true
}
// === SOCIAL ===
ADict.Achievements.define("first_share") {
title = "Społecznik"
description = "Udostępnij wynik znajomym"
points = 30
type = AchievementType.ONE_TIME
category = "social"
tier = 1
}
// === SKILL ===
ADict.Achievements.define("perfect_level") {
title = "Perfekcja"
description = "Ukończ poziom z 3 gwiazdkami"
points = 50
type = AchievementType.ONE_TIME
category = "skill"
tier = 2
}
ADict.Achievements.define("no_damage") {
title = "Nietykalny"
description = "Ukończ poziom bez obrażeń"
points = 100
type = AchievementType.ONE_TIME
category = "skill"
tier = 3
hidden = true
}
// Callback
ADict.Achievements.onUnlock { achievement ->
GameManager.showAchievementPopup(achievement)
GameManager.addCoins(achievement.points)
}
}
}
// Użycie w grze
class GameManager {
fun onLevelComplete(level: Level, stars: Int, damage: Int) {
// Podstawowe
ADict.Achievements.unlock("first_level")
ADict.Achievements.increment("levels_10")
ADict.Achievements.increment("levels_50")
ADict.Achievements.increment("levels_100")
// Skill-based
if (stars == 3) {
ADict.Achievements.unlock("perfect_level")
}
if (damage == 0) {
ADict.Achievements.unlock("no_damage")
}
}
fun onDailyLogin() {
ADict.Achievements.recordStreakDay("daily_3")
ADict.Achievements.recordStreakDay("daily_7")
ADict.Achievements.recordStreakDay("daily_30")
}
fun onShare() {
ADict.Achievements.unlock("first_share")
}
fun onAllCharactersUnlocked() {
ADict.Achievements.unlock("collect_all_characters")
}
}
📚 Klasy danych
data class AchievementDefinition
- idString
- titleString
- descriptionString
- iconInt?
- pointsInt
- typeAchievementType
- targetCountInt
- hiddenBoolean
- expiresAtLong?
- availableFromLong?
- categoryString?
- tierInt
- prerequisiteIdsList<String>
data class AchievementState
- idString
- unlockedBoolean
- unlockedAtLong?
- currentCountInt
- streakDaysInt
- lastStreakDateString?
- progressFloat
data class Stats
- totalIntŁączna liczba osiągnięć
- unlockedIntLiczba odblokowanych
- totalPointsIntSuma punktów
- completionRateFloatProcent ukończenia (0-1)