前端的朋友们,福利来了,今天小编给大家带来了一款经典的H5实现的像素风格的3D赛车源码,请看图:
测试视频
https://githubs.xyz/show/167.mp4
全部源码下载:
https://www.githubs.xyz/y167.html
游戏运行
找到index.html直接运行,final 为最终的游戏模式,支持电脑机器人车。左边的设置面板可以调节难度,路的宽度,场景的远近等。自己可以把英文翻译过来。
项目截图
部分js代码
//=========================================================================
// minimalist DOM helpers
//=========================================================================
var Dom = {
get: function(id) { return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id); },
set: function(id, html) { Dom.get(id).innerHTML = html; },
on: function(ele, type, fn, capture) { Dom.get(ele).addEventListener(type, fn, capture); },
un: function(ele, type, fn, capture) { Dom.get(ele).removeEventListener(type, fn, capture); },
show: function(ele, type) { Dom.get(ele).style.display = (type || 'block'); },
blur: function(ev) { ev.target.blur(); },
addClassName: function(ele, name) { Dom.toggleClassName(ele, name, true); },
removeClassName: function(ele, name) { Dom.toggleClassName(ele, name, false); },
toggleClassName: function(ele, name, on) {
ele = Dom.get(ele);
var classes = ele.className.split(' ');
var n = classes.indexOf(name);
on = (typeof on == 'undefined') ? (n < 0) : on;
if (on && (n < 0))
classes.push(name);
else if (!on && (n >= 0))
classes.splice(n, 1);
ele.className = classes.join(' ');
},
storage: window.localStorage || {}
}
//=========================================================================
// general purpose helpers (mostly math)
//=========================================================================
var Util = {
timestamp: function() { return new Date().getTime(); },
toInt: function(obj, def) { if (obj !== null) { var x = parseInt(obj, 10); if (!isNaN(x)) return x; } return Util.toInt(def, 0); },
toFloat: function(obj, def) { if (obj !== null) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return Util.toFloat(def, 0.0); },
limit: function(value, min, max) { return Math.max(min, Math.min(value, max)); },
randomInt: function(min, max) { return Math.round(Util.interpolate(min, max, Math.random())); },
randomChoice: function(options) { return options[Util.randomInt(0, options.length-1)]; },
percentRemaining: function(n, total) { return (n%total)/total; },
accelerate: function(v, accel, dt) { return v + (accel * dt); },
interpolate: function(a,b,percent) { return a + (b-a)*percent },
easeIn: function(a,b,percent) { return a + (b-a)*Math.pow(percent,2); },
easeOut: function(a,b,percent) { return a + (b-a)*(1-Math.pow(1-percent,2)); },
easeInOut: function(a,b,percent) { return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5); },
exponentialFog: function(distance, density) { return 1 / (Math.pow(Math.E, (distance * distance * density))); },
increase: function(start, increment, max) { // with looping
var result = start + increment;
while (result >= max)
result -= max;
while (result < 0)
result += max;
return result;
},
project: function(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
p.camera.x = (p.world.x || 0) - cameraX;
p.camera.y = (p.world.y || 0) - cameraY;
p.camera.z = (p.world.z || 0) - cameraZ;
p.screen.scale = cameraDepth/p.camera.z;
p.screen.x = Math.round((width/2) + (p.screen.scale * p.camera.x * width/2));
p.screen.y = Math.round((height/2) - (p.screen.scale * p.camera.y * height/2));
p.screen.w = Math.round( (p.screen.scale * roadWidth * width/2));
},
overlap: function(x1, w1, x2, w2, percent) {
var half = (percent || 1)/2;
var min1 = x1 - (w1*half);
var max1 = x1 + (w1*half);
var min2 = x2 - (w2*half);
var max2 = x2 + (w2*half);
return ! ((max1 < min2) || (min1 > max2));
}
}
//=========================================================================
// POLYFILL for requestAnimationFrame
//=========================================================================
if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback, element) {
window.setTimeout(callback, 1000 / 60);
}
}
//=========================================================================
// GAME LOOP helpers
//=========================================================================
var Game = { // a modified version of the game loop from my previous boulderdash game - see http://codeincomplete.com/posts/2011/10/25/javascript_boulderdash/#gameloop
run: function(options) {
Game.loadImages(options.images, function(images) {
options.ready(images); // tell caller to initialize itself because images are loaded and we're ready to rumble
Game.setKeyListener(options.keys);
var canvas = options.canvas, // canvas render target is provided by caller
update = options.update, // method to update game logic is provided by caller
render = options.render, // method to render the game is provided by caller
step = options.step, // fixed frame step (1/fps) is specified by caller
stats = options.stats, // stats instance is provided by caller
now = null,
last = Util.timestamp(),
dt = 0,
gdt = 0;
function frame() {
now = Util.timestamp();
dt = Math.min(1, (now - last) / 1000); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
gdt = gdt + dt;
while (gdt > step) {
gdt = gdt - step;
update(step);
}
render();
stats.update();
last = now;
requestAnimationFrame(frame, canvas);
}
frame(); // lets get this party started
Game.playMusic();
});
},
//---------------------------------------------------------------------------
loadImages: function(names, callback) { // load multiple images and callback when ALL images have loaded
var result = [];
var count = names.length;
var onload = function() {
if (--count == 0)
callback(result);
};
for(var n = 0 ; n < names.length ; n++) {
var name = names[n];
result[n] = document.createElement('img');
Dom.on(result[n], 'load', onload);
result[n].src = "images/" + name + ".png";
}
},
//---------------------------------------------------------------------------
setKeyListener: function(keys) {
var onkey = function(keyCode, mode) {
var n, k;
for(n = 0 ; n < keys.length ; n++) {
k = keys[n];
k.mode = k.mode || 'up';
if ((k.key == keyCode) || (k.keys && (k.keys.indexOf(keyCode) >= 0))) {
if (k.mode == mode) {
k.action.call();
}
}
}
};
Dom.on(document, 'keydown', function(ev) { onkey(ev.keyCode, 'down'); } );
Dom.on(document, 'keyup', function(ev) { onkey(ev.keyCode, 'up'); } );
},
//---------------------------------------------------------------------------
stats: function(parentId, id) { // construct mr.doobs FPS counter - along with friendly good/bad/ok message box
var result = new Stats();
result.domElement.id = id || 'stats';
Dom.get(parentId).appendChild(result.domElement);
var msg = document.createElement('div');
msg.style.cssText = "border: 2px solid gray; padding: 5px; margin-top: 5px; text-align: left; font-size: 1.15em; text-align: right;";
msg.innerHTML = "Your canvas performance is ";
Dom.get(parentId).appendChild(msg);
var value = document.createElement('span');
value.innerHTML = "...";
msg.appendChild(value);
setInterval(function() {
var fps = result.current();
var ok = (fps > 50) ? 'good' : (fps < 30) ? 'bad' : 'ok';
var color = (fps > 50) ? 'green' : (fps < 30) ? 'red' : 'gray';
value.innerHTML = ok;
value.style.color = color;
msg.style.borderColor = color;
}, 5000);
return result;
},
//---------------------------------------------------------------------------
playMusic: function() {
var music = Dom.get('music');
music.loop = true;
music.volume = 0.05; // shhhh! annoying music!
music.muted = (Dom.storage.muted === "true");
music.play();
Dom.toggleClassName('mute', 'on', music.muted);
Dom.on('mute', 'click', function() {
Dom.storage.muted = music.muted = !music.muted;
Dom.toggleClassName('mute', 'on', music.muted);
});
}
}
//=========================================================================
// canvas rendering helpers
//=========================================================================
var Render = {
polygon: function(ctx, x1, y1, x2, y2, x3, y3, x4, y4, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.lineTo(x4, y4);
ctx.closePath();
ctx.fill();
},
//---------------------------------------------------------------------------
segment: function(ctx, width, lanes, x1, y1, w1, x2, y2, w2, fog, color) {
var r1 = Render.rumbleWidth(w1, lanes),
r2 = Render.rumbleWidth(w2, lanes),
l1 = Render.laneMarkerWidth(w1, lanes),
l2 = Render.laneMarkerWidth(w2, lanes),
lanew1, lanew2, lanex1, lanex2, lane;
ctx.fillStyle = color.grass;
ctx.fillRect(0, y2, width, y1 - y2);
Render.polygon(ctx, x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, color.rumble);
Render.polygon(ctx, x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, color.rumble);
Render.polygon(ctx, x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, color.road);
if (color.lane) {
lanew1 = w1*2/lanes;
lanew2 = w2*2/lanes;
lanex1 = x1 - w1 + lanew1;
lanex2 = x2 - w2 + lanew2;
for(lane = 1 ; lane < lanes ; lanex1 += lanew1, lanex2 += lanew2, lane++)
Render.polygon(ctx, lanex1 - l1/2, y1, lanex1 + l1/2, y1, lanex2 + l2/2, y2, lanex2 - l2/2, y2, color.lane);
}
Render.fog(ctx, 0, y1, width, y2-y1, fog);
},
//---------------------------------------------------------------------------
background: function(ctx, background, width, height, layer, rotation, offset) {
rotation = rotation || 0;
offset = offset || 0;
var imageW = layer.w/2;
var imageH = layer.h;
var sourceX = layer.x + Math.floor(layer.w * rotation);
var sourceY = layer.y
var sourceW = Math.min(imageW, layer.x+layer.w-sourceX);
var sourceH = imageH;
var destX = 0;
var destY = offset;
var destW = Math.floor(width * (sourceW/imageW));
var destH = height;
ctx.drawImage(background, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
if (sourceW < imageW)
ctx.drawImage(background, layer.x, sourceY, imageW-sourceW, sourceH, destW-1, destY, width-destW, destH);
},
//---------------------------------------------------------------------------
sprite: function(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY, clipY) {
// scale for projection AND relative to roadWidth (for tweakUI)
var destW = (sprite.w * scale * width/2) * (SPRITES.SCALE * roadWidth);
var destH = (sprite.h * scale * width/2) * (SPRITES.SCALE * roadWidth);
destX = destX + (destW * (offsetX || 0));
destY = destY + (destH * (offsetY || 0));
var clipH = clipY ? Math.max(0, destY+destH-clipY) : 0;
if (clipH < destH)
ctx.drawImage(sprites, sprite.x, sprite.y, sprite.w, sprite.h - (sprite.h*clipH/destH), destX, destY, destW, destH - clipH);
},
//---------------------------------------------------------------------------
player: function(ctx, width, height, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown) {
var bounce = (1.5 * Math.random() * speedPercent * resolution) * Util.randomChoice([-1,1]);
var sprite;
if (steer < 0)
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_LEFT : SPRITES.PLAYER_LEFT;
else if (steer > 0)
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_RIGHT : SPRITES.PLAYER_RIGHT;
else
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_STRAIGHT : SPRITES.PLAYER_STRAIGHT;
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY + bounce, -0.5, -1);
},
//---------------------------------------------------------------------------
fog: function(ctx, x, y, width, height, fog) {
if (fog < 1) {
ctx.globalAlpha = (1-fog)
ctx.fillStyle = COLORS.FOG;
ctx.fillRect(x, y, width, height);
ctx.globalAlpha = 1;
}
},
rumbleWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(6, 2*lanes); },
laneMarkerWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(32, 8*lanes); }
}
//=============================================================================
// RACING GAME CONSTANTS
//=============================================================================
var KEY = {
LEFT: 37,
UP: 38,
RIGHT: 39,
DOWN: 40,
A: 65,
D: 68,
S: 83,
W: 87
};
var COLORS = {
SKY: '#72D7EE',
TREE: '#005108',
FOG: '#005108',
LIGHT: { road: '#6B6B6B', grass: '#10AA10', rumble: '#555555', lane: '#CCCCCC' },
DARK: { road: '#696969', grass: '#009A00', rumble: '#BBBBBB' },
START: { road: 'white', grass: 'white', rumble: 'white' },
FINISH: { road: 'black', grass: 'black', rumble: 'black' }
};
var BACKGROUND = {
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
SKY: { x: 5, y: 495, w: 1280, h: 480 },
TREES: { x: 5, y: 985, w: 1280, h: 480 }
};
var SPRITES = {
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
TREE1: { x: 625, y: 5, w: 360, h: 360 },
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
STUMP: { x: 995, y: 330, w: 195, h: 140 },
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
};
SPRITES.SCALE = 0.3 * (1/SPRITES.PLAYER_STRAIGHT.w) // the reference sprite width should be 1/3rd the (half-)roadWidth
SPRITES.BILLBOARDS = [SPRITES.BILLBOARD01, SPRITES.BILLBOARD02, SPRITES.BILLBOARD03, SPRITES.BILLBOARD04, SPRITES.BILLBOARD05, SPRITES.BILLBOARD06, SPRITES.BILLBOARD07, SPRITES.BILLBOARD08, SPRITES.BILLBOARD09];
SPRITES.PLANTS = [SPRITES.TREE1, SPRITES.TREE2, SPRITES.DEAD_TREE1, SPRITES.DEAD_TREE2, SPRITES.PALM_TREE, SPRITES.BUSH1, SPRITES.BUSH2, SPRITES.CACTUS, SPRITES.STUMP, SPRITES.BOULDER1, SPRITES.BOULDER2, SPRITES.BOULDER3];
SPRITES.CARS = [SPRITES.CAR01, SPRITES.CAR02, SPRITES.CAR03, SPRITES.CAR04, SPRITES.SEMI, SPRITES.TRUCK];
// code download : https://gitee.com/hadluo/html_code.git
完整全部文件找小编
结尾语
我是分享好物+教程+源码的老罗,欢迎关注!