Cleanup unnecessary files

This commit is contained in:
fnicon 2026-03-22 20:58:49 +09:00
parent 0d8c45e621
commit 7f0b9101b3
113 changed files with 0 additions and 7223 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

View File

@ -1,22 +0,0 @@
Copyright (c) 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
===============================================================================

View File

@ -1,111 +0,0 @@
Javascript Pseudo 3D Racer
==========================
An Outrun-style pseudo-3d racing game in HTML5 and Javascript
* [play the game](https://jakesgordon.com/games/racer/)
* view the [source](https://github.com/jakesgordon/javascript-racer)
* read about [how it works](https://jakesgordon.com/writing/javascript-racer/)
Incrementally built up in 4 parts:
* play the [straight road demo](https://jakesgordon.com/games/racer/v1-straight/)
* play the [curves demo](https://jakesgordon.com/games/racer/v2-curves/)
* play the [hills demo](https://jakesgordon.com/games/racer/v3-hills/)
* play the [final version](https://jakesgordon.com/games/racer/)
With detailed descriptions of how each part works:
* read more about [v1 - straight roads](https://jakesgordon.com/writing/javascript-racer-v1-straight/)
* read more about [v2 - curves](https://jakesgordon.com/writing/javascript-racer-v2-curves/)
* read more about [v3 - hills](https://jakesgordon.com/writing/javascript-racer-v3-hills/)
* read more about v4 - final (coming soon)
A note on performance
=====================
The performance of this game is **very** machine/browser dependent. It works quite well in modern
browsers, especially those with GPU canvas acceleration, but a bad graphics driver can kill it stone
dead. So your mileage may vary. There are controls provided to change the rendering resolution
and the draw distance to scale to fit your machine.
Currently supported browsers include:
* Firefox (v12+) works great, 60fps at high res - Nice!
* Chrome (v19+) works great, 60fps at high res... provided you dont have a bad GPU driver
* IE9 - ok, 30fps at medium res... not great, but at least it works
The current state of mobile browser performance is pretty dismal. Dont expect this to be playable on
any mobile device.
>> _NOTE: I havent actually spent anytime optimizing for performance yet. So it might be possible to
make it play well on older browsers, but that's not really what this project is about._
A note on code structure
========================
This project happens to be implemented in javascript (because its easy for prototyping) but
is not intended to demonstrate javascript techniques or best practices. In fact, in order to
keep it simple to understand it embeds the javascript for each example directly in the HTML
page (horror!) and, even worse, uses global variables and functions (OMG!).
If I was building a real game I would have much more structure and organization to the
code, but since its just a racing game tech demo, I have elected to [KISS](http://en.wikipedia.org/wiki/KISS_principle).
FUTURE
======
It's quite astounding what it takes to actually [finish](https://jakesgordon.com/writing/defining-finished/)
a game, even a simple one. And this is not a project that I plan on polishing into a finished state. It should
really be considered just how to get started with a pseudo-3d racing game.
If we were to try to turn it into a real game we would have to consider:
* car sound fx
* better synchronized music
* full screen mode
* HUD fx (flash on fastest lap, confetti, color coded speedometer, etc)
* more accurate sprite collision
* better car AI (steering, braking etc)
* an actual crash when colliding at high speed
* more bounce when car is off road
* screen shake when off-road or collision
* throw up dirt particles when off road
* more dynamic camera (lower at faster speed, swoop over hills etc)
* automatic resolution & drawDistance detection
* projection based curves ? x,y rotation
* sub-pixel aliasing artifacts on curves
* smarter fog to cover sprites (blue against sky, cover sprites)
* multiple stages, different maps
* a lap map, with current position indicator
* road splits and joins
* day/night cycle
* weather effects
* tunnels, bridges, clouds, walls, buildings
* city, desert, ocean
* add city of seattle and space needle to background
* 'bad guys' - add some competetor drivers to race against as well as the 'traffic'
* different game modes - fastest lap, 1-on-1 racing, collect coins ? shoot bad guys ?
* a whole lot of gameplay tuning
* ...
* ...
Related Links
=============
* [Lou's Pseudo-3d Page](http://www.extentofthejam.com/pseudo/) - high level how-to guide
* [Racer 10k](https://github.com/onaluf/RacerJS) - another javascript racing game
License
=======
[MIT](http://en.wikipedia.org/wiki/MIT_License) license.
>> NOTE: the music tracks included in this project are royalty free resources paid for and licensed
from [Lucky Lion Studios](http://luckylionstudios.com/). They are licensed ONLY for use in this
project and should not be reproduced.
>> NOTE: the sprite graphics are placeholder graphics [borrowed](http://pixel.garoux.net/game/44) from the old
genesis version of outrun and used here as teaching examples. If there are any pixel artists out there who want to
provide original art to turn this into a real game please get in touch!

View File

@ -1,36 +0,0 @@
desc 'recreate sprite sheets'
task 'resprite' do
require 'sprite_factory'
SpriteFactory.run!('images/sprites', :layout => :packed, :output_style => 'images/sprites.js', :margin => 5, :nocomments => true) do |images|
SpriteHelper.javascript_style("SPRITES", images)
end
SpriteFactory.run!('images/background', :layout => :vertical, :output_style => 'images/background.js', :margin => 5, :nocomments => true) do |images|
SpriteHelper.javascript_style("BACKGROUND", images)
end
end
#------------------------------------------------------------------------------
module SpriteHelper
# slightly unusual use of sprite-factory to generate a javascript object structure instead of CSS attributes...
def self.javascript_style(variable, images)
maxname = images.keys.inject(0) {|n,key| [n,key.length].max }
rules = []
images.each do |name, i|
name = name.upcase
whitespace = ' '*(maxname-name.length)
x = '%4d' % i[:cssx]
y = '%4d' % i[:cssy]
w = '%4d' % i[:cssw]
h = '%4d' % i[:cssh]
rules << " #{name}: #{whitespace}{ x: #{x}, y: #{y}, w: #{w}, h: #{h} }"
end
"var #{variable} = {\n#{rules.join(",\n")}\n};"
end
end

View File

@ -1,28 +0,0 @@
/****************************************/
/* common styles used for v1 through v4 */
/****************************************/
body { font-family: Arial, Helvetica, sans-serif; }
#stats { border: 2px solid black; }
#controls { width: 28em; float: left; padding: 1em; font-size: 0.7em; }
#controls th { text-align: right; vertical-align: middle; }
#instructions { clear: left; float: left; width: 17em; padding: 1em; border: 1px solid black; box-shadow: 0 0 5px black; }
#racer { position: relative; z-index: 0; width: 640px; height: 480px; margin-left: 20em; border: 2px solid black; }
#canvas { position: absolute; z-index: 0; width: 640px; height: 480px; z-index: 0; background-color: #72D7EE; }
#mute { background-position: 0px 0px; width: 32px; height: 32px; background: url(images/mute.png); display: inline-block; cursor: pointer; position: absolute; margin-left: 20em; }
#mute.on { background-position: -32px 0px; }
/**************************************************/
/* rudimentary heads up display (only used in v4) */
/**************************************************/
#hud { position: absolute; z-index: 1; width: 640px; padding: 5px 0; font-family: Verdana, Geneva, sans-serif; font-size: 0.8em; background-color: rgba(255,0,0,0.4); color: black; border-bottom: 2px solid black; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
#hud .hud { background-color: rgba(255,255,255,0.6); padding: 5px; border: 1px solid black; margin: 0 5px; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
#hud #speed { float: right; }
#hud #current_lap_time { float: left; }
#hud #last_lap_time { float: left; display: none; }
#hud #fast_lap_time { display: block; width: 12em; margin: 0 auto; text-align: center; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
#hud .value { color: black; font-weight: bold; }
#hud .fastest { background-color: rgba(255,215,0,0.5); }

View File

@ -1,414 +0,0 @@
//=========================================================================
// 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 https://jakesgordon.com/writing/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];

View File

@ -1,5 +0,0 @@
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 }
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,36 +0,0 @@
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 }
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

View File

@ -1,18 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Racer</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<ul>
<li><a href='v1.straight.html'>Version 1 - Straight</a></li>
<li><a href='v2.curves.html'>Version 2 - Curves</a></li>
<li><a href='v3.hills.html'>Version 3 - Hills</a></li>
<li><a href='v4.final.html'>Version 4 - Final</a></li>
</ul>
</body>
</html>

View File

@ -1,143 +0,0 @@
/**
* @author mrdoob / http://mrdoob.com/
*/
var Stats = function () {
var startTime = Date.now(), prevTime = startTime;
var ms = 0, msMin = 1000, msMax = 0;
var fps = 0, fpsMin = 1000, fpsMax = 0;
var frames = 0, mode = 0;mode
var container = document.createElement( 'div' );
container.id = 'stats';
container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false );
container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
var fpsDiv = document.createElement( 'div' );
fpsDiv.id = 'fps';
fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002';
container.appendChild( fpsDiv );
var fpsText = document.createElement( 'div' );
fpsText.id = 'fpsText';
fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
fpsText.innerHTML = 'FPS';
fpsDiv.appendChild( fpsText );
var fpsGraph = document.createElement( 'div' );
fpsGraph.id = 'fpsGraph';
fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff';
fpsDiv.appendChild( fpsGraph );
while ( fpsGraph.children.length < 74 ) {
var bar = document.createElement( 'span' );
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113';
fpsGraph.appendChild( bar );
}
var msDiv = document.createElement( 'div' );
msDiv.id = 'ms';
msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none';
container.appendChild( msDiv );
var msText = document.createElement( 'div' );
msText.id = 'msText';
msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
msText.innerHTML = 'MS';
msDiv.appendChild( msText );
var msGraph = document.createElement( 'div' );
msGraph.id = 'msGraph';
msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
msDiv.appendChild( msGraph );
while ( msGraph.children.length < 74 ) {
var bar = document.createElement( 'span' );
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
msGraph.appendChild( bar );
}
var setMode = function ( value ) {
mode = value;
switch ( mode ) {
case 0:
fpsDiv.style.display = 'block';
msDiv.style.display = 'none';
break;
case 1:
fpsDiv.style.display = 'none';
msDiv.style.display = 'block';
break;
}
}
var updateGraph = function ( dom, value ) {
var child = dom.appendChild( dom.firstChild );
child.style.height = value + 'px';
}
return {
domElement: container,
setMode: setMode,
current: function() { return fps; },
begin: function () {
startTime = Date.now();
},
end: function () {
var time = Date.now();
ms = time - startTime;
msMin = Math.min( msMin, ms );
msMax = Math.max( msMax, ms );
msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')';
updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) );
frames ++;
if ( time > prevTime + 1000 ) {
fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
fpsMin = Math.min( fpsMin, fps );
fpsMax = Math.max( fpsMax, fps );
fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')';
updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) );
prevTime = time;
frames = 0;
}
return time;
},
update: function () {
startTime = this.end();
}
}
};

View File

@ -1,314 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Racer - v1 (straight)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>
<body>
<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">
<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>
<div id='instructions'>
<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>
<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the &lt;canvas&gt; element
</canvas>
Loading...
</div>
<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>
<script src="stats.js"></script>
<script src="common.js"></script>
<script>
var fps = 60; // how many 'update' frames per second
var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var segments = []; // array of road segments
var stats = Game.stats('fps'); // mr.doobs FPS counter
var canvas = Dom.get('canvas'); // our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded below)
var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence (computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble strip
var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of view
var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen (computed)
var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
var playerZ = null; // player relative z distance from camera (computed)
var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
var breaking = -maxSpeed; // deceleration rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
var keyLeft = false;
var keyRight = false;
var keyFaster = false;
var keySlower = false;
//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================
function update(dt) {
position = Util.increase(position, dt * speed, trackLength);
var dx = dt * 2 * (speed/maxSpeed); // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;
if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);
playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of bounds
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
}
//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================
function render() {
var baseSegment = findSegment(position);
var maxy = height;
ctx.clearRect(0, 0, width, height);
Render.background(ctx, background, width, height, BACKGROUND.SKY);
Render.background(ctx, background, width, height, BACKGROUND.HILLS);
Render.background(ctx, background, width, height, BACKGROUND.TREES);
var n, segment;
for(n = 0 ; n < drawDistance ; n++) {
segment = segments[(baseSegment.index + n) % segments.length];
segment.looped = segment.index < baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
Util.project(segment.p1, (playerX * roadWidth), cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth), cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
if ((segment.p1.camera.z <= cameraDepth) || // behind us
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
continue;
Render.segment(ctx, width, lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);
maxy = segment.p2.screen.y;
}
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
cameraDepth/playerZ,
width/2,
height,
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
0);
}
//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================
function resetRoad() {
segments = [];
for(var n = 0 ; n < 500 ; n++) {
segments.push({
index: n,
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}
segments[findSegment(playerZ).index + 2].color = COLORS.START;
segments[findSegment(playerZ).index + 3].color = COLORS.START;
for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;
trackLength = segments.length * segmentLength;
}
function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}
//=========================================================================
// THE GAME LOOP
//=========================================================================
Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background = images[0];
sprites = images[1];
reset();
}
});
function reset(options) {
options = options || {};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height, height);
lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
resetRoad(); // only rebuild road when necessary
}
//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================
Dom.on('resolution', 'change', function(ev) {
var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width; break;
case 'low': w = 480; h = 360; ratio=w/width; break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}
//=========================================================================
</script>
</body>

View File

@ -1,387 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Racer - v2 (curves)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>
<body>
<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">
<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>
<div id='instructions'>
<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>
<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the &lt;canvas&gt; element
</canvas>
Loading...
</div>
<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>
<script src="stats.js"></script>
<script src="common.js"></script>
<script>
var fps = 60; // how many 'update' frames per second
var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
var skyOffset = 0; // current sky scroll offset
var hillOffset = 0; // current hill scroll offset
var treeOffset = 0; // current tree scroll offset
var segments = []; // array of road segments
var stats = Game.stats('fps'); // mr.doobs FPS counter
var canvas = Dom.get('canvas'); // our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded below)
var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence (computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble strip
var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of view
var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen (computed)
var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
var playerZ = null; // player relative z distance from camera (computed)
var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
var breaking = -maxSpeed; // deceleration rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
var keyLeft = false;
var keyRight = false;
var keyFaster = false;
var keySlower = false;
//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================
function update(dt) {
var playerSegment = findSegment(position+playerZ);
var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to +1) in 1 second
position = Util.increase(position, dt * speed, trackLength);
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * speedPercent, 1);
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);
if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);
playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of bounds
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
}
//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================
function render() {
var baseSegment = findSegment(position);
var basePercent = Util.percentRemaining(position, segmentLength);
var maxy = height;
var x = 0;
var dx = - (baseSegment.curve * basePercent);
ctx.clearRect(0, 0, width, height);
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset);
var n, segment;
for(n = 0 ; n < drawDistance ; n++) {
segment = segments[(baseSegment.index + n) % segments.length];
segment.looped = segment.index < baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
Util.project(segment.p1, (playerX * roadWidth) - x, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth) - x - dx, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
x = x + dx;
dx = dx + segment.curve;
if ((segment.p1.camera.z <= cameraDepth) || // behind us
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
continue;
Render.segment(ctx, width, lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);
maxy = segment.p2.screen.y;
}
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
cameraDepth/playerZ,
width/2,
height,
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
0);
}
//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================
function addSegment(curve) {
var n = segments.length;
segments.push({
index: n,
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
curve: curve,
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}
function addRoad(enter, hold, leave, curve) {
var n;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter));
for(n = 0 ; n < hold ; n++)
addSegment(curve);
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave));
}
var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};
function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0);
}
function addCurve(num, curve) {
num = num || ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
addRoad(num, num, num, curve);
}
function addSCurves() {
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM);
}
function resetRoad() {
segments = [];
addStraight(ROAD.LENGTH.SHORT/4);
addSCurves();
addStraight(ROAD.LENGTH.LONG);
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
addStraight();
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
addStraight();
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.EASY);
segments[findSegment(playerZ).index + 2].color = COLORS.START;
segments[findSegment(playerZ).index + 3].color = COLORS.START;
for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;
trackLength = segments.length * segmentLength;
}
function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}
//=========================================================================
// THE GAME LOOP
//=========================================================================
Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background = images[0];
sprites = images[1];
reset();
}
});
function reset(options) {
options = options || {};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height, height);
lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
resetRoad(); // only rebuild road when necessary
}
//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================
Dom.on('resolution', 'change', function(ev) {
var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width; break;
case 'low': w = 480; h = 360; ratio=w/width; break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}
//=========================================================================
</script>
</body>

View File

@ -1,419 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Racer - v3 (hills)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>
<body>
<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">
<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>
<div id='instructions'>
<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>
<div id="racer">
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the &lt;canvas&gt; element
</canvas>
Loading...
</div>
<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>
<script src="stats.js"></script>
<script src="common.js"></script>
<script>
var fps = 60; // how many 'update' frames per second
var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
var skyOffset = 0; // current sky scroll offset
var hillOffset = 0; // current hill scroll offset
var treeOffset = 0; // current tree scroll offset
var segments = []; // array of road segments
var stats = Game.stats('fps'); // mr.doobs FPS counter
var canvas = Dom.get('canvas'); // our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded below)
var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence (computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble strip
var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of view
var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen (computed)
var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
var playerZ = null; // player relative z distance from camera (computed)
var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
var breaking = -maxSpeed; // deceleration rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
var keyLeft = false;
var keyRight = false;
var keyFaster = false;
var keySlower = false;
//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================
function update(dt) {
var playerSegment = findSegment(position+playerZ);
var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
position = Util.increase(position, dt * speed, trackLength);
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * speedPercent, 1);
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);
if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
speed = Util.accelerate(speed, offRoadDecel, dt);
playerX = Util.limit(playerX, -2, 2); // dont ever let it go too far out of bounds
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
}
//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================
function render() {
var baseSegment = findSegment(position);
var basePercent = Util.percentRemaining(position, segmentLength);
var playerSegment = findSegment(position+playerZ);
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
var maxy = height;
var x = 0;
var dx = - (baseSegment.curve * basePercent);
ctx.clearRect(0, 0, width, height);
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset, resolution * hillSpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset, resolution * treeSpeed * playerY);
var n, segment;
for(n = 0 ; n < drawDistance ; n++) {
segment = segments[(baseSegment.index + n) % segments.length];
segment.looped = segment.index < baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
x = x + dx;
dx = dx + segment.curve;
if ((segment.p1.camera.z <= cameraDepth) || // behind us
(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
continue;
Render.segment(ctx, width, lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);
maxy = segment.p2.screen.y;
}
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
cameraDepth/playerZ,
width/2,
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * height/2),
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
playerSegment.p2.world.y - playerSegment.p1.world.y);
}
//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================
function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y; }
function addSegment(curve, y) {
var n = segments.length;
segments.push({
index: n,
p1: { world: { y: lastY(), z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { y: y, z: (n+1)*segmentLength }, camera: {}, screen: {} },
curve: curve,
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}
function addRoad(enter, hold, leave, curve, y) {
var startY = lastY();
var endY = startY + (Util.toInt(y, 0) * segmentLength);
var n, total = enter + hold + leave;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
for(n = 0 ; n < hold ; n++)
addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY, (enter+hold+n)/total));
}
var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};
function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0, 0);
}
function addHill(num, height) {
num = num || ROAD.LENGTH.MEDIUM;
height = height || ROAD.HILL.MEDIUM;
addRoad(num, num, num, 0, height);
}
function addCurve(num, curve, height) {
num = num || ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
height = height || ROAD.HILL.NONE;
addRoad(num, num, num, curve, height);
}
function addLowRollingHills(num, height) {
num = num || ROAD.LENGTH.SHORT;
height = height || ROAD.HILL.LOW;
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, -height);
addRoad(num, num, num, 0, height);
addRoad(num, num, num, 0, 0);
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, 0);
}
function addSCurves() {
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.NONE);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, -ROAD.HILL.LOW);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, -ROAD.HILL.MEDIUM);
}
function addDownhillToEnd(num) {
num = num || 200;
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
}
function resetRoad() {
segments = [];
addStraight(ROAD.LENGTH.SHORT/2);
addHill(ROAD.LENGTH.SHORT, ROAD.HILL.LOW);
addLowRollingHills();
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
addLowRollingHills();
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addStraight();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
addStraight();
addDownhillToEnd();
segments[findSegment(playerZ).index + 2].color = COLORS.START;
segments[findSegment(playerZ).index + 3].color = COLORS.START;
for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;
trackLength = segments.length * segmentLength;
}
function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}
//=========================================================================
// THE GAME LOOP
//=========================================================================
Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background = images[0];
sprites = images[1];
reset();
}
});
function reset(options) {
options = options || {};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height, height);
lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
resetRoad(); // only rebuild road when necessary
}
//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================
Dom.on('resolution', 'change', function(ev) {
var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width; break;
case 'low': w = 480; h = 360; ratio=w/width; break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}
//=========================================================================
</script>
</body>

View File

@ -1,688 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Javascript Racer - v4 (final)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link href="common.css" rel="stylesheet" type="text/css" />
</head>
<body>
<table id="controls">
<tr>
<td colspan="2">
<a href='v1.straight.html'>straight</a> |
<a href='v2.curves.html'>curves</a> |
<a href='v3.hills.html'>hills</a> |
<a href='v4.final.html'>final</a>
</td>
</tr>
<tr><td id="fps" colspan="2" align="right"></td></tr>
<tr>
<th><label for="resolution">Resolution :</label></th>
<td>
<select id="resolution" style="width:100%">
<option value='fine'>Fine (1280x960)</option>
<option selected value='high'>High (1024x768)</option>
<option value='medium'>Medium (640x480)</option>
<option value='low'>Low (480x360)</option>
</select>
</td>
</tr>
<tr>
<th><label for="lanes">Lanes :</label></th>
<td>
<select id="lanes">
<option>1</option>
<option>2</option>
<option selected>3</option>
<option>4</option>
</select>
</td>
</tr>
<tr>
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
</tr>
<tr>
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
</tr>
<tr>
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
</tr>
<tr>
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
</tr>
<tr>
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
</tr>
</table>
<div id='instructions'>
<p>Use the <b>arrow keys</b> to drive the car.</p>
</div>
<div id="racer">
<div id="hud">
<span id="speed" class="hud"><span id="speed_value" class="value">0</span> mph</span>
<span id="current_lap_time" class="hud">Time: <span id="current_lap_time_value" class="value">0.0</span></span>
<span id="last_lap_time" class="hud">Last Lap: <span id="last_lap_time_value" class="value">0.0</span></span>
<span id="fast_lap_time" class="hud">Fastest Lap: <span id="fast_lap_time_value" class="value">0.0</span></span>
</div>
<canvas id="canvas">
Sorry, this example cannot be run because your browser does not support the &lt;canvas&gt; element
</canvas>
Loading...
</div>
<audio id='music'>
<source src="music/racer.ogg">
<source src="music/racer.mp3">
</audio>
<span id="mute"></span>
<script src="stats.js"></script>
<script src="common.js"></script>
<script>
var fps = 60; // how many 'update' frames per second
var step = 1/fps; // how long is each frame (in seconds)
var width = 1024; // logical canvas width
var height = 768; // logical canvas height
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
var skyOffset = 0; // current sky scroll offset
var hillOffset = 0; // current hill scroll offset
var treeOffset = 0; // current tree scroll offset
var segments = []; // array of road segments
var cars = []; // array of cars on the road
var stats = Game.stats('fps'); // mr.doobs FPS counter
var canvas = Dom.get('canvas'); // our canvas...
var ctx = canvas.getContext('2d'); // ...and its drawing context
var background = null; // our background image (loaded below)
var sprites = null; // our spritesheet (loaded below)
var resolution = null; // scaling factor to provide resolution independence (computed)
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
var segmentLength = 200; // length of a single segment
var rumbleLength = 3; // number of segments per red/white rumble strip
var trackLength = null; // z length of entire track (computed)
var lanes = 3; // number of lanes
var fieldOfView = 100; // angle (degrees) for field of view
var cameraHeight = 1000; // z height of camera
var cameraDepth = null; // z distance camera is from screen (computed)
var drawDistance = 300; // number of segments to draw
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
var playerZ = null; // player relative z distance from camera (computed)
var fogDensity = 5; // exponential fog density
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
var speed = 0; // current speed
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
var breaking = -maxSpeed; // deceleration rate when braking
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
var totalCars = 200; // total number of cars on the road
var currentLapTime = 0; // current lap time
var lastLapTime = null; // last lap time
var keyLeft = false;
var keyRight = false;
var keyFaster = false;
var keySlower = false;
var hud = {
speed: { value: null, dom: Dom.get('speed_value') },
current_lap_time: { value: null, dom: Dom.get('current_lap_time_value') },
last_lap_time: { value: null, dom: Dom.get('last_lap_time_value') },
fast_lap_time: { value: null, dom: Dom.get('fast_lap_time_value') }
}
//=========================================================================
// UPDATE THE GAME WORLD
//=========================================================================
function update(dt) {
var n, car, carW, sprite, spriteW;
var playerSegment = findSegment(position+playerZ);
var playerW = SPRITES.PLAYER_STRAIGHT.w * SPRITES.SCALE;
var speedPercent = speed/maxSpeed;
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
var startPosition = position;
updateCars(dt, playerSegment, playerW);
position = Util.increase(position, dt * speed, trackLength);
if (keyLeft)
playerX = playerX - dx;
else if (keyRight)
playerX = playerX + dx;
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
if (keyFaster)
speed = Util.accelerate(speed, accel, dt);
else if (keySlower)
speed = Util.accelerate(speed, breaking, dt);
else
speed = Util.accelerate(speed, decel, dt);
if ((playerX < -1) || (playerX > 1)) {
if (speed > offRoadLimit)
speed = Util.accelerate(speed, offRoadDecel, dt);
for(n = 0 ; n < playerSegment.sprites.length ; n++) {
sprite = playerSegment.sprites[n];
spriteW = sprite.source.w * SPRITES.SCALE;
if (Util.overlap(playerX, playerW, sprite.offset + spriteW/2 * (sprite.offset > 0 ? 1 : -1), spriteW)) {
speed = maxSpeed/5;
position = Util.increase(playerSegment.p1.world.z, -playerZ, trackLength); // stop in front of sprite (at front of segment)
break;
}
}
}
for(n = 0 ; n < playerSegment.cars.length ; n++) {
car = playerSegment.cars[n];
carW = car.sprite.w * SPRITES.SCALE;
if (speed > car.speed) {
if (Util.overlap(playerX, playerW, car.offset, carW, 0.8)) {
speed = car.speed * (car.speed/speed);
position = Util.increase(car.z, -playerZ, trackLength);
break;
}
}
}
playerX = Util.limit(playerX, -3, 3); // dont ever let it go too far out of bounds
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
if (position > playerZ) {
if (currentLapTime && (startPosition < playerZ)) {
lastLapTime = currentLapTime;
currentLapTime = 0;
if (lastLapTime <= Util.toFloat(Dom.storage.fast_lap_time)) {
Dom.storage.fast_lap_time = lastLapTime;
updateHud('fast_lap_time', formatTime(lastLapTime));
Dom.addClassName('fast_lap_time', 'fastest');
Dom.addClassName('last_lap_time', 'fastest');
}
else {
Dom.removeClassName('fast_lap_time', 'fastest');
Dom.removeClassName('last_lap_time', 'fastest');
}
updateHud('last_lap_time', formatTime(lastLapTime));
Dom.show('last_lap_time');
}
else {
currentLapTime += dt;
}
}
updateHud('speed', 5 * Math.round(speed/500));
updateHud('current_lap_time', formatTime(currentLapTime));
}
//-------------------------------------------------------------------------
function updateCars(dt, playerSegment, playerW) {
var n, car, oldSegment, newSegment;
for(n = 0 ; n < cars.length ; n++) {
car = cars[n];
oldSegment = findSegment(car.z);
car.offset = car.offset + updateCarOffset(car, oldSegment, playerSegment, playerW);
car.z = Util.increase(car.z, dt * car.speed, trackLength);
car.percent = Util.percentRemaining(car.z, segmentLength); // useful for interpolation during rendering phase
newSegment = findSegment(car.z);
if (oldSegment != newSegment) {
index = oldSegment.cars.indexOf(car);
oldSegment.cars.splice(index, 1);
newSegment.cars.push(car);
}
}
}
function updateCarOffset(car, carSegment, playerSegment, playerW) {
var i, j, dir, segment, otherCar, otherCarW, lookahead = 20, carW = car.sprite.w * SPRITES.SCALE;
// optimization, dont bother steering around other cars when 'out of sight' of the player
if ((carSegment.index - playerSegment.index) > drawDistance)
return 0;
for(i = 1 ; i < lookahead ; i++) {
segment = segments[(carSegment.index+i)%segments.length];
if ((segment === playerSegment) && (car.speed > speed) && (Util.overlap(playerX, playerW, car.offset, carW, 1.2))) {
if (playerX > 0.5)
dir = -1;
else if (playerX < -0.5)
dir = 1;
else
dir = (car.offset > playerX) ? 1 : -1;
return dir * 1/i * (car.speed-speed)/maxSpeed; // the closer the cars (smaller i) and the greated the speed ratio, the larger the offset
}
for(j = 0 ; j < segment.cars.length ; j++) {
otherCar = segment.cars[j];
otherCarW = otherCar.sprite.w * SPRITES.SCALE;
if ((car.speed > otherCar.speed) && Util.overlap(car.offset, carW, otherCar.offset, otherCarW, 1.2)) {
if (otherCar.offset > 0.5)
dir = -1;
else if (otherCar.offset < -0.5)
dir = 1;
else
dir = (car.offset > otherCar.offset) ? 1 : -1;
return dir * 1/i * (car.speed-otherCar.speed)/maxSpeed;
}
}
}
// if no cars ahead, but I have somehow ended up off road, then steer back on
if (car.offset < -0.9)
return 0.1;
else if (car.offset > 0.9)
return -0.1;
else
return 0;
}
//-------------------------------------------------------------------------
function updateHud(key, value) { // accessing DOM can be slow, so only do it if value has changed
if (hud[key].value !== value) {
hud[key].value = value;
Dom.set(hud[key].dom, value);
}
}
function formatTime(dt) {
var minutes = Math.floor(dt/60);
var seconds = Math.floor(dt - (minutes * 60));
var tenths = Math.floor(10 * (dt - Math.floor(dt)));
if (minutes > 0)
return minutes + "." + (seconds < 10 ? "0" : "") + seconds + "." + tenths;
else
return seconds + "." + tenths;
}
//=========================================================================
// RENDER THE GAME WORLD
//=========================================================================
function render() {
var baseSegment = findSegment(position);
var basePercent = Util.percentRemaining(position, segmentLength);
var playerSegment = findSegment(position+playerZ);
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
var maxy = height;
var x = 0;
var dx = - (baseSegment.curve * basePercent);
ctx.clearRect(0, 0, width, height);
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset, resolution * hillSpeed * playerY);
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset, resolution * treeSpeed * playerY);
var n, i, segment, car, sprite, spriteScale, spriteX, spriteY;
for(n = 0 ; n < drawDistance ; n++) {
segment = segments[(baseSegment.index + n) % segments.length];
segment.looped = segment.index < baseSegment.index;
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
segment.clip = maxy;
Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
x = x + dx;
dx = dx + segment.curve;
if ((segment.p1.camera.z <= cameraDepth) || // behind us
(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
(segment.p2.screen.y >= maxy)) // clip by (already rendered) hill
continue;
Render.segment(ctx, width, lanes,
segment.p1.screen.x,
segment.p1.screen.y,
segment.p1.screen.w,
segment.p2.screen.x,
segment.p2.screen.y,
segment.p2.screen.w,
segment.fog,
segment.color);
maxy = segment.p1.screen.y;
}
for(n = (drawDistance-1) ; n > 0 ; n--) {
segment = segments[(baseSegment.index + n) % segments.length];
for(i = 0 ; i < segment.cars.length ; i++) {
car = segment.cars[i];
sprite = car.sprite;
spriteScale = Util.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent);
spriteX = Util.interpolate(segment.p1.screen.x, segment.p2.screen.x, car.percent) + (spriteScale * car.offset * roadWidth * width/2);
spriteY = Util.interpolate(segment.p1.screen.y, segment.p2.screen.y, car.percent);
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, car.sprite, spriteScale, spriteX, spriteY, -0.5, -1, segment.clip);
}
for(i = 0 ; i < segment.sprites.length ; i++) {
sprite = segment.sprites[i];
spriteScale = segment.p1.screen.scale;
spriteX = segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth * width/2);
spriteY = segment.p1.screen.y;
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite.source, spriteScale, spriteX, spriteY, (sprite.offset < 0 ? -1 : 0), -1, segment.clip);
}
if (segment == playerSegment) {
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
cameraDepth/playerZ,
width/2,
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * height/2),
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
playerSegment.p2.world.y - playerSegment.p1.world.y);
}
}
}
function findSegment(z) {
return segments[Math.floor(z/segmentLength) % segments.length];
}
//=========================================================================
// BUILD ROAD GEOMETRY
//=========================================================================
function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y; }
function addSegment(curve, y) {
var n = segments.length;
segments.push({
index: n,
p1: { world: { y: lastY(), z: n *segmentLength }, camera: {}, screen: {} },
p2: { world: { y: y, z: (n+1)*segmentLength }, camera: {}, screen: {} },
curve: curve,
sprites: [],
cars: [],
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
});
}
function addSprite(n, sprite, offset) {
segments[n].sprites.push({ source: sprite, offset: offset });
}
function addRoad(enter, hold, leave, curve, y) {
var startY = lastY();
var endY = startY + (Util.toInt(y, 0) * segmentLength);
var n, total = enter + hold + leave;
for(n = 0 ; n < enter ; n++)
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
for(n = 0 ; n < hold ; n++)
addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
for(n = 0 ; n < leave ; n++)
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY, (enter+hold+n)/total));
}
var ROAD = {
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
};
function addStraight(num) {
num = num || ROAD.LENGTH.MEDIUM;
addRoad(num, num, num, 0, 0);
}
function addHill(num, height) {
num = num || ROAD.LENGTH.MEDIUM;
height = height || ROAD.HILL.MEDIUM;
addRoad(num, num, num, 0, height);
}
function addCurve(num, curve, height) {
num = num || ROAD.LENGTH.MEDIUM;
curve = curve || ROAD.CURVE.MEDIUM;
height = height || ROAD.HILL.NONE;
addRoad(num, num, num, curve, height);
}
function addLowRollingHills(num, height) {
num = num || ROAD.LENGTH.SHORT;
height = height || ROAD.HILL.LOW;
addRoad(num, num, num, 0, height/2);
addRoad(num, num, num, 0, -height);
addRoad(num, num, num, ROAD.CURVE.EASY, height);
addRoad(num, num, num, 0, 0);
addRoad(num, num, num, -ROAD.CURVE.EASY, height/2);
addRoad(num, num, num, 0, 0);
}
function addSCurves() {
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.NONE);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, -ROAD.HILL.LOW);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.MEDIUM);
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, -ROAD.HILL.MEDIUM);
}
function addBumps() {
addRoad(10, 10, 10, 0, 5);
addRoad(10, 10, 10, 0, -2);
addRoad(10, 10, 10, 0, -5);
addRoad(10, 10, 10, 0, 8);
addRoad(10, 10, 10, 0, 5);
addRoad(10, 10, 10, 0, -7);
addRoad(10, 10, 10, 0, 5);
addRoad(10, 10, 10, 0, -2);
}
function addDownhillToEnd(num) {
num = num || 200;
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
}
function resetRoad() {
segments = [];
addStraight(ROAD.LENGTH.SHORT);
addLowRollingHills();
addSCurves();
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
addBumps();
addLowRollingHills();
addCurve(ROAD.LENGTH.LONG*2, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
addStraight();
addHill(ROAD.LENGTH.MEDIUM, ROAD.HILL.HIGH);
addSCurves();
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM, ROAD.HILL.NONE);
addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
addBumps();
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
addStraight();
addSCurves();
addDownhillToEnd();
resetSprites();
resetCars();
segments[findSegment(playerZ).index + 2].color = COLORS.START;
segments[findSegment(playerZ).index + 3].color = COLORS.START;
for(var n = 0 ; n < rumbleLength ; n++)
segments[segments.length-1-n].color = COLORS.FINISH;
trackLength = segments.length * segmentLength;
}
function resetSprites() {
var n, i;
addSprite(20, SPRITES.BILLBOARD07, -1);
addSprite(40, SPRITES.BILLBOARD06, -1);
addSprite(60, SPRITES.BILLBOARD08, -1);
addSprite(80, SPRITES.BILLBOARD09, -1);
addSprite(100, SPRITES.BILLBOARD01, -1);
addSprite(120, SPRITES.BILLBOARD02, -1);
addSprite(140, SPRITES.BILLBOARD03, -1);
addSprite(160, SPRITES.BILLBOARD04, -1);
addSprite(180, SPRITES.BILLBOARD05, -1);
addSprite(240, SPRITES.BILLBOARD07, -1.2);
addSprite(240, SPRITES.BILLBOARD06, 1.2);
addSprite(segments.length - 25, SPRITES.BILLBOARD07, -1.2);
addSprite(segments.length - 25, SPRITES.BILLBOARD06, 1.2);
for(n = 10 ; n < 200 ; n += 4 + Math.floor(n/100)) {
addSprite(n, SPRITES.PALM_TREE, 0.5 + Math.random()*0.5);
addSprite(n, SPRITES.PALM_TREE, 1 + Math.random()*2);
}
for(n = 250 ; n < 1000 ; n += 5) {
addSprite(n, SPRITES.COLUMN, 1.1);
addSprite(n + Util.randomInt(0,5), SPRITES.TREE1, -1 - (Math.random() * 2));
addSprite(n + Util.randomInt(0,5), SPRITES.TREE2, -1 - (Math.random() * 2));
}
for(n = 200 ; n < segments.length ; n += 3) {
addSprite(n, Util.randomChoice(SPRITES.PLANTS), Util.randomChoice([1,-1]) * (2 + Math.random() * 5));
}
var side, sprite, offset;
for(n = 1000 ; n < (segments.length-50) ; n += 100) {
side = Util.randomChoice([1, -1]);
addSprite(n + Util.randomInt(0, 50), Util.randomChoice(SPRITES.BILLBOARDS), -side);
for(i = 0 ; i < 20 ; i++) {
sprite = Util.randomChoice(SPRITES.PLANTS);
offset = side * (1.5 + Math.random());
addSprite(n + Util.randomInt(0, 50), sprite, offset);
}
}
}
function resetCars() {
cars = [];
var n, car, segment, offset, z, sprite, speed;
for (var n = 0 ; n < totalCars ; n++) {
offset = Math.random() * Util.randomChoice([-0.8, 0.8]);
z = Math.floor(Math.random() * segments.length) * segmentLength;
sprite = Util.randomChoice(SPRITES.CARS);
speed = maxSpeed/4 + Math.random() * maxSpeed/(sprite == SPRITES.SEMI ? 4 : 2);
car = { offset: offset, z: z, sprite: sprite, speed: speed };
segment = findSegment(car.z);
segment.cars.push(car);
cars.push(car);
}
}
//=========================================================================
// THE GAME LOOP
//=========================================================================
Game.run({
canvas: canvas, render: render, update: update, stats: stats, step: step,
images: ["background", "sprites"],
keys: [
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
],
ready: function(images) {
background = images[0];
sprites = images[1];
reset();
Dom.storage.fast_lap_time = Dom.storage.fast_lap_time || 180;
updateHud('fast_lap_time', formatTime(Util.toFloat(Dom.storage.fast_lap_time)));
}
});
function reset(options) {
options = options || {};
canvas.width = width = Util.toInt(options.width, width);
canvas.height = height = Util.toInt(options.height, height);
lanes = Util.toInt(options.lanes, lanes);
roadWidth = Util.toInt(options.roadWidth, roadWidth);
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
drawDistance = Util.toInt(options.drawDistance, drawDistance);
fogDensity = Util.toInt(options.fogDensity, fogDensity);
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
segmentLength = Util.toInt(options.segmentLength, segmentLength);
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
playerZ = (cameraHeight * cameraDepth);
resolution = height/480;
refreshTweakUI();
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
resetRoad(); // only rebuild road when necessary
}
//=========================================================================
// TWEAK UI HANDLERS
//=========================================================================
Dom.on('resolution', 'change', function(ev) {
var w, h, ratio;
switch(ev.target.options[ev.target.selectedIndex].value) {
case 'fine': w = 1280; h = 960; ratio=w/width; break;
case 'high': w = 1024; h = 768; ratio=w/width; break;
case 'medium': w = 640; h = 480; ratio=w/width; break;
case 'low': w = 480; h = 360; ratio=w/width; break;
}
reset({ width: w, height: h })
Dom.blur(ev);
});
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
function refreshTweakUI() {
Dom.get('lanes').selectedIndex = lanes-1;
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
}
//=========================================================================
</script>
</body>
</html>

