提问人:septimus 提问时间:11/17/2023 最后编辑:septimus 更新时间:11/17/2023 访问量:15
使多人绘图游戏画布具有响应性
Make multiplayer drawing game canvas responsive
问:
我目前正在使用 react(客户端)、nestjs(后端)和 WebSockets(就像 https://skribbl.io/)构建一个多人绘图游戏。只要画布元素保持在固定的高度 (800x600) 上,我的绘图逻辑就可以正常工作。 但我希望能够使其响应,以便纵横比仍然是 800x600 的纵横比,但如果需要,或者如果有人调整窗口大小或拥有小型设备,画布可以更大/更小。
这会导致我的绘图逻辑出现问题,如果画布的大小不是 800x600,我的鼠标指针就不是我的线条绘制的位置。
我制作了一个小视频,在 youtube 上展示了这个问题:https://youtu.be/DhLzhbDCDII
我的代码目前是这个(前端,React)DrawingCanvas.tsx:
import React, { useEffect, useState } from 'react';
import { useDraw } from '../hooks/useDraw';
import {
Draw,
DrawingCanvasProps,
DrawLineProps,
} from '../interfaces/draw.interface';
import { ChromePicker } from 'react-color';
import { Button } from 'react-bootstrap';
import { drawLine } from '../utils/drawLine';
import {
SOCKET_EVENT_CANVAS_STATE,
SOCKET_EVENT_CANVAS_STATE_FROM_SERVER,
SOCKET_EVENT_CLEAR_CANVAS,
SOCKET_EVENT_CLIENT_READY,
SOCKET_EVENT_DRAW_LINE,
SOCKET_EVENT_GET_CANVAS_STATE,
} from '../config/socket.config';
export const DrawingCanvas: React.FC<DrawingCanvasProps> = ({ socket }) => {
const { canvasRef, onMouseDown, clearCanvas } = useDraw(createLine);
const [color, setColor] = useState<string>('#000');
useEffect(() => {
const ctx = canvasRef.current?.getContext('2d');
socket.emit(SOCKET_EVENT_CLIENT_READY);
socket.on(SOCKET_EVENT_GET_CANVAS_STATE, () => {
if (!canvasRef.current?.toDataURL()) {
return;
}
socket.emit(SOCKET_EVENT_CANVAS_STATE, canvasRef.current.toDataURL());
});
socket.on(SOCKET_EVENT_CANVAS_STATE_FROM_SERVER, (state: string) => {
const img = new Image();
img.src = state;
img.onload = () => {
ctx?.drawImage(img, 0, 0);
};
});
socket.on(
SOCKET_EVENT_DRAW_LINE,
({ previousPoint, currentPoint, color }: DrawLineProps) => {
if (!ctx) {
return;
}
drawLine({ previousPoint, currentPoint, ctx, color });
},
);
socket.on(SOCKET_EVENT_CLEAR_CANVAS, clearCanvas);
// cleanup like with event listeners
return () => {
socket.off(SOCKET_EVENT_GET_CANVAS_STATE);
socket.off(SOCKET_EVENT_CANVAS_STATE_FROM_SERVER);
socket.off(SOCKET_EVENT_DRAW_LINE);
socket.off(SOCKET_EVENT_CLEAR_CANVAS);
};
}, [canvasRef, socket]);
function createLine({ previousPoint, currentPoint, ctx }: Draw) {
socket.emit('draw-line', { previousPoint, currentPoint, color });
drawLine({ previousPoint, currentPoint, ctx, color });
}
return (
<div className="container d-flex justify-content-center align-items-center">
<div className="d-flex flex-column gap-4 px-4">
<ChromePicker color={color} onChange={(e) => setColor(e.hex)} />
<Button
onClick={() => socket.emit(SOCKET_EVENT_CLEAR_CANVAS)}
className="p-2 rounded border border-black"
variant="secondary"
>
Reset
</Button>
</div>
<canvas
style={{ width: '100%', height: 'auto' }} // make it responsive
width={800}
height={600}
className="border border-black rounded"
ref={canvasRef}
onMouseDown={onMouseDown}
></canvas>
</div>
);
};
我还有一个用于绘图的自定义钩子:UseDraw.ts
import { useEffect, useRef, useState } from 'react';
import { Draw, Point } from '../interfaces/draw.interface';
export const useDraw = (
onDraw: ({ ctx, currentPoint, previousPoint }: Draw) => void,
) => {
const [mouseDown, setMouseDown] = useState(false);
const canvasRef = useRef<HTMLCanvasElement>(null);
const previousPoint = useRef<null | Point>(null);
const onMouseDown = () => setMouseDown(true);
const clearCanvas = () => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const ctx = canvas.getContext('2d');
if (!ctx) {
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
};
useEffect(() => {
const handler = (e: MouseEvent) => {
if (!mouseDown) {
return;
}
const currentPoint = computePointInCanvas(e);
const ctx = canvasRef.current?.getContext('2d');
if (!ctx || !currentPoint) {
return;
}
onDraw({ ctx, currentPoint, previousPoint: previousPoint.current });
previousPoint.current = currentPoint;
};
const computePointInCanvas = (e: MouseEvent) => {
const canvas = canvasRef.current;
if (!canvas) {
return;
}
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
return { x, y };
};
const mouseUpHandler = () => {
setMouseDown(false);
previousPoint.current = null; // reset the previous point
};
// add event listeners
canvasRef.current?.addEventListener('mousemove', handler);
window.addEventListener('mouseup', mouseUpHandler);
// remove event listeners
return () => {
canvasRef.current?.removeEventListener('mousemove', handler);
window.removeEventListener('mouseup', mouseUpHandler);
};
}, [onDraw]);
return { canvasRef, onMouseDown, clearCanvas };
};
还有一个小的辅助函数:drawLine.ts
import { DrawLineProps } from '../interfaces/draw.interface';
export const drawLine = ({
previousPoint,
currentPoint,
ctx,
color,
}: DrawLineProps) => {
const { x: currentX, y: currentY } = currentPoint;
const lineColor = color;
const lineWidth = 5;
let startPoint = previousPoint ?? currentPoint;
ctx.beginPath();
ctx.lineWidth = lineWidth;
ctx.strokeStyle = lineColor;
ctx.moveTo(startPoint.x, startPoint.y);
ctx.lineTo(currentX, currentY);
ctx.stroke();
ctx.fillStyle = lineColor;
ctx.beginPath();
ctx.arc(startPoint.x, startPoint.y, 2, 0, 2 * Math.PI);
ctx.fill();
};
在后端,我在 nestjs 中定义了一个网关,用于向我的客户端发送绘图坐标:
@SubscribeMessage('draw-line')
handleDrawLine(client: Socket, data: DrawLine): void {
const player = this.roomService.getPlayerById(client.id);
if (player) {
const room = this.roomService
.getRooms()
.find((r) => r.players.includes(player));
if (room) {
this.emitToRoomClients(room, 'draw-line', data);
}
}
}
DrawLine - 接口包含以下属性:
export interface DrawLine {
previousPoint: Point | null;
currentPoint: Point;
color: string;
}
export interface Point {
x: number;
y: number;
}
我已经尝试通过调整当前画布大小和高度的点来解决问题,但这不起作用。似乎没有任何效果,几天来我一直在努力让它发挥作用。
答: 暂无答案
上一个:如何隐藏特定标签和与之相关的数据
评论