Commit e3bc91dc authored by Tt's avatar Tt

feat(form): 添加全局图片大小设置并优化鱼生成逻辑

parent 2b477b30
...@@ -115,6 +115,23 @@ ...@@ -115,6 +115,23 @@
</nz-radio-group> </nz-radio-group>
</div> </div>
</div> </div>
<!-- 图片大小设置区 -->
<div class="border-dashed" style="margin: 20px;width: 1000px;">
<div class="word-input-title">
<span style="font-size: 20px;">图片大小设置: </span>
</div>
<div style="margin-top: 10px;">
<nz-radio-group [(ngModel)]="item.imgSize" (ngModelChange)="ngChange()">
<label nz-radio nzValue="big">
<span [style]="{color:item.imgSize=='big' ? '#169BD5':'#000'}">大图片(一屏3个图)</span>
</label>
<label nz-radio nzValue="small">
<span [style]="{color:item.imgSize=='small' ? '#169BD5':'#000'}">小图片(一屏4个图)</span>
</label>
</nz-radio-group>
</div>
</div>
</div> </div>
<div style="padding: 10px;background-color: #fff;display:none"> <div style="padding: 10px;background-color: #fff;display:none">
<!-- 标题区 --> <!-- 标题区 -->
...@@ -154,16 +171,6 @@ ...@@ -154,16 +171,6 @@
</div> </div>
<div> <div>
<span style="font-size: 20px;">图片: </span> <span style="font-size: 20px;">图片: </span>
<div style="margin-top: 5px;">
<nz-radio-group [(ngModel)]="question.imgSize" (ngModelChange)="ngChange()">
<label nz-radio nzValue="big">
<span [style]="{color:question.imgSize=='big' ? '#169BD5':'#000'}">大图片(一屏3个图)</span>
</label>
<label nz-radio nzValue="small">
<span [style]="{color:question.imgSize=='small' ? '#169BD5':'#000'}">小图片(一屏4个图)</span>
</label>
</nz-radio-group>
</div>
<div style="width:300px; margin-top: 10px;"> <div style="width:300px; margin-top: 10px;">
<app-upload-image-with-preview [picUrl]="question.image" <app-upload-image-with-preview [picUrl]="question.image"
(imageUploaded)="onImageUploadSuccess($event,'image',question)"></app-upload-image-with-preview> (imageUploaded)="onImageUploadSuccess($event,'image',question)"></app-upload-image-with-preview>
......
...@@ -21,6 +21,7 @@ export class FormComponent extends ComponentBase implements OnInit, OnChanges, O ...@@ -21,6 +21,7 @@ export class FormComponent extends ComponentBase implements OnInit, OnChanges, O
questionTextAudio: "", questionTextAudio: "",
bgAudio: "", bgAudio: "",
doubleModal: false, // 游戏模式:false为单人模式,true为双人模式 doubleModal: false, // 游戏模式:false为单人模式,true为双人模式
imgSize: 'big', // 统一设置图片大小
questions: [], questions: [],
}; };
...@@ -50,7 +51,6 @@ export class FormComponent extends ComponentBase implements OnInit, OnChanges, O ...@@ -50,7 +51,6 @@ export class FormComponent extends ComponentBase implements OnInit, OnChanges, O
addQuestion() { addQuestion() {
this.item.questions.push({ this.item.questions.push({
type: 'img', type: 'img',
imgSize: 'big', // 默认大图片
audio: '', audio: '',
image: '', image: '',
time: null, time: null,
......
...@@ -43,6 +43,9 @@ export interface FormData { ...@@ -43,6 +43,9 @@ export interface FormData {
bgAudio: string; bgAudio: string;
/** 背景音频名称 */ /** 背景音频名称 */
audioBgName?: string; audioBgName?: string;
/** 图片尺寸 */
imgSize: 'big' | 'small';
/** 问题列表 */ /** 问题列表 */
questions: Question[]; questions: Question[];
/** 类型 */ /** 类型 */
......
...@@ -53,6 +53,7 @@ let defaultFormData: FormData = { ...@@ -53,6 +53,7 @@ let defaultFormData: FormData = {
"doubleModal": false, "doubleModal": false,
"questionText": "", "questionType": "read", "questionTextAudio": "", "questionText": "", "questionType": "read", "questionTextAudio": "",
"bgAudio": "", "bgAudio": "",
imgSize: 'big',
"questions": [ "questions": [
{ {
"type": "txt", "audio": "http://staging-teach.cdn.ireadabc.com/736c616e8b66a3ae03c8e8636ca46f0f.mp3", "text": "", "image": "http://staging-teach.cdn.ireadabc.com/bde25b1b2a7fdf976f17ae80b6a20a25.png", "time": null, "duration": "", "audioName": "", "imgSize": "big" "type": "txt", "audio": "http://staging-teach.cdn.ireadabc.com/736c616e8b66a3ae03c8e8636ca46f0f.mp3", "text": "", "image": "http://staging-teach.cdn.ireadabc.com/bde25b1b2a7fdf976f17ae80b6a20a25.png", "time": null, "duration": "", "audioName": "", "imgSize": "big"
...@@ -348,6 +349,7 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -348,6 +349,7 @@ export class PlayComponent implements OnInit, OnDestroy {
this.clearFishes(); // 清空所有鱼元素 this.clearFishes(); // 清空所有鱼元素
this.m_fishArray = []; // 清空鱼群管理数组 this.m_fishArray = []; // 清空鱼群管理数组
this.m_lastFishTime = 0; // 重置上次生成鱼的时间 this.m_lastFishTime = 0; // 重置上次生成鱼的时间
this.m_fishTimeAccumulator = 0; // 重置鱼生成的累积时间
this.currentFishIndex = 0; // 重置当前鱼索引 this.currentFishIndex = 0; // 重置当前鱼索引
this.clickedFishData = []; // 清空用户点击的鱼的数据 this.clickedFishData = []; // 清空用户点击的鱼的数据
this.touchFish = null; // 清空当前触摸的鱼 this.touchFish = null; // 清空当前触摸的鱼
...@@ -987,9 +989,26 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -987,9 +989,26 @@ export class PlayComponent implements OnInit, OnDestroy {
// 修改鱼的生成逻辑,使用当前阶段的随机问题列表 // 修改鱼的生成逻辑,使用当前阶段的随机问题列表
const type = currentQuestion.questionRandomList.length > 3 ? 'small' : 'big'; const type = this.g_formData.imgSize === 'small' ? 'small' : 'big';
this.m_fishInterval = type === 'big' ? 4700 : 3500;
this.m_lastFishTime = 0; // 重置上次生成鱼的时间,使下一帧立即生成鱼 // 计算鱼从右侧游到左侧所需的时间(毫秒)
// 假设鱼的速度为1像素/帧,帧率约为60fps,则速度约为60像素/秒
// 屏幕宽度加上鱼的宽度,再除以速度,得到鱼游过整个屏幕的时间
const fishSpeed = 1; // 鱼的移动速度
const fishWidth = 374 * this.g_mapScale * (type === 'small' ? 0.7 : 1); // 估计的鱼宽度
const screenWidth = this.g_canvasWidth + fishWidth * 0; // 屏幕宽度加上鱼的宽度
const fishCrossingTime = (screenWidth / fishSpeed) * (1000 / 60); // 鱼游过屏幕所需的时间(毫秒)
// 根据imgSize类型确定最大鱼数量
const maxFishCount = type === 'small' ? 4 : 3;
// 计算合适的鱼生成间隔时间
// 鱼游过屏幕的时间除以最大鱼数量,确保屏幕上最多同时有maxFishCount条鱼
this.m_fishInterval = Math.floor(fishCrossingTime / maxFishCount);
console.log(`鱼类型: ${type}, 最大鱼数: ${maxFishCount}, 鱼生成间隔: ${this.m_fishInterval}ms`);
this.m_fishTimeAccumulator = this.m_fishInterval; // 重置累积时间,使下一帧立即生成鱼
// 每一个stage开始的时候播放一次正确问题的音频 // 每一个stage开始的时候播放一次正确问题的音频
if (currentQuestion.correctQuestion && currentQuestion.correctQuestion.audio) { if (currentQuestion.correctQuestion && currentQuestion.correctQuestion.audio) {
...@@ -1213,6 +1232,7 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -1213,6 +1232,7 @@ export class PlayComponent implements OnInit, OnDestroy {
} }
createFish(type, data) { createFish(type, data) {
console.log('createFish', type)
const mapScale = this.g_mapScale > 1 ? 1 : this.g_mapScale; const mapScale = this.g_mapScale > 1 ? 1 : this.g_mapScale;
// 创建鱼元素 // 创建鱼元素
const fishId = `fish_${Math.floor(Math.random() * 1000)}_${Date.now()}` const fishId = `fish_${Math.floor(Math.random() * 1000)}_${Date.now()}`
...@@ -2810,14 +2830,45 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2810,14 +2830,45 @@ export class PlayComponent implements OnInit, OnDestroy {
private m_lastFishTime = 0; private m_lastFishTime = 0;
private m_fishInterval = 3000; // 每隔10秒生成一条新鱼 private m_fishInterval = 3000; // 每隔10秒生成一条新鱼
// 添加帧率控制相关属性
private m_targetFPS = 60; // 目标帧率:60帧/秒
private m_frameInterval = 1000 / 60; // 每帧间隔时间(毫秒)
private m_lastFrameTime = 0; // 上一帧的时间戳
/** /**
* 更新游戏画面 * 更新游戏画面
* 负责清除画布、更新动画和渲染所有元素 * 负责清除画布、更新动画和渲染所有元素
* 控制帧率为60帧/秒,避免因显示频率144Hz导致刷新过快
*/ */
update() { update() {
this.g_animationId && window.cancelAnimationFrame(this.g_animationId); this.g_animationId && window.cancelAnimationFrame(this.g_animationId);
// 请求下一帧动画,形成动画循环 // 请求下一帧动画,形成动画循环
this.g_animationId = window.requestAnimationFrame(this.update.bind(this)); this.g_animationId = window.requestAnimationFrame(this.update.bind(this));
// 帧率控制:计算当前时间与上一帧时间的差值
const currentTime = Date.now();
const elapsed = currentTime - this.m_lastFrameTime;
// 计算时间增量(秒)
const dt = elapsed / 1000;
// 如果经过的时间小于帧间隔,则跳过本次更新
if (elapsed < this.m_frameInterval) {
return;
}
// 如果时间间隔过大(大于0.1秒),说明可能是后台运行或性能问题,限制最大时间增量
// 避免因为大的时间间隔导致渲染异常
if (dt > 0.1) {
console.log('时间间隔过大,限制更新:', dt, '秒');
// 更新时间戳但不执行渲染
this.m_lastFrameTime = currentTime;
return;
}
// 更新上一帧时间(考虑实际执行时间的偏差)
this.m_lastFrameTime = currentTime - (elapsed % this.m_frameInterval);
// 清除画布内容,准备绘制新的一帧 // 清除画布内容,准备绘制新的一帧
this.g_ctx.clearRect(0, 0, this.g_canvasWidth, this.g_canvasHeight); this.g_ctx.clearRect(0, 0, this.g_canvasWidth, this.g_canvasHeight);
// 更新TWEEN动画系统 // 更新TWEEN动画系统
...@@ -2828,8 +2879,8 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2828,8 +2879,8 @@ export class PlayComponent implements OnInit, OnDestroy {
this.updateArr(this.m_endPageArr); this.updateArr(this.m_endPageArr);
// 更新元素花瓣效果数组中的所有元素 // 更新元素花瓣效果数组中的所有元素
this.updateArr(this.m_elementPetalArr); this.updateArr(this.m_elementPetalArr);
// 更新鱼的运动 // 更新鱼的运动,传入时间增量dt
this.updateFish(); this.updateFish(dt);
// 如果游戏已开始,更新时间显示 // 如果游戏已开始,更新时间显示
if (this.isGameStart && this.g_formData.doubleModal) { if (this.isGameStart && this.g_formData.doubleModal) {
...@@ -2880,11 +2931,14 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2880,11 +2931,14 @@ export class PlayComponent implements OnInit, OnDestroy {
/** /**
* 更新鱼的运动 * 更新鱼的运动
* 管理鱼从右侧游到左侧的动画效果 * 管理鱼从右侧游到左侧的动画效果
* @param dt 时间增量(秒),用于计算鱼的移动距离和生成间隔
*/ */
private isGameStart = false; private isGameStart = false;
private currentFishIndex = 0; private currentFishIndex = 0;
updateFish() { private m_fishTimeAccumulator = 0; // 累积的时间,用于鱼的生成
updateFish(dt: number = 0) {
// 如果没有游戏阶段或当前阶段索引超出范围,不生成鱼 // 如果没有游戏阶段或当前阶段索引超出范围,不生成鱼
if (!this.gameStages || this.currentStageIndex >= this.gameStages.length) { if (!this.gameStages || this.currentStageIndex >= this.gameStages.length) {
return; return;
...@@ -2894,10 +2948,11 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2894,10 +2948,11 @@ export class PlayComponent implements OnInit, OnDestroy {
// 获取当前游戏阶段 // 获取当前游戏阶段
const currentStage = this.gameStages[this.currentStageIndex]; const currentStage = this.gameStages[this.currentStageIndex];
// 检查是否需要创建新的鱼 // 累积时间,用于鱼的生成
const currentTime = Date.now(); this.m_fishTimeAccumulator += dt * 1000; // 转换为毫秒
const mapScale = this.g_mapScale < 1 ? 1 : this.g_mapScale;
if (currentTime - this.m_lastFishTime > this.m_fishInterval) { // 使用累积的时间来控制鱼的生成频率,从而间接控制屏幕上的鱼的数量
if (this.m_fishTimeAccumulator >= this.m_fishInterval) {
// 创建新的鱼 // 创建新的鱼
if (currentStage && currentStage.questionRandomList && currentStage.questionRandomList.length > 0) { if (currentStage && currentStage.questionRandomList && currentStage.questionRandomList.length > 0) {
// 从当前阶段的随机问题列表中选择一个问题数据 // 从当前阶段的随机问题列表中选择一个问题数据
...@@ -2906,7 +2961,8 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2906,7 +2961,8 @@ export class PlayComponent implements OnInit, OnDestroy {
const fishData = currentStage.questionRandomList[randomIndex]; const fishData = currentStage.questionRandomList[randomIndex];
this.currentFishIndex++; this.currentFishIndex++;
const type = currentStage.questionRandomList.length > 3 ? 'small' : 'big'; const type = this.g_formData.imgSize == 'small' ? 'small' : 'big';
// 创建小型鱼 // 创建小型鱼
const fish = this.createFish(type, fishData); const fish = this.createFish(type, fishData);
...@@ -2918,7 +2974,7 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2918,7 +2974,7 @@ export class PlayComponent implements OnInit, OnDestroy {
fish.ref.y = (this.currentFishIndex % 2 == 0 ? 400 : 300) * this.g_mapScale; // Math.random() * (this.g_canvasHeight * 0.35) + this.g_canvasHeight * 0.25; // 在屏幕中间区域随机高度 fish.ref.y = (this.currentFishIndex % 2 == 0 ? 400 : 300) * this.g_mapScale; // Math.random() * (this.g_canvasHeight * 0.35) + this.g_canvasHeight * 0.25; // 在屏幕中间区域随机高度
// 设置鱼的游动速度 // 设置鱼的游动速度
fish.speed = 0.45 * 2 * this.g_mapScale; fish.speed = 1;
// 确保鱼已添加到渲染队列中 // 确保鱼已添加到渲染队列中
// createFish 方法中已经调用了 render 方法,但为了确保,这里再次检查 // createFish 方法中已经调用了 render 方法,但为了确保,这里再次检查
...@@ -2930,8 +2986,8 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2930,8 +2986,8 @@ export class PlayComponent implements OnInit, OnDestroy {
// 将鱼添加到管理数组 // 将鱼添加到管理数组
this.m_fishArray.push(fish); this.m_fishArray.push(fish);
// 更新最后创建鱼的时间 // 重置累积时间(减去间隔时间,保留余数,避免时间损失)
this.m_lastFishTime = currentTime; this.m_fishTimeAccumulator -= this.m_fishInterval;
} }
} }
...@@ -2939,8 +2995,13 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -2939,8 +2995,13 @@ export class PlayComponent implements OnInit, OnDestroy {
for (let i = this.m_fishArray.length - 1; i >= 0; i--) { for (let i = this.m_fishArray.length - 1; i >= 0; i--) {
const fish = this.m_fishArray[i]; const fish = this.m_fishArray[i];
// 基于时间增量dt计算鱼的移动距离,确保在不同帧率下移动速度一致
// 鱼的基础速度是每秒60像素(1像素/帧 * 60帧/秒)
const baseSpeed = 60; // 每秒移动的像素数
const moveDistance = baseSpeed * dt * fish.speed;
// 向左移动鱼 // 向左移动鱼
fish.ref.x -= fish.speed; fish.ref.x -= moveDistance;
// 如果鱼游出了屏幕左侧,从数组中移除 // 如果鱼游出了屏幕左侧,从数组中移除
if (fish.ref.x < -fish.ref.width * fish.ref.scaleX / 2) { if (fish.ref.x < -fish.ref.width * fish.ref.scaleX / 2) {
...@@ -3343,6 +3404,10 @@ export class PlayComponent implements OnInit, OnDestroy { ...@@ -3343,6 +3404,10 @@ export class PlayComponent implements OnInit, OnDestroy {
// 初始化舞台 // 初始化舞台
this.initStage(); this.initStage();
// 初始化帧率控制的时间戳
this.m_lastFrameTime = Date.now();
// 更新画面渲染 // 更新画面渲染
this.update(); this.update();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment