HTML实现钓鱼游戏:物理钩子摆动效果开发指南
一、游戏核心逻辑概述
钓鱼游戏的核心交互包含三个部分:场景渲染、钩子物理模拟、碰撞检测。其中物理钩子的摆动效果需要模拟真实摆锤的运动规律,结合HTML5 Canvas的绘图能力和JavaScript的动画逻辑实现。
二、基础场景搭建
首先创建游戏画布和基础的场景元素,包括水面、鱼竿、初始状态的钩子:
// 初始化画布
const canvas = document.getElementById('fishingCanvas');
const ctx = canvas.getContext('2d');
canvas.width = 800;
canvas.height = 600;
// 基础场景参数
const scene = {
waterLevel: 400, // 水面Y坐标
rodBaseX: 100, // 鱼竿底部X坐标
rodBaseY: 100, // 鱼竿底部Y坐标
rodLength: 200 // 鱼竿长度
};三、物理钩子摆动实现
钩子的摆动可以简化为一维单摆运动,核心参数包括摆长、摆角、角速度、重力加速度。单摆的运动公式如下:
角加速度 = - (重力加速度 / 摆长) * sin(当前摆角)
通过每帧更新角速度、摆角,再转换为钩子的实际坐标,即可实现自然摆动效果:
// 钩子物理参数
const hook = {
length: 150, // 钩子摆长(鱼线长度)
angle: 0, // 当前摆角(弧度)
angularVelocity: 0,// 角速度
gravity: 9.8, // 重力加速度
damping: 0.98, // 阻尼系数,模拟空气阻力
x: 0, // 钩子实际X坐标
y: 0 // 钩子实际Y坐标
};
// 更新钩子物理状态
function updateHookPhysics() {
// 计算角加速度
const angularAcceleration = - (hook.gravity / hook.length) * Math.sin(hook.angle);
// 更新角速度(加入阻尼)
hook.angularVelocity += angularAcceleration * 0.016; // 假设每帧16ms
hook.angularVelocity *= hook.damping;
// 更新摆角
hook.angle += hook.angularVelocity;
// 计算钩子坐标:以鱼竿顶端为摆动中心
const rodTopX = scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4); // 鱼竿顶端X
const rodTopY = scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4); // 鱼竿顶端Y
hook.x = rodTopX + hook.length * Math.sin(hook.angle);
hook.y = rodTopY + hook.length * Math.cos(hook.angle);
}四、绘制与动画循环
结合物理计算的结果,在画布上绘制鱼竿、鱼线、钩子,并通过requestAnimationFrame实现动画循环:
// 绘制场景元素
function drawScene() {
// 清空画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 绘制水面
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, scene.waterLevel, canvas.width, canvas.height - scene.waterLevel);
// 绘制鱼竿
ctx.beginPath();
ctx.moveTo(scene.rodBaseX, scene.rodBaseY);
ctx.lineTo(
scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4),
scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4)
);
ctx.lineWidth = 5;
ctx.strokeStyle = '#8B4513';
ctx.stroke();
// 绘制鱼线
const rodTopX = scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4);
const rodTopY = scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4);
ctx.beginPath();
ctx.moveTo(rodTopX, rodTopY);
ctx.lineTo(hook.x, hook.y);
ctx.lineWidth = 1;
ctx.strokeStyle = '#333';
ctx.stroke();
// 绘制钩子
ctx.beginPath();
ctx.arc(hook.x, hook.y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#FFD700';
ctx.fill();
}
// 动画主循环
function gameLoop() {
updateHookPhysics();
drawScene();
requestAnimationFrame(gameLoop);
}
// 启动游戏
gameLoop();五、交互扩展
可以通过监听鼠标或触摸事件,给钩子施加外力改变摆动状态,模拟玩家操作鱼竿的效果:
// 监听鼠标点击,给钩子施加瞬时力
canvas.addEventListener('mousedown', () => {
hook.angularVelocity += 0.5; // 点击时增加角速度,改变摆动幅度
});六、注意事项
物理参数中的时间步长需要和动画帧率匹配,避免摆动速度异常
阻尼系数可以根据需求调整,数值越小阻力越大,摆动停止越快
如果需要更真实的物理效果,可以加入鱼线弹性、水流阻力等额外参数
开发时如果需要参考外部资源,可以访问示例地址https://www.ipipp.com查看相关Canvas动画案例
七、完整示例代码
以下是整合所有功能的完整可运行代码,保存为HTML文件即可在浏览器中查看效果:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>钓鱼游戏物理钩子演示</title>
</head>
<body>
<canvas id="fishingCanvas" width="800" height="600" style="border:1px solid #ccc"></canvas>
<script>
const canvas = document.getElementById('fishingCanvas');
const ctx = canvas.getContext('2d');
const scene = {
waterLevel: 400,
rodBaseX: 100,
rodBaseY: 100,
rodLength: 200
};
const hook = {
length: 150,
angle: 0,
angularVelocity: 0.1,
gravity: 9.8,
damping: 0.98,
x: 0,
y: 0
};
function updateHookPhysics() {
const angularAcceleration = - (hook.gravity / hook.length) * Math.sin(hook.angle);
hook.angularVelocity += angularAcceleration * 0.016;
hook.angularVelocity *= hook.damping;
hook.angle += hook.angularVelocity;
const rodTopX = scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4);
const rodTopY = scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4);
hook.x = rodTopX + hook.length * Math.sin(hook.angle);
hook.y = rodTopY + hook.length * Math.cos(hook.angle);
}
function drawScene() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#87CEEB';
ctx.fillRect(0, scene.waterLevel, canvas.width, canvas.height - scene.waterLevel);
ctx.beginPath();
ctx.moveTo(scene.rodBaseX, scene.rodBaseY);
ctx.lineTo(
scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4),
scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4)
);
ctx.lineWidth = 5;
ctx.strokeStyle = '#8B4513';
ctx.stroke();
const rodTopX = scene.rodBaseX + scene.rodLength * Math.cos(Math.PI/4);
const rodTopY = scene.rodBaseY + scene.rodLength * Math.sin(Math.PI/4);
ctx.beginPath();
ctx.moveTo(rodTopX, rodTopY);
ctx.lineTo(hook.x, hook.y);
ctx.lineWidth = 1;
ctx.strokeStyle = '#333';
ctx.stroke();
ctx.beginPath();
ctx.arc(hook.x, hook.y, 5, 0, Math.PI * 2);
ctx.fillStyle = '#FFD700';
ctx.fill();
}
function gameLoop() {
updateHookPhysics();
drawScene();
requestAnimationFrame(gameLoop);
}
canvas.addEventListener('mousedown', () => {
hook.angularVelocity += 0.5;
});
gameLoop();
</script>
</body>
</html>