(附源码)HTML+JavaScript+Canvas编写2D小游戏

Canvas简介 

        Canvas是HTML5中的标签,用以生成图像。这里的生成图像有点类似于我们自己在纸面上绘画,需要指定渲染位置以及大小等参数,同时,这也使得一旦元素被绘制出来,便再也无法编辑,只能擦除后重新绘制。

Canvas支持的浏览器

        除IE8及更早版本外,其余浏览器如IE9、Edge、Chrome、FireFox、 Safari等支持Canvas。

预期结果

         其中,(1)为游戏新加载时显示的图像,有两个大小为80px*100px的矩形,一个半径为15px的红色小球;(2)当鼠标左键被按下时,木棍增长,最高不超过Canvas页面外;(3)松开鼠标左键,木棍倒下,如果木棍顶端正好位于另外一个矩形顶部范围内,游戏继续,清除页面内容,开始生成下一个矩形;(4)如果木棍顶端没有到达或超出矩形顶部范围,游戏结束,弹出“重新开始”按钮。

项目结构 

 

        包含HTML主页面stickGrow.html、css样式文件stick.css和JavaScript文件stick.js。 

HTML主页面:stickGrow.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Horizon Project</title>
		<link rel="stylesheet" href="./stick.css">
	</head>
	<body>
		<div class="con">
			<div class="score">0</div>
			<canvas width="400px" height="600px" id="cvs"></canvas>
			<button class="restart">重新开始</button>
		</div>
	</body>
</html>
<script src="./stick.js"></script>

        Canvas使用<Canvas>标签创建,并直接向其赋值:宽width和高height。创建用来显示得分的div--”score“,其内部赋初始值0。最后是“重新开始”按钮。运行后得到如下结果:

        按照正常使用习惯,我们接下来要将得分和按钮移动进Canvas内,且暂时隐藏“重新开始”按钮,因此需要使用css来完成。

 css样式文件:stick.css

body{
	margin: 0;
	padding: 0;
	background-color: black;
}

.con{
	width: 400px;
	height: 660px;
	margin-top: 50px;
	margin-left: auto;
	margin-right: auto;
}

.score{
	width: 180px;
	height: 60px;
	text-align: center;
	margin-left: auto;
	margin-right: auto;
	color: #eb4b16;
	font-size: 60px;
	position: relative;
	top: 120px;
	text-shadow: 5px 5px 10px #b18253;
}

         首先给Canvas的父级div--“con”赋宽高值。此处使用position: relative;移动了分数的位置,但是HTML还是认为score在Canvas上方占据60px。因此con的高 = Canvas的高 + score的高,为660px。同时将margin-left和margin-right设为auto使其始终位于页面中央。

.restart{
	color: #fef7ec;
	border: 0;
	border-radius: 15px;
	background-color: #b16145;
	font-size: 25px;
	width: 120px;
	height: 40px;
	position: relative;
	top: -300px;
	left: 140px;
	display: none;
	box-shadow: 3px 3px 10px #865746;
}

.restart:hover{
	background-color: #8a4831;
	cursor: pointer;
}

canvas{
	background-color: #fef7ec;
	border-radius: 15px;
}

         这里设置“重新开始”按钮display: none;,只有在JavaScript判定游戏结束时才会使它重现。运行后我们可以看到如下结果:

         JavaScript文件stick.js

var cvs = document.getElementById("cvs");
var ctx = cvs.getContext("2d");

var sc = document.getElementsByClassName("score");
var btns = document.getElementsByTagName("button");

var scores = 0;

        首先取出Canvas,Canvas只是一张画布,没有提供绘图能力和相关函数。为了使用JavaScript进行绘制,我们需要对Canvas进行getContext("2d");操作得到ctx对象,这样ctx对象就可以调用getContext()里的属性和方法了。接下来取出得分和按钮,注意他们的取出方法都是getElements,因此即使只有一个元素返回的也是一个列表,我们在使用时需要添加索引如sc[0]、btns[0]。最后定义全局变量scores,scores运算结束后可把值赋给页面上的得分div框中。

