date:02/24
author:yjxcoder
1. 打砖块
1.1. 今日所学内容
1.1.1. 打砖块游戏现版本实现逻辑
-
定义好游戏相关的数据结构。有几大重要数据结构如下:
-
brickList[]代表第i行,j列砖块的颜色类型。blockImage则是存着第i行,j列砖块的图片对象。
//绘制方块 function drawBrick () { for (var i = 0; i < 5; i++) { for(var j = 0; j < 4; j++){ if(brickList[i][j] > 0){ // list代表i行,j列砖块的颜色类型 // blockImage则是存着每个图片对象 var img = brickImage[brickList[i][j] - 1]; gameContext.drawImage(img, i * brickWidth + 2*i + 7, j * brickHeight + 2*j + 50, brickWidth, brickHeight); } } } }
-
小球和砖块对象
// 小球初始化 var ball = new Ball(); ball.img.onload = function() { ball.drawBall(); } var board = new Border(); board.img.onload = function() { board.drawBoard(); } createBrick(); drawBrick();
-
-
刚进入游戏时,对定义的游戏变量进行初始化,创建好砖块,小球和挡板。使用gameContext的drawImage()在显示板中画出他们来。
//加载图片 loadImage(); function loadImage () { for (var i = 0; i < 6; i++) { var img = new Image(); var index = i + 4; var src = "img/" + index + ".png" img.src = src; brickImage[i] = img; } } // 小球初始化 var ball = new Ball(); ball.img.onload = function() { ball.drawBall(); } var board = new Border(); board.img.onload = function() { board.drawBoard(); } createBrick(); drawBrick(); function createBrick(){ for (var i = 0; i < 5; i++) { // 3. brickList[i] = []; for(var j = 0; j < 4; j++){ brickList[i][j] = Math.floor(Math.random()*6) + 1; } } }
-
设置一个定时器,在定时器中不断更新小球位置、并重新绘制小球、挡板和砖块。
timer = setInterval(function () { //清空画布 gameContext.clearRect(0, 0, gameCanvas.width, gameCanvas.height); //检测碰撞 collide(); //绘制上方柱子 // gameContext.drawImage(pillar,325,250,565,10,5,40,515,8); //更新小球位置 ball.updateBallFrame(); //绘制小球 ball.drawBall(); //绘制挡板 board.drawBoard(); //绘制砖块 drawBrick(); },30);
1.1.2. 打砖块游戏重要代码分析
-
生成一张随机图片放在第i行,j列砖块的图片对象数组中。
//加载图片 loadImage(); function loadImage () { for (var i = 0; i < 6; i++) { var img = new Image(); var index = i + 4; var src = "img/" + index + ".png" img.src = src; brickImage[i] = img; } } //获取小球图片对象 function getBallImage () { var img = new Image(); img.src = "img/ball.png"; return img; }
-
根据第i行,j列的图片对象二维数组,绘制4行,5列的砖块。使用此数据结构的优点是:用二维数组存好砖块图片信息,当刷新画布时,重新绘制的砖块也是从数组中读取,则不会,即使在定时器中有不断地调用绘制砖块,但也不会改变原来的砖块的颜色。
//绘制砖块 function drawBrick () { for (var i = 0; i < 5; i++) { for(var j = 0; j < 4; j++){ if(brickList[i][j] > 0){ // list代表i行,j列砖块的颜色类型 // blockImage则是存着每个图片对象 var img = brickImage[brickList[i][j] - 1]; gameContext.drawImage(img, i * brickWidth + 2*i + 7, j * brickHeight + 2*j + 50, brickWidth, brickHeight); } } } } // this则代表new的对象 function Brick() { this.currentX = 0; this.currentY = 0; this.img = getImage(); //画砖块 this.drawBrick = function(i, j) { gameContext.drawImage(this.img, 0, 0, brickWidth, brickHeight, brickWidth * i, brickHeight * (this.currentY + j), brickWidth, brickHeight); } }
-
小球对象内部的构造函数,包括球的位置,宽高,移速,图片等属性。
// 在构造函数中,定义小球的位置,宽高,移速,图片等属性 2. function Ball() { //位置 this.x = 250; this.y = 300; //宽高 this.width = 30; this.height = 30; //移动速度 this.vx = 2; this.vy = -2; //小球图片 this.img = getBallImage(); //更新小球位置 this.updateBallFrame = function () { this.x = this.x + this.vx; this.y = this.y + this.vy; } //绘制小球 this.drawBall = function () { gameContext.drawImage(this.img,this.x,this.y,this.width,this.height); } }
-
定时器中不断刷新,并且消除画布,绘制画布。
timer = setInterval(function () { //清空画布 gameContext.clearRect(0, 0, gameCanvas.width, gameCanvas.height); //检测碰撞 collide(); //绘制上方柱子 // gameContext.drawImage(pillar,325,250,565,10,5,40,515,8); //更新小球位置 ball.updateBallFrame(); //绘制小球 ball.drawBall(); //绘制挡板 board.drawBoard(); //绘制砖块 drawBrick(); },30);
-
小球的碰撞检测,这里根据小球的移速,做了小球与砖块和挡板之间的碰撞检测。还未完善。此处是本游戏的难重点。
//碰撞检测 function collide () { //小球的的下一次位置 var nextX = ball.x + ball.vx; var nextY = ball.y + ball.vy; //小球的中间位置的x,y坐标 var midX = ball.x + ball.width / 2; var midY = ball.y+ ball.height / 2; //与挡板的碰撞检测 if( ball.y + ball.height > board.y //球的下端接触到挡板 && ball.y + ball.height < board.y + board.height //但球的下端不超过挡板 && midX > board.x //球队中部位于挡板上 && midX < board.x + board.width //球队中部位于挡板上 ) { ball.vy = -2; } //与砖块碰撞检测 for (var i = 0; i < 5; i++) { for(var j = 0; j < 4; j++){ if(brickList[i][j] > 0) { //砖块的四条边的位置 var leftX = i * brickWidth + 2*i + 7; var rightX = leftX + brickWidth; var topY = j * brickHeight + 2*j + 50; var bottomY = topY + brickHeight; //底部碰撞 if(nextY > topY && nextY < bottomY && midX > leftX && midX < rightX) { ball.vy *= -1; brickList[i][j] = 0; } } } } }
1.2. 残留问题
1.2.1. 小球的碰撞还没有完全做完,小球与地面碰撞时显示游戏失败,与左右面板碰撞时反弹,再给我一些时间应该能完成。此版本之前我想了好几个绘制的方案,方案一会出现只绘制一种颜色的砖块的问题,原因是brick.img.onload()加载时间过长,canvas的drawImage()无法绘制所有种类的颜色图片方案二导致在定时器的刷新下,所有砖块的颜色不断变化。最后采用brickList[]代表第i行,j列砖块的颜色类型。blockImage则是存着第i行,j列砖块的图片对象的数据结构解决了此问题。此份代码在定时器中不断调用函数的方法,后续在对代码改动时,会十分简单方便。
1.3. 完整代码
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<style type="text/css">
*{
margin: 0;
padding: 0;
}
.container{
height:402px;
width: 532px;
}
/*1. 默认为本层(本级)目录. 2.canvas不好设置定位。*/
#canvasBoard{
background-image: url(img/bg2.png);
}
#baffle{
background-image: url(img/board.png);
width: 126px;
height: 9px;
}
</style>
</head>
<body>
<div class="container">
<canvas id="canvasBoard" width="524px" height="375px">
</canvas>
</div>
</body>
<script type="text/javascript">
// 整个游戏画布
var gameCanvas = document.getElementById("canvasBoard");
var gameContext = gameCanvas.getContext("2d");
var timer;
var brick;
//定义砖块宽高
var brickWidth = 100;
var brickHeight = 25;
//砖块位置数组
var brickList = [];
//定义砖块图片对象数组
var brickImage = [];
//加载图片
loadImage();
function loadImage () {
for (var i = 0; i < 6; i++) {
var img = new Image();
var index = i + 4;
var src = "img/" + index + ".png"
img.src = src;
brickImage[i] = img;
}
}
// 小球初始化
var ball = new Ball();
ball.img.onload = function()
{
ball.drawBall();
}
var board = new Border();
board.img.onload = function()
{
board.drawBoard();
}
createBrick();
drawBrick();
function createBrick(){
for (var i = 0; i < 5; i++) {
// 3.
brickList[i] = [];
for(var j = 0; j < 4; j++){
brickList[i][j] = Math.floor(Math.random()*6) + 1;
}
}
}
//绘制砖块
function drawBrick () {
for (var i = 0; i < 5; i++) {
for(var j = 0; j < 4; j++){
if(brickList[i][j] > 0){
// list代表i行,j列砖块的颜色类型
// blockImage则是存着每个图片对象
var img = brickImage[brickList[i][j] - 1];
gameContext.drawImage(img,
i * brickWidth + 2*i + 7,
j * brickHeight + 2*j + 50,
brickWidth,
brickHeight);
}
}
}
}
// 在构造函数中,定义小球的位置,宽高,移速,图片等属性 2.
function Ball()
{
//位置
this.x = 250;
this.y = 300;
//宽高
this.width = 30;
this.height = 30;
//移动速度
this.vx = 2;
this.vy = -2;
//小球图片
this.img = getBallImage();
//更新小球位置
this.updateBallFrame = function () {
this.x = this.x + this.vx;
this.y = this.y + this.vy;
}
//绘制小球
this.drawBall = function () {
gameContext.drawImage(this.img,this.x,this.y,this.width,this.height);
}
}
function Border()
{
//挡板位置
this.x = 242;
this.y = 330;
//挡板宽高
this.width = 50;
this.height = 22;
//挡板图片
this.img = getBoardImage();
//挡板绘制
this.drawBoard = function () {
gameContext.drawImage(this.img,this.x,this.y,this.width,this.height);
}
}
// this则代表new的对象
function Brick()
{
this.currentX = 0;
this.currentY = 0;
this.img = getImage();
//画砖块
this.drawBrick = function(i, j)
{
gameContext.drawImage(this.img, 0, 0, brickWidth, brickHeight, brickWidth * i, brickHeight * (this.currentY + j), brickWidth, brickHeight);
}
}
timer = setInterval(function () {
//清空画布
gameContext.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
//检测碰撞
collide();
//绘制上方柱子
// gameContext.drawImage(pillar,325,250,565,10,5,40,515,8);
//更新小球位置
ball.updateBallFrame();
//绘制小球
ball.drawBall();
//绘制挡板
board.drawBoard();
//绘制砖块
drawBrick();
},30);
function getBoardImage()
{
var img = new Image();
img.src = "img/board.png";
return img;
}
//获取小球图片对象
function getBallImage () {
var img = new Image();
img.src = "img/ball.png";
return img;
}
//碰撞检测
function collide () {
//小球的的下一次位置
var nextX = ball.x + ball.vx;
var nextY = ball.y + ball.vy;
//小球的中间位置的x,y坐标
var midX = ball.x + ball.width / 2;
var midY = ball.y+ ball.height / 2;
//与挡板的碰撞检测
if( ball.y + ball.height > board.y //球的下端接触到挡板
&& ball.y + ball.height < board.y + board.height //但球的下端不超过挡板
&& midX > board.x //球队中部位于挡板上
&& midX < board.x + board.width //球队中部位于挡板上
)
{
ball.vy = -2;
}
//与砖块碰撞检测
for (var i = 0; i < 5; i++) {
for(var j = 0; j < 4; j++){
if(brickList[i][j] > 0)
{
//砖块的四条边的位置
var leftX = i * brickWidth + 2*i + 7;
var rightX = leftX + brickWidth;
var topY = j * brickHeight + 2*j + 50;
var bottomY = topY + brickHeight;
//底部碰撞
if(nextY > topY && nextY < bottomY
&& midX > leftX && midX < rightX)
{
ball.vy *= -1;
brickList[i][j] = 0;
}
}
}
}
}
//鼠标移动函数
gameCanvas.onmousemove = function (e) {
var ev = e || window.event;
//清除上次挡板痕迹
gameContext.clearRect(board.x,board.y,board.width,board.height);
//判断挡板右侧是否超出屏幕
if(ev.clientX < gameCanvas.width - board.width)
{
board.x = ev.clientX;
}
else
{
board.x = gameCanvas.width - board.width;
}
board.drawBoard();
}
</script>
</html>