Розробка Gamification-системи для LMS (баллы, бейджи, рейтинг)
Геймірування в LMS працює не через «ігрову оболонку», а через систематичне визнання прогресу. Баллы, бейджи та рейтинги дають студентам зовнішню мотивацію в моменти, коли внутрішньої недостатньо.
Елементи системи
Баллы досвіду (XP) — накопичуються за будь-які дії: проходження уроку, здавання завдання, участь у форумі, streak днів. Баллы ніколи не витрачаються — вони відображають сумарну активність.
Рівні — автоматично присвоюються при накопленні порогового кількості XP. Відкривають нові можливості (доступ до іспитів, bonusні матеріали).
Бейджи — присвоюються за конкретні досягнення. Видні в профілі студента.
Leaderboard — рейтинг студентів за XP у рамках потоку або курсу.
Streak — серія днів з активністю поряд. Мотивує до регулярних занять.
Модель даних
CREATE TABLE xp_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
student_id UUID REFERENCES users(id),
course_id UUID REFERENCES courses(id),
event_type VARCHAR(100) NOT NULL,
entity_id UUID,
xp_awarded INT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE badge_definitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(200),
description TEXT,
icon_url VARCHAR(2000),
condition_type VARCHAR(100),
condition_value JSONB,
xp_reward INT DEFAULT 0
);
CREATE TABLE student_badges (
student_id UUID REFERENCES users(id),
badge_id UUID REFERENCES badge_definitions(id),
awarded_at TIMESTAMPTZ DEFAULT NOW(),
course_id UUID REFERENCES courses(id),
PRIMARY KEY(student_id, badge_id)
);
CREATE TABLE student_xp (
student_id UUID REFERENCES users(id),
course_id UUID REFERENCES courses(id),
total_xp INT DEFAULT 0,
level INT DEFAULT 1,
streak_days INT DEFAULT 0,
last_activity DATE,
PRIMARY KEY(student_id, course_id)
);
Присудження XP та перевірка бейджів
Подійно-орієнтована архітектура: при будь-якій дії студента публікуємо подію, subscriber начисляє XP та перевіряє умови бейджів:
class GamificationService {
static XP_REWARDS = {
lesson_completed: 50,
assignment_submitted: 30,
assignment_graded_pass: 100,
assignment_graded_excellent: 150,
forum_post_created: 10,
forum_post_upvoted: 5,
streak_7_days: 200,
streak_30_days: 1000,
course_completed: 500,
};
async awardXp(studentId, courseId, eventType, entityId = null) {
const xp = GamificationService.XP_REWARDS[eventType] ?? 0;
if (xp === 0) return;
await db.xpEvents.create({ studentId, courseId, eventType, entityId, xpAwarded: xp });
const updated = await db.studentXp.increment({ studentId, courseId }, 'total_xp', xp);
const newLevel = this.calculateLevel(updated.totalXp);
if (newLevel > updated.level) {
await db.studentXp.update({ studentId, courseId }, { level: newLevel });
await this.notifyLevelUp(studentId, newLevel);
}
await this.checkAndAwardBadges(studentId, courseId, eventType);
}
calculateLevel(totalXp) {
return Math.floor(1 + Math.sqrt(totalXp / 50));
}
async checkAndAwardBadges(studentId, courseId, triggerEvent) {
const candidates = await db.badgeDefinitions.findAll({
conditionType: { in: this.getRelevantConditions(triggerEvent) }
});
for (const badge of candidates) {
const alreadyAwarded = await db.studentBadges.exists({ studentId, badgeId: badge.id });
if (alreadyAwarded) continue;
const earned = await this.checkCondition(studentId, courseId, badge);
if (earned) {
await db.studentBadges.create({ studentId, badgeId: badge.id, courseId });
await this.awardXp(studentId, courseId, 'badge_earned');
await this.notifyBadgeEarned(studentId, badge);
}
}
}
}
Leaderboard
async function getLeaderboard(courseId: string, limit = 20): Promise<LeaderboardEntry[]> {
const cacheKey = `leaderboard:${courseId}`;
const cached = await redis.get(cacheKey);
if (cached) return JSON.parse(cached);
const data = await db.studentXp.findAll({
where: { courseId },
order: [['totalXp', 'DESC']],
limit,
include: [{ model: db.users, attributes: ['name', 'avatar'] }],
});
const result = data.map((row, idx) => ({
rank: idx + 1,
studentId: row.studentId,
name: row.user.name,
avatar: row.user.avatar,
xp: row.totalXp,
level: row.level,
streakDays: row.streakDays,
}));
await redis.setex(cacheKey, 60, JSON.stringify(result));
return result;
}
UI компоненти
XP bar — прогрес до наступного рівня з анімацією заповнення. При отриманні нових XP — вспливаючий +50 XP тост.
Badge showcase — сітка бейджів у профілі. Отримані — кольорові, ще не отримані — сірі з іконкою замка та підказкою про умову.
Streak counter — іконка вогню з кількістю днів. При щоденному заході — анімоване підтвердження продовження streak.
Часові межи
Базова система XP + рівні + 10–15 бейджів + leaderboard — 7–10 днів. UI компоненти з анімаціями (XP bar, тости, badge showcase) — 3–4 дні. Редактор бейджів для адміністратора — 2–3 дні.







