console
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>bezierCurveTo</title>
<style>
.canvas{
display: inline-block;
float: left;
width: 300px;
margin: 10px;
}
canvas {
border:1px solid #d3d3d3;
}
</style>
</head>
<body>
<div class="canvas">
<div>------------射箭游戏-----------</div>
<canvas id="myCanvas" width="900" height="300"></canvas>
</div>
<script>
// 射箭游戏
class ShotAnArrow {
constructor(id){
let idNode = document.getElementById(id);
this.id = idNode;
this.width = idNode.width;
this.height = idNode.height;
this.ctx = idNode.getContext('2d');
this.top = idNode.getBoundingClientRect().top;
this.left = idNode.getBoundingClientRect().left;
this.startX = 700;
this.startY = 100;
this.startText = '';
this.endX = 700;
this.endY = 200;
this.endText = '';
this.controlX = 700;
this.controlY = 150;
this.controlText = '';
this.pointName = '';
// 箭的属性值
this.arrowEndX = 700;
this.arrowEndY = 150;
this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100;
this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100;
this.fly = false; // 箭头是否释放
this.angle = 0;
// 分数面板属性
this.score = 0;
this.mouseupfn = this.mouseupfn.bind(this);
this.mousemovefn = this.mousemovefn.bind(this);
this.mousedownfn = this.mousedownfn.bind(this);
this.getMousePos = this.getMousePos.bind(this);
this.sampleMousemovefn = this.sampleMousemovefn.bind(this);
}
init() {
this.ctx.clearRect(0,0,this.width,this.height);
this.drawLine();
this.drawTarget();
this.drawthreeArc();
this.dragFn();
this.drawBow();
this.drawArrow();
this.drawEnergySolid();
this.drawScore();
}
// 绘制贝塞尔曲线 以及控制点到贝塞尔曲线两端的直线
drawLine() {
let {startX, startY, endX, endY, controlX, controlY} = this;
this.ctx.beginPath();
this.ctx.strokeStyle = 'pink';
this.ctx.lineWidth = 1;
this.ctx.moveTo(startX, startY);
this.ctx.quadraticCurveTo(controlX, controlY, endX, endY);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'pink';
this.ctx.lineWidth = 1;
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(controlX, controlY);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'pink';
this.ctx.lineWidth = 1;
this.ctx.moveTo(endX, endY);
this.ctx.lineTo(controlX, controlY);
this.ctx.stroke();
}
// 控制点增加圆环样式
drawArc(x, y, r, lineWidth) {
this.ctx.beginPath();
this.ctx.strokeStyle = "#999";
this.ctx.lineWidth = 1;
this.ctx.arc(x,y,r,0,2*Math.PI);
// if (lineWidth) this.ctx.lineWidth = lineWidth;
this.ctx.fillText("("+ x +","+ y +")", x + 10, y);
this.ctx.stroke();
}
// 绘制三个控制点 直线的开始点 结束点 贝塞尔曲线的控制点
drawthreeArc(){
let {controlX, controlY} = this;
this.drawArc(controlX, controlY, 6);
}
//获取鼠标在画布中的绝对位置
getMousePos(evt){
let rect = this.id.getBoundingClientRect();
return {
x: evt.clientX - rect.left * (this.width / rect.width),
y: evt.clientY - rect.top * (this.height / rect.height)
}
}
// 点被选中后的鼠标移动事件
mousemovefn(evt){
let {x, y} = this.getMousePos(evt);
let {top, left} = this;
let {startX, startY, endX, endY, controlX, controlY} = this;
if (x > controlX - 10 && y > controlY - 10 && x < controlX + 10 && y < controlY + 10){
this.pointName = 'control';
this.id.style.cssText = "cursor: pointer;";
this.drawArc(controlX, controlY, 8, 1);
this.controlX = evt.clientX - left;
this.controlY = evt.clientY - top;
this.avoidStartAndEndPointOverlay();
this.fly = false;
this.init();
}else {
this.pointName = '';
this.id.style.cssText = "cuosor: default;";
this.fly = true;
this.init();
}
}
// 避免三个控制点位置重叠
avoidStartAndEndPointOverlay(){
let {startX, startY, endX, endY, controlX, controlY} = this;
if (Math.abs(startX - controlX) < 5 || Math.abs(startY - controlY) < 5) {
if (this.pointName === 'start') {
this.startX += 5;
this.startY += 5;
}
if (this.pointName === 'control') {
this.controlX += 5;
this.controlY += 5;
}
}
if (Math.abs(endX - controlX) < 5 || Math.abs(endY - controlY) < 5) {
if (this.pointName === 'end') {
this.endX += 5;
this.endY += 5;
}
if (this.pointName === 'control') {
this.controlX += 5;
this.controlY += 5;
}
}
}
// 鼠标移动事件。当鼠标移动到控制点时控制点增加样式
sampleMousemovefn(evt){
let {x, y} = this.getMousePos(evt);
let {top, left} = this;
let {startX, startY, endX, endY, controlX, controlY} = this;
if (x > controlX - 10 && y > controlY - 10 && x < controlX + 10 && y < controlY + 10){
this.id.style.cssText = "cursor: pointer;";
this.drawArc(controlX, controlY, 6, 1);
}else {
this.id.style.cssText = "cuosor: default;";
}
}
mousedownfn(){
// 重置各项参数
this.startX = 700;
this.startY = 100;
this.startText = '';
this.endX = 700;
this.endY = 200;
this.endText = '';
this.controlX = 700;
this.controlY = 150;
this.controlText = '';
this.pointName = '';
// 箭的属性值
this.arrowEndX = 700;
this.arrowEndY = 150;
this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100;
this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100;
this.fly = false; // 箭头是否释放
this.angle = 0;
// 分数面板属性
this.score = 0;
this.id.addEventListener("mousemove", this.mousemovefn, false);
}
mouseupfn(){
this.fly = true;
this.id.removeEventListener("mousemove", this.mousemovefn, false);
this.controlXStatic = this.controlX;
this.controlYStatic = this.controlY;
this.controlAnimation();
this.arrawAnimation();
}
// 控制点被释放后的动画
controlAnimation(){
let {controlX, controlY} = this;
let speedX = 5, speedY = 5 * (controlY - 150) / (controlX - 700);
let controlTimer = setInterval(()=>{
this.controlX = (this.controlX - speedX).toFixed(2);
this.controlY = (this.controlY - speedY).toFixed(2);
if(this.controlX <= 700){
clearInterval(controlTimer);
controlTimer = null;
this.controlX = 700;
this.controlY = 150;
}
},30);
}
// 圆点拖拽功能
dragFn(){
this.id.removeEventListener('mousemove', this.sampleMousemovefn, false);
this.id.removeEventListener('mousedown', this.mousedownfn, false);
this.id.removeEventListener('mouseup', this.mouseupfn, false);
this.id.addEventListener("mousemove", this.sampleMousemovefn, false);
this.id.addEventListener("mousedown", this.mousedownfn, false);
this.id.addEventListener("mouseup", this.mouseupfn, false);
}
// 绘制弓的形状
drawBow(){
this.ctx.beginPath();
this.ctx.lineCap = 'round';
this.ctx.moveTo(700,95);
this.ctx.lineTo(700, 100);
this.ctx.quadraticCurveTo(650, 100, 650, 150);
this.ctx.quadraticCurveTo(650, 200, 700, 200);
this.ctx.lineTo(700, 205);
this.ctx.lineWidth = 5;
this.ctx.strokeStyle='#795548';
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.lineWidth=4;
this.ctx.moveTo(646, 144);
this.ctx.lineTo(646, 156);
this.ctx.stroke();
}
// 绘制箭
drawArrow(){
this.ctx.beginPath();
// controlY controlX
if(this.fly){
}else {
this.arrowEndX = this.controlX;
this.arrowEndY = this.controlY;
}
this.arrowStartX = this.arrowEndX - Math.cos(this.angle / 180 * Math.PI) * 100;
this.arrowStartY = this.arrowEndY + Math.sin(this.angle / 180 * Math.PI) * 100;
this.ctx.lineWidth = 1;
this.ctx.strokeStyle = 'red';
this.ctx.moveTo(this.arrowEndX, this.arrowEndY);
this.ctx.lineTo(this.arrowStartX, this.arrowStartY);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.lineWidth = 3;
this.ctx.strokeStyle = 'red';
// this.ctx.moveTo(this.arrowStartX + Math.cos((90 + this.angle) / 180) * 10, this.arrowStartY - Math.sin((90 + this.angle) / 180) *8);
// this.ctx.lineTo(this.arrowStartX, this.arrowStartY);
// this.ctx.lineTo(this.arrowStartX + Math.cos((this.angle+135) / 180) * 10, this.arrowStartY + Math.sin((this.angle+135) / 180) *8)
this.ctx.arc(this.arrowStartX, this.arrowStartY, 4, 0, 2 * Math.PI);
this.ctx.stroke();
}
// 箭的飞行动画
arrawAnimation(){
let {controlXStatic, controlYStatic} = this;
let rate = (controlXStatic - 700) / 100 > 1?1:(controlYStatic - 700) / 100;
// 初始水平速度
let vx = rate * 70;
let time = 0; // 箭的飞行时间
let arrowTimer = setInterval(()=>{
// 水平移动量
let s = vx * time;
// 竖直移动量
let H = 9.8 * time * time / 2;
this.angle = Math.atan(9.8 * time / vx ) * 180 > 90 ? 85 : Math.atan(9.8 * time / vx ) * 180 ;
// 更新箭结束点的位置
this.arrowEndX -= s;
this.arrowEndY += H;
if (this.arrowStartX < 100) {
clearInterval(arrowTimer);
arrowTimer = null;
this.arrowStartX = 100;
this.arrowStartY = Math.sin(this.angle / 180 * Math.PI) * 100 + 9.8 * time * time / 2 + this.controlYStatic;
this.arrowEndX = 100 + Math.cos(this.angle / 180 * Math.PI) * 100;
this.arrowEndY = 9.8 * time * time / 2 + this.controlYStatic;
this.controlX = 700;
this.controlY = 150;
this.pointName = '';
this.id.style.cssText = "cuosor: default;";
this.getScore();
this.init();
return ;
}
if (this.arrowStartY > 300) {
clearInterval(arrowTimer);
arrowTimer = null;
this.controlX = 700;
this.controlY = 150;
this.pointName = '';
this.id.style.cssText = "cuosor: default;";
this.getScore();
this.init();
return;
}
this.init();
time += 0.1;
}, 30);
}
// 获得射箭的分数
getScore(){
let {arrowStartY} = this;
this.score = 100 * (1-(Math.abs(arrowStartY - 150) / 95)).toFixed(2) > 0 ? 100 * (1-(Math.abs(arrowStartY - 150) / 95)).toFixed(2) : 0;
}
// 绘制能量柱
drawEnergySolid(){
let {controlX, controlY} = this;
let rate = (controlX - 700) / 100 > 1?1:(controlX - 700) / 100;
let colorArr = ['#3988dc','#318e27','#c6d222', '#da8b12', '#ca2929']; // 能量越来越高
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.strokeRect(850, 200, 50, 100);
this.ctx.globalCompositeOperation = 'source-over';
let gradient = this.ctx.createLinearGradient(875, 300 - 100 * rate, 875, 300);
if (rate > 0 && rate < 0.25){
gradient.addColorStop(0,"#3988dc");
gradient.addColorStop(1,"#9cb0da");
}else if (rate >= 0.25 && rate < 0.5){
gradient.addColorStop(0,"#318e27");
gradient.addColorStop(0.5,"#3988dc");
gradient.addColorStop(1,"#9cb0da");
}
else if (rate >= 0.5 && rate < 0.7){
gradient.addColorStop(0,"#c6d222");
gradient.addColorStop(0.3,"#318e27");
gradient.addColorStop(0.6,"#3988dc");
gradient.addColorStop(1,"#9cb0da");
}
else if (rate >= 0.7 && rate < 0.9){
gradient.addColorStop(0,"#da8b12");
gradient.addColorStop(0.25,"#c6d222");
gradient.addColorStop(0.5,"#318e27");
gradient.addColorStop(0.75,"#3988dc");
gradient.addColorStop(1,"#9cb0da");
}else if (rate >= 0.9 && rate <= 1){
gradient.addColorStop(0,"#ca2929");
gradient.addColorStop(0.25,"#da8b12");
gradient.addColorStop(0.5,"#c6d222");
gradient.addColorStop(0.75,"#318e27");
gradient.addColorStop(0.9,"#3988dc");
gradient.addColorStop(1,"#9cb0da");
}
this.ctx.fillStyle = gradient;
this.ctx.fillRect(850, 300 - 100 * rate, 50, 100 * rate);
}
// 绘制靶子
drawTarget(){
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 5, 35, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 15, 45, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 25, 55, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 35, 65, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 45, 75, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 55, 85, 0, 0, 2 * Math.PI);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 1;
this.ctx.ellipse(100, 150, 65, 95, 0, 0, 2 * Math.PI);
this.ctx.stroke();
}
// 绘制分数面板
drawScore(){
this.ctx.beginPath();
this.ctx.fillStyle = '#999';
this.ctx.fillRect(800, 0, 100, 50);
this.ctx.textAlign = "center";
this.ctx.font = '20px Arial';
this.ctx.fillStyle = 'red';
this.ctx.fillText('得分:' + this.score, 850, 30);
this.ctx.stroke();
let time = 4 * (this.score / 100);
let score = 0;
let scoreTimer = setInterval(()=>{
this.ctx.clearRect(800, 0, 100, 50);
this.ctx.beginPath();
this.ctx.fillStyle = '#999';
this.ctx.fillRect(800, 0, 100, 50);
this.ctx.textAlign = "center";
this.ctx.font = '20px Arial';
this.ctx.fillStyle = 'red';
this.ctx.fillText('得分:' + score, 850, 30);
this.ctx.stroke();
score = score + 1;
if(score > this.score) {
clearInterval(scoreTimer);
scoreTimer = null;
}
}, time);
}
}
const myCanvas = new ShotAnArrow('myCanvas');
myCanvas.init();
</script>
</body>
</html>