View File

@ -1,3 +0,0 @@
return {
}

View File

@ -1,82 +0,0 @@
[
{
"enter": 1,
"hold" : 1,
"leave": 90,
"curve": 9,
"height": 80,
"width" : 3000
},
{
"enter": 25,
"hold" : 25,
"leave": 25,
"curve": 0,
"height": 0,
"width" : 1000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 2,
"height": 0,
"width" : 2000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 2,
"height": 20,
"width" : 2000
},
{
"enter": 100,
"hold" : 100,
"leave": 100,
"curve": 4,
"height": 40,
"width" : 4000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 0,
"height": 0,
"width" : 1000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 6,
"height": 60,
"width" : 3000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 0,
"height": 60,
"width" : 2000
},
{
"enter": 50,
"hold" : 50,
"leave": 50,
"curve": 0,
"height": 20,
"width" : 3000
},
{
"enter": 100,
"hold" : 100,
"leave": 100,
"curve": 0,
"height": -280,
"width" : 4000
}
]

View File

@ -1,17 +0,0 @@
[
{
"index" : 200,
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png",
"offset" : -0.1
},
{
"index" : 200,
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/cactus.png",
"offset" : 4
},
{
"index" : 150,
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/stump.png",
"offset" : 0
}
]

View File

@ -1,55 +0,0 @@
local racing = require("love_src.src.system.racing_phy")
local entity = {}
entity.__index = entity
function entity.load(actor, finish)
local self = setmetatable({}, entity)
self.data = {
pos = {0, 100, 0},
color = {255/255, 255/255, 255/255},
actor = actor,
race = {
speed = 0.0,
accel = 10.0,
},
finish = finish
}
return self
end
function entity:update(dt)
if (self.data.pos[1] > self.data.finish[1]) then
self.data.pos[1] = 0
self.data.race.speed = 0
self.data.race.accel = 10.0
end
self.data.race.speed = racing.accelerate(dt, 1, self.data.race.speed, self.data.actor.data.max_speed, self.data.race.accel)
-- self:accel(dt)
self.data.pos[1] = self.data.pos[1] + self.data.race.speed * dt
end
-- function entity:accel(dt)
-- if (self.data.current_accel <= self.data.actor.data.accel) then
-- self.data.current_accel = self.data.current_accel + dt
-- end
-- if (self.data.current_speed <= self.data.actor.data.max_speed) then
-- self.data.current_speed = self.data.current_speed + self.data.current_accel * dt
-- end
-- end
function entity:draw()
love.graphics.push()
love.graphics.setColor(self.data.color[1], self.data.color[2], self.data.color[3])
love.graphics.points(self.data.pos[1], self.data.pos[2])
love.graphics.print(self.data.race.speed, self.data.pos[1], self.data.pos[2])
-- love.graphics.print(string.format("Current Accel : %s", self.data.current_accel), 0, 40)
love.graphics.pop()
self.data.actor:draw()
end
return entity