function init(){
	ctx.fillStyle = "rgb(190, 23, 47)";
	ctx.beginPath();
	ctx.arc(50, 485, 15, 0, Math.PI * 2, false);
	ctx.fill();
	
	ctx.fillStyle = "rgb(38, 54, 69)";
	ctx.shadowOffsetX = 5;
	ctx.shadowOffsetY = 5;
	ctx.shadowBlur = 4;
	ctx.shadowColor = "rgba(140, 140, 140, 0.6)"
	ctx.fillRect(0, 500, 80, 100); //x, y, wid, hei
}

        定义函数init(),用来生成角色--“红色小球”,其中绘制圆弧函数ctx.arc()中传入参数分别为:(相对于屏幕左上角的原点)横坐标,纵坐标,圆弧的半径,起始角度,终止角度,顺逆时针(true逆时针,false顺时针,不过此处绘制的是正圆,因此无需考虑顺逆时针)。Math.PI表示π,即Math.PI * 2 = 2π = 360度。绘制的圆弧为空心,使用ctx.fill();填充内部。

        绘制角色所在的矩形块,为了使其更立体,我们为其赋上阴影,不过如果想要清除图形,要连带其X轴Y轴上的阴影一同清除,不然就会留下一条明显的阴影。矩形使用ctx.fillRect();函数绘制,传入参数分别为:(相对于屏幕左上角的原点)横坐标,纵坐标,矩形宽,矩形高。

        特别需要注意的一点是,在绘制前需要先指明它的填充颜色ctx.fillStyle();,这就像选择画笔一样,且可在后面修改,定义颜色后可开始绘制。同样的,指定阴影样式后绘制的图形都会带上阴影,在上面的代码中我不想要角色带阴影,所以将角色定义于阴影样式之前。

window.onload = function(){
	init();
}

        定义页面加载函数,在页面打开或刷新时调用init(),运行得到以下结果:

function stageGen(){
	var x = 80 + 20 + Math.floor(Math.random()*10*22);
	ctx.fillStyle = "rgb(38, 54, 69)";
	ctx.fillRect(x, 500, 80, 100);
	return x;
}

        接下来定义函数stageGen(),以生成新的目标方块,与小球所在方块大小一致,为80px宽、100px高。它在页面X轴的位置需要满足:不能与小球所在方块重叠或太近、不能超出Canvas显示范围。在这里,为了防止两方块距离过近,设置至少间距为20,因此它的X轴位置(左上角的点的位置)在80+20 到 400-80之间取随机数,最终返回其横坐标。

function start(){
	init();
	res = stageGen();
	console.log(res);
}

        新定义start()函数,将stageGen()与init()函数封装,并在控制台输出新生成的方块的X轴坐标。 

window.onload = function(){
	start();
}

        直接修改页面加载执行函数window.onload调用封装好的start()函数,运行可看到另一块矩形成功生成,可刷新页面,随着页面刷新,其位置也会发生变化,同时在控制台输出其X轴坐标(不是它相对于左边方块空出来的距离),如下图:

function stickDraw(){
	ctx.shadowBlur = 0;
	ctx.shadowOffsetX = 0;
	ctx.shadowOffsetY = 0;
	stickY = 0;
	val = setInterval(function(){
		if (stickY >= 500 ) {
			clearInterval(val);
		}else{
			stickY++;
			ctx.clearRect(70, 500, 50, -500);
			ctx.fillStyle = "rgb(73, 36, 21)";
			ctx.fillRect(70, 500, 10, -stickY);
		}
	},10);
}

         定义木棒增长函数stickDraw(),为了防止木棒在绘制中出现错误,我们将它的阴影完全去除,并定义一个全局变量stickY记录棒长。然后使用周期调用函数setInterval();实现每10毫秒让木棒长度自增1px,当木棒长度达到Canvas纵向最高点时,为500px,将木棒旋转也早已超过Canvas横向最大值,因此设定当stickY大于等于500px时停止setInterval()函数。特别的,在下一帧绘制前,我们要将上一帧绘制的木棒清除,所以使用ctx.clearRect();来清除,它传入的参数与ctx.fillRect()函数类似,分别为:(相对于屏幕左上角的原点)横坐标,纵坐标,要清除的矩形宽,要清除的矩形高。这里直接清除了木棒上方全部的内容。

