今天翻了翻以前的项目文件夹,发现了当初刚学前端的时候写的一个“小游戏”,遂决定整理到博客。
写这个“游戏”是在三年前刚接触HTML+JS还不到三天的时候,纯粹是为了巩固刚学到的基础知识才自己“设计”并一步步的实现了这个“游戏”。只用到了HTML和JS的最基础的内容,碰撞检测等功能也是自己设计的“笨方法”。所以虽然当年在自己那 720P+火狐浏览器 的环境下运行并没有问题,但是如今在新设备跑却发现了在一些 屏幕分辨率+浏览器+放大设置 的组合上碰撞检测功能无法工作。
细心的读者可能会发现,在前言中,所有的游戏两字上都被我打上了双引号。没错,因为堂而皇之的称我写的这“玩意”叫游戏,实在是对广大游戏开发人员的不尊重,顶多算个小demo吧。所以被我高大上的标题吸引进来的朋友们可以考虑撤了。。。
整个游戏的内容就是一个小人在不断的向前跑,需要通过跳或二级跳躲开随机生成的方块,存活时间越久,得分越高。
说好的只是为了巩固前端基础知识,所以以下实现部分以展示基础知识的用法为主。
首先是游戏界面的宽高以及方块出现的概率:
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, //最高纪录
}
绘制小人的函数,一个奔跑的小人动画本应该是通过几张图片交替显示来实现。可是既然说了纯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);
}
通过定时调用函数来按帧绘制游戏图像:
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绘制一帧
);
}
当时本只是想随便写写,但是,后来由于强迫症发作···硬是从晚上八九点写到了凌晨三四点(刚刚接触前端,debug占了大量的时间),把它变成了一个真正可以玩的“游戏”。所有的实现包括图像在内都纯粹只用到了HTML+JS的最基础内容,虽然游戏本身不具备多少的可玩性,但以巩固基础为目的,还是值得一试的。