View File

@ -1,41 +0,0 @@
local entity = {}
entity.__index = entity
function entity.load(actor)
local self = setmetatable({}, entity)
self.data = {
actor = actor,
increase_max_speed = 1,
increase_accel = 1
}
return self
end
function entity:update(dt)
end
function entity:draw()
love.graphics.push()
love.graphics.print(string.format("[(a)-Max Speed+(d)] : %s", self.data.increase_max_speed), 0, 40)
love.graphics.print(string.format("[(q)-Accel+(e)] : %s", self.data.increase_accel), 0, 60)
love.graphics.pop()
self.data.actor:draw()
end
function entity:keyreleased(key, scancode)
if (key == "a") then
self.data.actor.data.max_speed = self.data.actor.data.max_speed - self.data.increase_max_speed
elseif (key == "d") then
self.data.actor.data.max_speed = self.data.actor.data.max_speed + self.data.increase_max_speed
elseif (key == "q") then
self.data.actor.data.accel = self.data.actor.data.accel - self.data.increase_accel
elseif (key == "e") then
self.data.actor.data.accel = self.data.actor.data.accel + self.data.increase_accel
end
end
return entity

View File

@ -1,37 +0,0 @@
local entity = {}
entity.__index = entity
_data = {
name = "p1",
max_speed = 100.0,
accel = 2.0,
grip = 2.0,
brake = 2.0,
ui = {0, 0}
}
function entity.load(data)
local self = setmetatable({}, entity)
self.data = data or _data
return self
end
function entity:update(dt)
end
function entity:draw()
love.graphics.push()
love.graphics.print(string.format("Name : %s", self.data.name, self.data.ui[1], self.data.ui[2]))
love.graphics.print(string.format("Max Speed : %s", self.data.max_speed), self.data.ui[1], self.data.ui[2] + 20)
love.graphics.print(string.format("Accel : %s", self.data.accel),self.data.ui[1], self.data.ui[2] + 40)
love.graphics.print(string.format("Grip : %s", self.data.grip), self.data.ui[1], self.data.ui[2] + 60)
love.graphics.print(string.format("Brake : %s", self.data.brake), self.data.ui[1], self.data.ui[2] + 80)
love.graphics.pop()
end
return entity

View File

@ -1,24 +0,0 @@
local mode = {}
function mode.load(player)
end
function mode.update(dt)
end
function mode.draw()
end
function mode.keyreleased(key, scancode)
end
function mode.keypressed(key, scancode, isrepeat)
end
function mode.mousereleased(x, y, button, istouch, presses)
end
return mode

View File

@ -1,80 +0,0 @@
local slick = require("love_src.lib.slick")
local racer = require("love_src.src.entities.racing.racer")
local player = require("love_src.src.entities.shared.actor").load()
local mode = {}
local finish = {100, 100, 0}
local time = 0
local w, h = 800, 600
function mode:load()
self.entities = {}
self.level = {}
self.world = slick.newWorld(w, h, {
quadTreeX = 0,
quadTreeY = 0
})
local new_racer = racer.load(player, finish)
self.world:add(new_racer, 200, 500, slick.newCircleShape(0, 0, 16))
table.insert(self.entities, new_racer)
self.world:add(self.level, 0, 0, slick.newShapeGroup(
-- Boxes surrounding the map
slick.newRectangleShape(0, 0, w, 8), -- top
slick.newRectangleShape(0, 0, 8, h), -- left
slick.newRectangleShape(w - 8, 0, 8, h), -- right
slick.newRectangleShape(0, h - 8, w, 8), -- bottom
-- Triangles in corners
slick.newPolygonShape({ 8, h - h / 8, w / 4, h - 8, 8, h - 8 }),
slick.newPolygonShape({ w - w / 4, h, w - 8, h / 2, w - 8, h }),
-- Convex shape
slick.newPolygonMeshShape({ w / 2 + w / 4, h / 4, w / 2 + w / 4 + w / 8, h / 4 + h / 8, w / 2 + w / 4, h / 4 + h / 4, w / 2 + w / 4 + w / 16, h / 4 + h / 8 })
))
end
local function movePlayer(p, dt)
local goalX, goalY = p.x + dt * p.velocityX, p.y + dt * p.velocityY
p.x, p.y = world:move(p, goalX, goalY)
end
function mode:update(dt)
for _,e in pairs(self.entities) do
e:update(dt)
-- local goalX, goalY = w / 2, -1000
-- local actualX, actualY, collisions, count = self.world:move(e, goalX, goalY, function(item, other, shape, otherShape)
-- return "slide"
-- end)
-- print(actualX, actualY)
-- movePlayer(e, dt)
end
time = time + dt
end
function mode:draw()
for _,e in pairs(self.entities) do
e:draw()
end
slick.drawWorld(self.world)
love.graphics.print(time, love.graphics.getWidth() / 2, love.graphics.getHeight()/ 2)
end
function mode:keyreleased(key, scancode)
end
function mode:keypressed(key, scancode, isrepeat)
end
function mode:mousereleased(x, y, button, istouch, presses)
end
return mode