function handleMouseDown() {  
    stickDraw();  
    console.log('down' + stickY);  
}  
  
cvs.addEventListener('mousedown', handleMouseDown); 

        定义函数handleMouseDown(),再为Canvas页面绑定鼠标按下事件,因此当鼠标按下时,木棒会向上增长,同时控制台输出down + 木棒起始长度。此时运行,木棒会持续向上增长,如下:

function rotateBlock(){
	ctx.translate(80, 500);
	ctx.clearRect(0, 0, -10, -500);
	ctx.save();
	ctx.rotate(90 * Math.PI / 180);
	ctx.fillStyle = "rgb(73, 36, 21)";
	ctx.fillRect(0, 0, -10, -stickY);
	ctx.restore();
}

        木棒已经出现,现在要当松开鼠标时停止木棒增长并使其顺时针旋转90度。首先处理木棒旋转函数rotateBlock();,这里选择了木棒右下角(即小球所在方块右上角)为旋转原点,因此使用ctx.translate();将原点设为(80, 500),在这之后的坐标均以此为原点(0, 0)。使用ctx.save();保存当前木棒状态,调用旋转函数ctx.rotate();,此处使其顺时针旋转90度即π/2后重新填充木棒,并使用ctx.restore();来还原之前保存的状态。

function handleMouseUp() {  
	if (val){
		clearInterval(val);
	}
	rotateBlock();
	console.log('up'+stickY);
	//judgeOver();
}

cvs.addEventListener('mouseup', handleMouseUp);

         定义函数handleMouseUp(),setInterval()函数会有返回值,在前面我们将此返回值存入val全局变量中,当调用函数时,该周期调用函数会被关闭,以此达到木棒长度不再增加,同时调用rotateBlock()函数旋转木棒,并输出此时木棒的长度。向Canvas页面添加鼠标按键抬起触发器,当鼠标抬起,执行以上操作。运行程序,长按鼠标左键,数秒后松开,可得以下结果:

function judgeOver(){
	if(stickY < (res-80) || stickY > res){
		console.log("游戏结束");
		ctx.translate(-80, -500);
		cvs.removeEventListener('mousedown', handleMouseDown);
		ctx.shadowOffsetX = 5;
		ctx.shadowOffsetY = 5;
		ctx.shadowBlur = 4;
		ctx.shadowColor = "rgba(140, 140, 140, 0.6)"
		ctx.fillStyle = "rgb(89, 109, 143)";
		ctx.font = '60px Verdana';
		ctx.textAlign = 'center';
		ctx.fillText("游戏结束", 200, 200);
		cvs.removeEventListener('mouseup', handleMouseUp);
		btns[0].style.display = "inline";
	}else{
		console.log("继续");
		scores++;
		sc[0].innerHTML = scores;
		stickY = 0;
		setTimeout(function(){
			ctx.translate(-80, -500);
			ctx.clearRect(0, 0, 400, 600);
			start();
		},500);
	}  
}

         最后我们要处理的是游戏是否结束,如果未结束,清除木棒并重新生成方块,分数+1;如果结束,则清除鼠标按下释放事件,弹出“游戏结束”提示和“重新开始”按钮。

        判断的条件是木棒长度小于两矩形间距(即res - 80px)或大于左边矩形右上角到右边矩形右上角的距离(即res本身的值)为游戏失败。此时清除鼠标的所有事件,防止游戏结束后的误操作,并使用ctx.fillText();绘制文字“游戏结束”,通过设置btns[0].style.display属性使得“重新开始”按钮显现。 

        如果木棒落在了矩形上方,游戏继续,令scores自增1后赋值给显示分数的div的innerHTML,分数可实时更新显示出来。再通过延时函数setTimeout();设置0.5秒后还原原点位置至左上角、清除屏幕上所有内容并调用start()函数绘制下一轮的图像。

