纯HTML+JS实现跑酷游戏

今天翻了翻以前的项目文件夹,发现了当初刚学前端的时候写的一个“小游戏”,遂决定整理到博客。

0、前言

写这个“游戏”是在三年前刚接触HTML+JS还不到三天的时候,纯粹是为了巩固刚学到的基础知识才自己“设计”并一步步的实现了这个“游戏”。只用到了HTML和JS的最基础的内容,碰撞检测等功能也是自己设计的“笨方法”。所以虽然当年在自己那 720P+火狐浏览器 的环境下运行并没有问题,但是如今在新设备跑却发现了在一些 屏幕分辨率+浏览器+放大设置 的组合上碰撞检测功能无法工作。

1、游戏概述

细心的读者可能会发现,在前言中,所有的游戏两字上都被我打上了双引号。没错,因为堂而皇之的称我写的这“玩意”叫游戏,实在是对广大游戏开发人员的不尊重,顶多算个小demo吧。所以被我高大上的标题吸引进来的朋友们可以考虑撤了。。。
整个游戏的内容就是一个小人在不断的向前跑,需要通过跳或二级跳躲开随机生成的方块,存活时间越久,得分越高。

2、代码实现

说好的只是为了巩固前端基础知识,所以以下实现部分以展示基础知识的用法为主。

2.1 游戏数据

首先是游戏界面的宽高以及方块出现的概率:

var BANNER_WIDTH = document.body.clientWidth;
var BANNER_HEIGHT = 100;
var PRB = 1/60; //方块生成的概率,会随游戏时间的增加而增加。

然后是用来储存方块(房子)的队列:

//创建一个队列用于储存房子
var house = [];

当然,跑酷游戏肯定少不了一个奔跑的小人:

var people = {
 x:BANNER_WIDTH/3,
 y:BANNER_HEIGHT*9/10,		//x,y为小人的位置。
 ground:BANNER_HEIGHT*9/10,	//小人的落地地面的位置
 ground_width:0,			//小人可在房子上行走的距离
 vy:0,					//y方向的速度
 g:1,					//重力加速度
 state:1,					//小人的当前运动形态
 score:0,					//当前得分
 failnum:0,				//失败次数
 mouseClick:2,				//剩余可连跳次数
 highest:0,				//最高纪录
}
2.2 功能函数

绘制小人的函数,一个奔跑的小人动画本应该是通过几张图片交替显示来实现。可是既然说了纯HTML+JS,那么,自然就得用代码来画呀!

//绘制小人的函数
function drawPeople(state,ctx){	//state为当前要绘制的小人的运动状态,ctx提供了在canvas上绘画的方法和属性。
 switch(state){
  case 1:						//运动状态1的绘制。
   ctx.beginPath();
   ctx.moveTo(people.x,people.y);
   ctx.lineTo(people.x+9,people.y-20);
   ctx.lineTo(people.x+18,people.y-15);
   ctx.lineTo(people.x+12,people.y-6);
   ctx.moveTo(people.x+9,people.y-20);
   ctx.lineTo(people.x+12,people.y-37);
   ctx.arc(people.x+12,people.y-37,2,0,Math.PI*2);
   ctx.moveTo(people.x+10,people.y-30);
   ctx.lineTo(people.x+14,people.y-23);
   ctx.lineTo(people.x+17,people.y-30);
   ctx.moveTo(people.x+10,people.y-30);
   ctx.lineTo(people.x+4,people.y-20);
   ctx.stroke();
   break;
  case -1:					//运动状态-1的绘制
   ctx.beginPath();
   ctx.moveTo(people.x-4,people.y-3);
   ctx.lineTo(people.x+11,people.y-15);
   ctx.lineTo(people.x+24,people.y-4);
   ctx.moveTo(people.x+11,people.y-15);
   ctx.lineTo(people.x+8,people.y-32);
   ctx.arc(people.x+8,people.y-32,2,0,Math.PI*2);
   ctx.moveTo(people.x+11,people.y-22);
   ctx.lineTo(people.x+14,people.y-22);
   ctx.lineTo(people.x+12,people.y-27);
   ctx.moveTo(people.x+10,people.y-25);
   ctx.lineTo(people.x,people.y-15);
   ctx.stroke();
   break;
   //运动状态1和-1交替,形成向前奔跑的动画。
 }
}

添加房子的函数:

//向队列中添加新的house
function addHouse(){
 var hus = {
 x:BANNER_WIDTH,
 y:BANNER_HEIGHT*9/10,		//初始位置在游戏界面的最右边。
 height:Math.ceil(Math.random()*5),	//高度一到五随机(向上取整)。
 width:Math.ceil(Math.random()*4),	//宽度一到四随机(向上取整)。
 }
 house.push(hus);		//添加到队列。
}

绘制房子的函数:

//绘制house
function drawHouse(ctx){
//循环绘制队列里面的每一座房子。
 for(var i=0;i<house.length;i++){
  ctx.lineWidth = 2;
  ctx.fillStyle = "#D8D8D8";
  
  ctx.beginPath();
  ctx.moveTo(house[i].x,house[i].y);
  ctx.lineTo(house[i].x,house[i].y-house[i].height*10);
  ctx.lineTo(house[i].x+house[i].width*10,house[i].y-house[i].height*10);
  ctx.lineTo(house[i].x+house[i].width*10,house[i].y);
  ctx.closePath();
  
  ctx.fill();
 }
 
//更新house队列状态
 for(var i=0;i<house.length;i++){
  house[i].x-=3;  //将所有房子向左移三个像素,以形成人在向前跑的错觉。
  if(house[i].x<-40){
   house.shift();	//去掉已经移出屏幕的房子。
  }
 }
 //根据概率确定是否要添加新的房子。
 if(Math.random()<(PRB)){
  addHouse();
 }
}

