libraries_setup #3
22
game/asset/image/sample/javascript-racer-master/LICENSE
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
111
game/asset/image/sample/javascript-racer-master/README.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
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!
|
||||||
|
|
||||||
36
game/asset/image/sample/javascript-racer-master/Rakefile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
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
|
||||||
28
game/asset/image/sample/javascript-racer-master/common.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
/****************************************/
|
||||||
|
/* 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); }
|
||||||
|
|
||||||
414
game/asset/image/sample/javascript-racer-master/common.js
Normal file
@ -0,0 +1,414 @@
|
|||||||
|
//=========================================================================
|
||||||
|
// 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];
|
||||||
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
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 }
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
BIN
game/asset/image/sample/javascript-racer-master/images/mute.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,36 @@
|
|||||||
|
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 }
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 908 B |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 790 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 745 B |
|
After Width: | Height: | Size: 742 B |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 786 B |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 973 B |
18
game/asset/image/sample/javascript-racer-master/index.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<!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>
|
||||||
BIN
game/asset/image/sample/javascript-racer-master/music/racer.mp3
Normal file
BIN
game/asset/image/sample/javascript-racer-master/music/racer.ogg
Normal file
143
game/asset/image/sample/javascript-racer-master/stats.js
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
314
game/asset/image/sample/javascript-racer-master/v1.straight.html
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
<!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 <canvas> 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>
|
||||||
|
|
||||||
387
game/asset/image/sample/javascript-racer-master/v2.curves.html
Normal file
@ -0,0 +1,387 @@
|
|||||||
|
<!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 <canvas> 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>
|
||||||
|
|
||||||
419
game/asset/image/sample/javascript-racer-master/v3.hills.html
Normal file
@ -0,0 +1,419 @@
|
|||||||
|
<!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 <canvas> 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>
|
||||||
688
game/asset/image/sample/javascript-racer-master/v4.final.html
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
<!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 <canvas> 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>
|
||||||
82
game/data/map/segment.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
17
game/data/map/sprites.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"index" : 200,
|
||||||
|
"texture" : "asset/image/sample/javascript-racer-master/images/sprites/column.png",
|
||||||
|
"offset" : -0.1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index" : 200,
|
||||||
|
"texture" : "asset/image/sample/javascript-racer-master/images/sprites/cactus.png",
|
||||||
|
"offset" : 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"index" : 150,
|
||||||
|
"texture" : "asset/image/sample/javascript-racer-master/images/sprites/stump.png",
|
||||||
|
"offset" : 0
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -65,11 +65,12 @@ end
|
|||||||
---@param cameraDepth number distance from camera to projection plane
|
---@param cameraDepth number distance from camera to projection plane
|
||||||
---@param resolution table render resolution {width, height}
|
---@param resolution table render resolution {width, height}
|
||||||
---@param size table object {width, height}
|
---@param size table object {width, height}
|
||||||
---@return table,table {x, y, z}, {w, h}
|
---@return table,table,table {x, y, z}, {x, y}, {w, h}
|
||||||
function projection.projectWorldToCam(world, cameraPos, cameraDepth, resolution, size)
|
function projection.projectWorldToCam(world, cameraPos, cameraDepth, resolution, size)
|
||||||
local relativetocamera = projection.translateToRelativeCamera(world, cameraPos)
|
local relativetocamera = projection.translateToRelativeCamera(world, cameraPos)
|
||||||
local scale = cameraDepth / relativetocamera[3];
|
local scale = cameraDepth / relativetocamera[3];
|
||||||
return
|
return
|
||||||
|
relativetocamera,
|
||||||
projection.scalePosToRelativeProjectCamera(relativetocamera, scale, resolution),
|
projection.scalePosToRelativeProjectCamera(relativetocamera, scale, resolution),
|
||||||
projection.scaleSizeToRelativeProjectCamera(size, scale, resolution)
|
projection.scaleSizeToRelativeProjectCamera(size, scale, resolution)
|
||||||
end
|
end
|
||||||
|
|||||||
@ -8,7 +8,7 @@ world = {
|
|||||||
["train"] = require("src.world.train")(),
|
["train"] = require("src.world.train")(),
|
||||||
};
|
};
|
||||||
|
|
||||||
current = wm["main_menu"]
|
current = wm["2_town_square"]
|
||||||
|
|
||||||
function load_world(world_to_load)
|
function load_world(world_to_load)
|
||||||
current = world_to_load
|
current = world_to_load
|
||||||
|
|||||||
75
game/src/world/2_town_square/component/perspective.lua
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
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",
|
||||||
|
|
||||||
|
scroll = "perspective.scroll",
|
||||||
|
|
||||||
|
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.scroll (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
|
||||||
@ -5,6 +5,7 @@ local BASE = reap.base_path(...)
|
|||||||
local world = require("wrapper.Concord.world")
|
local world = require("wrapper.Concord.world")
|
||||||
|
|
||||||
local debug_entity = require("src.world.common.template.debug_entity")
|
local debug_entity = require("src.world.common.template.debug_entity")
|
||||||
|
local road = require("src.world.2_town_square.template.road")
|
||||||
|
|
||||||
local wm = require("world_map")
|
local wm = require("world_map")
|
||||||
|
|
||||||
@ -16,7 +17,8 @@ end
|
|||||||
|
|
||||||
function wrapper:load(_args)
|
function wrapper:load(_args)
|
||||||
wrapper.super.load(self, {
|
wrapper.super.load(self, {
|
||||||
"src/world/common/system/"
|
"src/world/common/system/",
|
||||||
|
"src/world/2_town_square/system"
|
||||||
}, {
|
}, {
|
||||||
{
|
{
|
||||||
assemblage = debug_entity.assembleDebug,
|
assemblage = debug_entity.assembleDebug,
|
||||||
@ -25,6 +27,10 @@ function wrapper:load(_args)
|
|||||||
label = "2_town_square"
|
label = "2_town_square"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
assemblage = road.assemble,
|
||||||
|
data = road.default_data
|
||||||
|
},
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
89
game/src/world/2_town_square/pseudo3d/render/road.lua
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
local ease = require("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
|
||||||
72
game/src/world/2_town_square/pseudo3d/render/sprite.lua
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
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
|
||||||
37
game/src/world/2_town_square/pseudo3d/render/uv.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
--- 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
|
||||||
|
}
|
||||||
@ -77,9 +77,9 @@ function utils.addQuads(path, slice, w1, w2)
|
|||||||
local percent = easeFN.percentRemaining(i, sh - 1)
|
local percent = easeFN.percentRemaining(i, sh - 1)
|
||||||
local destW = easeFN.interpolate(w1, w2, percent)
|
local destW = easeFN.interpolate(w1, w2, percent)
|
||||||
table.insert(quads,
|
table.insert(quads,
|
||||||
QuadCache:load_to(string.format("%s %s %s %s %s %s %s",
|
QuadCache:load_to(string.format("%s_%s_%s_%s_%s_%s_%s",
|
||||||
path, 0, i, sw, 1, sw, sh),
|
path, 0, i, sw, 1, sw, sh),
|
||||||
path, 0, i, sw, 1, sw, sh)
|
0, i, sw, 1, sw, sh)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
elseif (slice == "horizontal") then
|
elseif (slice == "horizontal") then
|
||||||
@ -88,9 +88,9 @@ function utils.addQuads(path, slice, w1, w2)
|
|||||||
local destH = easeFN.interpolate(w1, w2, percent)
|
local destH = easeFN.interpolate(w1, w2, percent)
|
||||||
table.insert(quads,
|
table.insert(quads,
|
||||||
QuadCache:load_to(
|
QuadCache:load_to(
|
||||||
string.format("%s %s %s %s %s %s %s",
|
string.format("%s_%s_%s_%s_%s_%s_%s",
|
||||||
path, i, 0, 1, sh, sw, sh),
|
path, i, 0, 1, sh, sw, sh),
|
||||||
path, i, 0, 1, sh, sw, sh)
|
i, 0, 1, sh, sw, sh)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -315,12 +315,6 @@ function utils.loadSpriteMapData(segments, path)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local imageData = {
|
|
||||||
["car"] = {
|
|
||||||
path = "asset/image/sample/javascript-racer-master/images/sprites/car01.png"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function utils.addSprite(segments, index, sprite, offset, key)
|
function utils.addSprite(segments, index, sprite, offset, key)
|
||||||
table.insert(segments[index].sprites, {
|
table.insert(segments[index].sprites, {
|
||||||
source= sprite,
|
source= sprite,
|
||||||
@ -329,8 +323,8 @@ function utils.addSprite(segments, index, sprite, offset, key)
|
|||||||
})
|
})
|
||||||
local _sIndex = #segments[index].sprites
|
local _sIndex = #segments[index].sprites
|
||||||
segments[index].sprites[_sIndex].data = {
|
segments[index].sprites[_sIndex].data = {
|
||||||
path = imageData[key].path,
|
path = key,
|
||||||
type = imageData[key].type
|
type = "static"
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
254
game/src/world/2_town_square/system/perspective.lua
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
local system_constructor = require("wrapper.Concord.system")
|
||||||
|
|
||||||
|
local roadsegment = require("src.world.2_town_square.pseudo3d.roadsegment")
|
||||||
|
local perspective = require("src.world.2_town_square.component.perspective")
|
||||||
|
local projection = require("lib.choro.projection")
|
||||||
|
local ease = require("src.world.2_town_square.pseudo3d.ease")
|
||||||
|
local vm = require("lib.vornmath")
|
||||||
|
local roaddraw = require("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.scroll,
|
||||||
|
perspective.dict.segment_count,
|
||||||
|
perspective.dict.segment_length,
|
||||||
|
perspective.dict.segment_path,
|
||||||
|
perspective.dict.segment_sprite_map_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.scroll] = perspective.scroll,
|
||||||
|
[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
|
||||||
|
}
|
||||||
|
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 scroll = e[perspective.dict.scroll].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.player = {}
|
||||||
|
e.player.pos = {0, scroll, player_z}
|
||||||
|
|
||||||
|
e.segments = segments
|
||||||
|
e.track_length = track_length
|
||||||
|
|
||||||
|
e.camera_depth = camera_depth
|
||||||
|
|
||||||
|
-- entityBuilder.setComponent(e, "camera_depth", projection.distanceCamToProjection(field_of_view))
|
||||||
|
|
||||||
|
-- local camera_depth = e.camera_depth.data
|
||||||
|
-- entityBuilder.setComponent(e, "player_z", projection.getPlayerZ(camera_height, camera_depth))
|
||||||
|
|
||||||
|
-- entityBuilder.setComponent(e, "prev_frame", 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function system:update(dt)
|
||||||
|
for _, e in ipairs(self.pool) do
|
||||||
|
if love.keyboard.isDown("up") then
|
||||||
|
e.player.pos[3] = e.player.pos[3] + 1000 * dt
|
||||||
|
elseif love.keyboard.isDown("down") then
|
||||||
|
e.player.pos[3] = e.player.pos[3] - 1000 * dt
|
||||||
|
end
|
||||||
|
if love.keyboard.isDown("left") then
|
||||||
|
e.player.pos[1] = e.player.pos[1] + 1 * dt
|
||||||
|
elseif love.keyboard.isDown("right") then
|
||||||
|
e.player.pos[1] = e.player.pos[1] - 1 * dt
|
||||||
|
end
|
||||||
|
if love.keyboard.isDown("w") then
|
||||||
|
e.player.pos[2] = e.player.pos[2] + 1000 * dt
|
||||||
|
elseif love.keyboard.isDown("s") then
|
||||||
|
e.player.pos[2] = e.player.pos[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 scroll = e.player.pos[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.player.pos[1] -- pos[1]
|
||||||
|
local camera_depth = e.camera_depth
|
||||||
|
local track_length = e.track_length
|
||||||
|
local player_z = e.player.pos[3] -- pos[3]
|
||||||
|
|
||||||
|
love.graphics.push()
|
||||||
|
drawRoad(
|
||||||
|
scroll, 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, scroll, player_z), 20, 20)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return system
|
||||||
52
game/src/world/2_town_square/template/road.lua
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
local perspective = require("src.world.2_town_square.component.perspective")
|
||||||
|
|
||||||
|
local template = {}
|
||||||
|
|
||||||
|
template.default_data = {
|
||||||
|
resolution = {love.graphics.getDimensions()}, -- resolution x, y
|
||||||
|
lanes = 3,
|
||||||
|
draw_distance = 300,
|
||||||
|
road_width = 2000,
|
||||||
|
|
||||||
|
fog_density = 0,
|
||||||
|
|
||||||
|
-- player
|
||||||
|
player_x = 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,
|
||||||
|
|
||||||
|
scroll = 1,
|
||||||
|
|
||||||
|
segment_path = "data/map/segment.json",
|
||||||
|
segment_sprite_map_path = "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.scroll, data.scroll)
|
||||||
|
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)
|
||||||
|
end
|
||||||
|
|
||||||
|
return template
|
||||||
@ -5,7 +5,7 @@ local _cache = cache:extend()
|
|||||||
|
|
||||||
--- new function
|
--- new function
|
||||||
function _cache:new()
|
function _cache:new()
|
||||||
_cache.super.new(self, love.image.newQuad, ".quad")
|
_cache.super.new(self, love.graphics.newQuad, ".quad")
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||