Implementing Gacha/Lootbox Mechanics for Mobile Games
Gacha is one of most profitable mechanisms in mobile games and simultaneously one of most regulated. Apple since 2022 requires disclosing odds for all lootbox mechanics. In some countries (Belgium, Netherlands) gacha with real money is equated to gambling. Proper implementation is balance between attractive mechanic, mathematically honest odds and legal transparency.
Mathematical Probability Model
Basic gacha — weighted random selection from reward pool. Simple implementation:
data class GachaItem(val id: String, val rarity: Rarity, val weight: Int)
fun rollGacha(pool: List<GachaItem>): GachaItem {
val totalWeight = pool.sumOf { it.weight }
var random = Random.nextInt(totalWeight)
for (item in pool) {
random -= item.weight
if (random < 0) return item
}
return pool.last()
}
Example weight distribution:
- Common: weight 1000 → ~58.8% chance
- Rare: weight 500 → ~29.4%
- Epic: weight 150 → ~8.8%
- Legendary: weight 50 → ~2.9%
Total weight: 1700. Chance of legendary: 50/1700 ≈ 2.94%.
Pity System: Mandatory for Fair Mechanics
Pity (guaranteed reward) — mechanic guaranteeing legendary after N failed attempts. Without pity player can streak 200 rolls without legendary — mathematically possible, practically ruins experience.
Soft pity — starting N-th roll probability gradually increases. Genshin Impact uses this: from 74th roll 5* chance grows 6% each roll.
Hard pity — exactly at N-th roll legendary guaranteed. Simpler to implement and fairer to communicate to player.
data class PlayerGachaPity(
val rollsSinceLastLegendary: Int,
val softPityStart: Int = 74,
val hardPityAt: Int = 90
)
fun calculateEffectiveProbability(baseProbability: Float, pity: PlayerGachaPity): Float {
if (pity.rollsSinceLastLegendary >= pity.hardPityAt) return 1.0f
if (pity.rollsSinceLastLegendary >= pity.softPityStart) {
val excess = pity.rollsSinceLastLegendary - pity.softPityStart
return minOf(1.0f, baseProbability + excess * 0.06f)
}
return baseProbability
}
Pity counter stored on server, not client. Client counter resets on reinstall — player loses accumulated pity, which causes justified anger and chargebacks.
Server-Side Result Generation
Gacha roll result cannot be generated client-side. Scheme:
- Client sends request
POST /gacha/rollwithuserId,gachaPoolId, roll count, payment token - Server verifies balance/transaction, fetches pity counter, generates result with seeded PRNG
- Server saves result, updates pity, returns
rollId+ results - Client displays animation and shows results
Seed generated on server, client cannot predict or influence result.
Opening Animation
Gacha roll animation — not just decoration, it's psychologically significant moment. 2–4 second duration, building suspense before reveal. For mobile implementation use:
- Unity Animator or Spine for 2D card/egg animations
- Particle System for effects on rare reward reveal
- Haptic feedback (
UIImpactFeedbackGeneratoron iOS,VibrationEffecton Android) at reveal moment
Multi-pull animations (x10 roll) need separate logic: show results sequentially with increasing tempo, common quick, legendary with full-screen effect.
Regulatory Requirements
App Store (since 2022): apps with lootbox must display odds "before purchase". This is not optional — required condition, violating leads to rejection on review.
Google Play: similar requirement since 2024. Odds must be accessible in interface before purchase confirmation.
Implementation: "Gacha Pool Details" screen with odds table by reward type. This screen opens via "i" button next to roll button.
| Rarity | Probability | Guarantee (hard pity) |
|---|---|---|
| Common | 58.8% | — |
| Rare | 29.4% | — |
| Epic | 8.8% | 40 rolls |
| Legendary | 2.9% | 90 rolls |
Gacha Pool Types
Rate-up banner — temporary pool with increased chance of specific character/item. Limited by time (7–14 days). Creates urgency and revenue peaks.
Standard banner — permanent pool without rate-up. Accepts "lost" pity wins in games with "guaranteed featured 50/50 system".
Featured banner with guarantee — if rate-up didn't win on 1st legendary, next legendary guaranteed to be featured. "50/50 system" — well communicated and creates fair expectation.
Timeline: basic gacha with pity and server generation — 3–4 days. Full system with multiple banner types, odds UI, animations and analytics — 7–10 days.