View File

@ -1,31 +0,0 @@
local trainee = require("love_src.src.entities.raising.trainee")
local mode = {}
local entities = {}
function mode.load(player)
entities.trainee = trainee.load(player)
end
function mode.update(dt)
entities.trainee:update(dt)
end
function mode.draw()
entities.trainee:draw()
end
function mode.keyreleased(key, scancode)
entities.trainee:keyreleased(key, scancode)
end
function mode.keypressed(key, scancode, isrepeat)
end
function mode.mousereleased(x, y, button, istouch, presses)
end
return mode

View File

@ -1,58 +0,0 @@
local function accelerate(dt, gas_input, speed, max_speed, accel, min_speed)
if (min_speed == nil) then
min_speed = 0
end
local new_speed = speed + accel * dt * gas_input
if (new_speed >= max_speed) then
new_speed = max_speed
elseif (new_speed <= min_speed) then
new_speed = min_speed
elseif(new_speed <= -max_speed) then
new_speed = -max_speed
end
return new_speed
end
local function centrifugal_force(dt, mass, speed, max_speed)
local speed_percent = (speed/max_speed)
local dx = dt * 2 * speed_percent
local dy = dt * 2 * speed_percent
return mass * dy * speed * speed
end
local function drift(dt, pos_x, pos_y, speed, max_speed, angle, scale_x, scale_y)
local speed_percent = (speed/max_speed)
local dx = dt * 2.0 * speed_percent -- at top speed, should be able to cross from left to right (-1 to 1) in 1 second
local dy = dt * 2.0 * speed_percent
cos = math.cos(angle)
sin = math.sin(angle)
return pos_x + (dx * speed_percent * cos) * (scale_x or 1),
pos_y + (dy * speed_percent * sin) * (scale_y or 1)
end
-- local function gas(accel, drag, friction)
-- friction = friction or 0
-- return accel - drag - friction
-- end
-- local function brake(decel, drag, grip, friction)
-- friction = friction or 0
-- return - decel - drag - grip - friction
-- end
-- local function roam(decel, drag, friction)
-- friction = friction or 0
-- return - decel - drag - friction
-- end
-- local function steer(_steer, grip, drag, velocity, friction)
-- friction = friction or 0
-- return _steer + grip + drag - velocity + friction
-- end
return {
accelerate = accelerate,
drift = drift,
centrifugal_force = centrifugal_force
}

View File

@ -1,68 +0,0 @@
local reap = require("lib.reap")
local BASE = reap.base_path(...)
local world = require("love_src.wrapper.Concord.world")
local debug_entity = require("love_src.src.world.common.template.debug_entity")
local button = require("love_src.src.world.common.template.button")
local video = require("love_src.src.world.common.template.video")
local c_video = require("love_src.src.world.common.component.video")
local wm = require("world_map")
local wrapper = world:extend()
local function button_func()
load_world(wm["2_town_square"])
end
function wrapper:new()
wrapper.super.new(self, BASE, ".1_intro")
end
function wrapper:load(_args)
wrapper.super.load(self, {
"love_src/src/world/common/system/"
}, {
{
assemblage = debug_entity.assembleDebug,
data = {
position = {0, 0},
label = "1_intro"
}
},
{
assemblage = button.assemble,
data = {
collider = {
x = 20,
y = 20,
w = 20,
h = 20
},
func = button_func,
label = "skip"
}
},
{
assemblage = video.assemble,
data = {
path = "love_src/asset/video/1_intro.ogv"
}
}
})
end
function wrapper:update(dt)
wrapper.super.update(self, dt)
for k, v in pairs(self.entities) do
local c_v = v[c_video.dict.video]
if c_v ~= nil then
if (not c_v.data.video:isPlaying()) then
load_world(wm["2_town_square"])
end
end
end
end
return wrapper

View File

@ -1,69 +0,0 @@
local components = {}
components.dict = {
resolution = "perspective.resolution",
lanes = "perspective.lanes",
draw_distance = "perspective.draw_distance",
road_width = "perspective.road_width",
fog_density = "perspective.fog_density",
field_of_view = "perspective.field_of_view",
camera_height = "perspective.camera_height",
segment_length = "perspective.segment_length",
segment_count = "perspective.segment_count",
rumble_length = "perspective.rumble_length",
segment_path = "perspective.segment_path",
segment_sprite_map_path = "perspective.segment_sprite_map_path"
}
function components.resolution (c, x, y)
c.data = {love.graphics.getDimensions()}
end
function components.lanes (c, x)
c.data = x
end
function components.draw_distance (c, x)
c.data = x
end
function components.road_width (c, x)
c.data = x
end
function components.fog_density (c, x)
c.data = x
end
function components.field_of_view (c, x)
c.data = x
end
function components.camera_height (c, x)
c.data = x
end
function components.segment_length (c, x)
c.data = x
end
function components.segment_count (c, x)
c.data = x
end
function components.rumble_length (c, x)
c.data = x
end
function components.segment_path (c, x)
c.data = x
end
function components.segment_sprite_map_path (c, x)
c.data = x
end
return components

View File

@ -1,13 +0,0 @@
local vm = require("lib.vornmath")
local components = {}
components.dict = {
pos = "pos3d.pos"
}
function components.pos (c, x, y, z)
c.data = vm.vec3({x, y, z})
end
return components

View File

@ -1,37 +0,0 @@
local reap = require("lib.reap")
local BASE = reap.base_path(...)
local world = require("love_src.wrapper.Concord.world")
local debug_entity = require("love_src.src.world.common.template.debug_entity")
local road = require("love_src.src.world.2_town_square.template.road")
local wm = require("world_map")
local wrapper = world:extend()
function wrapper:new()
wrapper.super.new(self, BASE, ".2_town_square")
end
function wrapper:load(_args)
wrapper.super.load(self, {
"love_src/src/world/common/system/",
"love_src/src/world/2_town_square/system"
}, {
{
assemblage = debug_entity.assembleDebug,
data = {
position = {0, 0},
label = "2_town_square"
}
},
{
assemblage = road.assemble,
data = road.default_data
},
})
end
return wrapper