function handleMouseUp() {  
	if (val){
		clearInterval(val);
	}
	rotateBlock();
	console.log('up'+stickY);
	judgeOver();//上面此行被注释掉了,删除注释即可
}

        在handleMouseUp()中封装judgeOver()函数,此时程序也可正常运行了:

btns[0].onclick = function(){
	location.reload();
}

         “重新开始”按钮简单粗暴地刷新页面达到重新开始的效果。

未解决的点

  •  全局无动画。最初设想给木棒旋转、小球移动和新方块生成添加动画,但是能力有限,最终没有完成动画的制作。
  •  少数情况下会有bug存在。在多次点击鼠标或其他极端情况下程序在方块生成、移除上有bug出现,目前未找到原因。

源码 

        Hori03/HTML+JavaScript+Canvas编写2D小游戏 - 码云 - 开源中国 (gitee.com)

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
以下是一个简单的 web 前端小游戏,使用 HTML、CSS 和 JavaScript 编写HTML 代码: ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>小游戏</title> <style> body { margin: 0; padding: 0; background-color: #f1f1f1; } #canvas { display: block; margin: auto; background-color: #fff; border: 2px solid #000; box-shadow: 0 0 10px #000; } </style> </head> <body> <canvas id="canvas" width="400" height="400"></canvas> <script src="game.js"></script> </body> </html> ``` CSS 代码用于设置页面样式,这里只设置了页面背景色、画布的样式。 JavaScript 代码: ```javascript var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d"); // 设置小球的初始位置和速度 var x = canvas.width / 2; var y = canvas.height - 30; var dx = 2; var dy = -2; var ballRadius = 10; // 设置挡板的初始位置和大小 var paddleHeight = 10; var paddleWidth = 75; var paddleX = (canvas.width - paddleWidth) / 2; // 设置按键状态 var rightPressed = false; var leftPressed = false; // 监听按键事件 document.addEventListener("keydown", keyDownHandler, false); document.addEventListener("keyup", keyUpHandler, false); function keyDownHandler(e) { if(e.keyCode == 39) { rightPressed = true; } else if(e.keyCode == 37) { leftPressed = true; } } function keyUpHandler(e) { if(e.keyCode == 39) { rightPressed = false; } else if(e.keyCode == 37) { leftPressed = false; } } // 绘制小球 function drawBall() { ctx.beginPath(); ctx.arc(x, y, ballRadius, 0, Math.PI*2); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } // 绘制挡板 function drawPaddle() { ctx.beginPath(); ctx.rect(paddleX, canvas.height-paddleHeight, paddleWidth, paddleHeight); ctx.fillStyle = "#0095DD"; ctx.fill(); ctx.closePath(); } // 绘制游戏场景 function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布 drawBall(); // 绘制小球 drawPaddle(); // 绘制挡板 // 小球碰到左右边界时反弹 if(x + dx > canvas.width-ballRadius || x + dx < ballRadius) { dx = -dx; } // 小球碰到上边界时反弹 if(y + dy < ballRadius) { dy = -dy; } // 小球碰到挡板时反弹 else if(y + dy > canvas.height-ballRadius-paddleHeight && x > paddleX && x < paddleX + paddleWidth) { dy = -dy; } // 移动挡板 if(rightPressed && paddleX < canvas.width-paddleWidth) { paddleX += 7; } else if(leftPressed && paddleX > 0) { paddleX -= 7; } // 更新小球的位置 x += dx; y += dy; } setInterval(draw, 10); // 每隔10毫秒调用一次draw函数,实现动画效果 ``` JavaScript 代码实现了游戏的逻辑,包括小球和挡板的移动、边界碰撞检测等。同时,使用 `setInterval` 函数每隔一定时间调用 `draw` 函数,实现动画效果。 将上面的 HTML、CSS 和 JavaScript 代码保存到同一目录下,将 HTML 文件在浏览器中打开,即可开始游戏。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值