用js每一秒改一个颜色_用p5.js来制作一个小恐龙游戏

8bae98d02968b42fe6fe2a1957b69172.gif

0fdd5875b343db380915371b9f0c52af.gif

如果你用的是谷歌浏览器,一定对这只小恐龙很熟悉了。没有网络的时候,它就会出现。根据游戏设计者的说法,在今天这个互联网的时代,如果断网了,就如同是回到了原始的恐龙时代。

485acb593f684c3e13374a9be19c95a6.gif
谷歌浏览器无网络时的恐龙游戏

接下来,我们就用p5.js来制作一个类似的恐龙小游戏。这里会继续使用面向对象的编程方法,也会用到类的概念。

第一步:添加文件

我们会发现这个游戏主要由三个部分组成。一个是我们可以控制的小恐龙,另一个是不断随机出现的障碍物,还有一个是不住移动的背景。背景可以最后再添加,我们先处理小恐龙和障碍物的部分。

在这里先添加两个JavaScript文件,一个是恐龙的dinosaur.js文件,另一个是障碍物仙人掌的cactus.js文件。具体操作是点击Sketch(草图),再点击Add File(添加文件),输入文件名,再点击Add File 按钮。

fc546cb7cffefc862e8e266c1c283bd7.png

然后,我们就会在左侧的草图文件管理器(Sketch Files)中看到这两个js文件。

c5d7375125dca4718b7edb568a0bd199.png

然后需要把这两个js文件引用到html文件中,只有这样,在程序开始时它们才能够一起运行。打开html文件,我们会发现在body部分中已经有了引用sketch.js的语句,我们在这条语句下面再添加两条语句,如下:

<body>
    <script src="sketch.js"></script>
    <script src="dinosaur.js"></script>
    <script src="cactus.js"></script>
</body>

第二步:编写恐龙部分的代码

打开dinosaur.js文件。新建一个恐龙Dinosaur的类,并定义它的属性和方法。代码如下:

class Dinosaur{               //定义一个名为Dinosaur的类,这个类定义了恐龙的一些属性和方法
     constructor(){           //构造函数,通过它可以定义和设置类的一些属性
     this.w =50;              //定义类的宽,并设置它的初始值为50
     this.h =80;              //定义类的高,并设置它的初始值为80
     this.x =80;              //定义类的横坐标,并设置它的初始值为80
     this.y =height-this.h;   //定义类的纵坐标,并设置它的初始值,使它的底部与画布的底部对齐
     this.vy=0;               //定义类的垂直方向的速度,并设置它的初始值为0
     this.gravity = 1;        //定义类的重力参数,并设置它的初始值为1 
     }  

  jump(){                     //定义恐龙类的跳跃方法
  if(this.y ==height-this.h){ //只有恐龙的脚在地面上,才能够跳跃。否则的话,在半空中也能跳跃了
     this.vy= -18;            //使恐龙有垂直向上的起跳初速度
    }
  }

  move(){                                       //定义恐龙类的移动方法
  this.y += this.vy;                            //使恐龙有竖直方向的位移,移动的初速度是起跳初速度
  this.vy += this.gravity;                      //使恐龙的垂直速度加上重力加速度
  this.y = constrain(this.y, 0, height-this.h); //使恐龙在竖直方向上,不会超出屏幕的范围
  }

  show(){                                       //定义恐龙类的显示方法
     rect(this.x,this.y,this.w,this.h);         //先用长方形来代替恐龙,稍后替换为恐龙的图片
  } 
}

这里是通过构造函数设置它的属性。恐龙类的属性有它的长,宽,横坐标,纵坐标,竖直方向的速度和重力参数。恐龙类的方法有跳跃,移动和显示。

这里的恐龙与之前做的乒乓球游戏中的乒乓球很相似,它们的垂直速度都会受到重力的影响。

接着编写主程序的代码,打开sketch.js文件。定义一些需要用到的变量,定义一个恐龙类的对象,调用恐龙类的方法来绘制出恐龙,并且通过键盘来控制它。以下是代码:

function setup() {            //设置部分
  createCanvas(800, 400);     //设置画布大小
  dinosaur = new Dinosaur();  //定义一个名为dinosaur的对象,这个对象是属于恐龙类(Dinosaur类)
}

function draw() {             //绘图部分
  background(220);            //设置背景颜色为灰色
  dinosaur.move();            //通过调用恐龙类的移动方法,将恐龙能够移动
  dinosaur.show();            //通过调用恐龙类的显示方法,将恐龙显示出来
}

function keyPressed(){        //定义一个按键的事件,如果按下键盘的键,就调用此函数
   if(key == ' '){            //如果按下的键是空格键
    dinosaur.jump();          //通过调用恐龙类的跳跃方法,使恐龙跳跃起来
   }
}

此时,如果按下空格键,恐龙就会跳起来,然后因为重力的作用,它会再落下来。如下图:

e0e4d4b944760387cb5131b4a19aa0fa.gif

第三步:编写仙人掌部分的代码

打开cactus.js文件。新建一个仙人掌Cactus的类,并定义它的属性和方法。代码如下:

class Cactus{                  //定义一个名为Cactus的类,这个类定义了仙人掌的一些属性和方法
     constructor(){            //构造函数,通过它可以定义和设置类的一些属性
       this.w=50               //定义类的宽,并设置它的初始值为50
       this.h=90;              //定义类的高,并设置它的初始值为90
       this.x= width;          //定义类的横坐标,并设置它的初始值为画布的宽度
       this.y= height -this.h; //定义类的纵坐标,并设置它的初始值,使它的底部与画布的底部对齐
     }
  move(){                              //定义仙人掌类的移动方法
   this.x -=8;                         //使仙人掌有水平方向的位移,始终以固定的速度向左移动

  }
  show(){                              //定义仙人掌类的显示方法
   rect(this.x,this.y,this.w,this.h);  //先用长方形来代替仙人掌,稍后替换为仙人掌的图片
  }
}

仙人掌类的属性有它的长,宽,横坐标,纵坐标。仙人掌类的方法有移动和显示。这里的仙人掌与之前做的乒乓球游戏中的板子很相似。它们都会一直朝着一个方向匀速的移动,给人产生一种错觉,觉的是乒乓球和恐龙在移动。

接着在sketch.js文件中,添加仙人掌的代码。定义一个存储仙人掌类的数组,定义一个仙人掌类的对象,隔一段时间在数组中添加一个仙人掌的对象。在绘制部分,调用仙人掌类的方法来让仙人掌移动和显示。以下是代码:

var lastAddTime = 0;          //定义记录上次增加障碍物的时间的变量,并且设置初值为0
let cactuses=[];              //定义一个类的数组,用来存储多个仙人掌的类
function setup() {            //设置部分
 // ...
  cactus = new Cactus();      //定义一个名为cactus的对象,这个对象是属于仙人掌类(Cactus类)
}

function draw() {                         //绘图部分
 var interval=random(700,5000);           //设置前后两个仙人掌出现的时间间隔是700到5000毫秒中的一个随机数  
 if (millis()-lastAddTime > interval) {   //如果当前时间与上次添加仙人掌类的时间,相差超过一个时间间隔,就增加一个新的仙人掌类
     cactuses.push(new Cactus());         //将这个新的仙人掌类添加到仙人掌类的数组中
     lastAddTime = millis();              //将上次增加障碍物的时间设置为当前的时间
  }

   for(let c of cactuses){                //使用for循环,使仙人掌类数组中的每一个仙人掌对象移动和显示
     c.move();                            //调用仙人掌类的移动方法
     c.show();                            //调用仙人掌类的显示方法
   }
}

这时运行程序后,结果如下:

cff532ed81a2f127a62dcf07d3a2ba12.gif

第四步:添加碰撞检测

我们可以看到现在恐龙和仙人掌相遇后只是路过,并没有碰撞。要想让它们发生碰撞,可以使用p5.js的一个名为p5.collide2D的附加函数库(library),在这个函数库中有一系列用来检测2维几何形状碰撞的函数。我们只需要引入这个函数库,然后调用里面的函数就可以检测出两个图形是否发生了碰撞,而不必自己重写这些函数。

打开p5.js的官网,点击菜单栏中的程式库。可以在附加程式库中找到p5.collide2D。

71d0372b7b1aee5c5f04c0a49ef25960.png

打开这个函数库,我们会发现里面有很多种检测图形碰撞的函数。在这个恐龙小游戏中,可以把恐龙和仙人掌看作是两个矩形,因此我们选择其中用来检测两个矩形碰撞的 collideRectRect()这个函数。它有八个参数,分别是两个矩形的横坐标、纵坐标、宽和高。如果两个矩形发生了碰撞,这个函数的逻辑值为真,否则为假。

下面我们就要把p5.collide2D这个函数库引用到我们的代码中。一种方法是把这个函数库下载后,添加到代码中。另一种是把这个函数库的链接引入代码中。这里我们用第二种方法。首先要生成函数库的链接。通过jsDelivr这个免费的函数库托管网站,我们可以把这个在GitHub上的函数库存储在jsDelivr的服务器上。具体操作是先打开 p5.collide2d.min.js

这个函数库文件,我们在文件的开头会看到这些介绍的信息。

/*
Repo: https://github.com/bmoren/p5.collide2D/
Created by http://benmoren.com
Some functions and code modified version from http://www.jeffreythompson.org/collision-detection
Version 0.6 | Nov 28th, 2018
CC BY-NC-SA 4.0
*/


我们重点关注其中的两个信息:Rope和Version。根据jsdelivr网站对GitHub中的函数库的书写规则,我们可以写出这个文件的网络地址:

https://cdn.jsdelivr.net/gh/bmoren/p5.collide2D@0.6/p5.collide2d.min.js

然后把这个链接引入到html文件的head标签中:

<head>
  ...
    <script src="https://rinovethamoses97.github.io/Chrome-Dino-Game/public/collide2d.js"></script>
  ...
</head>

接下来要给恐龙类添加一个碰撞检测的方法。通过它来判断两者是否发生碰撞。

class Dinosaur{               //定义一个名为Dinosaur的类,这个类包括了恐龙的一些属性和方法
  ...
  hits(cactus){               //定义恐龙类的碰撞检测方法
  return collideRectRect(this.x, this.y, this.w, this.h, cactus.x, cactus.y, cactus.w, cactus.h); //返回两个矩形是否碰撞的检测结果的逻辑值,发生碰撞为真,未发生碰撞为假
  }
}

接着在sketch.js文件中,添加碰撞检测的代码。我们需要检测恐龙与出现的每一个仙人掌对象是否发生了碰撞,一旦与其中的一个发生碰撞,就结束游戏。因此要对仙人掌类数组中的每个对象都进行判断,只需要在for循环语句中添加以下代码:

  for(let c of cactuses){       //使仙人掌类数组中的每一个仙人掌对象移动和显示
     //...
     if(dinosaur.hits(c)){      //判断恐龙是否与仙人掌对象发生碰撞,如果发生碰撞
      textAlign(CENTER);        //设置文字的对齐方式为居中对齐  
      textSize(70);             //设置文字的大小为70
      text("game over",width/2, height/2);//在屏幕中央输出文字“game over”
      noLoop();                           //设置为不再循环,也就是停止在画布上绘制,画面静止
    }

这时,再次运行程序,就会看到这样的结果:

4290d9932ca60428de9ca6fe28ab6422.gif

当两个矩形相遇后,会发生碰撞,并且游戏结束。

第五步:添加恐龙和仙人掌的图片

首先准备几张恐龙的图片

d3e35ae260513fd19a1360c1cbf93561.png

然后把恐龙的图片添加到程序的文件管理器中。为了便于管理文件,我们可以先添加一个文件夹,具体操作是点击Sketch(草图),再点击Add Folder(添加文件夹),输入文件夹名称,再点击Add Folder按钮。

1fd8e2012b20f83ecae9fd11eda0e404.png

把这五张图片添加到这个文件夹中。接着在sketch.js文件中,新建一个图片的数组,把这些图片加载到数组中。再新建一个用来记录是否发生碰撞的变量,一旦发生碰撞就显示第五张图片。最后调用恐龙类的方法,使恐龙在画布上显示出来。

//...
var imgDino= [];              //定义一个恐龙图片的数组,用来存放一些恐龙的图片
var cld =0;                   //定义一个变量,用来判断恐龙和障碍物是否发生了碰撞,值为0表示没有发生碰撞
var index=1;                  //定义一个变量作为指示器,来控制显示不同的图片

function draw() {                         //绘图部分
// ...
 if (frameCount%6==0) index++;            //设置恐龙动画的快慢
  for(let c of cactuses){                 //使仙人掌类数组中的每一个仙人掌对象移动和显示
     ...
     if(dinosaur.hits(c)){                //判断恐龙是否与仙人掌对象发生碰撞,如果发生碰撞
      cld=1;                              //将变量的值设为1,表示已经发生了碰撞
     ...
     }
   }
  dinosaur.update();                      //通过调用恐龙类的更新方法,将恐龙的图片设置为它当前状态的图片
  //... 
}
function preload(){                     //预加载函数,通过它把图片加载到数组中
  for (let i=1; i<=5; i++) {            //使用for循环,把这些恐龙图片依次加载到恐龙数组中
    var str1 ="dinosaur/dino"+i+".png"; //定义一个字符变量,并将恐龙图片的名字的字符串赋值给它
    imgDino[i]= loadImage(str1);        //将这个名字的图片加载到数组中
  }
}


然后修改恐龙类的代码,添加一个图片的属性,把恐龙的宽和高设置为图片的宽和高。添加一个恐龙类的更新的方法,使恐龙的图片能够变化,产生动画的效果。最后修改恐龙类的显示方法,不再显示矩形,而是把图片显示出来。

class Dinosaur{               //定义一个名为Dinosaur的类,这个类定义了恐龙的一些属性和方法
     constructor(){           //构造函数,通过它可以定义和设置类的一些属性
     this.img= imgDino[3];    //定义类的图像,并设置它的初始值为图3
     this.w = this.img.width; //定义类的宽度,并设置宽度值为它的图像的宽度
     this.h =this.img.height; //定义类的高度,并设置高度值为它的图像的高度
     //... 
     }
  update(){                                     //定义恐龙的更新方法,把恐龙设置为当前状态的图片
     if(cld==0){                                //如果没有发生碰撞
       if(this.y<height-100){                   //并且恐龙离开地面
         this.img = imgDino[2];                 //使恐龙显示为跳跃的图2
         }
       else {                                   //如果恐龙没有离开地面
          this.img = imgDino[index%2+3];        //使恐龙显示为行走的图3和图4
       } 
     }  
     else{                                      //如果发生了碰撞
       this.img = imgDino[5];                   //使恐龙显示为发生碰撞的图5
     }
  }

   show(){                                          //定义恐龙类的显示方法
    image(this.img,this.x,this.y-20,this.w*1.5,this.h*1.5);  //显示当前状态恐龙的图片
  } 

运行代码后,可以看到恐龙动起来了,并且在跳跃和碰撞时,会显示不同的图片。

9c4a611a4d854e86d746e8584da4c6a2.gif

这里的恐龙和之前制作的赛马动画的马很像,使用的显示方法也相同,都是通过多张图片连续播放,实现动画效果,并且在不同的条件下,显示不同的图片。

接着添加仙人掌的图片

0d675ae7d256602005a11a44f38cf734.png


在主程序sketch.js文件中,新建一个图片的数组,并把这些图片加载到数组中。

var imgCactus= [];              //定义一个仙人掌图片的数组,用来存放一些仙人掌的图片
//...

function preload(){                     //预加载函数,通过它把图片加载到数组中
//...

 for (let j=1; j<=12; j++) {             //使用for循环,把这些仙人掌的图片依次加载到仙人掌数组中
    var str2 ="cactus/cactus"+j+".png";  //定义一个字符变量,并将仙人掌图片的名字的字符串赋值给它
    imgCactus[j]= loadImage(str2);       //将这个名字的图片加载到数组中
  }
}

然后修改仙人掌类的代码,添加一个图片的属性,把仙人掌的宽和高设置为图片的宽和高。最后修改仙人掌类的显示方法,不再显示矩形,而是把仙人掌数组中的每个仙人掌都依次在画布上显示出来。

class Cactus{                          //定义一个名为Cactus的类,这个类定义了仙人掌的一些属性和方法
     constructor(){                    //构造函数,通过它可以定义和设置类的一些属性
       this.img= imgCactus[int(random(1,12))]; //定义类的图像,并设置它的初始值为12张图中的随机的一张
       this.w = this.img.width*1.5;    //定义类的宽度,并设置宽度值为它的图像的宽度的1.5倍
       this.h =this.img.height*1.5;    //定义类的高度,并设置高度值为它的图像的高度的1.5倍
       ...
     }

  show(){                              //定义仙人掌类的显示方法 
     image(this.img,this.x,this.y+5 ,this.w,this.h); //显示当前的仙人掌图片
  }
}

这时运行代码后,可以看到有不同的仙人掌不断随机出现。

4a41de26255c694a50730410af958539.gif

现在控制恐龙跳跃的是空格键,为了方便在手机上运行,我们需要再增加一个鼠标点击事件,来模拟手指点击屏幕的事件。在sketch.js文件中,添加下面几条语句。

function mouseClicked() { //定义一个鼠标点击的事件,如果单击鼠标左键,就调用此函数
    dinosaur.jump();      //通过调用恐龙类的跳跃方法,使恐龙跳跃起来
}

第六步:添加翼龙的部分

1. 添加翼龙的图片

a9e2c8f9456b4b35a7d2a71426a19e79.png

翼龙和仙人掌都属于障碍物,代码也比较相似。区别主要是翼龙有动画的效果,而仙人掌是静止的。在主程序sketch.js文件中,新建一个翼龙图片的数组,并把这些图片加载到数组中。

var imgBird= [];                        //定义一个翼龙图片的数组,用来存放一些翼龙的图片
//...
function preload(){                     //预加载函数,通过它把图片加载到数组中
//...
  for (let k=1; k<=2; k++) {            //使用for循环,把这些翼龙的图片依次加载到翼龙数组中
    var str3 ="bird/bird"+j+".png";     //定义一个字符变量,并将翼龙图片的名字的字符串赋值给它
    imgBird[k]= loadImage(str3);        //将这个名字的图片加载到数组中
  }
}

2. 添加翼龙的js文件,定义翼龙的类

在Sketch Files中添加一个翼龙的的bird.js文件,并把这个文件引用到index.html文件中。然后在bird.js文件中,新建一个名为bird的类,定义它的属性和方法。代码如下:

class Bird{                                    //定义一个名为Bird的类,这个类定义了翼龙的一些属性和方法
    constructor(){                             //构造函数,通过它可以定义和设置类的一些属性
       this.img= imgBird[1];                   //定义类的图像,并设置它的初始值为图一
       this.w= this.img.width*1.5;             //定义类的宽度,并设置宽度值为它的图像的宽度的1.5倍
       this.h= this.img.height*1.5;            //定义类的高度,并设置高度值为它的图像的高度的1.5倍
       this.x= width;                          //定义类的横坐标,并设置它的初始值为画布的宽度
       this.y= random(2*height/3,5*height/6);  //定义类的纵坐标,并设置它的初始值为一个随机值
     }

     move(){               //定义仙人掌类的移动方法
        this.x -=9;        //使仙人掌有水平方向的位移,始终以固定的速度向左移动
     }

     show(){                                                   //定义翼龙类的显示方法
        image(imgBird[index%2+1],this.x,this.y,this.w,this.h); //显示当前翼龙的图片
     }

     update(){                         //定义翼龙的更新方法,把翼龙设置为当前状态的图片
        this.img = imgBird[index%2+1]; //使恐龙的图片为图3和图4交替显示
     } 
}

3.在主程序中添加翼龙的代码

定义一个存储翼龙类的数组,再定义一个翼龙类的对象,每隔一段时间在翼龙数组中添加一个翼龙的对象或者在仙人掌数组中添加一个仙人掌对象。为了增加代码的可读性,可以新建一个添加障碍物的函数,来控制添加仙人掌对象和翼龙对象。在绘制部分,调用翼龙类的方法来让翼龙移动和显示。并且添加碰撞检测,如果恐龙和翼龙发生碰撞,则游戏结束。以下是代码:

//...
let birds =[];                //定义一个翼龙类的数组,用来存储多个翼龙的类
//...
function setup() {            //设置部分
//...
  bird = new Bird();          //定义一个名为bird的对象,这个对象是属于翼龙类(Cactus类)
}
function draw() {                        //绘图部分
//...
 addObstacle();                          //调用添加障碍物的函数
 for(let b of birds){                    //使翼龙类数组中的每一个翼龙对象移动和显示
     b.update();                          //调用翼龙类的更新方法
     b.move();                            //调用翼龙类的移动方法
     b.show();                            //调用翼龙类的显示方法

    if(dinosaur.hits(b)){                 //判断恐龙是否与翼龙对象发生碰撞,如果发生碰撞
      cld=1;                              //将变量的值设为1,表示已经发生了碰撞
      textAlign(CENTER);                  //设置文字的对齐方式为居中对齐  
      textSize(70);                       //设置文字的大小为70像素
      text("game over",width/2, height/2);//在屏幕中央输出文字“game over”
      noLoop();                           //设置为不再循环,也就是停止在画布上绘制,画面静止
    }
//...
  }
}
function addObstacle(){                    //定义增加障碍物的函数
  var interval=random(800,4000);           //设置前后两个仙人掌出现的时间间隔是700到4000毫秒中的一个随机数 
  if (millis()-lastAddTime > interval) {   //如果当前时间与上次添加仙人掌类的时间,相差超过一个时间间隔,就增加一个新的仙人掌类

     if (int(interval)%2==0){              //如果时间间隔取整后可以被2整除
        cactuses.push(new Cactus());       //添加一个新的仙人掌对象到仙人掌类的数组中
     }
     else{                                 //如果时间间隔取整后不可以被2整除
       birds.push(new Bird());             //添加一个新的翼龙对象到翼龙类的数组中
     }
      lastAddTime = millis();              //将上次增加障碍物的时间设置为当前的时间
  }
}


运行代码后,可以看到会一直随机出现仙人掌和翼龙这样的障碍物。

9f3f08bac35c46ab206671db43ebd780.gif

第七步:添加背景图片

4c29937b518bc54fdce1088587856cad.png
云彩图片

b63fec2a637ef2ccc5d653f62455cfdc.png
山的图片

a45a21e8f7e3d94068f374a247b75350.png
地面图片

这三张图片作为游戏的背景图片。在游戏开始后,他们都会从右向左移动,给人一种恐龙向右跑的错觉。为了使背景图片可以无限循环地从右向左移动,这里用到一个小技巧。就是把原来的图片复制一份,然后拼接起来作为背景图片。这样背景图片的左半部分和右半部分是相同的,它的宽度是画布宽度的两倍。程序开始时,最初画布上是背景图片的左半部分,然后图片向左移动,直到它的左半部分完全移出画布,此时画布上是背景图片的右半部分,接着就让背景图片再次回到原来的位置,因为左右两部分完全相同,这样就可以无缝的衔接,实现背景图片一直从右向左移动的效果。

在主程序sketch.js文件中,新建云彩,山,地面图片的变量,并把这些图片加载到相应的变量中。还要新建这些图片位置的横坐标的变量,再新建一个背景图片的函数,通过控制图片的横坐标变量来控制图片的移动。最后在draw函数中添加这个背景图片的函数。

//...
var imgGround;                //定义一个地面图片的变量
var imgMountoun;              //定义一个山的图片的变量
var imgCloud;                 //定义一个云彩图片的变量
var groundX=0;                //定义地面图片位置的横坐标变量,并设置初值为0
var mountounX=0;              //定义大山图片位置的横坐标变量,并设置初值为0
var cloudX=0;                 //定义云彩图片位置的横坐标变量,并设置初值为0
//...
function preload(){                        //预加载函数,通过它把图片加载到数组中
  imgGround = loadImage("ground.png");     //将地面的图片加载到地面图片的变量中
  imgCloud = loadImage("cloud.png");       //将云彩的图片加载到云彩图片的变量中
  imgMountoun = loadImage("mountoun.png"); //将大山的图片加载到大山图片的变量中
//...
}
function backGroundPicture(){              //定义一个背景图片的函数
   if(groundX>(-1)*(imgGround.width)/2){   //如果图片的左半部分在画布上
    groundX -= 8;                          //就使图片的横坐标减少一定的数值,实现图片向左移动
   }
    else    groundX=0;                     //如果图片的左半部分完全离开了画布,就使图片回到初始的位置 
   image(imgGround,groundX,height-20,imgGround.width,imgGround.height);  //显示地面的图片

   if(cloudX>(-1)*(imgCloud.width)/2){     //如果图片的左半部分在画布上
    cloudX -= 1;                           //就使图片的横坐标减少一定的数值,实现图片向左移动
   }
    else   cloudX=0;                       //如果图片的左半部分完全离开了画布,就使图片回到初始的位置
   image(imgCloud,cloudX,50,imgCloud.width,imgCloud.height);  //显示云彩的图片

  if(mountounX>(-1)*(imgMountoun.width)/2){ //如果图片的左半部分在画布上
    mountounX -= 0.3;                       //就使图片的横坐标减少一定的数值,实现图片向左移动
   }
    else   mountX=0;                        //如果图片的左半部分完全离开了画布,就使图片回到初始的位置
   image(imgMountoun,mountounX,60,imgMountoun.width,imgMountoun.height);  //显示大山的图片
}
//...
function draw() {                          //绘图部分
//...
 backGroundPicture();                    //调用背景图片的函数,将背景图片显示出来
//...
}

此时运行代码,就可以看到背景图片会一直从右向左移动,给人一种恐龙在向右奔跑的错觉。

8685a5c0d28a088f1a3dc7da22209482.gif

第八步:添加游戏得分

给游戏设置得分,恐龙每次通过一个障碍物,就得一分。可以通过给恐龙类添加一个得分的方法来实现。还需要给仙人掌类和翼龙类添加一个分数的属性。

class Cactus{          //定义一个名为Cactus的类,这个类定义了仙人掌的一些属性和方法
    constructor(){     //构造函数,通过它可以定义和设置类的一些属性
       //...
       this.score =0;  //定义类的得分,设置初始值为0,用来记录恐龙是否经过了这个仙人掌
     }   
}
class Bird{              //定义一个名为Bird的类,这个类定义了翼龙的一些属性和方法
    constructor(){       //构造函数,通过它可以定义和设置类的一些属性
       //...
       this.score =0;    //定义类的得分,设置初始值为0,用来记录恐龙是否经过了这个翼龙
     }   
}
class Dinosaur{               //定义一个名为Dinosaur的类,这个类定义了恐龙的一些属性和方法
  //...
  addScore(obstacle){                                              //定义恐龙类的得分方法
      if((obstacle.x+obstacle.w<this.x)&&(obstacle.score==0)&&(cld==0)){ //如果某个障碍物的右侧部分在恐龙的左边,并且恐龙之前没有经过这个障碍物,并且没有发生碰撞
         obstacle.score=1;                                          //使这个障碍物的分数为1,表示恐龙已经经过了这个障碍物,就不再继续得分
         score+=1;                                                  //得分加一
      }
   }
}

然后在主程序中,定义一个得分的变量。并在draw函数中,调用恐龙类的得分方法。同时定义一个打印分数的函数,将当前的得分在画布上显示。

var score=0;                       //定义游戏的得分的变量,并设置初始值为0
//...
function draw() {                  //绘图部分         
  for(let c of cactuses){          //使仙人掌类数组中的每一个仙人掌对象移动和显示
    //...   
    dinosaur.addScore(c);          //调用恐龙类的得分方法
   }
  for(let b of birds){             //使翼龙类数组中的每一个翼龙对象移动和显示
  //...
     dinosaur.addScore(b);         //调用恐龙类的得分方法
   }
   printScore();                   //调用打印得分的函数,将得分显示在画布上
   //...
}
 function printScore() {           //打印得分方法
   textAlign(LEFT);                //设置文本对齐方式为左对齐
   fill(50);                       //设置文本颜色为黑色
   textSize(30);                   //设置字体大小
   text("得分: "+score, 5*width/6, height/9); //输出得分,设置文本的位置
 } 

运行程序后,就可以看到在右上角有实时显示的分数

81e032f357f0f14f837f2eee0725f29e.gif

第九步:添加游戏界面

给游戏设置界面,包括游戏开始界面,游戏中的界面,游戏结束界面。在之前的乒乓球游戏中已经用到了这三个游戏界面,在此可以借鉴那些代码。

1.游戏开始界面

function initScreen() {               //游戏开始界面
   background(236, 240, 241);         //设置背景色为白灰色
   image(imgGround,0,height-20,imgGround.width,imgGround.height);
   image(imgCloud, 0,50,imgCloud.width,imgCloud.height);
   image(imgDino[index%4+1],80,height-imgDino[index%4+1].height*1.5-10,imgDino[index%4+1].width*1.5,imgDino[index%6+1].height*1.5);//循环显示恐龙图片的图1至图4,使恐龙有一些动作
   textAlign(CENTER);                 //设置文本对齐方式为居中对齐
   fill(52, 73, 94);                  //设置文本颜色
   textSize(100);                     //设置字体大小
   text("恐龙跳跃", width/2, height/2); //输出文字,并设置文字的位置

   fill(92,167,182);                   //填充长方形按钮的颜色
   noStroke();                         //设置长方形的外边框为无
   rectMode(CENTER);                   //设置画长方形的模式为正中心
   rect(width/2, height-40, 200,60,5); //设置长方形的长宽和位置,圆角的大小
   fill(236,240,241);                  //设置文本颜色
   textSize(30);                       //设置字体大小
   text("开始", width/2, height-30);   //输出文字,并设置文字的位置
 } 

2.游戏结束界面

   function gameOverScreen() {  //游戏结束界面

   background(23, 24, 24,3);     //设置背景颜色,通过不断重复绘制,实现画面减隐的效果
   textAlign(CENTER);            //设置文本对齐方式为居中对齐

   if(bestScore<score){          //比较最高分和得分并得出当前最高分
    bestScore = score;
   }
   fill(255, 227, 132);                     //设置文本颜色
   textSize(30);                            //设置字体大小
   text("最高分", width/2, height/10);       //输出文字并设置文字的位置   
   textSize(40);                            //设置字体大小
   text(bestScore, width/2, height/5);      //输出文字并设置文字的位置

   fill(230, 180, 80);                      //设置文本颜色
   textSize(30);                            //设置字体大小
   text("得分", width/2, height/2-110);     //输出文字,并设置文字的位置
   textSize(150);                           //设置字体大小
   text(score, width/2, height/2+50);       //输出文字并设置文字的位置

   fill(92,167,182);                        //填充长方形按钮颜色
   rectMode(CENTER);                        //设置画长方形的模式为正中心
   noStroke();                              //设置长方形的外边框为无
   rect(width/2, height-40, 200,60,5);      //设置长方形的长宽和位置,圆角的大小
   fill(236,240,241);                       //设置文本颜色
   textSize(30);                            //设置字体大小
   text("重新开始", width/2, height-30);     //输出文字并设置文字的位置
 } 

3.游戏中的界面

function gamePlayScreen() {               //游戏中的界面
  background(236, 240, 241);              //设置背景颜色为白灰色
  backGroundPicture();                    //调用背景图片的函数,将背景图片显示出来
  addObstacle();                          //调用添加障碍物的函数
  printScore();                           //调用打印得分的函数,将得分显示在画布上
  dinosaur.update();                      //通过调用恐龙类的更新方法,将恐龙的图片设置为它当前状态的图片
  dinosaur.move();                        //通过调用恐龙类的移动方法,使恐龙发生移动
  dinosaur.show();                        //通过调用恐龙类的显示方法,将恐龙显示出来 
  if (frameCount%6==0) index++;           //设置恐龙动画的快慢
  for(let c of cactuses){                 //使仙人掌类数组中的每一个仙人掌对象移动和显示
     c.move();                            //调用仙人掌类的移动方法
     c.show();                            //调用仙人掌类的显示方法
     if(dinosaur.hits(c)){                //判断恐龙是否与仙人掌对象发生碰撞,如果发生碰撞
      cld=1;                              //将变量的值设为1,表示已经发生了碰撞
      gameoverSound.play();
      textAlign(CENTER);                  //设置文字的对齐方式为居中对齐  
      textSize(70);                       //设置文字的大小为70像素

      text("game over",width/2, height/2);//在屏幕中央输出文字“game over”
      noLoop();                           //设置为不再循环,也就是停止在画布上绘制,画面静止
    }
    dinosaur.addScore(c);                 //调用恐龙类的得分方法
   }
  for(let b of birds){                    //使翼龙类数组中的每一个翼龙对象移动和显示
     b.update();                          //调用翼龙类的更新方法
     b.move();                            //调用翼龙类的移动方法
     b.show();                            //调用翼龙类的显示方法

    if(dinosaur.hits(b)){                 //判断恐龙是否与翼龙对象发生碰撞,如果发生碰撞
      cld=1;                              //将变量的值设为1,表示已经发生了碰撞
      textAlign(CENTER);                  //设置文字的对齐方式为居中对齐  
      textSize(70);                       //设置文字的大小为70像素
      gameoverSound.play();
      text("game over",width/2, height/2);//在屏幕中央输出文字“game over”
      noLoop();                           //设置为不再循环,也就是停止在画布上绘制,画面静止
    }
     dinosaur.addScore(b);                //调用恐龙类的得分方法
  }
}

接下来需要定义一个表示界面的变量,还要再定义几个函数: 游戏开始的函数、结束的函数、重新开始的函数,通过它们来控制游戏的界面。使用鼠标的点击事件来开始游戏,控制恐龙跳跃以及重新开始游戏。最后使用draw函数来将这些界面显示出来。(将原来显示game over文字的地方,替换为显示游戏结束的界面。 定义一个游戏最高分的变量,在游戏结束时,显示出最高分。)

//...
var gameScreen=0;      //定义游戏界面的变量,并设置初始值为0
var bestScore=0;              //定义游戏的最高分的变量,并设置初始值为0
//...
function draw() {                          //绘图部分
   //判断当前界面状态,进入不同界面
   if (gameScreen == 0) {         //如果当前是游戏准备开始界面状态
     initScreen();                //调用游戏准备方法,进入游戏准备界面
   } else if (gameScreen == 1) {  //如果当前是游戏界面状态
     gamePlayScreen();            //调用开始游戏方法,进入游戏界面
   } else if (gameScreen == 2) {  //如果当前是游戏结束界面状态
     gameOverScreen();            //调用游戏结束方法,进入游戏结束界面
   } 
}
function startGame() {   //游戏开始函数
  gameScreen=1;          //设置当前界面状态为游戏界面
  } 

function gameOver() {    //游戏结束界面
   gameScreen=2;         //设置当前界面状态为游戏结束界面
 } 

function restart() {     //游戏重新开始的方法
   gameScreen= 1;        //使当前界面为游戏界面
   lastAddTime= 0;       //重置增加障碍物的时间
   birds=[];             //初始化翼龙类的数组,将原来存储的翼龙类对象清空
   cactuses=[];          //初始化翼龙类的数组,将原来存储的翼龙类对象清空 
   cld=0;                //重置用来判断是否发生碰撞的变量
   score=0;              //重置分数的变量
 }  
function mouseClicked() { //定义一个鼠标点击事件,如果点击鼠标,就调用此函数
   if(gameScreen==0){     //按下鼠标时,如果当前界面是游戏准备开始界面
     startGame();         //调用游戏开始方法,来开始游戏
    } 
   if(gameScreen==2){     //按下鼠标时,如果当前界面是游戏结束界面
     restart();           //调用游戏重新开始函数,来重新开始游戏
    } 
   if(gameScreen==1){     //按下鼠标时,如果当前界面是游戏中的界面
     dinosaur.jump();     //调用恐龙类的跳跃方法,使恐龙跳跃
    }
}

运行程序后,就会出现三个游戏的界面

ce473baa084f2e6a174b51952e794741.gif

第十步:添加音效

添加音效和添加图片的方法很相似,先准备好音效的素材

jump12.mp3恐龙跳跃音效

gameover.wav 游戏结束音效

将这两个音频添加到文件管理器中。然后在主程序中,新建两个音效的变量,并且将这两个音频加载到变量中。最后在程序中需要用到它们的地方播放。

//...
var jumpSound;                //定义恐龙跳跃的音效的变量
var gameoverSound;            //定义游戏结束的音效的变量
//...
function preload(){           //预加载函数,通过它把图片加载到数组中
//...
  jumpSound=loadSound("jump12.mp3");
  gameoverSound=loadSound("gameover.wav");
}
//...
function gameOver(){     //游戏结束界面
   gameScreen=2;         //设置当前界面状态为游戏结束界面
   gameoverSound.play(); //播放游戏结束的音效
 } 

function mouseClicked() { //定义一个鼠标点击事件,如果点击鼠标,就调用此函数
   //...
   if(gameScreen==1){     //按下鼠标时,如果当前界面是游戏中的界面
     dinosaur.jump();     //调用恐龙类的跳跃方法,使恐龙跳跃
     jumpSound.play();    //播放恐龙跳跃的音效
    }

}

到此恐龙小游戏就完成了,点击下面的链接就可以玩。

p5.js Web Editor​editor.p5js.org

全部的代码可以在这个链接查看

p5.js Web Editor​editor.p5js.org

游戏用到的素材可以在这个链接下载

全部素材​onedrive.live.com

推荐阅读

jack:用Processing(p5.js)制作一个乒乓球游戏​zhuanlan.zhihu.com
304a72506fe380985fc3839fc6b5f084.png
参考内容: https://www. youtube.com/watch? v=l0HoJHc-63Q&list=WL&index=23&t=597s

版权声明:本文为weixin_35677065原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。