View File

@ -1,12 +0,0 @@
local utils = {}
function utils.overlap(x1, w1, x2, w2, percent)
local half = (percent or 1)/2;
local min1 = x1 - (w1*half);
local max1 = x1 + (w1*half);
local min2 = x2 - (w2*half);
local max2 = x2 + (w2*half);
return not ((max1 < min2) or (min1 > max2));
end
return utils

View File

@ -1,55 +0,0 @@
local vm = require("lib.vornmath")
local utils = {}
function utils.easeIn(a,b,percent)
return a + (b-a)*math.pow(percent,2);
end
function utils.easeOut(a,b,percent)
return a + (b-a)*(1-math.pow(1-percent,2));
end
function utils.easeInOut(a,b,percent)
return a + (b-a)*((-math.cos(percent*math.pi)/2) + 0.5);
end
function utils.percentRemaining(n, total)
return (n%total)/total;
end
function utils.interpolate(a, b, percent)
return a + (b-a)*percent
end
function utils.randomInt(min, max)
return vm.round(utils.interpolate(min, max, math.random()));
end
function utils.randomChoice(options)
return options[utils.randomInt(1, #options)]
end
function utils.limit(value, min, max)
return math.max(min, math.min(value, max))
end
function utils.accelerate(v, accel, dt)
return v + (accel * dt)
end
function utils.exponentialFog(distance, density)
return 1 / (math.exp(distance * distance * density))
end
function utils.increase(start, increment, max, is_loop) -- with looping
local result = start + increment
if (result > max) and is_loop then
result = 1
elseif result <= 0 and is_loop then
result = max - 1
end
return result
end
return utils

View File

@ -1,89 +0,0 @@
local ease = require("love_src.src.world.2_town_square.pseudo3d.ease")
local utils = {}
local function drawQuad(slice, image, quads, x1, y1, x2, y2, w1, w2, h1, h2, sw, sh)
for i = 1, #quads do
local percent = ease.percentRemaining(i, #quads)
local destY = ease.interpolate(y1, y2, percent)
local destX = ease.interpolate(x1, x2, percent)
local destW = ease.interpolate(w1, w2, percent)
local destH = ease.interpolate(h1, h2, percent)
if (slice == "vertical") then
love.graphics.draw(image,quads[i],
destX, destY, 0, destW / sw, 1
)
elseif (slice == "horizontal") then
love.graphics.draw(image,quads[i],
destX, destY, 0, 1, destH / sh
)
end
end
end
local function drawSection(texture, key, x1, y1, x2, y2, w1, w2, h1, h2, resolution)
local image = texture[key].image
local quads = texture[key].quads
local sw, sh = image:getDimensions()
if (key == "floor") then
drawQuad("vertical", image, quads,
x1, y1,
x2, y2,
w1, w2,
h1, h2,
sw, sh
)
elseif (key == "ceil") then
drawQuad("vertical", image, quads,
x1, -y1 + resolution[2]/2,
x2, -y2 + resolution[2]/2,
w1, w2,
h1, h2,
sw, sh
)
elseif (key == "wallL") then
drawQuad("horizontal", image, quads,
x1, -y1 + h1,
x2, -y2 + h2,
w1, w2,
h1, h2,
sw, sh
)
elseif (key == "wallR") then
drawQuad("horizontal", image, quads,
x1 + w1, -y1,
x2 + w2, -y2,
w1, w2,
h1 + y1,
h2 + y2,
sw, sh
)
end
end
function utils.draw(p1screenpos, p2screenpos, p1screensize, p2screensize, texture, resolution, maxy)
local x1 = p1screenpos[1]
local y1 = p1screenpos[2]
local x2 = p2screenpos[1]
local y2 = p2screenpos[2]
local w1 = p1screensize[1]
local w2 = p2screensize[1]
local h1 = (y1)
local h2 = (y2)
local x1e = x1 + w1
local x2e = x2 + w2
drawSection(texture, "wallL", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
drawSection(texture, "wallR", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
drawSection(texture, "floor", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
drawSection(texture, "ceil", x1 , y1, x2, y2, w1, w2, h1, h2, resolution)
end
return utils

View File

@ -1,72 +0,0 @@
local projection = require("lib.choro.projection")
local UV = require("engine.utils.obj3d.texture.uv")
local quadToUV = UV.quadToUV
local imageToUV = UV.imageToUV
local QuadCache = require("engine.cache.loveapi.quadcache").obj
local utils = {}
function utils.drawstatic(segment, roadWidth, resolution)
-- render roadside sprites
local w2 = resolution[1]/2
for i = 1, #segment.sprites do
local sprite = segment.sprites[i].source;
local spriteoffset = segment.sprites[i].offset;
local spritePath = segment.sprites[i].data.path;
local spriteType = segment.sprites[i].data.type;
local spriteState = segment.sprites[i].data.state;
local quad
local img
if (spriteType == "aseprite") then
img = sprite.image
quad = sprite.frame.quad
else
img = sprite
quad = QuadCache:load(spritePath, 0, 0, 1, 1, img:getWidth(), img:getHeight(), spriteType)
end
local u, v, nu, nv = quadToUV(quad)
local spriteScale = segment.p1.screen.scale;
local spriteX = segment.p1.screen.pos.x + (spriteScale * spriteoffset * roadWidth * w2);
local spriteY = segment.p1.screen.pos.y;
local offsetX
if (spriteoffset < 0) then
offsetX = -1
else
offsetX = 0
end
local offsetY = -1
-- scale for projection AND relative to roadWidth (for tweakUI)
local destW = (img:getWidth() * spriteScale * w2) * (projection.getSpriteScale(resolution[1]/3) * roadWidth);
local destH = (img:getHeight() * spriteScale * w2) * (projection.getSpriteScale(resolution[1]/3) * roadWidth);
local destX = spriteX + (destW * (offsetX or 0));
local destY = spriteY + (destH * (offsetY or 0));
local clipH = 0;
if (segment.clip) then
clipH = math.max(0, destY+destH-segment.clip)
end
if (clipH < destH) then
local _nv = nv - (nv * clipH/destH)
local _destH = destH - clipH
local _quad = quad
if (_nv ~= nv) then
_quad = QuadCache:load(spritePath, u, v, nu, _nv, img:getWidth(), img:getHeight(), spriteType, spriteState)
end
love.graphics.draw(img, _quad, destX, destY, 0, destW, _destH)
end
end
end
return utils

View File

@ -1,37 +0,0 @@
--- get texture UV
---@param x number y
---@param y number x
---@param frameSizeX number size Frame X
---@param frameSizeY number size Frame Y
---@param sw number size Width
---@param sh number size Height
---@return number, number, number, number
local function getUV(x, y, frameSizeX, frameSizeY, sw, sh)
return
x / sw,
y / sh,
(x + 1 * frameSizeX) / sw,
(y + 1 * frameSizeY) / sh
end
--- get uv from quad
---@param quad love.Quad
local function quadToUV(quad)
local sw, sh = quad:getTextureDimensions()
local x, y, w, h = quad:getViewport()
local u, v, nu, nv = getUV(x, y, w, h, sw, sh)
return u, v, nu, nv
end
--- get uv from image
---@param image love.Image
local function imageToUV(image)
local u, v, nu, nv = getUV(0, 0, image:getWidth(), image:getHeight(),image:getWidth(), image:getHeight())
return u, v, nu, nv
end
return {
getUV = getUV,
imageToUV = imageToUV,
quadToUV = quadToUV
}

View File

@ -1,332 +0,0 @@
local reap = require("lib.reap")
local BASE = reap.base_path(...)
local easeFN = require(BASE .. ".ease")
local json = require("lib.json")
local QuadCache = require("love_src.wrapper.lappy.new.quad").obj
local ImageCache = require("love_src.wrapper.lappy.new.image").obj
local utils = {}
local function loadJSON(path)
local data = json.decode(love.filesystem.read(path))
return data
end
local ROAD = {
LENGTH= { NONE= 0, SHORT= 25, MEDIUM= 50, LONG= 100 }, -- num segments
HILL= { NONE= 0, LOW= 20, MEDIUM= 40, HIGH= 60 },
CURVE= { NONE= 0, EASY= 2, MEDIUM= 4, HARD= 6 }
};
-- function utils.getRumbleColor(index, rumbleLength)
-- local colorDark = hex2color("#1a1d33")
-- local colorBright = hex2color("#c8d0fa")
-- if (math.floor(index/rumbleLength)%2 == 0) then
-- return colorBright
-- else
-- return colorDark
-- end
-- end
-- function utils.getFinishStartColor()
-- local colorDark = hex2color("#CCFFCC")
-- local colorBright = hex2color("#FFCCFF")
-- return colorDark, colorBright
-- end
local function lastY(segments)
if (#segments == 0) then
return 0
else
return segments[#segments].p2.world.y
end
end
local function lastWidth(segments, fallback)
if (#segments == 0) then
return fallback
else
return segments[#segments].p2.world.w
end
end
function utils.findSegment(zPosition, segments, segmentLength)
local index = (math.floor(zPosition/segmentLength) % #segments) + 1
if (index >= #segments) then
index = #segments
end
return segments[index];
end
--- add quad and image
---@param path string
---@param slice "horizontal" | "vertical"
---@param w1 number
---@param w2 number
function utils.addQuads(path, slice, w1, w2)
local image = ImageCache:load_to(path, path)
local sw, sh = image:getDimensions()
local quads = {}
if (slice == "vertical") then
for i = 0, sh - 1 do
local percent = easeFN.percentRemaining(i, sh - 1)
local destW = easeFN.interpolate(w1, w2, percent)
table.insert(quads,
QuadCache:load_to(string.format("%s_%s_%s_%s_%s_%s_%s",
path, 0, i, sw, 1, sw, sh),
0, i, sw, 1, sw, sh)
)
end
elseif (slice == "horizontal") then
for i = 0, sw - 1 do
local percent = easeFN.percentRemaining(i, sw - 1)
local destH = easeFN.interpolate(w1, w2, percent)
table.insert(quads,
QuadCache:load_to(
string.format("%s_%s_%s_%s_%s_%s_%s",
path, i, 0, 1, sh, sw, sh),
i, 0, 1, sh, sw, sh)
)
end
end
return quads, image
end
--- add new segment to segments
---@param segments any
---@param segmentLength number z coord length
---@param rumbleLength number for debug purpose only
---@param curve number curve degree
---@param y number height
---@param width number
---@return table segment it's basically part that are drawn
function utils.addSegment(segments, segmentLength, rumbleLength, curve, y, width)
local index = #segments
local w1 = lastWidth(segments, width)
local w2 = width
local y1 = lastY(segments)
local y2 = y
local fQuads, fImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)
local cQuads, cImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "vertical", w1, w2)
local lQuads, lImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)
local rQuads, rImage = utils.addQuads("love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png", "horizontal", y1, y2)
return {
--- index for seeking
index= index + 1,
--- part 1 start
p1 = {
world =
{
w = w1,
y = y1,
z = index * segmentLength,
},
camera = {},
screen = {}
},
--- part 2 end
p2 = {
world = {
w = w2,
y = y2,
z = (index+1)* segmentLength,
},
camera = {},
screen = {}
},
--- curve to follow along
curve = curve,
sprites= {},
---p1 -> p2 quads
texture = {
floor = {
image = fImage,
quads = fQuads
},
ceil = {
image = cImage,
quads = cQuads
},
wallL = {
image = lImage,
quads = lQuads
},
wallR = {
image = rImage,
quads = rQuads
},
}
}
end
function utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, y, roadWidth)
local startY = lastY(segments);
local endY = startY + ((y or 0) * segmentLength);
local startWidth = lastWidth(segments, (roadWidth or 0));
local endWidth = (roadWidth or 0)
local total = enter + hold + leave;
for n = 1, enter do
table.insert(
segments,
utils.addSegment(
segments,
segmentLength,
rumbleLength,
easeFN.easeIn(0, curve, n/enter),
easeFN.easeInOut(startY, endY, n/total),
easeFN.easeInOut(startWidth, endWidth, n/total)
)
)
end
for n = 1, hold do
table.insert(
segments,
utils.addSegment(
segments,
segmentLength,
rumbleLength,
curve,
easeFN.easeInOut(startY, endY, (enter + n)/total),
easeFN.easeInOut(startWidth, endWidth, (enter + n)/total)
)
)
end
for n = 1, leave do
table.insert(
segments,
utils.addSegment(
segments,
segmentLength,
rumbleLength,
easeFN.easeInOut(curve, 0, n/leave),
easeFN.easeInOut(startY, endY, (enter+hold+n)/total),
easeFN.easeInOut(startWidth, endWidth, (enter + hold + n)/total)
)
)
end
return segments
end
function utils.addStraight(segments, segmentLength, rumbleLength, num)
num = num or ROAD.LENGTH.MEDIUM;
return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 2000);
end
function utils.addHill(segments, segmentLength, rumbleLength, num, height)
num = num or ROAD.LENGTH.MEDIUM;
height = height or ROAD.HILL.MEDIUM;
return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000);
end
function utils.addCurve(segments, segmentLength, rumbleLength, num, curve, height)
num = num or ROAD.LENGTH.MEDIUM;
curve = curve or ROAD.CURVE.MEDIUM;
height = height or ROAD.HILL.NONE;
return utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, curve, height, 2000);
end
function utils.addLowRollingHills(segments, segmentLength, rumbleLength, num, height)
num = num or ROAD.LENGTH.SHORT;
height = height or ROAD.HILL.LOW;
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, -height, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, height/2, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, 0, 0, 2000);
return segments
end
function utils.addSCurves(segments, segmentLength, rumbleLength)
utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, 2000);
utils.addRoad(segments, segmentLength, rumbleLength, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, 2000);
return segments
end
function utils.addDownhillToEnd(segments, segmentLength, rumbleLength, num)
num = num or 200;
utils.addRoad(segments, segmentLength, rumbleLength, num, num, num, -ROAD.CURVE.EASY, -lastY(segments)/segmentLength, 2000);
return segments
end
function utils.resetCars(cars, segments, segmentLength, totalCars, maxSpeed)
local n, car, segment, offset, z, sprite, speed;
for n = 0, totalCars do
offset = math.random() * easeFN.randomChoice({-0.8, 0.8});
z = math.floor(math.random() * segments.length) * segmentLength;
sprite = easeFN.randomChoice({
ImageCache:load_to("asset/image/sample/javascript-racer-master/images/sprites/bush1.png",
"asset/image/sample/javascript-racer-master/images/sprites/bush1.png")
});
speed = maxSpeed/4 + math.random() * maxSpeed;
car = { offset = offset, z = z, sprite = sprite, speed = speed };
segment = utils.findSegment(car.z, segments, segmentLength);
table.insert(segment.cars, car);
table.insert(cars, car);
end
end
function utils.loadRoad(segments, segmentLength, rumbleLength, path)
local segmentData = loadJSON(path)
for _, v in ipairs(segmentData) do
local enter = v.enter
local hold = v.hold
local leave = v.leave
local curve = v.curve
local height = v.height
local width = v.width
utils.addRoad(segments, segmentLength, rumbleLength, enter, hold, leave, curve, height, width);
end
end
function utils.resetRoad(path, pathSprite, segmentLength, rumbleLength, playerZ)
local segments = {}
utils.loadRoad(segments, segmentLength, rumbleLength, path)
if (pathSprite) then
utils.loadSpriteMapData(segments, pathSprite);
end
local trackLength = #segments * segmentLength;
return segments, trackLength
end
function utils.loadSpriteMapData(segments, path)
local segmentData = loadJSON(path)
for _, v in ipairs(segmentData) do
local index = v.index
local texture = v.texture
local offset = v.offset
utils.addSprite(segments, index, ImageCache:load_to(texture, texture), offset, texture);
end
end
function utils.addSprite(segments, index, sprite, offset, key)
table.insert(segments[index].sprites, {
source= sprite,
offset= offset,
data = {}
})
local _sIndex = #segments[index].sprites
segments[index].sprites[_sIndex].data = {
path = key,
type = "static"
}
end
return utils

View File

@ -1,252 +0,0 @@
local system_constructor = require("love_src.wrapper.Concord.system")
local roadsegment = require("love_src.src.world.2_town_square.pseudo3d.roadsegment")
local perspective = require("love_src.src.world.2_town_square.component.perspective")
local pos3d = require("love_src.src.world.2_town_square.component.pos3d")
local projection = require("lib.choro.projection")
local ease = require("love_src.src.world.2_town_square.pseudo3d.ease")
local vm = require("lib.vornmath")
local roaddraw = require("love_src.src.world.2_town_square.pseudo3d.render.road")
local system = {}
system.__index = system
system.pool = {
pool = {
perspective.dict.camera_height,
perspective.dict.draw_distance,
perspective.dict.field_of_view,
perspective.dict.fog_density,
perspective.dict.lanes,
perspective.dict.resolution,
perspective.dict.road_width,
perspective.dict.rumble_length,
perspective.dict.segment_count,
perspective.dict.segment_length,
perspective.dict.segment_path,
perspective.dict.segment_sprite_map_path,
pos3d.dict.pos,
}
}
system.components = {
[perspective.dict.camera_height] = perspective.camera_height,
[perspective.dict.draw_distance] = perspective.draw_distance,
[perspective.dict.field_of_view] = perspective.field_of_view,
[perspective.dict.fog_density] = perspective.fog_density,
[perspective.dict.lanes] = perspective.lanes,
[perspective.dict.resolution] = perspective.resolution,
[perspective.dict.road_width] = perspective.road_width,
[perspective.dict.rumble_length] = perspective.rumble_length,
[perspective.dict.segment_count] = perspective.segment_count,
[perspective.dict.segment_length] = perspective.segment_length,
[perspective.dict.segment_path] = perspective.segment_path,
[perspective.dict.segment_sprite_map_path] = perspective.segment_sprite_map_path,
[pos3d.dict.pos] = pos3d.pos,
}
function system.new()
local new_system = system_constructor.new("perspective", system.pool)
if (new_system) then
for k, v in pairs(system) do
new_system[k] = v
end
return new_system
else
return nil
end
end
function system:load()
for _, e in ipairs(self.pool) do
local segment_path = e[perspective.dict.segment_path].data
local segment_sprite_map_path = e[perspective.dict.segment_sprite_map_path].data
local segment_length = e[perspective.dict.segment_length].data
local rumble_length = e[perspective.dict.rumble_length].data
local player_y = e[pos3d.dict.pos].data
local segments, track_length = roadsegment.resetRoad(segment_path, segment_sprite_map_path, segment_length, rumble_length, scroll)
-- entityBuilder.setComponent(e, "segments", segments)
-- entityBuilder.setComponent(e, "track_length", track_length)
local field_of_view = e[perspective.dict.field_of_view].data
local camera_height = e[perspective.dict.camera_height].data
local camera_depth = projection.distanceCamToProjection(field_of_view)
local player_z = camera_depth * camera_height
e[pos3d.dict.pos].data[3] = player_z
e.segments = segments
e.track_length = track_length
e.camera_depth = camera_depth
end
end
function system:update(dt)
for _, e in ipairs(self.pool) do
if love.keyboard.isDown("up") then
e[pos3d.dict.pos].data[3] = e[pos3d.dict.pos].data[3] + 1000 * dt
elseif love.keyboard.isDown("down") then
e[pos3d.dict.pos].data[3] = e[pos3d.dict.pos].data[3] - 1000 * dt
end
if love.keyboard.isDown("left") then
e[pos3d.dict.pos].data[1] = e[pos3d.dict.pos].data[1] - 1 * dt
elseif love.keyboard.isDown("right") then
e[pos3d.dict.pos].data[1] = e[pos3d.dict.pos].data[1] + 1 * dt
end
if love.keyboard.isDown("w") then
e[pos3d.dict.pos].data[2] = e[pos3d.dict.pos].data[2] + 1000 * dt
elseif love.keyboard.isDown("s") then
e[pos3d.dict.pos].data[2] = e[pos3d.dict.pos].data[2] - 1000 * dt
end
end
end
local function drawRoad(
zPosition, drawDistance,
segments,
segment_length, track_length,
playerX, cameraHeight, cameraDepth,
resolution, lanes,
playerZ, fogDensity
)
if (zPosition == 0) then
zPosition = 1
end
local baseSegment = roadsegment.findSegment(zPosition, segments, segment_length);
local basePercent = ease.percentRemaining(zPosition, segment_length);
local playerSegment = roadsegment.findSegment(zPosition + playerZ, segments, segment_length);
local playerPercent = ease.percentRemaining(zPosition+playerZ, segment_length);
local playerY = ease.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
local maxy = resolution[2];
local x = 0;
local dx = - ((baseSegment.curve or 0) * basePercent);
local n, segment;
for n = 0, drawDistance do
local index = (baseSegment.index + n) % #segments
if (index >= #segments) then
index = n + 1
elseif (index <= 0) then
index = #segments - (n + 1)
end
segment = segments[index];
local loopPos = 0
if (segment.index < baseSegment.index) then
loopPos = track_length
end
local p1Width = segment.p1.world.w
local p2Width = segment.p2.world.w
local camera1 = vm.vec3({
(playerX * p1Width) - x, --x
playerY + cameraHeight, --y
zPosition - loopPos --z
})
local camera2 = vm.vec3({
(playerX * p2Width) - x - dx, --x
playerY + cameraHeight, --y
zPosition - loopPos --z
})
segment.fog = ease.exponentialFog(n/drawDistance, fogDensity);
segment.clip = maxy
local p1cam, p1screenpos, p1screensize = projection.projectWorldToCam({
segment.p1.world.w,
segment.p1.world.y,
segment.p1.world.z
}, camera1, cameraDepth, resolution, {
p1Width,
1
});
local p2cam, p2screenpos, p2screensize = projection.projectWorldToCam({
segment.p2.world.w,
segment.p2.world.y,
segment.p2.world.z
}, camera2, cameraDepth, resolution, {
p2Width,
1
});
x = x + dx;
dx = dx + (segment.curve or 0);
if ((p1cam[3] <= cameraDepth) or -- behind us
(p2screenpos[2] >= p1screenpos[2]) or -- back face cull
(p2screenpos[2] >= maxy)) -- clip by (already rendered) segment
then
else
-- segmentdebugdraw.draw(segment, resolution, maxy)
roaddraw.draw(p1screenpos, p2screenpos, p1screensize, p2screensize, segment.texture, resolution, maxy)
maxy = p1screenpos[2];
end
end
for n = (drawDistance), 0, -1 do
local index = (baseSegment.index + n) % #segments
if (index >= #segments) then
index = n + 1
elseif (index <= 0) then
index = #segments - (n + 1)
end
segment = segments[index];
local p1Width = segment.p1.world.w
-- for i = 1, #segment.sprites do
-- drawstatic.drawstatic(
-- segment, p1Width, resolution)
-- end
end
end
function system:draw()
for _, e in ipairs(self.pool) do
local player_y = e[pos3d.dict.pos].data[2] -- pos[2]
local draw_distance = e[perspective.dict.draw_distance].data
local camera_height = e[perspective.dict.camera_height].data
local resolution = e[perspective.dict.resolution].data
local lanes = e[perspective.dict.lanes].data
local segment_length = e[perspective.dict.segment_length].data
local fogDensity = e[perspective.dict.fog_density].data
local segments = e.segments
local player_x = e[pos3d.dict.pos].data[1] -- pos[1]
local camera_depth = e.camera_depth
local track_length = e.track_length
local player_z = e[pos3d.dict.pos].data[3] -- pos[3]
love.graphics.push()
drawRoad(
player_y, draw_distance,
segments,
segment_length, track_length,
player_x, camera_height, camera_depth,
resolution, lanes,
player_z,
fogDensity
)
love.graphics.pop()
love.graphics.print(string.format("%s,%s,%s",player_x, player_y, player_z), 20, 20)
end
end
return system

View File

@ -1,53 +0,0 @@
local perspective = require("love_src.src.world.2_town_square.component.perspective")
local pos3d = require("love_src.src.world.2_town_square.component.pos3d")
local template = {}
template.default_data = {
resolution = {love.graphics.getDimensions()}, -- resolution x, y
lanes = 3,
draw_distance = 300,
road_width = 2000,
fog_density = 0,
-- player
pos = {0, 1, 0},
player_width = 50,
centrifugal = 0.3,
player_speed = 0,
player_max_speed = 5000,
player_accel = 100,
-- camera
field_of_view = 140, -- field of view
camera_height = 1000,
-- road segment
segment_length = 200,
segment_count = 500,
rumble_length = 3,
segment_path = "love_src/data/map/segment.json",
segment_sprite_map_path = "love_src/data/map/sprites.json",
}
function template.assemble(e, data)
e:give(perspective.dict.camera_height, data.camera_height)
e:give(perspective.dict.draw_distance, data.draw_distance)
e:give(perspective.dict.field_of_view, data.field_of_view)
e:give(perspective.dict.fog_density, data.fog_density)
e:give(perspective.dict.lanes, data.lanes)
e:give(perspective.dict.resolution, data.resolution.x, data.resolution.y)
e:give(perspective.dict.road_width, data.road_width)
e:give(perspective.dict.rumble_length, data.rumble_length)
e:give(perspective.dict.segment_count, data.segment_count)
e:give(perspective.dict.segment_length, data.segment_length)
e:give(perspective.dict.segment_path, data.segment_path)
e:give(perspective.dict.segment_sprite_map_path, data.segment_sprite_map_path)
e:give(pos3d.dict.pos, data.pos[1], data.pos[2], data.pos[3])
end
return template

View File

@ -1,49 +0,0 @@
local image = require("love_src.wrapper.lappy.new.image").obj
local components = {}
components.dict = {
image_frame = "image_frame",
trigger_frame = "trigger_frame",
frame_i = "frame_i",
run_time = "run_time",
anim_direction = "anim_direction",
next_trigger_frame = "next_trigger_frame",
fps = "fps"
}
function components.image_frame (c, paths)
c.data = {
images = {},
path = paths or {}
}
for k, v in ipairs(paths) do
table.insert(c.data.images, image:load_to(v, v))
end
end
function components.trigger_frame (c, array)
c.data = array or {}
end
function components.frame_i (c, index)
c.data = index or 1
end
function components.run_time (c, float)
c.data = float or 0
end
function components.anim_direction (c, one)
c.data = one or 1
end
function components.next_trigger_frame (c, index)
c.data = index or 1
end
function components.fps (c, frame_per_second)
c.data = frame_per_second or 12
end
return components

View File

@ -1,24 +0,0 @@
local source = require("love_src.wrapper.lappy.new.source").obj
local components = {}
components.dict = {
bgm = "bgm",
sfx = "sfx",
}
function components.bgm (c, path)
c.data = {
source = source:load_to(path, path, "stream"),
path = path
}
end
function components.sfx (c, path)
c.data = {
source = source:load_to(path, path, "static"),
path = path
}
end
return components

View File

@ -1,30 +0,0 @@
local components = {}
components.dict = {
collider = "button.collider",
func = "button.func",
label = "button.label"
}
function components.collider (c, x, y, w, h)
c.data = {
x = x or 0,
y = y or 0,
w = w or 0,
h = h or 0
}
end
function default()
print("click")
end
function components.func (c, func)
c.data = func or default
end
function components.label (c, label)
c.data = label or ""
end
return components

View File

@ -1,11 +0,0 @@
local components = {}
components.dict = {
debug_label = "debug_label",
}
function components.debug_label (c, label)
c.data = label
end
return components

View File

@ -1,42 +0,0 @@
local quad = require("love_src.wrapper.lappy.new.quad").obj
local image = require("love_src.wrapper.lappy.new.image").obj
local components = {}
components.dict = {
quad = "quad",
texture = "texture",
}
local function load_image(path)
image:put_to(love.graphics.newImage(path), path)
end
local function load_quad(x, y, w, h, path, key)
load_image(path)
local i = image:load_from(path)
local iw, ih = i:getDimensions()
quad:put_to(love.graphics.newQuad(x or 0, y or 0, w or iw, h or ih, i), key)
end
function components.quad (c, quads)
c.data = {}
for _, q_data in pairs(quads) do
for k_q, q in pairs(q_data.quad) do
load_quad(q[1], q[2], q[3], q[4], q_data.image, k_q)
c.data[k_q] = {}
c.data[k_q].quad = quad:load_from(k_q)
c.data[k_q].image = image:load_from(q_data.image)
end
end
end
function components.texture (c, images)
c[components.dict.texture] = {}
for k, i in pairs(images) do
load_image(i)
c[components.dict.texture][k] = image:load_from(i)
end
end
return components

View File

@ -1,23 +0,0 @@
local vm = require("lib.vornmath")
local components = {}
components.dict = {
position = "position",
scale = "scale",
rotation = "rotation"
}
function components.position (c, x, y, z)
c.data = vm.vec3(x or 0, y or 0, z or 0)
end
function components.scale (c, sx, sy, sz)
c.data = vm.vec3(sx or 1, sy or 1, sz or 1)
end
function components.rotation (c, rx, ry, rz)
c.data = vm.vec3(rx or 0, ry or 0, rz or 0)
end
return components

View File

@ -1,16 +0,0 @@
local video = require("love_src.wrapper.lappy.new.video").obj
local components = {}
components.dict = {
video = "video",
}
function components.video (c, path)
c.data = {
video = video:load_to(path, path),
path = path
}
end
return components

View File

@ -1,48 +0,0 @@
local system_constructor = require("love_src.wrapper.Concord.system")
local debug_label = require("love_src.src.world.common.component.debug_label")
local transform = require("love_src.src.world.common.component.transform")
local system = {}
system.__index = system
system.pool = {
pool = {
debug_label.dict.debug_label,
transform.dict.position
}
}
system.components = {
[debug_label.dict.debug_label] = debug_label.debug_label,
[transform.dict.position] = transform.position
}
function system.new()
local new_system = system_constructor.new("debug_world_draw", system.pool)
if (new_system) then
for k, v in pairs(system) do
new_system[k] = v
end
return new_system
else
return nil
end
end
local function draw(text, x, y)
love.graphics.push()
love.graphics.print(text, x, y)
love.graphics.pop()
end
function system:draw()
for _, e in ipairs(self.pool) do
local text = e[debug_label.dict.debug_label].data
local x = e[transform.dict.position].data[1]
local y = e[transform.dict.position].data[2]
--draw(text, x, y)
end
end
return system

View File

@ -1,97 +0,0 @@
local floor = math.floor
local system_constructor = require("love_src.wrapper.Concord.system")
local audio = require("love_src.src.world.common.component.audio")
local animation = require("love_src.src.world.common.component.animation")
local system = {}
system.__index = system
system.pool = {
pool = {
audio.dict.sfx,
animation.dict.anim_direction,
animation.dict.fps,
animation.dict.frame_i,
animation.dict.image_frame,
animation.dict.next_trigger_frame,
animation.dict.run_time,
animation.dict.trigger_frame,
}
}
system.components = {
[audio.dict.sfx] = audio.sfx,
[animation.dict.anim_direction] = animation.anim_direction,
[animation.dict.fps] = animation.fps,
[animation.dict.frame_i] = animation.frame_i,
[animation.dict.image_frame] = animation.image_frame,
[animation.dict.next_trigger_frame] = animation.next_trigger_frame,
[animation.dict.run_time] = animation.run_time,
[animation.dict.trigger_frame] = animation.trigger_frame,
}
function system.new()
local new_system = system_constructor.new("flip", system.pool)
if (new_system) then
for k, v in pairs(system) do
new_system[k] = v
end
return new_system
else
return nil
end
end
--- system frame
local function update_time(time, delta, indicator)
return time + (delta * indicator)
end
local function get_loop(index, _max)
local md = index % _max
if (md == 0) then
return _max
else
return index % _max
end
end
local function update_fps_time(time, fps)
-- floor time to 1. check x fps per second
return floor(time / (1 / fps))
end
-- system trigger frame
local function is_trigger(trigger_frames, trigger_id, frame_id)
return (trigger_frames[trigger_id] == frame_id)
end
function system:update(dt)
for _, e in ipairs(self.pool) do
e[animation.dict.run_time].data = update_time(
e[animation.dict.run_time].data, dt, e[animation.dict.anim_direction].data
)
local new_index = update_fps_time(
e[animation.dict.run_time].data, e[animation.dict.fps].data
)
e[animation.dict.frame_i].data = get_loop(
new_index,
#e[animation.dict.image_frame].data.images
)
if (is_trigger(
e[animation.dict.trigger_frame].data,
e[animation.dict.next_trigger_frame].data,
e[animation.dict.frame_i].data
)) then
e[animation.dict.next_trigger_frame].data = get_loop(
e[animation.dict.next_trigger_frame].data + 1, #e[animation.dict.trigger_frame].data
)
e[audio.dict.sfx].data.source:play()
end
end
end
return system

Some files were not shown because too many files have changed in this diff Show More