计算小人运动状态的函数:

//计算小人运动的函数
function runpeople(){
 //如果小人的y坐标不等于小人此时的地面坐标或小人的y方向速度不等于0,则说明小人正在跳跃动作中,需要计算小人本帧的y坐标。
 if(people.y!=people.ground||people.vy!=0){ 
  people.y+=people.vy;
  people.vy+=people.g;
  people.score++;
 }
 else{
  //否则增加分数并切换小人当前帧的运动状态即可。注:在跳跃动作中是不需要切换运动状态的。
  people.score+=3;
  if(people.score%2==0){
   people.state*=-1;
  }
 }
 //如果小人此时正在方块上,那需要减去当前前进的距离,以便在走过方块时下落。
 if(people.ground_width>0){
  people.ground_width-=3;
 }
 for(var i=0;i<house.length;i++){
  //碰撞检测,如果小人的x坐标加20(即小人图像的右边框位置)等于房子的x坐标,则说明此时小人与该房子处于位置交叉状态。
  //判断三个坐标是因为房子每次右移三个坐标,所以交叉时并不一定刚好和小人的坐标相等。
  if(house[i].x==people.x+20||house[i].x==people.x+21||house[i].x==people.x+19){
   //输赢检测,当处于交叉状态时,检测小人的y坐标是否高于房子的高度,若不高于,则小人与房子相撞,游戏失败。
   if(people.y>BANNER_HEIGHT*9/10-house[i].height*10){
    //初始化数据
    //更新最高分
    if(people.score>people.highest){
     people.highest=people.score;
    }
    PRB=1/60;
    people.score=0;
    people.failnum++;
    var j = house.length;
    for(var i=0;i<j;i++){
     house.shift();
    }
    break;
   }
   
   //如果小人跳上了房子,那么小人的落地地面就在房子上。
   if(people.ground>BANNER_HEIGHT*9/10-house[i].height*10){
   people.ground=BANNER_HEIGHT*9/10-house[i].height*10;
   //小人在房子上可行走的距离。
   people.ground_width=house[i].width*10+20;
   }
  }
 }
 //当小人在房子上可行走的距离小于零时,小人落地的地面重新回到游戏“地面”。
 if(people.ground_width<=0){
  people.ground=BANNER_HEIGHT*9/10;
 }
 
 //落地判断,如果落到了地面,则重置连跳次数等数据。
 if(people.y>=people.ground){
  people.vy=0;
  people.y=people.ground;
  people.mouseClick=2;
  //console.log(people.score);
  }
 //随着分数的增加,游戏难度也相应增加,添加房子的概率越来越大。
 PRB =(1/60)*(Math.pow(2,(Math.floor(people.score/1000))));
 //难度上限
 if(PRB>0.3){
  PRB=0.3;
 }
}

监听器

//定义canvas的单击监听器
function cvs_click(){
 if(people.mouseClick){
  people.vy=-10;  //跳跃获得y方向向上的10点初速度。
  people.mouseClick--; //可连跳次数减1.
 }
}

绘制游戏logo、背景、以及显示游戏数据:

function drawLogo(ctx){
 var image = new Image();
 image.onload = function(){
  ctx.drawImage(image,2,2,191,95);
 }
 image.src = "img/personalportfolio.png";
 
 ctx.fillStyle = "#FFFFFF";
 ctx.font = "normal 37px 楷体";
 ctx.textAlign = "center";
 ctx.fillText("千里之行 始于足下",BANNER_WIDTH*17/20+20,BANNER_HEIGHT*7/10+5);
 
 ctx.fillStyle = "#FFFFFF";
 ctx.font = "normal 15px 楷体";
 ctx.textAlign = "center";
 ctx.fillText("分数:"+people.score.toString(),BANNER_WIDTH/5,BANNER_HEIGHT*3/10);
 
 ctx.fillStyle = "#FFFFFF";
 ctx.font = "normal 15px 楷体";
 ctx.textAlign = "center";
 ctx.fillText("最高分:"+people.highest.toString(),BANNER_WIDTH/5,BANNER_HEIGHT*7/10);
 
 ctx.fillStyle = "#FFFFFF";
 ctx.font = "normal 15px 楷体";
 ctx.textAlign = "center";
 ctx.fillText("失败次数:"+people.failnum.toString(),BANNER_WIDTH/5,BANNER_HEIGHT*5/10);
}
2.3 游戏动画显示

通过定时调用函数来按帧绘制游戏图像:

window.onload = function(){
//设置canvas标签
 var canvas = document.getElementById("forbanner");
 canvas.width = BANNER_WIDTH;
 canvas.height = BANNER_HEIGHT;
 canvas.addEventListener("mousedown",function(e){cvs_click();});
 
 var ctx = canvas.getContext("2d");
 
 var intT = setInterval(
  function(){
   ctx.clearRect(0,0,BANNER_WIDTH,BANNER_HEIGHT);  //清除上一帧图像。
   drawHouse(ctx); //绘制当前所有的房子。
   drawLogo(ctx);
   runpeople();  //计算小人的运动状态
   drawPeople(people.state,ctx);  //绘制小人图像
  }
  ,
  45   //每45ms绘制一帧
 );
}

3、总结

当时本只是想随便写写,但是,后来由于强迫症发作···硬是从晚上八九点写到了凌晨三四点(刚刚接触前端,debug占了大量的时间),把它变成了一个真正可以玩的“游戏”。所有的实现包括图像在内都纯粹只用到了HTML+JS的最基础内容,虽然游戏本身不具备多少的可玩性,但以巩固基础为目的,还是值得一试的。

你可能感兴趣的:(前端基础)