鸿蒙PC Electron框架实战:起床战争技术实现详解
欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/
atomgit仓库地址:https://atomgit.com/feng8403000/qichuangzhanzheng





一、项目概述
1.1 项目背景
起床战争是一款将日常起床行为游戏化的桌面应用。每个人都经历过早上被闹钟叫醒却难以离开温暖被窝的痛苦,这款应用通过将闹钟和床铺拟人化为对战双方,让用户以"战斗"的方式完成起床过程。游戏化的设计不仅让起床变得有趣,还能通过统计和成就系统帮助用户养成良好的作息习惯。
本项目基于鸿蒙PC Electron框架开发,采用纯前端技术实现,无需复杂的后端支持,即可提供流畅的交互体验和可靠的数据持久化功能。
1.2 游戏设计理念
传统的起床应用往往是简单的计时器或提醒工具,用户很容易忽略或关闭它们。起床战争的核心设计理念是将起床行为转化为一场"能量消耗战":
- 赖床能量:代表睡意和对被窝的留恋,初始值较高(80%)
- 闹钟攻势:代表起床的动力和紧迫感,初始值较低(20%)
用户通过使用各种"技能"来消耗赖床能量,当赖床能量归零时,起床成功。这种设计让抽象的起床过程变得可视化、可量化,用户可以清晰地看到自己的"战斗进度"。
1.3 功能特性
| 功能模块 | 说明 |
|---|---|
| 战斗场景 | 闹钟与床铺的实时对战动画 |
| 能量系统 | 双能量条可视化显示 |
| 技能按钮 | 贪睡、起床、泼水、咖啡四种技能 |
| 闹钟设置 | 自定义时间、开关、重复日期 |
| 战斗记录 | 实时记录操作时间线 |
| 战绩统计 | 成功次数、连胜天数、最佳成绩 |
| 成就系统 | 五项成就条件触发解锁 |
| 数据持久化 | localStorage保存所有数据 |
二、技术架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 │
│ (HTML/CSS/JavaScript + 起床战斗游戏) │
├─────────────────────────────────────────────────────────────┤
│ Electron + Preload 层 │
│ (IPC 通信、API 暴露) │
├─────────────────────────────────────────────────────────────┤
│ HarmonyOS 原生层 │
│ (libadapter.so + ETS Adapters) │
└─────────────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 | 职责 | 文件 |
|---|---|---|
| 视图层 | HTML结构、战斗场景、UI组件 | index.html |
| 样式层 | 星空/日出主题、动画效果 | style.css |
| 业务层 | 游戏状态、技能逻辑、成就判定 | js/app.js |
| 数据层 | 游戏数据、成就定义 | js/app.js (内置) |
2.3 游戏状态机
let gameState = {
bedPower: 80, // 赖床能量 0-100
alarmPower: 20, // 闹钟攻势 0-100
snoozeCount: 0, // 贪睡次数
wakeUpTime: null, // 起床时间
alarmStartTime: null, // 闹钟响起时间
isBattleActive: false, // 战斗是否进行中
currentStreak: 0, // 当前连胜
totalWakeups: 0, // 总成功起床次数
bestTime: null, // 最佳用时(分钟)
achievements: new Set(), // 已解锁成就
battleLog: [], // 战斗记录
alarmSettings: { // 闹钟配置
hour: 7,
minute: 0,
enabled: true,
days: [6]
}
};
三、核心功能实现详解
3.1 游戏初始化流程
应用启动时执行完整的初始化流程:
function initApp() {
// 1. 从本地存储加载游戏状态
loadGameState();
// 2. 更新时钟显示
updateClock();
// 3. 更新日期显示
updateDate();
// 4. 更新战斗界面
updateBattleUI();
// 5. 更新统计数据
updateStats();
// 6. 更新成就显示
updateAchievements();
// 7. 渲染战斗记录
renderBattleLog();
// 8. 设置事件监听
setupEventListeners();
// 9. 启动定时器每秒更新时间
setInterval(updateClock, 1000);
// 10. 开始战斗
startBattle();
}
时钟更新函数每秒刷新一次,保持界面上时间与系统时间同步:
function updateClock() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
document.getElementById('clockTime').textContent = `${hours}:${minutes}`;
}
3.2 战斗系统核心逻辑
开始战斗函数初始化所有战斗相关状态:
function startBattle() {
gameState.isBattleActive = true;
gameState.bedPower = 80; // 赖床能量初始化
gameState.alarmPower = 20; // 闹钟攻势初始化
gameState.snoozeCount = 0;
gameState.wakeUpTime = null;
gameState.alarmStartTime = new Date(); // 记录战斗开始时间
gameState.battleLog = [];
addBattleLog('⏰ 闹钟响起!战斗开始!', false);
updateBattleUI();
}
更新战斗UI是核心渲染函数,负责同步界面与状态:
function updateBattleUI() {
// 更新能量条宽度
document.getElementById('bedPower').style.width = `${gameState.bedPower}%`;
document.getElementById('bedPowerValue').textContent = `${gameState.bedPower}%`;
document.getElementById('alarmPower').style.width = `${gameState.alarmPower}%`;
document.getElementById('alarmPowerValue').textContent = `${gameState.alarmPower}%`;
// 更新角色表情
updateCharacterExpressions();
// 更新对话气泡
updateSpeeches();
}
3.3 技能系统实现
贪睡技能是最特殊的技能,它会增加赖床能量、减少闹钟攻势,模拟"再睡一会儿"的行为:
function useSnooze() {
if (!gameState.isBattleActive) return;
gameState.snoozeCount++;
gameState.bedPower = Math.min(100, gameState.bedPower + 5); // 上限100
gameState.alarmPower = Math.max(0, gameState.alarmPower - 10); // 下限0
addBattleLog('⏰ 使用了「贪睡5分钟」', false);
updateBattleUI();
}
起床技能直接清空赖床能量,是最有效的结束战斗方式:
function useWakeUp() {
if (!gameState.isBattleActive) return;
gameState.bedPower = 0;
gameState.alarmPower = Math.min(100, gameState.alarmPower + 50);
addBattleLog('💪 使用了「起床!」', false);
// 检查是否胜利
if (gameState.bedPower <= 0) {
victory();
}
updateBattleUI();
}
泼水清醒和咖啡续命是中间效率的技能:
function useWater() {
if (!gameState.isBattleActive) return;
gameState.bedPower = Math.max(0, gameState.bedPower - 30);
gameState.alarmPower = Math.min(100, gameState.alarmPower + 30);
addBattleLog('💦 使用了「泼水清醒」', false);
if (gameState.bedPower <= 0) {
victory();
}
updateBattleUI();
}
function useCoffee() {
if (!gameState.isBattleActive) return;
gameState.bedPower = Math.max(0, gameState.bedPower - 20);
gameState.alarmPower = Math.min(100, gameState.alarmPower + 20);
addBattleLog('☕ 使用了「咖啡续命」', false);
if (gameState.bedPower <= 0) {
victory();
}
updateBattleUI();
}
3.4 角色表情与对话系统
根据赖床能量值动态更新角色表情:
function updateCharacterExpressions() {
const sleepingPerson = document.getElementById('sleepingPerson');
const clockRings = document.getElementById('clockRings');
// 根据赖床能量决定表情
if (gameState.bedPower > 60) {
sleepingPerson.textContent = '😴'; // 熟睡
} else if (gameState.bedPower > 30) {
sleepingPerson.textContent = '😐'; // 勉强清醒
} else if (gameState.bedPower > 10) {
sleepingPerson.textContent = '😣'; // 挣扎
} else {
sleepingPerson.textContent = '😤'; // 即将起床
}
// 闹钟是否在响
clockRings.classList.toggle('active', gameState.alarmPower < 100);
}
对话气泡内容根据能量比例动态选择:
function updateSpeeches() {
const bedSpeech = document.getElementById('bedSpeech');
const alarmSpeech = document.getElementById('alarmSpeech');
// 赖床方的对话
const bedSpeeches = [
'再睡5分钟...',
'被子太暖和了...',
'再眯一会儿...',
'闹钟别叫了...',
'好困啊...',
'让我再躺会儿...'
];
// 闹钟方的对话
const alarmSpeeches = [
'响铃时间到!',
'快起床!',
'不能再睡了!',
'起床起床!',
'早安!该起床了!'
];
if (gameState.bedPower > 50) {
bedSpeech.textContent = bedSpeeches[Math.floor(Math.random() * bedSpeeches.length)];
} else if (gameState.bedPower > 20) {
bedSpeech.textContent = '好吧...再最后5分钟...';
} else {
bedSpeech.textContent = '好好好...起来了...';
}
if (gameState.alarmPower > 50) {
alarmSpeech.textContent = alarmSpeeches[Math.floor(Math.random() * alarmSpeeches.length)];
} else if (gameState.alarmPower > 20) {
alarmSpeech.textContent = '加油!快赢了!';
} else {
alarmSpeech.textContent = '坚持就是胜利!';
}
}
3.5 胜利判定与处理
当赖床能量归零时触发胜利:
function victory() {
gameState.isBattleActive = false;
gameState.wakeUpTime = new Date();
// 计算用时(分钟)
const duration = Math.round(
(gameState.wakeUpTime - gameState.alarmStartTime) / 1000 / 60
);
// 更新统计数据
gameState.totalWakeups++;
gameState.currentStreak++;
// 更新最佳成绩
if (gameState.bestTime === null || duration < gameState.bestTime) {
gameState.bestTime = duration;
}
// 添加胜利记录
addBattleLog('🎉 起床成功!', true);
// 检查成就解锁
checkAchievements();
// 保存状态到本地
saveGameState();
// 显示胜利弹窗
showVictoryModal(duration);
// 更新显示
updateStats();
updateAchievements();
}
成就检查函数在每次胜利后执行:
function checkAchievements() {
for (const [id, def] of Object.entries(achievementDefs)) {
if (!gameState.achievements.has(id) && def.condition()) {
gameState.achievements.add(id);
addBattleLog(`🌟 解锁成就:「${def.name}」`, false);
}
}
saveGameState();
}
成就定义表:
const achievementDefs = {
first_wake: {
name: '第一次起床',
icon: '🌅',
condition: () => gameState.totalWakeups >= 1
},
streak_3: {
name: '三连胜',
icon: '🔥',
condition: () => gameState.currentStreak >= 3
},
fast_5: {
name: '5分钟内起床',
icon: '⚡',
condition: () => gameState.bestTime !== null && gameState.bestTime <= 5
},
no_snooze: {
name: '不贪睡',
icon: '🚫',
condition: () => gameState.totalWakeups >= 1 && gameState.snoozeCount === 0
},
week_warrior: {
name: '一周战士',
icon: '⚔️',
condition: () => gameState.currentStreak >= 7
}
};
3.6 战斗记录系统
function addBattleLog(message, isVictory) {
const now = new Date();
const time = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
gameState.battleLog.unshift({
time,
message,
isVictory
});
// 限制记录数量为20条
if (gameState.battleLog.length > 20) {
gameState.battleLog.pop();
}
renderBattleLog();
}
function renderBattleLog() {
const container = document.getElementById('logContainer');
container.innerHTML = gameState.battleLog.map(log => `
<div class="log-entry ${log.isVictory ? 'victory' : ''}">
<span class="log-time">${log.time}</span>
<span class="log-content">${log.message}</span>
</div>
`).join('');
}
3.7 数据持久化
保存游戏状态:
function saveGameState() {
const toSave = {
totalWakeups: gameState.totalWakeups,
currentStreak: gameState.currentStreak,
bestTime: gameState.bestTime,
achievements: [...gameState.achievements],
alarmSettings: gameState.alarmSettings
};
localStorage.setItem('wakeupWarState', JSON.stringify(toSave));
}
加载游戏状态:
function loadGameState() {
const saved = localStorage.getItem('wakeupWarState');
if (saved) {
const parsed = JSON.parse(saved);
gameState.totalWakeups = parsed.totalWakeups || 0;
gameState.currentStreak = parsed.currentStreak || 0;
gameState.bestTime = parsed.bestTime || null;
gameState.achievements = new Set(parsed.achievements || []);
gameState.alarmSettings = parsed.alarmSettings || gameState.alarmSettings;
// 恢复闹钟设置UI
document.getElementById('alarmHour').value = gameState.alarmSettings.hour;
document.getElementById('alarmMinute').value = gameState.alarmSettings.minute;
document.getElementById('alarmEnabled').checked = gameState.alarmSettings.enabled;
updateToggleLabel();
}
}
四、前端样式设计
4.1 主题色彩系统
:root {
/* 夜晚主题色 */
--night-dark: #1a1a2e;
--night-blue: #16213e;
--night-purple: #0f3460;
--star-gold: #ffd700;
/* 白天主题色 */
--sunrise-orange: #ff6b35;
--sunrise-pink: #ff8c61;
--sky-blue: #74c0fc;
--cloud-white: #f8f9fa;
/* 战斗色 */
--alarm-red: #e74c3c;
--alarm-orange: #e67e22;
--bed-purple: #9b59b6;
--bed-blue: #3498db;
}
4.2 星空背景动画
.night-sky {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 0;
}
.stars {
position: absolute;
width: 100%;
height: 100%;
background-image:
radial-gradient(2px 2px at 20px 30px, white, transparent),
radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.8), transparent),
/* ... 更多星星 */
background-repeat: repeat;
background-size: 500px 300px;
animation: twinkle 4s ease-in-out infinite;
}
@keyframes twinkle {
0%, 100% { opacity: 0.8; }
50% { opacity: 1; }
}
4.3 闹钟摇动动画
.logo-icon {
font-size: 3rem;
animation: alarmShake 1s ease-in-out infinite;
}
@keyframes alarmShake {
0%, 100% { transform: rotate(-10deg); }
50% { transform: rotate(10deg); }
}
4.4 VS标识动画
.vs-badge {
font-size: 2rem;
font-weight: 800;
color: var(--alarm-red);
text-shadow: 2px 2px 0 var(--alarm-orange);
animation: vsShake 0.5s ease-in-out infinite;
}
@keyframes vsShake {
0%, 100% { transform: rotate(-5deg); }
50% { transform: rotate(5deg); }
}
4.5 技能按钮样式
.battle-btn {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-md);
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: all var(--transition-fast);
}
.battle-btn:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-md);
}
.snooze-btn {
background: linear-gradient(135deg, #9b59b6, #8e44ad);
}
.wakeup-btn {
background: linear-gradient(135deg, #27ae60, #2ecc71);
}
.water-btn {
background: linear-gradient(135deg, #3498db, #2980b9);
}
.coffee-btn {
background: linear-gradient(135deg, #e67e22, #d35400);
}
4.6 开关切换样式
.switch {
position: relative;
width: 60px;
height: 34px;
}
.slider {
position: absolute;
cursor: pointer;
top: 0; left: 0; right: 0; bottom: 0;
background-color: #ccc;
transition: var(--transition-fast);
border-radius: var(--radius-full);
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: var(--transition-fast);
border-radius: 50%;
}
input:checked + .slider {
background-color: var(--alarm-red);
}
input:checked + .slider:before {
transform: translateX(26px);
}
五、事件监听系统
function setupEventListeners() {
// 战斗技能按钮
document.getElementById('snoozeBtn').addEventListener('click', useSnooze);
document.getElementById('wakeupBtn').addEventListener('click', useWakeUp);
document.getElementById('waterBtn').addEventListener('click', useWater);
document.getElementById('coffeeBtn').addEventListener('click', useCoffee);
// 闹钟设置
document.getElementById('alarmHour').addEventListener('change', updateAlarmSettings);
document.getElementById('alarmMinute').addEventListener('change', updateAlarmSettings);
document.getElementById('alarmEnabled').addEventListener('change', () => {
updateToggleLabel();
updateAlarmSettings();
});
// 星期按钮
setupDayButtons();
// 胜利弹窗
document.getElementById('closeVictoryModal').addEventListener('click', closeVictoryModal);
document.getElementById('victoryModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('victoryModal')) {
closeVictoryModal();
}
});
}
星期按钮设置:
function setupDayButtons() {
const buttons = document.querySelectorAll('.day-btn');
buttons.forEach(btn => {
const day = parseInt(btn.dataset.day);
if (gameState.alarmSettings.days.includes(day)) {
btn.classList.add('active');
}
btn.addEventListener('click', () => {
btn.classList.toggle('active');
if (btn.classList.contains('active')) {
if (!gameState.alarmSettings.days.includes(day)) {
gameState.alarmSettings.days.push(day);
}
} else {
gameState.alarmSettings.days = gameState.alarmSettings.days.filter(d => d !== day);
}
saveGameState();
});
});
}
六、代码优化建议
6.1 性能优化
当前实现使用直接DOM操作,对于频繁更新的场景(如动画),建议使用以下优化:
// 批量DOM更新
function updateBattleUI() {
// 创建文档片段减少重绘
const fragment = document.createDocumentFragment();
// 批量更新
const bedPowerEl = document.getElementById('bedPower');
const alarmPowerEl = document.getElementById('alarmPower');
bedPowerEl.style.width = `${gameState.bedPower}%`;
alarmPowerEl.style.width = `${gameState.alarmPower}%`;
// ... 其他更新
}
6.2 错误处理
function saveGameState() {
try {
const toSave = {
totalWakeups: gameState.totalWakeups,
currentStreak: gameState.currentStreak,
bestTime: gameState.bestTime,
achievements: [...gameState.achievements],
alarmSettings: gameState.alarmSettings
};
localStorage.setItem('wakeupWarState', JSON.stringify(toSave));
} catch (error) {
console.error('保存游戏状态失败:', error);
}
}
6.3 扩展性设计
添加新技能时,只需在HTML中添加按钮,在JS中添加处理函数:
// 在achievementDefs后添加技能定义
const skillDefs = {
splash: {
name: '冷水洗脸',
icon: '💧',
bedPowerChange: -40,
alarmPowerChange: 40,
logMessage: '使用了「冷水洗脸」'
}
};
// 添加对应的处理函数
function useSkill(skillId) {
const skill = skillDefs[skillId];
if (!skill || !gameState.isBattleActive) return;
gameState.bedPower = Math.max(0, gameState.bedPower + skill.bedPowerChange);
gameState.alarmPower = Math.min(100, gameState.alarmPower + skill.alarmPowerChange);
addBattleLog(skill.logMessage, false);
if (gameState.bedPower <= 0) {
victory();
}
updateBattleUI();
}
七、总结与展望
7.1 项目成果
起床战争项目成功实现了以下功能:
- 完整的战斗系统:赖床能量与闹钟攻势的双能量条机制
- 四个战斗技能:贪睡、起床、泼水、咖啡,各有不同效果
- 动态界面:角色表情和对话根据状态实时变化
- 闹钟设置:时间选择、开关控制、重复日期设置
- 战斗记录:实时记录操作时间线
- 战绩统计:累计次数、连胜天数、最佳成绩
- 成就系统:五项成就条件触发解锁
- 数据持久化:localStorage保存所有数据
7.2 技术亮点
- 游戏化设计:将日常行为转化为有趣的"战斗"
- 状态驱动:清晰的state管理,状态变化驱动UI更新
- 数据持久化:localStorage实现数据的本地存储
- 动画效果:CSS关键帧实现丰富的动画
- 响应式设计:适配多种屏幕尺寸
7.3 未来规划
| 优先级 | 功能 | 说明 |
|---|---|---|
| 高 | 音效系统 | 添加闹钟铃声和胜利音效 |
| 高 | 皮肤系统 | 解锁不同主题的皮肤 |
| 中 | 统计图表 | 显示每周/每月的起床统计 |
| 中 | 提醒推送 | 到达设定时间时系统通知 |
| 低 | 社交分享 | 分享战绩到社交平台 |
| 低 | 多人对战 | 与朋友比较起床速度 |
7.4 技术拓展
未来可以考虑引入以下技术:
- Web Audio API:实现闹钟铃声播放
- Service Worker:实现离线功能和推送通知
- Canvas/WebGL:实现更复杂的粒子特效
- IndexedDB:存储更大量的历史数据
- PWA:支持添加到桌面,实现离线使用
附录
核心文件列表
| 文件 | 说明 |
|---|---|
| index.html | 游戏主页面,包含战斗场景和所有UI组件 |
| style.css | 星空/日出主题样式,包含所有动画 |
| js/app.js | 游戏核心逻辑,状态管理和事件处理 |
游戏参数表
| 参数 | 默认值 | 范围 | 说明 |
|---|---|---|---|
| bedPower | 80 | 0-100 | 赖床能量 |
| alarmPower | 20 | 0-100 | 闹钟攻势 |
| snoozeBedBonus | +5 | - | 贪睡增加赖床 |
| snoozeAlarmPenalty | -10 | - | 贪睡减少闹钟 |
| wakeupBedPenalty | -100 | - | 起床清空赖床 |
| waterBedPenalty | -30 | - | 泼水减少赖床 |
| coffeeBedPenalty | -20 | - | 咖啡减少赖床 |
浏览器兼容性
| 浏览器 | 最低版本 |
|---|---|
| Chrome | 80+ |
| Firefox | 75+ |
| Safari | 13+ |
| Edge | 80+ |
参考资料
- MDN Web Docs - localStorage
- MDN Web Docs - CSS动画
- Electron官方文档
- HarmonyOS开发者文档
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)