feat: 星域故事汇小游戏初始版本
This commit is contained in:
132
client/js/data/AudioManager.js
Normal file
132
client/js/data/AudioManager.js
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* 音频管理器
|
||||
*/
|
||||
export default class AudioManager {
|
||||
constructor() {
|
||||
this.bgm = null;
|
||||
this.sfx = {};
|
||||
this.isMuted = false;
|
||||
this.bgmVolume = 0.5;
|
||||
this.sfxVolume = 0.8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放背景音乐
|
||||
*/
|
||||
playBGM(src) {
|
||||
if (this.isMuted || !src) return;
|
||||
|
||||
// 停止当前BGM
|
||||
this.stopBGM();
|
||||
|
||||
// 创建新的音频实例
|
||||
this.bgm = wx.createInnerAudioContext();
|
||||
this.bgm.src = src;
|
||||
this.bgm.loop = true;
|
||||
this.bgm.volume = this.bgmVolume;
|
||||
this.bgm.play();
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止背景音乐
|
||||
*/
|
||||
stopBGM() {
|
||||
if (this.bgm) {
|
||||
this.bgm.stop();
|
||||
this.bgm.destroy();
|
||||
this.bgm = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 暂停背景音乐
|
||||
*/
|
||||
pauseBGM() {
|
||||
if (this.bgm) {
|
||||
this.bgm.pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复背景音乐
|
||||
*/
|
||||
resumeBGM() {
|
||||
if (this.bgm && !this.isMuted) {
|
||||
this.bgm.play();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放音效
|
||||
*/
|
||||
playSFX(name, src) {
|
||||
if (this.isMuted || !src) return;
|
||||
|
||||
// 复用或创建音效实例
|
||||
if (!this.sfx[name]) {
|
||||
this.sfx[name] = wx.createInnerAudioContext();
|
||||
this.sfx[name].src = src;
|
||||
}
|
||||
|
||||
this.sfx[name].volume = this.sfxVolume;
|
||||
this.sfx[name].seek(0);
|
||||
this.sfx[name].play();
|
||||
}
|
||||
|
||||
/**
|
||||
* 播放点击音效
|
||||
*/
|
||||
playClick() {
|
||||
// 可以配置点击音效
|
||||
// this.playSFX('click', 'audio/click.mp3');
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置静音
|
||||
*/
|
||||
setMute(muted) {
|
||||
this.isMuted = muted;
|
||||
if (muted) {
|
||||
this.pauseBGM();
|
||||
} else {
|
||||
this.resumeBGM();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 切换静音状态
|
||||
*/
|
||||
toggleMute() {
|
||||
this.setMute(!this.isMuted);
|
||||
return this.isMuted;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置BGM音量
|
||||
*/
|
||||
setBGMVolume(volume) {
|
||||
this.bgmVolume = Math.max(0, Math.min(1, volume));
|
||||
if (this.bgm) {
|
||||
this.bgm.volume = this.bgmVolume;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置音效音量
|
||||
*/
|
||||
setSFXVolume(volume) {
|
||||
this.sfxVolume = Math.max(0, Math.min(1, volume));
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁所有音频
|
||||
*/
|
||||
destroy() {
|
||||
this.stopBGM();
|
||||
Object.values(this.sfx).forEach(audio => {
|
||||
audio.stop();
|
||||
audio.destroy();
|
||||
});
|
||||
this.sfx = {};
|
||||
}
|
||||
}
|
||||
147
client/js/data/StoryManager.js
Normal file
147
client/js/data/StoryManager.js
Normal file
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 故事数据管理器
|
||||
*/
|
||||
import { get, post } from '../utils/http';
|
||||
|
||||
export default class StoryManager {
|
||||
constructor() {
|
||||
this.storyList = [];
|
||||
this.currentStory = null;
|
||||
this.currentNodeKey = 'start';
|
||||
this.categories = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载故事列表
|
||||
*/
|
||||
async loadStoryList(options = {}) {
|
||||
try {
|
||||
this.storyList = await get('/stories', options);
|
||||
return this.storyList;
|
||||
} catch (error) {
|
||||
console.error('加载故事列表失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载热门故事
|
||||
*/
|
||||
async loadHotStories(limit = 10) {
|
||||
try {
|
||||
return await get('/stories/hot', { limit });
|
||||
} catch (error) {
|
||||
console.error('加载热门故事失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载分类列表
|
||||
*/
|
||||
async loadCategories() {
|
||||
try {
|
||||
this.categories = await get('/stories/categories');
|
||||
return this.categories;
|
||||
} catch (error) {
|
||||
console.error('加载分类失败:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载故事详情
|
||||
*/
|
||||
async loadStoryDetail(storyId) {
|
||||
try {
|
||||
this.currentStory = await get(`/stories/${storyId}`);
|
||||
this.currentNodeKey = 'start';
|
||||
|
||||
// 记录游玩次数
|
||||
await post(`/stories/${storyId}/play`);
|
||||
|
||||
return this.currentStory;
|
||||
} catch (error) {
|
||||
console.error('加载故事详情失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前节点
|
||||
*/
|
||||
getCurrentNode() {
|
||||
if (!this.currentStory || !this.currentStory.nodes) return null;
|
||||
return this.currentStory.nodes[this.currentNodeKey];
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择选项,前进到下一个节点
|
||||
*/
|
||||
selectChoice(choiceIndex) {
|
||||
const currentNode = this.getCurrentNode();
|
||||
if (!currentNode || !currentNode.choices || !currentNode.choices[choiceIndex]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const choice = currentNode.choices[choiceIndex];
|
||||
this.currentNodeKey = choice.nextNodeKey;
|
||||
|
||||
return this.getCurrentNode();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查当前节点是否为结局
|
||||
*/
|
||||
isEnding() {
|
||||
const currentNode = this.getCurrentNode();
|
||||
return currentNode && currentNode.is_ending;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取结局信息
|
||||
*/
|
||||
getEndingInfo() {
|
||||
const currentNode = this.getCurrentNode();
|
||||
if (!currentNode || !currentNode.is_ending) return null;
|
||||
|
||||
return {
|
||||
name: currentNode.ending_name,
|
||||
score: currentNode.ending_score,
|
||||
type: currentNode.ending_type,
|
||||
content: currentNode.content
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置故事进度
|
||||
*/
|
||||
resetStory() {
|
||||
this.currentNodeKey = 'start';
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞故事
|
||||
*/
|
||||
async likeStory(like = true) {
|
||||
if (!this.currentStory) return;
|
||||
await post(`/stories/${this.currentStory.id}/like`, { like });
|
||||
}
|
||||
|
||||
/**
|
||||
* AI改写结局
|
||||
*/
|
||||
async rewriteEnding(storyId, ending, prompt) {
|
||||
try {
|
||||
const result = await post(`/stories/${storyId}/rewrite`, {
|
||||
ending_name: ending?.name,
|
||||
ending_content: ending?.content,
|
||||
prompt: prompt
|
||||
});
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('AI改写失败:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
134
client/js/data/UserManager.js
Normal file
134
client/js/data/UserManager.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 用户数据管理器
|
||||
*/
|
||||
import { get, post } from '../utils/http';
|
||||
|
||||
export default class UserManager {
|
||||
constructor() {
|
||||
this.userId = null;
|
||||
this.openid = null;
|
||||
this.nickname = '';
|
||||
this.avatarUrl = '';
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化用户
|
||||
*/
|
||||
async init() {
|
||||
try {
|
||||
// 尝试从本地存储恢复用户信息
|
||||
const cached = wx.getStorageSync('userInfo');
|
||||
if (cached) {
|
||||
this.userId = cached.userId;
|
||||
this.openid = cached.openid;
|
||||
this.nickname = cached.nickname;
|
||||
this.avatarUrl = cached.avatarUrl;
|
||||
this.isLoggedIn = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取登录code
|
||||
const { code } = await this.wxLogin();
|
||||
|
||||
// 调用后端登录接口
|
||||
const result = await post('/user/login', { code });
|
||||
|
||||
this.userId = result.userId;
|
||||
this.openid = result.openid;
|
||||
this.nickname = result.nickname || '游客';
|
||||
this.avatarUrl = result.avatarUrl || '';
|
||||
this.isLoggedIn = true;
|
||||
|
||||
// 缓存用户信息
|
||||
wx.setStorageSync('userInfo', {
|
||||
userId: this.userId,
|
||||
openid: this.openid,
|
||||
nickname: this.nickname,
|
||||
avatarUrl: this.avatarUrl
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('用户初始化失败:', error);
|
||||
// 使用临时身份
|
||||
this.userId = 0;
|
||||
this.nickname = '游客';
|
||||
this.isLoggedIn = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信登录(带超时)
|
||||
*/
|
||||
wxLogin() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('登录超时'));
|
||||
}, 3000);
|
||||
|
||||
wx.login({
|
||||
success: (res) => {
|
||||
clearTimeout(timeout);
|
||||
resolve(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
clearTimeout(timeout);
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户游玩进度
|
||||
*/
|
||||
async getProgress(storyId = null) {
|
||||
if (!this.isLoggedIn) return null;
|
||||
return await get('/user/progress', { userId: this.userId, storyId });
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存用户进度
|
||||
*/
|
||||
async saveProgress(storyId, currentNodeKey, isCompleted = false, endingReached = '') {
|
||||
if (!this.isLoggedIn) return;
|
||||
await post('/user/progress', {
|
||||
userId: this.userId,
|
||||
storyId,
|
||||
currentNodeKey,
|
||||
isCompleted,
|
||||
endingReached
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞故事
|
||||
*/
|
||||
async likeStory(storyId, isLiked) {
|
||||
if (!this.isLoggedIn) return;
|
||||
await post('/user/like', {
|
||||
userId: this.userId,
|
||||
storyId,
|
||||
isLiked
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 收藏故事
|
||||
*/
|
||||
async collectStory(storyId, isCollected) {
|
||||
if (!this.isLoggedIn) return;
|
||||
await post('/user/collect', {
|
||||
userId: this.userId,
|
||||
storyId,
|
||||
isCollected
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取收藏列表
|
||||
*/
|
||||
async getCollections() {
|
||||
if (!this.isLoggedIn) return [];
|
||||
return await get('/user/collections', { userId: this.userId });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user