123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965 |
- <template>
- <div class="game-container">
- <canvas :id="'canvas' + type" @mousedown="handleMouseDown" @mouseup="handleMouseUp" @touchstart="handleTouchStart"
- @touchend="handleTouchEnd"></canvas>
- <canvas ref="canvasRef" :width="clientObj.width" :height="clientObj.height"
- style="position:absolute;left: 0; top: 0;"></canvas>
- </div>
- </template>
- <script setup name="Basketball" lang="ts">
- import { onMounted, ref, reactive, onBeforeUnmount } from 'vue';
- const { proxy } = getCurrentInstance() as any;
- const router = useRouter();
- const emit = defineEmits(['confirmExit']);
- const canvasRef = ref(null);
- //父值
- const props = defineProps({
- type: {
- type: String,
- default: ""
- },
- currentGameArea: {
- type: String,
- default: ""
- },
- areaStateList: {
- type: Array,
- default: () => []
- },
- });
- const data = reactive<any>({
- clientObj: {},//浏览器对象
- boxes: [],//四个点坐标
- proportion: null,//人框和屏幕比例
- myThrow: 0,//0放下1举投
- direction: null,//跑动
- });
- const { clientObj, boxes, proportion, myThrow, direction } = toRefs(data);
- /**
- * 监听投篮
- */
- watch(
- () => myThrow.value,
- (newData, oldData) => {
- if (oldData == undefined) {
- return false;
- }
- if (gameState.state !== 'play') return;
- console.log("ppp", oldData, newData)
- if (newData == 1) {
- console.log("投篮")
- handleMouseDown();
- setTimeout(() => {
- handleMouseUp();
- }, 100)
- } else {
- }
- },
- { immediate: true }
- );
- /**
- * 监听跑动
- */
- watch(
- () => direction.value,
- (newData, oldData) => {
- nextTick(() => {
- if (newData > oldData) {
- gameState.keyRight = true;
- gameState.keyLeft = false;
- } else {
- gameState.keyLeft = true;
- gameState.keyRight = false;
- }
- });
- },
- { immediate: true }
- );
- /**
- * 初始化
- */
- const getInit = (e: any) => {
- let arr = e.data.result.keypoints;
- let result = [];
- for (let i = 0; i < arr.length; i += 3) {
- result.push(arr.slice(i, i + 2));
- }
- //console.log("result", result)
- // if (boxes.value.length == 0) {
- // // speckText("识别成功");
- // // proxy?.$modal.msgWarning(`识别成功`);
- // let arr = e.data.result.boxes;
- // boxes.value = [{ x: arr[0], y: arr[3] }, { x: arr[0], y: arr[1] }, { x: arr[2], y: arr[1] }, { x: arr[2], y: arr[3] }]
- // proportion.value = (clientObj.value.height / (boxes.value[0].y - boxes.value[1].y)).toFixed(2);
- // }
- getCanvas(result);
- };
- const getCanvas = (result: any) => {
- //过肩或者过鼻子都算投篮
- let leftA = result[6][1];//右肩Y
- let rightA = result[5][1];//左肩Y
- let leftB = result[10][1];//右手Y
- let rightB = result[9][1];//左手Y
- let bizi = result[0][1];//鼻子Y
- if (leftB < leftA || rightB < rightA || leftB < bizi || rightB < bizi) {
- myThrow.value = 1;
- } else {
- myThrow.value = 0;
- }
- const canvas: any = canvasRef.value;
- const ctx = canvas.getContext('2d');
- // 清空整个画布
- ctx.clearRect(0, 0, canvas.width, canvas.height);
- // 保存当前状态
- ctx.save();
- function calculateOffset(a: any, b: any) {
- return {
- x: b.x - a.x,
- y: b.y - a.y
- };
- }
- const pointA = { x: clientObj.value.width / 2, y: clientObj.value.height / 2 };
- const pointB = { x: (boxes.value[2].x + boxes.value[0].x) / 2, y: (boxes.value[3].y + boxes.value[1].y) / 2 };
- const offset = calculateOffset(pointA, pointB);
- ctx.translate(-offset.x, -offset.y);
- // console.log("Canvas分辨率", clientObj.value);
- // console.log("人体图片四点坐标", boxes.value)
- // console.log("Canvas中心", pointA);
- // console.log("人体中心", pointB);
- // console.log("offset", offset)
- // console.log("proportion.value",proportion.value)
- const originalPoints = result;
- // 计算缩放后坐标
- const postData = originalPoints.map((point: any) => {
- const newX = (point[0] - pointB.x) * proportion.value + pointB.x;
- const newY = (point[1] - pointB.y) * proportion.value + pointB.y;
- return [newX, newY];
- });
- // console.log("原始坐标:", originalPoints);
- // console.log("缩放后坐标:", postData);
- direction.value = postData[0][0] - offset.x - (94 / 2);//鼻子X
- //绘制头部
- const point1 = { x: postData[4][0], y: postData[4][1] };
- const point2 = { x: postData[3][0], y: postData[3][1] };
- // 计算椭圆参数
- const centerX = (point1.x + point2.x) / 2; // 椭圆中心X
- const centerY = (point1.y + point2.y) / 2; // 椭圆中心Y
- const distance = Math.sqrt(
- Math.pow(point2.x - point1.x, 2) +
- Math.pow(point2.y - point1.y, 2)
- ); // 两个焦点之间的距离
- const radiusX = distance * 0.5; // 水平半径(可调整)
- const radiusY = distance * 0.6; // 垂直半径(可调整)
- // 1. 绘制填充椭圆
- ctx.beginPath();
- ctx.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, Math.PI * 2);
- ctx.fillStyle = 'red'; // 填充颜色
- ctx.fill(); // 填充
- // 2. 绘制边框
- ctx.strokeStyle = 'red';
- ctx.lineWidth = 5;
- ctx.stroke();
- // 绘制每个点
- postData.forEach((point: any, index: number) => {
- //眼睛鼻子不显示
- if (![0, 1, 2].includes(index)) {
- const [x, y] = point;
- ctx.beginPath();
- ctx.arc(x, y, 5, 0, Math.PI * 2); // 绘制半径为5的圆点
- ctx.fillStyle = 'red';
- ctx.fill();
- ctx.lineWidth = 1;
- ctx.stroke();
- }
- });
- // 根据点关系连线
- const arr = [[10, 8], [8, 6], [6, 5], [5, 7], [7, 9], [6, 12], [5, 11], [12, 11], [12, 14], [14, 16], [11, 13], [13, 15]]
- arr.forEach((point: any) => {
- let index1 = point[0];
- let index2 = point[1];
- //连线
- const dian1 = { x: postData[index1][0], y: postData[index1][1] };
- const dian2 = { x: postData[index2][0], y: postData[index2][1] };
- // 绘制连线
- ctx.beginPath();
- ctx.moveTo(dian1.x, dian1.y); // 起点
- ctx.lineTo(dian2.x, dian2.y); // 终点
- ctx.strokeStyle = 'red'; // 线条颜色
- ctx.lineWidth = 3; // 线条宽度
- ctx.stroke(); // 描边
- });
- ctx.restore(); // 恢复状态
- };
- // 游戏主类的响应式状态
- const gameState = reactive({
- balls: [],
- hoops: [],
- texts: [],
- res: {},
- score: 0,
- started: false,
- gameOver: false,
- ballX: (640 - 93) / 2, // 篮球初始X坐标(居中)
- ballY: 680, // 篮球初始Y坐标(底部)
- ballVel: 300,
- ballAngleVel: 100,
- ballAngle: 0,
- ballsShot: 1,
- ballCharge: 0,
- time: 60,
- toNextSecond: 1,
- sound: false,
- state: 'menu',
- click: false,
- canvas: null,
- ctx: null,
- animationFrameId: null,
- then: 0,
- // 新增:键盘控制相关状态
- keyLeft: false,
- keyRight: false,
- spaceShoot: false, // 空格键投篮状态
- exitTimer: null, // 用于存储退出定时器ID
- bounceHeight: 20, // 拍球高度
- bounceSpeed: 2, // 拍球速度
- bouncePhase: 0, // 拍球相位(0-2π)
- });
- // 篮筐类 - 修改后
- class Hoop {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- this.originalX = x;
- this.speed = 80; // 降低速度,适应更大尺寸
- this.direction = 1;
- // 滑动范围适配更大篮筐
- this.range = (clientObj.value.width - 300) / 2;
- // 碰撞点范围扩大(与视觉尺寸匹配)
- this.points = [
- { x: x + 20, y: y + 30 },
- { x: x + 280, y: y + 30 }
- ];
- // 新增:缩放比例(1.5倍放大)
- this.scale = 1.5;
- }
- update(delta) {
- this.x += this.speed * this.direction * delta;
- // 边界检测调整
- if (this.x > this.originalX + this.range) {
- this.x = this.originalX + this.range;
- this.direction = -1;
- } else if (this.x < this.originalX - this.range) {
- this.x = this.originalX - this.range;
- this.direction = 1;
- }
- // 更新碰撞点(与放大后的尺寸匹配)
- this.points = [
- { x: this.x + 20, y: this.y + 30 },
- { x: this.x + 280, y: this.y + 30 }
- ];
- }
- drawBack(ctx, game) {
- // 关键修改:通过scale参数放大绘制
- ctx.save();
- ctx.translate(this.x, this.y);
- ctx.scale(this.scale, this.scale); // 放大1.5倍
- drawImage(
- ctx,
- game.res['/static/images/basketball/hoop.png'],
- 0, 0, // 绘制起点(相对于缩放中心)
- 0, 0, 148, 22, // 原图裁剪范围
- 0, 0, 0 // 旋转参数
- );
- ctx.restore();
- }
- drawFront(ctx, game) {
- // 同样放大前景
- ctx.save();
- ctx.translate(this.x, this.y + 22 * this.scale); // 适配缩放后的Y位置
- ctx.scale(this.scale, this.scale);
- drawImage(
- ctx,
- game.res['/static/images/basketball/hoop.png'],
- 0, 0,
- 0, 22, 148, 178 - 22, // 原图裁剪范围
- 0, 0, 0
- );
- ctx.restore();
- }
- }
- // 篮球类
- class Ball {
- constructor(x, y) {
- this.x = x;
- this.y = y;
- this.vx = 0;
- this.vy = 0;
- this.speed = 100;
- this.canBounce = true;
- this.angle = 270;
- this.gravity = 0;
- this.falling = false;
- this.bounces = 0;
- this.scored = false;
- this.drawAngle = 0;
- this.angleVel = 150;
- this.solid = false;
- this.z = 1;
- }
- setAngle(angle) {
- this.angle = angle;
- this.vx = this.speed * Math.cos(this.angle * Math.PI / 180);
- this.vy = this.speed * Math.sin(this.angle * Math.PI / 180);
- this.gravity = 0;
- }
- shoot(power) {
- this.speed = power;
- this.setAngle(270);
- }
- update(delta) {
- this.y += this.gravity * delta;
- this.gravity += 10000 * delta;
- this.x += this.vx * delta;
- this.y += this.vy * delta;
- if (this.vx > 500) this.vx = 500;
- if (this.vy > 500) this.vy = 500;
- if (this.y < 300) {
- this.solid = true;
- }
- if (this.gravity > this.speed) {
- this.falling = true;
- }
- if (this.x + 47 > clientObj.value.width) {
- this.vx = this.vx * -1;
- this.x = clientObj.value.width - 47;
- }
- if (this.x - 47 < 0) {
- this.vx = this.vx * -1;
- this.x = 47;
- }
- this.drawAngle += this.angleVel * delta;
- }
- draw(ctx, game) {
- drawImage(
- ctx,
- game.res['/static/images/basketball/ball.png'],
- Math.floor(this.x - (93 / 2)),
- Math.floor(this.y - (93 / 2)),
- 0, 0, 93, 93, 93 / 2, 93 / 2,
- this.drawAngle
- );
- }
- }
- // 弹出文字类
- class PopText {
- constructor(string, x, y) {
- this.string = string;
- this.x = x;
- this.y = y;
- this.vy = -500;
- this.opacity = 1;
- }
- update(delta) {
- this.y += this.vy * delta;
- this.vy += 1000 * delta;
- if (this.vy > 0 && this.opacity > 0) {
- this.opacity -= 2 * delta;
- }
- if (this.opacity <= 0) {
- this.opacity = 0;
- }
- }
- draw(ctx, game) {
- ctx.globalAlpha = this.opacity;
- game.drawText(ctx, this.string, this.x + 15, this.y);
- ctx.globalAlpha = 1;
- }
- }
- // 工具函数:绘制旋转图像
- function drawImage(ctx, image, x, y, sx, sy, w, h, rx, ry, a) {
- ctx.save();
- ctx.translate(x + rx, y + ry);
- ctx.rotate(a * Math.PI / 180);
- ctx.drawImage(image, sx, sy, w, h, -rx, -ry, w, h);
- ctx.restore();
- }
- // 事件处理函数
- const handleMouseDown = () => {
- gameState.click = true;
- };
- const handleMouseUp = () => {
- gameState.click = false;
- };
- const handleTouchStart = () => {
- gameState.click = true;
- };
- const handleTouchEnd = () => {
- gameState.click = false;
- };
- // 键盘事件处理函数
- const handleKeyDown = (event: any) => {
- if (gameState.state !== 'play') return;
- if (event.key === 'ArrowLeft') {
- gameState.keyLeft = true;
- } else if (event.key === 'ArrowRight') {
- gameState.keyRight = true;
- } else if (event.key === ' ') { // 空格键触发投篮
- event.preventDefault(); // 防止页面滚动
- gameState.spaceShoot = true;
- }
- };
- const handleKeyUp = (event: any) => {
- if (event.key === 'ArrowLeft') {
- gameState.keyLeft = false;
- } else if (event.key === 'ArrowRight') {
- gameState.keyRight = false;
- } else if (event.key === ' ') { // 空格键释放
- gameState.spaceShoot = false;
- }
- };
- // 游戏方法
- const setupCanvas = () => {
- gameState.canvas = document.getElementById('canvas' + props.type);
- gameState.canvas.width = clientObj.value.width;
- gameState.canvas.height = clientObj.value.height;
- // gameState.canvas.width = document.documentElement.clientWidth / 2;
- // gameState.canvas.height = document.documentElement.clientHeight;
- gameState.ctx = gameState.canvas.getContext('2d');
- };
- const resizeToWindow = () => {
- // const w = gameState.canvas.width / gameState.canvas.height;
- // const h = window.innerHeight;
- // const ratio = h * w;
- // gameState.canvas.style.width = Math.floor(ratio) + 'px';
- // gameState.canvas.style.height = Math.floor(h) + 'px';
- };
- const drawLoadingScreen = () => {
- const ctx = gameState.ctx;
- ctx.fillStyle = 'black';
- ctx.fillRect(0, 0, 700, clientObj.value.width);
- ctx.textAlign = 'center';
- drawText(ctx, 'Loading...', clientObj.value.width / 2, 700 / 2, 40);
- ctx.textAlign = 'left';
- };
- const getResources = () => {
- const images = [
- '/static/images/basketball/background.png',
- '/static/images/basketball/title.png',
- '/static/images/basketball/ball.png',
- '/static/images/basketball/hoop.png'
- ];
- const sounds = [
- '/static/images/basketball/bounce_1.wav'
- ];
- return gameState.sound ? images.concat(sounds) : images;
- };
- const drawText = (ctx, string, x, y, size = 30) => {
- ctx.font = size + 'px Contrail One';
- ctx.lineWidth = 5;
- ctx.strokeStyle = 'white';
- ctx.strokeText(string, x, y);
- ctx.fillStyle = '#0098BF';
- ctx.fillText(string, x, y);
- };
- const playSound = (name) => {
- if (gameState.sound && gameState.res[name]) {
- gameState.res[name].currentTime = 0;
- gameState.res[name].play().catch(e => console.log('Sound play error:', e));
- }
- };
- const loadResources = () => {
- drawLoadingScreen();
- const resources = getResources();
- let loaded = 0;
- return new Promise((resolve) => {
- resources.forEach(resource => {
- const type = resource.split('.').pop();
- if (type === 'png') {
- const image = new Image();
- image.src = resource;
- image.addEventListener('load', () => {
- loaded++;
- gameState.res[resource] = image;
- if (loaded === resources.length) resolve();
- });
- } else if (['wav', 'mp3'].includes(type)) {
- const sound = new Audio();
- sound.src = resource;
- sound.addEventListener('canplaythrough', () => {
- loaded++;
- gameState.res[resource] = sound;
- if (loaded === resources.length) resolve();
- });
- }
- });
- });
- };
- const gameLoop = (timestamp) => {
- if (!gameState.then) gameState.then = timestamp;
- const delta = (timestamp - gameState.then) / 1000;
- // 更新游戏状态
- update(delta);
- // 绘制游戏
- draw();
- gameState.then = timestamp;
- gameState.animationFrameId = requestAnimationFrame(gameLoop);
- };
- const update = (delta) => {
- if (gameState.state === 'menu') {
- if (gameState.click) {
- gameState.state = 'play';
- gameState.click = false;
- }
- return;
- }
- if (gameState.state === 'play') {
- // // 更新篮球横向移动
- // gameState.ballX += gameState.ballVel * delta;
- // if (gameState.ballX > clientObj.value.width - 93) {
- // gameState.ballVel = -gameState.ballVel;
- // gameState.ballX = clientObj.value.width - 93;
- // }
- // if (gameState.ballX < 0) {
- // gameState.ballVel = -gameState.ballVel;
- // gameState.ballX = 0;
- // }
- // 添加篮筐更新逻辑
- gameState.hoops.forEach(hoop => {
- hoop.update(delta);
- });
- // 键盘控制篮球移动
- if (gameState.keyLeft) {
- //gameState.ballX -= gameState.ballVel * delta;
- gameState.ballX = direction.value;
- }
- if (gameState.keyRight) {
- //gameState.ballX += gameState.ballVel * delta;
- gameState.ballX = direction.value;
- }
- // 边界检查
- if (gameState.ballX > clientObj.value.width - 93) {
- gameState.ballX = clientObj.value.width - 93;
- }
- if (gameState.ballX < 0) {
- gameState.ballX = 0;
- }
- // 更新所有篮球
- for (let i = gameState.balls.length - 1; i >= 0; i--) {
- const ball = gameState.balls[i];
- if (ball.falling) {
- // 检测与篮筐的碰撞
- gameState.hoops.forEach(hoop => {
- // 篮筐中心计算(适配缩放后的尺寸)
- const scaledWidth = 148 * hoop.scale; // 原图宽度*缩放比例
- const cx = hoop.x + scaledWidth / 2;
- const cy = hoop.y + 40 * hoop.scale; // Y轴也适配缩放
- const dx = cx - ball.x;
- const dy = cy - ball.y;
- const mag = Math.sqrt(dx * dx + dy * dy);
- // 碰撞半径增大(适配放大后的篮筐)
- if (mag < 47 + 30 && !ball.scored) { // 从5→30
- ball.setAngle(90);
- gameState.score += 100;
- gameState.texts.push(new PopText('+ 100', hoop.x, hoop.y));
- ball.scored = true;
- }
- if (!ball.scored) {
- hoop.points.forEach(point => {
- const dx = point.x - ball.x;
- const dy = point.y - ball.y;
- const mag = Math.sqrt(dx * dx + dy * dy);
- // 调整碰撞检测阈值
- if (mag > 47 + 30 && !ball.canBounce) {
- ball.canBounce = true;
- }
- if (mag < 47 + 30 && ball.canBounce) {
- playSound('/static/images/basketball/bounce_1.wav');
- ball.bounces++;
- // 其他逻辑保持不变...
- }
- });
- }
- });
- }
- ball.update(delta);
- // 移除超出屏幕的球
- if (ball.y > clientObj.value.height) {
- gameState.ballX = ball.x;
- gameState.balls.splice(i, 1);
- }
- }
- // 更新计时器
- if (gameState.time > 0) {
- gameState.toNextSecond -= delta;
- if (gameState.toNextSecond <= 0) {
- gameState.time--;
- gameState.toNextSecond = 1;
- }
- } else {
- gameState.state = 'over';
- }
- // 处理投篮逻辑(修改后)
- const isShooting = gameState.click || gameState.spaceShoot; // 鼠标或键盘触发投篮
- if (isShooting && gameState.ballY <= clientObj.value.height) {
- if (gameState.balls.length < 1) {
- const ball = new Ball(gameState.ballX + (93 / 2), gameState.ballY);
- ball.drawAngle = gameState.ballAngle;
- // 使用计算好的力度投篮
- const shootPower = calculateShootPower();
- ball.shoot(shootPower);
- gameState.balls.push(ball);
- gameState.ballY = clientObj.value.height - 90;
- }
- }
- // 重置篮球位置
- if (gameState.balls.length < 1 && gameState.ballY > clientObj.value.height) {
- gameState.ballY -= 100 * delta;
- }
- if (!gameState.click) {
- gameState.ballsShot = 0;
- }
- // 更新弹出文字
- gameState.texts.forEach((text, i) => {
- text.update(delta);
- if (text.opacity <= 0) {
- gameState.texts.splice(i, 1);
- }
- });
- // 更新篮球角度
- gameState.ballAngle += 100 * delta;
- }
- // 游戏结束状态处理
- if (gameState.state === 'over' && gameState.click) {
- gameState.gameOver = false;
- gameState.started = false;
- gameState.score = 0;
- gameState.time = 60;
- gameState.balls = [];
- gameState.state = 'menu';
- gameState.click = false;
- }
- };
- /**
- * 计算确保超过篮筐的投篮力度
- * @returns 计算后的投篮力度
- */
- const calculateShootPower = () => {
- // 获取篮筐最高位置(取所有篮筐中最高的y坐标)
- const highestHoopY = Math.min(...gameState.hoops.map(hoop => hoop.y));
- // 计算需要超过篮筐的高度(额外增加200px安全距离)
- const requiredHeightAboveHoop = 200;
- // 计算从当前位置到目标高度的距离
- const distanceToTarget = gameState.ballY - (highestHoopY - requiredHeightAboveHoop);
- // 基础力度系数(可根据实际效果调整)
- const powerCoefficient = 4.3;
- // 最小力度保障(防止过小的屏幕尺寸导致力度不足)
- const minPower = clientObj.value.height;
- // 计算最终力度
- const calculatedPower = distanceToTarget * powerCoefficient;
- // 返回确保超过篮筐的力度(取计算值和最小值中的较大者)
- return Math.max(calculatedPower, minPower);
- };
- const draw = () => {
- const ctx = gameState.ctx;
- if (!ctx) return;
- // 绘制背景
- if (gameState.res['/static/images/basketball/background.png']) {
- const img = gameState.res['/static/images/basketball/background.png'];
- const canvasWidth = gameState.canvas.width;
- const canvasHeight = gameState.canvas.height;
- // 计算图片与画布的比例
- const imgRatio = img.width / img.height;
- const canvasRatio = canvasWidth / canvasHeight;
- let drawWidth, drawHeight, drawX = 0, drawY = 0;
- // 根据比例计算绘制尺寸
- if (canvasRatio > imgRatio) {
- // 画布更宽,按宽度缩放,高度可能超出
- drawWidth = canvasWidth;
- drawHeight = canvasWidth / imgRatio;
- drawY = (canvasHeight - drawHeight) / 2; // 垂直居中
- } else {
- // 画布更高,按高度缩放,宽度可能超出
- drawHeight = canvasHeight;
- drawWidth = canvasHeight * imgRatio;
- drawX = (canvasWidth - drawWidth) / 2; // 水平居中
- }
- // 绘制图片(可能裁剪边缘)
- ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight);
- }
- // 绘制菜单状态
- if (gameState.state === 'menu') {
- if (gameState.res['/static/images/basketball/title.png']) {
- ctx.drawImage(
- gameState.res['/static/images/basketball/title.png'],
- clientObj.value.width / 2 - (492 / 2),
- 100
- );
- }
- ctx.textAlign = 'center';
- drawText(ctx, '请做投篮动作开始游戏', clientObj.value.width / 2, 520, 40);
- }
- // 绘制游戏状态
- if (gameState.state === 'play') {
- // 绘制篮筐背景
- gameState.hoops.forEach(hoop => hoop.drawBack(ctx, gameState));
- // 绘制正在下落的球
- gameState.balls.forEach(ball => {
- if (ball.falling) ball.draw(ctx, gameState);
- });
- // 绘制篮筐前景
- gameState.hoops.forEach(hoop => hoop.drawFront(ctx, gameState));
- // 绘制未下落的球
- gameState.balls.forEach(ball => {
- if (!ball.falling) ball.draw(ctx, gameState);
- });
- // 绘制当前可投掷的球
- if (gameState.balls.length < 1 && gameState.res['/static/images/basketball/ball.png']) {
- drawImage(
- ctx,
- gameState.res['/static/images/basketball/ball.png'],
- gameState.ballX,
- gameState.ballY,
- 0, 0, 93, 93,
- 45, 45,
- gameState.ballAngle
- );
- }
- // 绘制分数和时间
- // 更靠左的得分
- drawText(ctx, '得分: ' + gameState.score, 150, 70, 50);
- // 更靠右的时间
- drawText(ctx, '时间: ' + gameState.time, clientObj.value.width - 115, 70, 50);
- // 绘制弹出文字
- gameState.texts.forEach(text => text.draw(ctx, { drawText }));
- }
- // 绘制游戏结束界面
- if (gameState.state === 'over') {
- ctx.textAlign = 'center';
- drawText(ctx, 'Game Over', clientObj.value.width / 2, 200, 80);
- drawText(ctx, '成绩:' + gameState.score, clientObj.value.width / 2, 400, 50);
- ctx.textAlign = 'center';
- // 游戏结束后2秒自动退出
- if (!gameState.exitTimer) {
- gameState.exitTimer = setTimeout(() => {
- // 退出游戏,这里可以根据实际需求调整,比如返回主菜单或跳转到其他页面
- gameState.gameOver = false;
- gameState.started = false;
- gameState.score = 0;
- gameState.time = 60;
- gameState.balls = [];
- //gameState.state = 'menu';
- gameState.click = false;
- gameState.exitTimer = null;
- // 如果需要跳转到其他页面,可以使用路由
- // router.push('/');
- emit('confirmExit', { type: 2, area: props.currentGameArea });
- }, 500);
- }
- // 保留点击退出的功能
- if (gameState.click) {
- clearTimeout(gameState.exitTimer);
- gameState.gameOver = false;
- gameState.started = false;
- gameState.score = 0;
- gameState.time = 60;
- gameState.balls = [];
- gameState.state = 'menu';
- gameState.click = false;
- gameState.exitTimer = null;
- }
- }
- };
- // 初始化游戏
- const initGame = async () => {
- setupCanvas();
- resizeToWindow();
- window.addEventListener('resize', resizeToWindow);
- // 添加键盘事件监听
- window.addEventListener('keydown', handleKeyDown);
- window.addEventListener('keyup', handleKeyUp);
- await loadResources();
- // 添加篮筐
- gameState.hoops = [
- //new Hoop(110, 300),
- // 计算居中位置(考虑放大后的宽度148*1.5≈222)
- new Hoop((clientObj.value.width - 222) / 2, 150),
- //new Hoop(clientObj.value.width - 148 - 110, 300),
- ];
- // 开始游戏循环
- gameState.animationFrameId = requestAnimationFrame(gameLoop);
- gameState.ballY = clientObj.value.height - 90;
- setTimeout(() => {
- handleMouseDown();
- setTimeout(() => {
- handleMouseUp();
- }, 100)
- }, 1000)
- };
- onBeforeMount(() => {
- });
- // 生命周期钩子
- onMounted(() => {
- clientObj.value = {
- width: document.querySelector('.game-container').offsetWidth,
- height: document.querySelector('.game-container').offsetHeight,
- }
- let obj: any = props.areaStateList.find((item: any) => {
- return item.area == props.currentGameArea
- })
- boxes.value = obj.boxes;
- proportion.value = (clientObj.value.height / (boxes.value[0].y - boxes.value[1].y)).toFixed(2);
- initGame();
- });
- onBeforeUnmount(() => {
- if (gameState.animationFrameId) {
- cancelAnimationFrame(gameState.animationFrameId);
- }
- // 清除退出定时器
- if (gameState.exitTimer) {
- clearTimeout(gameState.exitTimer);
- }
- window.removeEventListener('resize', resizeToWindow);
- window.removeEventListener('keydown', handleKeyDown);
- window.removeEventListener('keyup', handleKeyUp);
- });
- //暴露给父组件用
- defineExpose({
- getInit
- })
- </script>
- <style scoped>
- .game-container {
- width: 100%;
- height: 100vh;
- display: flex;
- justify-content: center;
- align-items: center;
- background: #000000;
- overflow: hidden;
- position: relative;
- }
- #canvas {
- display: block;
- touch-action: none;
- user-select: none;
- }
- </style>
|