2026-01-21 17:13:51 +08:00
|
|
|
|
<script setup>
|
|
|
|
|
|
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
|
|
|
|
|
|
import { useGameStore } from '@/store/game.js'
|
|
|
|
|
|
import { usePlayerStore } from '@/store/player.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
saveGame,
|
|
|
|
|
|
loadGame,
|
|
|
|
|
|
resetGame as resetGameStorage,
|
|
|
|
|
|
hasSaveData
|
|
|
|
|
|
} from '@/utils/storage.js'
|
|
|
|
|
|
import {
|
|
|
|
|
|
startGameLoop,
|
|
|
|
|
|
stopGameLoop,
|
|
|
|
|
|
isGameLoopRunning,
|
|
|
|
|
|
calculateOfflineEarnings,
|
|
|
|
|
|
applyOfflineEarnings,
|
|
|
|
|
|
updateLastSaveTime,
|
|
|
|
|
|
refreshMarketPrices
|
|
|
|
|
|
} from '@/utils/gameLoop.js'
|
|
|
|
|
|
import { checkAndTriggerEvent } from '@/utils/eventSystem.js'
|
|
|
|
|
|
import { addItemToInventory } from '@/utils/itemSystem.js'
|
|
|
|
|
|
import { unlockSkill } from '@/utils/skillSystem.js'
|
|
|
|
|
|
|
|
|
|
|
|
// 记录应用隐藏时间(用于计算离线收益)
|
|
|
|
|
|
let appHideTime = Date.now()
|
|
|
|
|
|
|
|
|
|
|
|
onLaunch(() => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
console.log('App Launch')
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化Store
|
|
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
|
|
|
|
|
|
// 设置自动保存触发器
|
|
|
|
|
|
gameStore.triggerAutoSave = () => {
|
|
|
|
|
|
performAutoSave()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置事件触发器
|
|
|
|
|
|
gameStore.triggerEvent = (type, context) => {
|
|
|
|
|
|
checkAndTriggerEvent(gameStore, playerStore, type, context)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 加载存档
|
|
|
|
|
|
const loadResult = loadGame(gameStore, playerStore)
|
|
|
|
|
|
|
|
|
|
|
|
if (loadResult.success) {
|
|
|
|
|
|
// 存档加载成功
|
|
|
|
|
|
console.log('Save loaded, version:', loadResult.version)
|
|
|
|
|
|
|
|
|
|
|
|
// 添加欢迎日志
|
|
|
|
|
|
gameStore.addLog('欢迎回来!', 'info')
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化市场价格(如果需要)
|
|
|
|
|
|
if (gameStore.gameTime.day !== gameStore.marketPrices.lastRefreshDay) {
|
|
|
|
|
|
refreshMarketPrices(gameStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (loadResult.isNewGame) {
|
|
|
|
|
|
// 新游戏
|
|
|
|
|
|
console.log('Starting new game')
|
|
|
|
|
|
initializeNewGame(gameStore, playerStore)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 加载失败,重置游戏
|
|
|
|
|
|
console.error('Load failed, resetting game')
|
|
|
|
|
|
resetGameStorage(gameStore, playerStore)
|
|
|
|
|
|
initializeNewGame(gameStore, playerStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动游戏循环
|
|
|
|
|
|
startGameLoop(gameStore, playerStore)
|
|
|
|
|
|
|
|
|
|
|
|
// 更新保存时间
|
|
|
|
|
|
updateLastSaveTime()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('App Launch failed:', error)
|
|
|
|
|
|
// 可以在这里显示友好的错误提示
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onShow(() => {
|
|
|
|
|
|
console.log('App Show')
|
|
|
|
|
|
|
|
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
|
|
|
|
|
|
// 检查离线收益
|
|
|
|
|
|
checkOfflineEarnings(gameStore, playerStore)
|
|
|
|
|
|
|
|
|
|
|
|
// 确保游戏循环正在运行
|
|
|
|
|
|
if (!isGameLoopRunning()) {
|
|
|
|
|
|
startGameLoop(gameStore, playerStore)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
onHide(() => {
|
|
|
|
|
|
console.log('App Hide')
|
|
|
|
|
|
|
|
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
|
|
|
|
|
|
// 记录隐藏时间
|
|
|
|
|
|
appHideTime = Date.now()
|
|
|
|
|
|
|
|
|
|
|
|
// 自动保存
|
|
|
|
|
|
performAutoSave()
|
|
|
|
|
|
|
|
|
|
|
|
// 停止游戏循环
|
|
|
|
|
|
stopGameLoop()
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 初始化新游戏
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
*/
|
|
|
|
|
|
function initializeNewGame(gameStore, playerStore) {
|
|
|
|
|
|
// 重置所有数据
|
|
|
|
|
|
resetGameStorage(gameStore, playerStore)
|
|
|
|
|
|
|
|
|
|
|
|
// 给予初始货币
|
|
|
|
|
|
playerStore.currency.copper = 500 // 500铜币 = 5银币
|
|
|
|
|
|
|
|
|
|
|
|
// 给予初始物品
|
|
|
|
|
|
addItemToInventory(playerStore, 'wooden_stick', 1)
|
|
|
|
|
|
addItemToInventory(playerStore, 'bread', 5)
|
|
|
|
|
|
|
|
|
|
|
|
// 解锁木棍精通技能
|
|
|
|
|
|
unlockSkill(playerStore, 'stick_mastery')
|
|
|
|
|
|
|
|
|
|
|
|
// 添加欢迎日志
|
|
|
|
|
|
gameStore.addLog('欢迎来到荒野求生!', 'info')
|
|
|
|
|
|
gameStore.addLog('你在营地醒来,身边有一些铜币和物资...', 'story')
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化市场价格
|
|
|
|
|
|
refreshMarketPrices(gameStore)
|
|
|
|
|
|
|
|
|
|
|
|
// 触发初始事件
|
|
|
|
|
|
checkAndTriggerEvent(gameStore, playerStore, 'enter', { locationId: 'camp' })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查并应用离线收益
|
|
|
|
|
|
* @param {Object} gameStore - 游戏Store
|
|
|
|
|
|
* @param {Object} playerStore - 玩家Store
|
|
|
|
|
|
*/
|
|
|
|
|
|
function checkOfflineEarnings(gameStore, playerStore) {
|
|
|
|
|
|
const now = Date.now()
|
|
|
|
|
|
const offlineSeconds = Math.floor((now - appHideTime) / 1000)
|
|
|
|
|
|
|
|
|
|
|
|
// 离线时间不足1分钟,不计算收益
|
|
|
|
|
|
if (offlineSeconds < 60) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算离线收益
|
|
|
|
|
|
const earnings = calculateOfflineEarnings(
|
|
|
|
|
|
gameStore,
|
|
|
|
|
|
playerStore,
|
|
|
|
|
|
offlineSeconds
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 应用离线收益
|
|
|
|
|
|
if (earnings && (Object.keys(earnings.skillExp || {}).length > 0 || earnings.currency > 0)) {
|
|
|
|
|
|
applyOfflineEarnings(gameStore, playerStore, earnings)
|
|
|
|
|
|
gameStore.addLog(`离线 ${earnings.timeDisplay} 期间获得了收益`, 'info')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新隐藏时间,避免重复计算
|
|
|
|
|
|
appHideTime = now
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 执行自动保存
|
|
|
|
|
|
* @returns {boolean} 是否保存成功
|
|
|
|
|
|
*/
|
|
|
|
|
|
function performAutoSave() {
|
|
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
|
|
|
|
|
|
const success = saveGame(gameStore, playerStore)
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
console.log('Auto-save completed')
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('Auto-save failed')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return success
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 手动保存游戏(暴露给全局)
|
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
window.manualSave = function () {
|
|
|
|
|
|
return performAutoSave()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取存档信息(暴露给全局)
|
|
|
|
|
|
* @returns {Object|null}
|
|
|
|
|
|
*/
|
|
|
|
|
|
window.getSaveInfo = function () {
|
|
|
|
|
|
const gameStore = useGameStore()
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
gameTime: gameStore.gameTime,
|
|
|
|
|
|
playerLevel: playerStore.level.current,
|
|
|
|
|
|
location: playerStore.currentLocation,
|
|
|
|
|
|
hasSave: hasSaveData()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 解锁木棍精通技能(用于测试)
|
|
|
|
|
|
*/
|
|
|
|
|
|
window.unlockStickSkill = function () {
|
|
|
|
|
|
const playerStore = usePlayerStore()
|
|
|
|
|
|
unlockSkill(playerStore, 'stick_mastery')
|
|
|
|
|
|
return 'stick_mastery unlocked'
|
|
|
|
|
|
}
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss">
|
|
|
|
|
|
/* 全局样式 */
|
|
|
|
|
|
@import '@/uni.scss';
|
|
|
|
|
|
|
|
|
|
|
|
page {
|
|
|
|
|
|
background-color: $bg-primary;
|
|
|
|
|
|
color: $text-primary;
|
|
|
|
|
|
font-size: 28rpx;
|
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 滚动条样式 */
|
|
|
|
|
|
::-webkit-scrollbar {
|
|
|
|
|
|
width: 8rpx;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
|
|
|
background-color: $bg-tertiary;
|
|
|
|
|
|
border-radius: 4rpx;
|
|
|
|
|
|
}
|
2026-01-23 16:20:10 +08:00
|
|
|
|
|
|
|
|
|
|
/* ==================== 过渡动画 ==================== */
|
|
|
|
|
|
|
|
|
|
|
|
/* 淡入淡出 */
|
|
|
|
|
|
.fade-enter-active,
|
|
|
|
|
|
.fade-leave-active {
|
|
|
|
|
|
transition: opacity 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.fade-enter-from,
|
|
|
|
|
|
.fade-leave-to {
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 滑动进入 - 从右侧 */
|
|
|
|
|
|
.slide-right-enter-active,
|
|
|
|
|
|
.slide-right-leave-active {
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-right-enter-from {
|
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-right-leave-to {
|
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 滑动进入 - 从左侧 */
|
|
|
|
|
|
.slide-left-enter-active,
|
|
|
|
|
|
.slide-left-leave-active {
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-left-enter-from {
|
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-left-leave-to {
|
|
|
|
|
|
transform: translateX(-100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 滑动进入 - 从下方 */
|
|
|
|
|
|
.slide-up-enter-active,
|
|
|
|
|
|
.slide-up-leave-active {
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-up-enter-from {
|
|
|
|
|
|
transform: translateY(100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.slide-up-leave-to {
|
|
|
|
|
|
transform: translateY(100%);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 缩放动画 */
|
|
|
|
|
|
.scale-enter-active,
|
|
|
|
|
|
.scale-leave-active {
|
|
|
|
|
|
transition: all 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.scale-enter-from,
|
|
|
|
|
|
.scale-leave-to {
|
|
|
|
|
|
transform: scale(0.9);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 弹跳动画 */
|
|
|
|
|
|
.bounce-enter-active {
|
|
|
|
|
|
animation: bounce-in 0.4s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.bounce-leave-active {
|
|
|
|
|
|
animation: bounce-in 0.3s ease reverse;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@keyframes bounce-in {
|
|
|
|
|
|
0% {
|
|
|
|
|
|
transform: scale(0);
|
|
|
|
|
|
opacity: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
|
|
|
transform: scale(1.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 闪烁动画 */
|
|
|
|
|
|
@keyframes flash {
|
|
|
|
|
|
0%, 100% { opacity: 1; }
|
|
|
|
|
|
50% { opacity: 0.5; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.flash {
|
|
|
|
|
|
animation: flash 0.5s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 脉冲动画 */
|
|
|
|
|
|
@keyframes pulse {
|
|
|
|
|
|
0%, 100% {
|
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
|
opacity: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
|
|
|
transform: scale(1.05);
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.pulse {
|
|
|
|
|
|
animation: pulse 1.5s ease infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 摇晃动画 */
|
|
|
|
|
|
@keyframes shake {
|
|
|
|
|
|
0%, 100% { transform: translateX(0); }
|
|
|
|
|
|
25% { transform: translateX(-10rpx); }
|
|
|
|
|
|
75% { transform: translateX(10rpx); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.shake {
|
|
|
|
|
|
animation: shake 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 旋转加载 */
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
|
from { transform: rotate(0deg); }
|
|
|
|
|
|
to { transform: rotate(360deg); }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.spin {
|
|
|
|
|
|
animation: spin 1s linear infinite;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 打字机效果 */
|
|
|
|
|
|
@keyframes typewriter {
|
|
|
|
|
|
from { width: 0; }
|
|
|
|
|
|
to { width: 100%; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* ==================== 工具类 ==================== */
|
|
|
|
|
|
|
|
|
|
|
|
/* 过渡效果 */
|
|
|
|
|
|
.transition-all {
|
|
|
|
|
|
transition: all 0.3s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.transition-fast {
|
|
|
|
|
|
transition: all 0.15s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.transition-slow {
|
|
|
|
|
|
transition: all 0.5s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 悬停效果 */
|
|
|
|
|
|
.hover-scale {
|
|
|
|
|
|
transition: transform 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hover-scale:active {
|
|
|
|
|
|
transform: scale(0.95);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hover-bright {
|
|
|
|
|
|
transition: filter 0.2s ease;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.hover-bright:active {
|
|
|
|
|
|
filter: brightness(1.2);
|
|
|
|
|
|
}
|
2026-01-21 17:13:51 +08:00
|
|
|
|
</style>
|