파일은 로직 부분이 완료되면 추가됩니다. : <body>
<canvas id='can1' width=1536 height=733></canvas>
<audio id="walk" autoplay></audio>
<audio id="push" autoplay></audio>
<audio id="win" autoplay></audio>
</body> <script>는 4개의 다른 .js 파일을 도입했으며 그 중 하나는 run() 메서드를 호출합니다. <span style="font-size:16px;"><pre class="brush:php;toolbar:false;"><script type="text/javascript" src="pojo.js"></script>
<script type="text/javascript" src="game.js"></script>
<script type="text/javascript" src="paint.js"></script>
<script type="text/javascript" src="allLevels.js"></script>
<script>
window.onload = function(){
run();
}
</script></pre></script>
2 pojo 클래스를 작성합니다.
먼저 총계를 알아야 합니다. 클래스 수:
사람, 상자, 대상 지점, 벽돌 및 벽, 매우 간단합니다. 모든 클래스에는 색상(색상), 크기(변 길이/반경), x(가로 좌표), y( 세로 좌표). 그런 다음 사람과 벽돌이 대상 지점과 일치할 수 있다는 점을 기억해야 하므로 상자와 사람의 클래스에 isOnTarget(대상 지점과 일치하는지 여부) 속성을 추가해야 합니다. 그러면 작업이 완료됩니다.
//人类
function Person(x, y){
this.color = 'pink';
this.size = 20;
this.x = x;
this.y = y;
//判断这个人是否在目标点上
this.isOnTarget = false;
}
//箱子类
function Box(x, y){
this.color = 'yellow';
this.size = 40;
this.x = x;
this.y = y;
//判断某个箱子是否在目标点上
this.isOnTarget = false;
}
//目标点类
function Target(x, y){
this.size = 12;
this.x = x;
this.y = y;
this.color = 'lime';
}
//地砖类
function Brick(x, y){
this.x = x;
this.y = y;
this.size = 40;
}
//围墙类
function Wall(x, y){
this.x = x;
this.y = y;
this.size = 40;
} 3. 페인트.js 클래스 작성pojo.js 클래스에 방금 작성된 모든 클래스에 대한 그리기 메소드를 작성해야 합니다.
주의해야 할 점: 예를 들어 특정 x, y 좌표를 사용하는 경우 , 상자를 그리려면:
ctx.fillRect(x, y, size, size); 다음과 같이 호출하면 모든 레벨에서 상자를 이렇게 그립니다.
ctx.fillRect(500, 500, 40, 40); 상자 옆에 무엇이 있는지 알 수 없고 판단하기 어렵기 때문에 로직 레이어를 작성할 방법이 없습니다.
그래서 우리는 이렇게 생각합니다. 2차원 배열을 사용하여 이 레벨을 구성하고 각 개체(상자, 사람, 대상 지점, 벽돌 또는 벽)가 이 배열 arr1[][]에 배치됩니다. like 이렇게:
var arr1 = [
['','wall','wall','wall','wall','wall','',''],
['','wall','brick','person','wall','wall','wall',''],
['','wall','brick','box','brick','brick','wall',''],
['wall','wall','wall','brick','wall','brick','wall','wall'],
['wall','target','wall','brick','wall','brick','brick','wall'],
['wall','target','box','brick','brick','wall','brick','wall'],
['wall','target','brick','brick','brick','box','brick','wall'],
['wall','wall','wall','wall','wall','wall','wall','wall']
]; 예를 들어 arr1[0][1]은 벽이므로 논리 레이어를 작성할 수 있습니다. 그리고 또 다른 장점은 나중에 새 레벨을 만들 때 좌표 순서대로 배열에 작성하면 매우 편리하다는 것입니다.
그래서 우리는 Paint.js에 다음과 같이 작성합니다. 각 메서드에서 웹 페이지에 올바르게 그려질 수 있도록 x와 y에 대해 몇 가지 작업을 수행합니다.
//首先还是清屏
function clearScreen(ctx){
ctx.clearRect(0,0,1536,750);
}
//画人
function paintPerson(ctx, x, y, size,color){
ctx.beginPath();
ctx.fillStyle = color;
//我们在内部就写好该往哪里画,传过来的x,y坐标值就可以直接在里面计算了
ctx.arc(530+x*size*2+20, 180+y*size*2+20, size, 0, 2*Math.PI);
ctx.fill();
}
//画箱子
function paintBox(ctx, x, y, size,color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.fillRect(530+x*size, 180+y*size, size, size);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.moveTo(530+x*size, 180+y*size);
ctx.lineTo(530+x*size+size, 180+y*size+size);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(530+x*size+size, 180+y*size);
ctx.lineTo(530+x*size, 180+y*size+size);
ctx.stroke();
ctx.strokeRect(530+x*size, 180+y*size, size, size);
}
//画目标点
function paintTarget(ctx, x, y, size,color){
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(530+x*size*4+20, 180+y*size*4+20, size, 0, 2*Math.PI);
ctx.fill();
}
//画地砖,其中用了for循环简化了代码量
function paintBrick(ctx, x, y, size){
ctx.beginPath();
ctx.fillStyle = 'blue';
ctx.fillRect(530+x*size, 180+y*size, size, size);
ctx.strokeStyle = 'lightblue';
for(var i = 0; i <= 3; i++){
ctx.beginPath();
ctx.moveTo(530+x*size, 180+y*size+0.25*(i+1)*size);
ctx.lineTo(530+x*size+size, 180+y*size+0.25*(i+1)*size);
ctx.stroke();
}
for(var i = 0; i < 4; i++){
ctx.beginPath();
if(i%2 == 0){
ctx.moveTo(530+x*size+0.5*size, 180+y*size+0.25*i*size);
ctx.lineTo(530+x*size+0.5*size, 180+y*size+0.25*(i+1)*size);
ctx.stroke();
}else{
ctx.moveTo(530+x*size+0.25*size, 180+y*size+0.25*i*size);
ctx.lineTo(530+x*size+0.25*size, 180+y*size+0.25*(i+1)*size);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(530+x*size+0.75*size, 180+y*size+0.25*i*size);
ctx.lineTo(530+x*size+0.75*size, 180+y*size+0.25*(i+1)*size);
ctx.stroke();
}
}
}
//画围墙
function paintWall(ctx, x, y, size){
ctx.beginPath();
ctx.fillStyle = 'gray';
ctx.fillRect(530+x*size, 180+y*size, size, size);
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.moveTo(530+x*size+0.5*size, 180+y*size);
ctx.lineTo(530+x*size+0.5*size, 180+y*size+size);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(530+x*size, 180+y*size+0.5*size);
ctx.lineTo(530+x*size+size, 180+y*size+0.5*size);
ctx.stroke();
} 그런 다음 페인팅 메서드를 호출하면 이렇게 하세요: 호출하고 배열의 각 개체 블록 위치에 따라 그립니다.
unction getMap(ctx, person){
clearScreen(ctx);
for(var i = 0; i<arr1.length; i++){
for(var j = 0; j<arr1[0].length; j++){
//画初始图
if(arr1[i][j] == 'wall'){
//这样我们就把坐标作为参数,引入到那些paint方法里,经过计算,在网页中正确显示了
paintWall(ctx,j,i,40);
}else if(arr1[i][j] == 'brick'){
paintBrick(ctx, j, i,40);
}else if(arr1[i][j] == 'target'){
paintBrick(ctx, j, i,40);
paintTarget(ctx, j, i,10,'lime');
}else if(arr1[i][j] == 'box'){
paintBrick(ctx, j, i,40);
var index = getBoxIndex(boxes,i,j);
if(boxes[index].isOnTarget){
paintBox(ctx, j, i,40,'red');
}else{
paintBox(ctx, j, i,40,'yellow');
}
}else if(arr1[i][j] == 'person'){
paintBrick(ctx, j, i,40);
paintPerson(ctx, j, i,20,'pink');
}
}
}
} game.js의 run()에서 getMap() 메서드를 호출한 후 효과는 다음과 같습니다.
4. game.js 클래스:
먼저 사람과 상자의 개체를 만들어야 합니다.
var person = new Person(0,0); var boxLevel1Count = 3;
var boxes = new Array(boxLevel1Count);
for(var i = 0;i<boxLevel1Count;i++){
boxes[i] = new Box(0,0);
} 그런 다음 2차원 배열에서 사람의 위치(좌표)와 배열에 있는 세 상자의 좌표를 알아야 합니다. 다음 두 가지 방법이 필요합니다.
//找到人的坐标
function findPerson(){
for (var i = 0; i < arr1.length; i++) {
var tmp = arr1[i];
for (var j = 0; j < tmp.length; j++) {
if (arr1[i][j] == 'person') {
//使用json传变量
return {personX:i,personY:j};
}
}
}
}
//找箱子的坐标,并把他们放到数组里
function findBox(){
var count = 0;
for (var i = 0; i < arr1.length; i++) {
var tmp = arr1[i];
for (var j = 0; j < tmp.length; j++) {
if (arr1[i][j] == 'box') {
boxes[count].x = i;
boxes[count].y = j;
count++;
}
}
}
return boxes;
} 사람의 좌표를 찾은 후 사람 개체의 x, y를 2차원 배열의 i, j와 연결해야 합니다. 즉,
//接收person的坐标
var position = findPerson();
//i是person的横坐标
var i = position.personX;
//j是person的纵坐标
var j = position.personY;
//使对象的属性和人在二维数组的坐标关联
person.x = i;
person.y = j; <br/> 그런 다음 예를 들어, 사용자가 왼쪽 화살표 키를 누르면 왼쪽에 무엇이 있는지 확인하고 상자인 경우 상자 왼쪽에 무엇이 있는지 확인합니다.
올바른 논리는 다음과 같습니다. (왼쪽으로 움직이는 사람을 예로 들자면 위쪽, 아래쪽, 오른쪽은 동일합니다.):
구체적인 구문은 매우 간단합니다. 예를 들어 첫 번째 예에 따르면 다음과 같습니다. 왼쪽에 벽돌이 있고 사람이 목표 지점이 아닌 다른 것을 밟은 경우:
if(arr1[i][j-1] == 'brick'){
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
Audio1.src = '走路emm.wav';
} 다음 단계를 밟고 사람이 목표 지점을 밟으면 사람을 넣어야 합니다. .isOnTarget이 true로 설정됩니다. 사람이 이사가면 이 속성을 false로 설정합니다
当人左边是箱子的时候,比较麻烦,首先必须明白有一点:我们到底推的是哪个箱子?之前已经有了一个存放所有箱子的数组了,所以现在需要一个方法,可以让我们知道我们推的是哪个箱子:
function getBoxIndex(boxes, i,j){
var index = 0;
for(var k = 0;k<boxes.length;k++){
if(boxes[k].x == i && boxes[k].y == j){
//找到了这个箱子的下标
index = k;
}
}
return index;
} 在实际调用中,里面的参数(i,j)就写下一步要走的那个位置,比如说向左走,就是
var index = getBoxIndex(boxes, i,j-1); 这个index就是我们要找的第i个箱子了,接下来就很好办了,我们按照刚才的逻辑一步一步写,一堆的if、else,只需注意两点,当人踩到目标点时,把person.isOnTarget = true,移开之后false;箱子踩到目标点时boxes[index].isOnTarget = true,移开之后false,然后再整理一下,简化代码量,就是:
//玩家操作
document.onkeydown = function(ev){
var oCan = document.getElementById('can1');
var ctx = oCan.getContext('2d');
var oEvent = ev || event;
var Audio1 = document.getElementById('walk');
var Audio2 = document.getElementById('push');
//接收person的坐标
var position = findPerson();
//i是person的横坐标
var i = position.personX;
//j是person的纵坐标
var j = position.personY;
//使对象的属性和人在二维数组的坐标关联
person.x = i;
person.y = j;
if(oEvent.keyCode == 37){
if(person.isOnTarget){
if(arr1[i][j-1] == 'brick'){
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
person.isOnTarget = false;
}else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){
var index = getBoxIndex(boxes, i,j-1);
if(!boxes[index].isOnTarget){
if(arr1[i][j-2] == 'brick'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
person.isOnTarget = false;
}else if(arr1[i][j-2] == 'target'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
person.isOnTarget = false;
boxes[index].isOnTarget = true;
}
}else if(boxes[index].isOnTarget){
if(arr1[i][j-2] == 'brick'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
boxes[index].isOnTarget = false;
}else if(arr1[i][j-2] == 'target'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
}
}
boxes[index].y--;
}else if(arr1[i][j-1] == 'target'){
arr1[i][j-1] = 'person';
arr1[i][j] = 'target';
}
}else if(!person.isOnTarget){
if(arr1[i][j-1] == 'brick'){
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
}else if(arr1[i][j-1] == 'box' && arr1[i][j-2] != 'wall' && arr1[i][j-2] != 'box'){
var index = getBoxIndex(boxes, i,j-1);
//箱子踩的不是目标点
if(!boxes[index].isOnTarget){
//箱子左边是地面
if(arr1[i][j-2] == 'brick'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
}//箱子左边是目标点
else if(arr1[i][j-2] == 'target'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
boxes[index].isOnTarget = true;
}
}else if(boxes[index].isOnTarget){
if(arr1[i][j-2] == 'brick'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
boxes[index].isOnTarget = false;
person.isOnTarget = true;
}else if(arr1[i][j-2] == 'target'){
arr1[i][j-2] = 'box';
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
person.isOnTarget = true;
}
}
boxes[index].y--;
}else if(arr1[i][j-1] == 'target'){
arr1[i][j-1] = 'person';
arr1[i][j] = 'brick';
person.isOnTarget = true;
}
}
} 这样,向左走的所有逻辑就完成了,然后是上,右,下,接着else if 就行,照猫画虎,把里面的i和j一更换就完成了。
所有逻辑写完,验证无误后,我们要判断通关条件,这个很简单,当所有的箱子都isOnTarget时候,成功,当然是在每次按完方向键之后都要判断:
function judgeWin(boxes){
var count = 0;
for(var p = 0;p<boxes.length;p++){
if(boxes[p].isOnTarget)
count++;
}if(count == boxes.length){
var Audio3 = document.getElementById('win');
Audio3.src = '鼓掌.mp3';
alert('You Win! 一共走了'+countStep+'步');
}
} 通关的画面如下:
最后,我们加入音效,主要划分成以下几类:人走到砖块的,人推箱子的,人碰到墙的(和人推箱子碰到墙的),成功后的掌声。
总结:
1.我开始以为这个很简单,和我之前做的那个flappy bird 差不多,没想到这里面的逻辑其实很复杂,我的flappy bird链接如下:点击打开链接。所以,一定要在刚开始的时候要构思好大局,别越写越麻烦,容易产生放弃心里。
2.利用二维数组存放地图,然后在paint()方法里面写画的位置,大小等,调用的时候paint()里面就填坐标,这样有两个好处:(1)可以知道每个物块的上下左右都是什么,易于判断;(2)新建关卡的时候易于创建,只需要按照坐标位置,把wall,brick,person,target,box放进去即可
3.在创建人的对象后,我们需要在地图数组中把人的坐标找出来,然后将对象的x,y属性和坐标关联;在创建箱子的对象数组后,我们需要getIndex()方法,找到人到底推的是哪个箱子,才能使这个箱子的isOnTarget改成true或者false,这两点很关键。