我大抵是废了φ(..) ,横竖都学不进去,上课知识不进脑子,学习光想划水摸鱼,心中仅剩的良知告诉我这样下去是铁定不行的哇,既然学不进去,何不打把游戏,既然要打游戏,为啥不自己写个小游戏嘞٩(๑>◡<๑)۶?手动狗头。
虽然我有着强烈的制作意愿,但是当初学的unity3D的知识早就还给老师了,而且就我一个人的话,做大型游戏先不说我一个人干不干的过来,首要面对的问题就是,没这个能力你知道吧。于是我想起来了小时候第一次去科技馆,玩的华容道,碰巧之前学过一点canvas,做的话应该是可以做的,虽然我上次用canvas还是在上次,那么......
好了,废话不多说,先写html页面吧。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>小辣鸡毛不会的华容道</title>
<style>
*{
margin: 0 ;
padding: 0 ;
}
</style>
</head>
<body>
<canvas id="canvas" width="240" height="240"></canvas>
<script type="text/javascript" src="./index.js"></script>
</body>
</html>
有一说一,这个html页面是真的简单,毕竟只用了一个canvas标签,也就没啥好说的了对吧,那接下来就上js吧。
我在写html的时候有多轻松,在写js的时候就有多难受。我最初以为写一个华容道是一件很简单的事情,毕竟用canvas画几个方块,然后写上左右移动的函数,写上碰撞检测的函数,写上判断通关的函数,这个游戏也就写完了。但是开始写了才发现,貌似实际情况和我幻想的有那么亿丝丝的不一样,其他几个都好写,就是这个碰撞检测有些难顶。怎么判断对应位置是否有方块?怎么判断墙在何处?要是直接用canvas划线来画方块的话,判断起来当是很困难的,虽然没有细细思考这样做的可行性是多少,但是哪怕能做,也一定是比较复杂的,而我之所以要写这个小游戏,不就是为了放松吗,所以此刻摆在我面前的有两条路,一条是放弃,另一条还是放弃。
当然,放弃是不可能放弃的,因为我想到了一个好办法,那就是用一个数组,来存值,存对应位置是否有方块,以及方块的大小形状都是啥。就好比接下来这个数组。
[
[['张飞','dir'],['曹操','big'],['曹操','big'],['赵云','dir']],
[['张飞','dir'],['曹操','big'],['曹操','big'],['赵云','dir']],
[['马超','dir'],['关羽','bro'],['关羽','bro'],['黄忠','dir']],
[['马超','dir'],['小兵','squ'],['小兵','squ'],['黄忠','dir']],
[['小兵','squ'],[null,null],[null,null],['小兵','squ']],
]
在这个数组里,每一个子数组都代表着一行中的四个方格,这个子数组中的每个子数组,第一个属性是当前方格的棋子名称,以及这个棋子的形状是怎么样的,dir是竖长方形,bro是横长方形,squ是只占当前一个格子的小正方形,big是四个格子之大的大正方形。
既然有了记录当前状态的数组,那么就要开始画棋盘和棋子了,我先定义了棋子的构造函数
function Rect(x, y, width, height, color) {
this.x = x
this.y = y
this.width = width
this.height = height
this.color = color
}
Rect.prototype.draw = function () {
ctx.beginPath()
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.width, this.height)//从x,y处填充矩形,宽度为width,高度为height
ctx.strokeRect(this.x, this.y, this.width, this.height)//从x,y处绘制矩形,宽度为width,高度为height
}
接下来就是画初始的棋盘了,我设置的每一个格子的宽度高度都是20,想要改变棋子大小的同学们记得将四种构造函数对应的数值一同变动一二。对于四种方块的绘制方法,我的思路是在遍历到第一个值的时候,也就是这个棋子的左上角那个方块的时候,绘制整个棋子,同时将棋子其他位置的值置为0,避免重复绘制。写好了绘制四种方块的方法后,我就用了一个双重for循环遍历数组,绘制整张图。最后则是判断是否闯关成功。
drawCheeks = () =>{
ctx.beginPath()
ctx.strokeRect(20,20,80,100)
ctx.stroke()
ctx.closePath()
let temp = JSON.parse(JSON.stringify(cheeks));
let cheekColor = []
cheekColor['张飞'] = 'black';
cheekColor['赵云'] = 'brown';
cheekColor['马超'] = 'silver';
cheekColor['关羽'] = 'red';
cheekColor['黄忠'] = 'yellow';
cheekColor['曹操'] = 'blue';
cheekColor['小兵'] = 'green';
function square(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,20,20,color);
cheek.draw();
temp[i][j] = 0;
}
function Bigsquare(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,40,40,color);
cheek.draw();
temp[i][j] = 0;
temp[i+1][j] = 0;
temp[i][j+1] = 0;
temp[i+1][j+1] = 0;
}
function dirOblang(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,20,40,color);
cheek.draw();
temp[i][j] = 0;
temp[i+1][j] = 0;
}
function broOblang(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,40,20,color);
cheek.draw();
temp[i][j] = 0;
temp[i][j+1] = 0;
}
function drawSqare(tag,i,j,color){
if(tag == 'squ'){
return square(i,j,color);
}else if(tag == 'big'){
return Bigsquare(i,j,color);
}else if(tag == 'dir'){
return dirOblang(i,j,color);
}else if(tag == 'bro'){
return broOblang(i,j,color);
}
}
for(let i=0;i<temp.length;i++){
for(let j=0;j<temp[0].length;j++){
if(temp[i][j] === null){
continue;
}else{
drawSqare(temp[i][j][1],i,j,cheekColor[temp[i][j][0]])
}
}
}
if(temp[3][2][0] == "曹操"){
const con = confirm(`闯关成功,本次共走了${this.grade}步,要进入下一关吗`)
if (con) {
custom++;
cheeks = customs[custom];
drawCheeks();
}
return
}
}
drawCheeks();
棋盘绘制成功后如图所示,emmm,有点简陋,但是无妨,只是没加皮肤罢了,以后有机会一定会加上的_(:3」∠)_。
那接下来就要写点击的函数了,这块也不难,就是根据点击事件来判断点击的区域是否位于棋盘内,位于棋盘内的话则判断选中哪个棋子,相信大家都能看明白,也就不做过多的解释了。
document.addEventListener("click",function(e){
let j = Math.floor((e.pageX-20)/20);
let i = Math.floor((e.pageY-20)/20);
if(i<5 && j<4){
clickIt(i,j)
}
})
clickIt = function(i,j){
if(cheeks[i][j]){
let cheekVal = cheeks[i][j][0];
if(cheeks[i][j][1] == 'dir'){
if(i>0 && cheeks[i-1][j][0]==cheekVal){
now(i-1,j,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}else if(cheeks[i][j][1] == 'bro'){
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}else if(cheeks[i][j][1] == 'big'){
if(i>0 && cheeks[i-1][j][0]==cheekVal){
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i-1,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i-1,j,cheekVal,cheeks[i][j][1]);
}
}else{
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}
}
now = function(i,j,cheekVal,tag){
x = j;
y = i;
val = cheekVal;
cheekTag = tag;
}
接下来就是监听键盘了,准确来说是监听上下左右四个按键,好根据当前选中的方块和按的是哪个按键来做出相应的动作。
document.onkeydown = function (e) {
e = e || window.event
// 左37 上38 右39 下40
switch (e.keyCode) {
case 37:
toLeft();
break
case 38:
toUp();
break
case 39:
toRight();
break
case 40:
toDown();
break
}
}
监听完键盘事件,就要写执行对应动作的函数了,这里四个函数大体上是相同的,我就以向左移动的函数为例说说吧。这块代码我写的其实不好,毕竟使用了过多ifelse,如果以后去工作了的话大概率会被测试同学打,但是谁叫我现在还在上学嘞(灬°ω°灬),本来其实是想要多写几个函数优化一下代码结构的,但是吧,不想写了。。。。
这串代码是根据选中位置,因为前面处理点击的时候已经自动将点击位置转换为点击的棋子的左上角了,因此这里只需要判断当前棋子是否是最左侧那一列的棋子,是的话无法移动,否则判断该棋子若左移的话是否会碰到其他棋子,会的话则无法移动。要是可以移动的话,就对储存棋盘状态的数组进行处理,然后重新执行绘制函数。
toLeft = () =>{
if(x!=0){
tag = cheekTag;
if(tag == 'bro'){
if(cheeks[y][x-1][0] === null){
cheeks[y][x+1] = [null,null];
cheeks[y][x-1] = [val,tag];
}
}else if(tag == 'dir'){
if(cheeks[y][x-1][0] === null && cheeks[y+1][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y+1][x-1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y+1][x] = [null,null];
}
}else if(tag == 'big'){
if(cheeks[y][x-1][0] === null && cheeks[y+1][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y+1][x-1] = [val,tag];
cheeks[y][x+1] = [null,null];
cheeks[y+1][x+1] = [null,null];
}
}else if(tag == 'squ'){
if(cheeks[y][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y][x] = [null,null];
}
}
console.log(cheeks)
ctx.clearRect(0, 0, canvas.width, canvas.height)
grade++;
drawCheeks();
}
}
js的全部代码如下,目前只在关卡的数组里写了三关,即便如此我目前还是一关也没有通过......
let x,y,val,cheekTag,grade=0,custom = 0;
play()
function play() {
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let customs = [
[
[['张飞','dir'],['曹操','big'],['曹操','big'],['小兵','squ']],
[['张飞','dir'],['曹操','big'],['曹操','big'],['小兵','squ']],
[['马超','dir'],['赵云','dir'],['黄忠','dir'],['小兵','squ']],
[['马超','dir'],['赵云','dir'],['黄忠','dir'],['小兵','squ']],
[[null,null],['关羽','bro'],['关羽','bro'],[null,null]],
],
[
[['赵云','dir'],['曹操','big'],['曹操','big'],['小兵','squ']],
[['赵云','dir'],['曹操','big'],['曹操','big'],['小兵','squ']],
[['关羽','bro'],['关羽','bro'],['小兵','squ'],['小兵','squ']],
[['马超','dir'],['张飞','bro'],['张飞','bro'],['黄忠','dir']],
[['马超','dir'],[null,null],[null,null],['黄忠','dir']],
],
[
[['张飞','dir'],['曹操','big'],['曹操','big'],['赵云','dir']],
[['张飞','dir'],['曹操','big'],['曹操','big'],['赵云','dir']],
[['马超','dir'],['关羽','bro'],['关羽','bro'],['黄忠','dir']],
[['马超','dir'],['小兵','squ'],['小兵','squ'],['黄忠','dir']],
[['小兵','squ'],[null,null],[null,null],['小兵','squ']],
]
]
let cheeks = customs[custom];
function Rect(x, y, width, height, color) {
this.x = x
this.y = y
this.width = width
this.height = height
this.color = color
}
Rect.prototype.draw = function () {
ctx.beginPath()
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.width, this.height)//从x,y处填充矩形,宽度为width,高度为height
ctx.strokeRect(this.x, this.y, this.width, this.height)//从x,y处绘制矩形,宽度为width,高度为height
}
x=0,y=0,val=1;//默认选中左上角的方块
drawCheeks = () =>{
ctx.beginPath()
ctx.strokeRect(20,20,80,100)
ctx.stroke()
ctx.closePath()
let temp = JSON.parse(JSON.stringify(cheeks));
let cheekColor = []
cheekColor['张飞'] = 'black';
cheekColor['赵云'] = 'brown';
cheekColor['马超'] = 'silver';
cheekColor['关羽'] = 'red';
cheekColor['黄忠'] = 'yellow';
cheekColor['曹操'] = 'blue';
cheekColor['小兵'] = 'green';
function square(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,20,20,color);
cheek.draw();
temp[i][j] = 0;
}
function Bigsquare(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,40,40,color);
cheek.draw();
temp[i][j] = 0;
temp[i+1][j] = 0;
temp[i][j+1] = 0;
temp[i+1][j+1] = 0;
}
function dirOblang(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,20,40,color);
cheek.draw();
temp[i][j] = 0;
temp[i+1][j] = 0;
}
function broOblang(i,j,color){
var cheek = new Rect(20+20*j,20+20*i,40,20,color);
cheek.draw();
temp[i][j] = 0;
temp[i][j+1] = 0;
}
function drawSqare(tag,i,j,color){
if(tag == 'squ'){
return square(i,j,color);
}else if(tag == 'big'){
return Bigsquare(i,j,color);
}else if(tag == 'dir'){
return dirOblang(i,j,color);
}else if(tag == 'bro'){
return broOblang(i,j,color);
}
}
for(let i=0;i<temp.length;i++){
for(let j=0;j<temp[0].length;j++){
if(temp[i][j] === null){
continue;
}else{
drawSqare(temp[i][j][1],i,j,cheekColor[temp[i][j][0]])
}
}
}
if(temp[3][2][0] == "曹操"){
const con = confirm(`闯关成功,本次共走了${this.grade}步,要进入下一关吗`)
if (con) {
custom++;
cheeks = customs[custom];
drawCheeks();
}
return
}
}
drawCheeks();
document.addEventListener("click",function(e){
let j = Math.floor((e.pageX-20)/20);
let i = Math.floor((e.pageY-20)/20);
if(i<5 && j<4){
clickIt(i,j)
}
})
clickIt = function(i,j){
if(cheeks[i][j]){
let cheekVal = cheeks[i][j][0];
if(cheeks[i][j][1] == 'dir'){
if(i>0 && cheeks[i-1][j][0]==cheekVal){
now(i-1,j,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}else if(cheeks[i][j][1] == 'bro'){
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}else if(cheeks[i][j][1] == 'big'){
if(i>0 && cheeks[i-1][j][0]==cheekVal){
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i-1,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i-1,j,cheekVal,cheeks[i][j][1]);
}
}else{
if(j>0 && cheeks[i][j-1][0]==cheekVal){
now(i,j-1,cheekVal,cheeks[i][j][1]);
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}
}else{
now(i,j,cheekVal,cheeks[i][j][1]);
}
}
}
now = function(i,j,cheekVal,tag){
x = j;
y = i;
val = cheekVal;
cheekTag = tag;
}
document.onkeydown = function (e) {
e = e || window.event
// 左37 上38 右39 下40
switch (e.keyCode) {
case 37:
toLeft();
break
case 38:
toUp();
break
case 39:
toRight();
break
case 40:
toDown();
break
}
}
toLeft = () =>{
if(x!=0){
tag = cheekTag;
if(tag == 'bro'){
if(cheeks[y][x-1][0] === null){
cheeks[y][x+1] = [null,null];
cheeks[y][x-1] = [val,tag];
}
}else if(tag == 'dir'){
if(cheeks[y][x-1][0] === null && cheeks[y+1][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y+1][x-1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y+1][x] = [null,null];
}
}else if(tag == 'big'){
if(cheeks[y][x-1][0] === null && cheeks[y+1][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y+1][x-1] = [val,tag];
cheeks[y][x+1] = [null,null];
cheeks[y+1][x+1] = [null,null];
}
}else if(tag == 'squ'){
if(cheeks[y][x-1][0] === null){
cheeks[y][x-1] = [val,tag];
cheeks[y][x] = [null,null];
}
}
console.log(cheeks)
ctx.clearRect(0, 0, canvas.width, canvas.height)
grade++;
drawCheeks();
}
}
toRight = () =>{
if(x!=3){
tag = cheekTag;
if(tag == 'bro' && x <2){
if(cheeks[y][x+2][0] === null){
cheeks[y][x] = [null,null];
cheeks[y][x+2] = [val,tag];
}
}else if(tag == 'dir'){
if(cheeks[y][x+1][0] === null && cheeks[y+1][x+1][0] === null){
cheeks[y][x+1] = [val,tag];
cheeks[y+1][x+1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y+1][x] = [null,null];
}
}else if(tag == 'big' && x <2){
if(cheeks[y][x+2][0] === null && cheeks[y+1][x+2][0] === null){
cheeks[y][x+2] = [val,tag];
cheeks[y+1][x+2] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y+1][x] = [null,null];
}
}else{
if(cheeks[y][x+1][0] === null){
cheeks[y][x+1] = [val,tag];
cheeks[y][x] = [null,null];
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height)
grade++;
drawCheeks();
}
}
toUp = () =>{
if(y!=0){
tag = cheekTag;
if(tag == 'dir'){
if(cheeks[y-1][x][0] === null){
cheeks[y+1][x] = [null,null];
cheeks[y-1][x] = [val,tag];
}
}else if(tag == 'bro'){
if(cheeks[y-1][x][0] === null && cheeks[y-1][x+1][0] === null){
cheeks[y-1][x] = [val,tag];
cheeks[y-1][x+1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y][x+1] = [null,null];
}
}else if(tag == 'big'){
if(cheeks[y-1][x][0] === null && cheeks[y-1][x+1][0] === null){
cheeks[y-1][x] = [val,tag];
cheeks[y-1][x+1] = [val,tag];
cheeks[y+1][x] = [null,null];
cheeks[y+1][x+1] = [null,null];
}
}else{
if(cheeks[y-1][x][0] === null){
cheeks[y-1][x] = [val,tag];
cheeks[y][x] = [null,null];
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height)
grade++;
drawCheeks();
}
}
toDown = () =>{
if(y!=4){
tag = cheekTag;
if(tag == 'dir'){
if(y<3 && cheeks[y+2][x][0] === null){
cheeks[y][x] = [null,null];
cheeks[y+2][x] = [val,tag];
}
}else if(tag == 'bro'){
if(cheeks[y+1][x][0] === null && cheeks[y+1][x+1][0] === null){
cheeks[y+1][x] = [val,tag];
cheeks[y+1][x+1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y][x+1] = [null,null];
}
}else if(y<3 && tag == 'big'){
if(cheeks[y+2][x][0] === null && cheeks[y+2][x+1][0] === null){
cheeks[y+2][x] = [val,tag];
cheeks[y+2][x+1] = [val,tag];
cheeks[y][x] = [null,null];
cheeks[y][x+1] = [null,null];
}
}else{
if(cheeks[y+1][x][0] === null){
cheeks[y+1][x] = [val,tag];
cheeks[y][x] = [null,null];
}
}
ctx.clearRect(0, 0, canvas.width, canvas.height)
grade++;
drawCheeks();
}
}
}
做的很简陋,玩的时候每次点击方块,移动方块后都要重新点击,导致游戏体验很差。虽说知道要如何去改进,但是现在又突然间有了学习的动力,改进这事,以后有机会再说吧。
结果视频:
1679730629453