Compare commits
36 Commits
afd7edd93d
...
5bf9e1841a
| Author | SHA1 | Date | |
|---|---|---|---|
| 5bf9e1841a | |||
| 92174baf7b | |||
| 8957b4427c | |||
| c84cf41222 | |||
| a9f4d30803 | |||
| e916cf63fa | |||
| 6a300c5b8f | |||
| 5c2aa31153 | |||
| 3e9f16dfbd | |||
| 7398450404 | |||
| 7f0b9101b3 | |||
| 0d8c45e621 | |||
| 5dbf8ce1d0 | |||
| a7fd31a83f | |||
| eef53745f3 | |||
| 9373054ebe | |||
| 91faa6f8de | |||
| eae97741d3 | |||
| 03c0f489b8 | |||
| ca11966ff4 | |||
| 6bba132f3e | |||
| 0a40f6554e | |||
| 7899b1444c | |||
| 11bfd4404e | |||
| 6f1b083076 | |||
| 08521535fe | |||
| 8f67a4de8b | |||
| f85339131a | |||
| e032650030 | |||
| 3cd0a1f8ae | |||
| 29c701ce6a | |||
| 391010b4c4 | |||
| aa87bdf016 | |||
| 9cee838411 | |||
| 1c2fad5a8a | |||
| dfb114b5fc |
@ -26,7 +26,7 @@ function love.conf(t)
|
||||
t.window.width = 1024
|
||||
t.window.height = 1024
|
||||
t.window.borderless = false
|
||||
t.window.resizable = true
|
||||
t.window.resizable = false
|
||||
t.window.minwidth = 10
|
||||
t.window.minheight = 9
|
||||
t.window.fullscreen = false
|
||||
|
||||
BIN
game/love_src/asset/audio/sfx/race/va/Test2.mp3
Normal file
BIN
game/love_src/asset/audio/sfx/race/va/Test3.mp3
Normal file
BIN
game/love_src/asset/audio/sfx/race/va/test1.mp3
Normal file
|
Before Width: | Height: | Size: 280 B |
|
Before Width: | Height: | Size: 496 B |
|
Before Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 643 B |
|
Before Width: | Height: | Size: 575 B |
|
Before Width: | Height: | Size: 639 B |
|
Before Width: | Height: | Size: 643 B |
|
Before Width: | Height: | Size: 739 B |
|
Before Width: | Height: | Size: 719 B |
|
Before Width: | Height: | Size: 653 B |
|
Before Width: | Height: | Size: 609 B |
|
Before Width: | Height: | Size: 557 B |
@ -1,22 +0,0 @@
|
||||
Copyright (c) 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
===============================================================================
|
||||
|
||||
@ -1,111 +0,0 @@
|
||||
Javascript Pseudo 3D Racer
|
||||
==========================
|
||||
|
||||
An Outrun-style pseudo-3d racing game in HTML5 and Javascript
|
||||
|
||||
* [play the game](https://jakesgordon.com/games/racer/)
|
||||
* view the [source](https://github.com/jakesgordon/javascript-racer)
|
||||
* read about [how it works](https://jakesgordon.com/writing/javascript-racer/)
|
||||
|
||||
Incrementally built up in 4 parts:
|
||||
|
||||
* play the [straight road demo](https://jakesgordon.com/games/racer/v1-straight/)
|
||||
* play the [curves demo](https://jakesgordon.com/games/racer/v2-curves/)
|
||||
* play the [hills demo](https://jakesgordon.com/games/racer/v3-hills/)
|
||||
* play the [final version](https://jakesgordon.com/games/racer/)
|
||||
|
||||
With detailed descriptions of how each part works:
|
||||
|
||||
* read more about [v1 - straight roads](https://jakesgordon.com/writing/javascript-racer-v1-straight/)
|
||||
* read more about [v2 - curves](https://jakesgordon.com/writing/javascript-racer-v2-curves/)
|
||||
* read more about [v3 - hills](https://jakesgordon.com/writing/javascript-racer-v3-hills/)
|
||||
* read more about v4 - final (coming soon)
|
||||
|
||||
A note on performance
|
||||
=====================
|
||||
|
||||
The performance of this game is **very** machine/browser dependent. It works quite well in modern
|
||||
browsers, especially those with GPU canvas acceleration, but a bad graphics driver can kill it stone
|
||||
dead. So your mileage may vary. There are controls provided to change the rendering resolution
|
||||
and the draw distance to scale to fit your machine.
|
||||
|
||||
Currently supported browsers include:
|
||||
|
||||
* Firefox (v12+) works great, 60fps at high res - Nice!
|
||||
* Chrome (v19+) works great, 60fps at high res... provided you dont have a bad GPU driver
|
||||
* IE9 - ok, 30fps at medium res... not great, but at least it works
|
||||
|
||||
The current state of mobile browser performance is pretty dismal. Dont expect this to be playable on
|
||||
any mobile device.
|
||||
|
||||
>> _NOTE: I havent actually spent anytime optimizing for performance yet. So it might be possible to
|
||||
make it play well on older browsers, but that's not really what this project is about._
|
||||
|
||||
A note on code structure
|
||||
========================
|
||||
|
||||
This project happens to be implemented in javascript (because its easy for prototyping) but
|
||||
is not intended to demonstrate javascript techniques or best practices. In fact, in order to
|
||||
keep it simple to understand it embeds the javascript for each example directly in the HTML
|
||||
page (horror!) and, even worse, uses global variables and functions (OMG!).
|
||||
|
||||
If I was building a real game I would have much more structure and organization to the
|
||||
code, but since its just a racing game tech demo, I have elected to [KISS](http://en.wikipedia.org/wiki/KISS_principle).
|
||||
|
||||
FUTURE
|
||||
======
|
||||
|
||||
It's quite astounding what it takes to actually [finish](https://jakesgordon.com/writing/defining-finished/)
|
||||
a game, even a simple one. And this is not a project that I plan on polishing into a finished state. It should
|
||||
really be considered just how to get started with a pseudo-3d racing game.
|
||||
|
||||
If we were to try to turn it into a real game we would have to consider:
|
||||
|
||||
* car sound fx
|
||||
* better synchronized music
|
||||
* full screen mode
|
||||
* HUD fx (flash on fastest lap, confetti, color coded speedometer, etc)
|
||||
* more accurate sprite collision
|
||||
* better car AI (steering, braking etc)
|
||||
* an actual crash when colliding at high speed
|
||||
* more bounce when car is off road
|
||||
* screen shake when off-road or collision
|
||||
* throw up dirt particles when off road
|
||||
* more dynamic camera (lower at faster speed, swoop over hills etc)
|
||||
* automatic resolution & drawDistance detection
|
||||
* projection based curves ? x,y rotation
|
||||
* sub-pixel aliasing artifacts on curves
|
||||
* smarter fog to cover sprites (blue against sky, cover sprites)
|
||||
* multiple stages, different maps
|
||||
* a lap map, with current position indicator
|
||||
* road splits and joins
|
||||
* day/night cycle
|
||||
* weather effects
|
||||
* tunnels, bridges, clouds, walls, buildings
|
||||
* city, desert, ocean
|
||||
* add city of seattle and space needle to background
|
||||
* 'bad guys' - add some competetor drivers to race against as well as the 'traffic'
|
||||
* different game modes - fastest lap, 1-on-1 racing, collect coins ? shoot bad guys ?
|
||||
* a whole lot of gameplay tuning
|
||||
* ...
|
||||
* ...
|
||||
|
||||
Related Links
|
||||
=============
|
||||
|
||||
* [Lou's Pseudo-3d Page](http://www.extentofthejam.com/pseudo/) - high level how-to guide
|
||||
* [Racer 10k](https://github.com/onaluf/RacerJS) - another javascript racing game
|
||||
|
||||
License
|
||||
=======
|
||||
|
||||
[MIT](http://en.wikipedia.org/wiki/MIT_License) license.
|
||||
|
||||
>> NOTE: the music tracks included in this project are royalty free resources paid for and licensed
|
||||
from [Lucky Lion Studios](http://luckylionstudios.com/). They are licensed ONLY for use in this
|
||||
project and should not be reproduced.
|
||||
|
||||
>> NOTE: the sprite graphics are placeholder graphics [borrowed](http://pixel.garoux.net/game/44) from the old
|
||||
genesis version of outrun and used here as teaching examples. If there are any pixel artists out there who want to
|
||||
provide original art to turn this into a real game please get in touch!
|
||||
|
||||
@ -1,36 +0,0 @@
|
||||
|
||||
desc 'recreate sprite sheets'
|
||||
task 'resprite' do
|
||||
require 'sprite_factory'
|
||||
|
||||
SpriteFactory.run!('images/sprites', :layout => :packed, :output_style => 'images/sprites.js', :margin => 5, :nocomments => true) do |images|
|
||||
SpriteHelper.javascript_style("SPRITES", images)
|
||||
end
|
||||
|
||||
SpriteFactory.run!('images/background', :layout => :vertical, :output_style => 'images/background.js', :margin => 5, :nocomments => true) do |images|
|
||||
SpriteHelper.javascript_style("BACKGROUND", images)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
|
||||
module SpriteHelper
|
||||
|
||||
# slightly unusual use of sprite-factory to generate a javascript object structure instead of CSS attributes...
|
||||
def self.javascript_style(variable, images)
|
||||
maxname = images.keys.inject(0) {|n,key| [n,key.length].max }
|
||||
rules = []
|
||||
images.each do |name, i|
|
||||
name = name.upcase
|
||||
whitespace = ' '*(maxname-name.length)
|
||||
x = '%4d' % i[:cssx]
|
||||
y = '%4d' % i[:cssy]
|
||||
w = '%4d' % i[:cssw]
|
||||
h = '%4d' % i[:cssh]
|
||||
rules << " #{name}: #{whitespace}{ x: #{x}, y: #{y}, w: #{w}, h: #{h} }"
|
||||
end
|
||||
"var #{variable} = {\n#{rules.join(",\n")}\n};"
|
||||
end
|
||||
|
||||
end
|
||||
@ -1,28 +0,0 @@
|
||||
|
||||
/****************************************/
|
||||
/* common styles used for v1 through v4 */
|
||||
/****************************************/
|
||||
|
||||
body { font-family: Arial, Helvetica, sans-serif; }
|
||||
#stats { border: 2px solid black; }
|
||||
#controls { width: 28em; float: left; padding: 1em; font-size: 0.7em; }
|
||||
#controls th { text-align: right; vertical-align: middle; }
|
||||
#instructions { clear: left; float: left; width: 17em; padding: 1em; border: 1px solid black; box-shadow: 0 0 5px black; }
|
||||
#racer { position: relative; z-index: 0; width: 640px; height: 480px; margin-left: 20em; border: 2px solid black; }
|
||||
#canvas { position: absolute; z-index: 0; width: 640px; height: 480px; z-index: 0; background-color: #72D7EE; }
|
||||
#mute { background-position: 0px 0px; width: 32px; height: 32px; background: url(images/mute.png); display: inline-block; cursor: pointer; position: absolute; margin-left: 20em; }
|
||||
#mute.on { background-position: -32px 0px; }
|
||||
|
||||
/**************************************************/
|
||||
/* rudimentary heads up display (only used in v4) */
|
||||
/**************************************************/
|
||||
|
||||
#hud { position: absolute; z-index: 1; width: 640px; padding: 5px 0; font-family: Verdana, Geneva, sans-serif; font-size: 0.8em; background-color: rgba(255,0,0,0.4); color: black; border-bottom: 2px solid black; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
|
||||
#hud .hud { background-color: rgba(255,255,255,0.6); padding: 5px; border: 1px solid black; margin: 0 5px; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
|
||||
#hud #speed { float: right; }
|
||||
#hud #current_lap_time { float: left; }
|
||||
#hud #last_lap_time { float: left; display: none; }
|
||||
#hud #fast_lap_time { display: block; width: 12em; margin: 0 auto; text-align: center; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
|
||||
#hud .value { color: black; font-weight: bold; }
|
||||
#hud .fastest { background-color: rgba(255,215,0,0.5); }
|
||||
|
||||
@ -1,414 +0,0 @@
|
||||
//=========================================================================
|
||||
// minimalist DOM helpers
|
||||
//=========================================================================
|
||||
|
||||
var Dom = {
|
||||
|
||||
get: function(id) { return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id); },
|
||||
set: function(id, html) { Dom.get(id).innerHTML = html; },
|
||||
on: function(ele, type, fn, capture) { Dom.get(ele).addEventListener(type, fn, capture); },
|
||||
un: function(ele, type, fn, capture) { Dom.get(ele).removeEventListener(type, fn, capture); },
|
||||
show: function(ele, type) { Dom.get(ele).style.display = (type || 'block'); },
|
||||
blur: function(ev) { ev.target.blur(); },
|
||||
|
||||
addClassName: function(ele, name) { Dom.toggleClassName(ele, name, true); },
|
||||
removeClassName: function(ele, name) { Dom.toggleClassName(ele, name, false); },
|
||||
toggleClassName: function(ele, name, on) {
|
||||
ele = Dom.get(ele);
|
||||
var classes = ele.className.split(' ');
|
||||
var n = classes.indexOf(name);
|
||||
on = (typeof on == 'undefined') ? (n < 0) : on;
|
||||
if (on && (n < 0))
|
||||
classes.push(name);
|
||||
else if (!on && (n >= 0))
|
||||
classes.splice(n, 1);
|
||||
ele.className = classes.join(' ');
|
||||
},
|
||||
|
||||
storage: window.localStorage || {}
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// general purpose helpers (mostly math)
|
||||
//=========================================================================
|
||||
|
||||
var Util = {
|
||||
|
||||
timestamp: function() { return new Date().getTime(); },
|
||||
toInt: function(obj, def) { if (obj !== null) { var x = parseInt(obj, 10); if (!isNaN(x)) return x; } return Util.toInt(def, 0); },
|
||||
toFloat: function(obj, def) { if (obj !== null) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return Util.toFloat(def, 0.0); },
|
||||
limit: function(value, min, max) { return Math.max(min, Math.min(value, max)); },
|
||||
randomInt: function(min, max) { return Math.round(Util.interpolate(min, max, Math.random())); },
|
||||
randomChoice: function(options) { return options[Util.randomInt(0, options.length-1)]; },
|
||||
percentRemaining: function(n, total) { return (n%total)/total; },
|
||||
accelerate: function(v, accel, dt) { return v + (accel * dt); },
|
||||
interpolate: function(a,b,percent) { return a + (b-a)*percent },
|
||||
easeIn: function(a,b,percent) { return a + (b-a)*Math.pow(percent,2); },
|
||||
easeOut: function(a,b,percent) { return a + (b-a)*(1-Math.pow(1-percent,2)); },
|
||||
easeInOut: function(a,b,percent) { return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5); },
|
||||
exponentialFog: function(distance, density) { return 1 / (Math.pow(Math.E, (distance * distance * density))); },
|
||||
|
||||
increase: function(start, increment, max) { // with looping
|
||||
var result = start + increment;
|
||||
while (result >= max)
|
||||
result -= max;
|
||||
while (result < 0)
|
||||
result += max;
|
||||
return result;
|
||||
},
|
||||
|
||||
project: function(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
|
||||
p.camera.x = (p.world.x || 0) - cameraX;
|
||||
p.camera.y = (p.world.y || 0) - cameraY;
|
||||
p.camera.z = (p.world.z || 0) - cameraZ;
|
||||
p.screen.scale = cameraDepth/p.camera.z;
|
||||
p.screen.x = Math.round((width/2) + (p.screen.scale * p.camera.x * width/2));
|
||||
p.screen.y = Math.round((height/2) - (p.screen.scale * p.camera.y * height/2));
|
||||
p.screen.w = Math.round( (p.screen.scale * roadWidth * width/2));
|
||||
},
|
||||
|
||||
overlap: function(x1, w1, x2, w2, percent) {
|
||||
var half = (percent || 1)/2;
|
||||
var min1 = x1 - (w1*half);
|
||||
var max1 = x1 + (w1*half);
|
||||
var min2 = x2 - (w2*half);
|
||||
var max2 = x2 + (w2*half);
|
||||
return ! ((max1 < min2) || (min1 > max2));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// POLYFILL for requestAnimationFrame
|
||||
//=========================================================================
|
||||
|
||||
if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function(callback, element) {
|
||||
window.setTimeout(callback, 1000 / 60);
|
||||
}
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// GAME LOOP helpers
|
||||
//=========================================================================
|
||||
|
||||
var Game = { // a modified version of the game loop from my previous boulderdash game - see https://jakesgordon.com/writing/javascript-boulderdash/#gameloop
|
||||
|
||||
run: function(options) {
|
||||
|
||||
Game.loadImages(options.images, function(images) {
|
||||
|
||||
options.ready(images); // tell caller to initialize itself because images are loaded and we're ready to rumble
|
||||
|
||||
Game.setKeyListener(options.keys);
|
||||
|
||||
var canvas = options.canvas, // canvas render target is provided by caller
|
||||
update = options.update, // method to update game logic is provided by caller
|
||||
render = options.render, // method to render the game is provided by caller
|
||||
step = options.step, // fixed frame step (1/fps) is specified by caller
|
||||
stats = options.stats, // stats instance is provided by caller
|
||||
now = null,
|
||||
last = Util.timestamp(),
|
||||
dt = 0,
|
||||
gdt = 0;
|
||||
|
||||
function frame() {
|
||||
now = Util.timestamp();
|
||||
dt = Math.min(1, (now - last) / 1000); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
|
||||
gdt = gdt + dt;
|
||||
while (gdt > step) {
|
||||
gdt = gdt - step;
|
||||
update(step);
|
||||
}
|
||||
render();
|
||||
stats.update();
|
||||
last = now;
|
||||
requestAnimationFrame(frame, canvas);
|
||||
}
|
||||
frame(); // lets get this party started
|
||||
Game.playMusic();
|
||||
});
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
loadImages: function(names, callback) { // load multiple images and callback when ALL images have loaded
|
||||
var result = [];
|
||||
var count = names.length;
|
||||
|
||||
var onload = function() {
|
||||
if (--count == 0)
|
||||
callback(result);
|
||||
};
|
||||
|
||||
for(var n = 0 ; n < names.length ; n++) {
|
||||
var name = names[n];
|
||||
result[n] = document.createElement('img');
|
||||
Dom.on(result[n], 'load', onload);
|
||||
result[n].src = "images/" + name + ".png";
|
||||
}
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
setKeyListener: function(keys) {
|
||||
var onkey = function(keyCode, mode) {
|
||||
var n, k;
|
||||
for(n = 0 ; n < keys.length ; n++) {
|
||||
k = keys[n];
|
||||
k.mode = k.mode || 'up';
|
||||
if ((k.key == keyCode) || (k.keys && (k.keys.indexOf(keyCode) >= 0))) {
|
||||
if (k.mode == mode) {
|
||||
k.action.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Dom.on(document, 'keydown', function(ev) { onkey(ev.keyCode, 'down'); } );
|
||||
Dom.on(document, 'keyup', function(ev) { onkey(ev.keyCode, 'up'); } );
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
stats: function(parentId, id) { // construct mr.doobs FPS counter - along with friendly good/bad/ok message box
|
||||
|
||||
var result = new Stats();
|
||||
result.domElement.id = id || 'stats';
|
||||
Dom.get(parentId).appendChild(result.domElement);
|
||||
|
||||
var msg = document.createElement('div');
|
||||
msg.style.cssText = "border: 2px solid gray; padding: 5px; margin-top: 5px; text-align: left; font-size: 1.15em; text-align: right;";
|
||||
msg.innerHTML = "Your canvas performance is ";
|
||||
Dom.get(parentId).appendChild(msg);
|
||||
|
||||
var value = document.createElement('span');
|
||||
value.innerHTML = "...";
|
||||
msg.appendChild(value);
|
||||
|
||||
setInterval(function() {
|
||||
var fps = result.current();
|
||||
var ok = (fps > 50) ? 'good' : (fps < 30) ? 'bad' : 'ok';
|
||||
var color = (fps > 50) ? 'green' : (fps < 30) ? 'red' : 'gray';
|
||||
value.innerHTML = ok;
|
||||
value.style.color = color;
|
||||
msg.style.borderColor = color;
|
||||
}, 5000);
|
||||
return result;
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
playMusic: function() {
|
||||
var music = Dom.get('music');
|
||||
music.loop = true;
|
||||
music.volume = 0.05; // shhhh! annoying music!
|
||||
music.muted = (Dom.storage.muted === "true");
|
||||
music.play();
|
||||
Dom.toggleClassName('mute', 'on', music.muted);
|
||||
Dom.on('mute', 'click', function() {
|
||||
Dom.storage.muted = music.muted = !music.muted;
|
||||
Dom.toggleClassName('mute', 'on', music.muted);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// canvas rendering helpers
|
||||
//=========================================================================
|
||||
|
||||
var Render = {
|
||||
|
||||
polygon: function(ctx, x1, y1, x2, y2, x3, y3, x4, y4, color) {
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.lineTo(x3, y3);
|
||||
ctx.lineTo(x4, y4);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
segment: function(ctx, width, lanes, x1, y1, w1, x2, y2, w2, fog, color) {
|
||||
|
||||
var r1 = Render.rumbleWidth(w1, lanes),
|
||||
r2 = Render.rumbleWidth(w2, lanes),
|
||||
l1 = Render.laneMarkerWidth(w1, lanes),
|
||||
l2 = Render.laneMarkerWidth(w2, lanes),
|
||||
lanew1, lanew2, lanex1, lanex2, lane;
|
||||
|
||||
ctx.fillStyle = color.grass;
|
||||
ctx.fillRect(0, y2, width, y1 - y2);
|
||||
|
||||
Render.polygon(ctx, x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, color.rumble);
|
||||
Render.polygon(ctx, x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, color.rumble);
|
||||
Render.polygon(ctx, x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, color.road);
|
||||
|
||||
if (color.lane) {
|
||||
lanew1 = w1*2/lanes;
|
||||
lanew2 = w2*2/lanes;
|
||||
lanex1 = x1 - w1 + lanew1;
|
||||
lanex2 = x2 - w2 + lanew2;
|
||||
for(lane = 1 ; lane < lanes ; lanex1 += lanew1, lanex2 += lanew2, lane++)
|
||||
Render.polygon(ctx, lanex1 - l1/2, y1, lanex1 + l1/2, y1, lanex2 + l2/2, y2, lanex2 - l2/2, y2, color.lane);
|
||||
}
|
||||
|
||||
Render.fog(ctx, 0, y1, width, y2-y1, fog);
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
background: function(ctx, background, width, height, layer, rotation, offset) {
|
||||
|
||||
rotation = rotation || 0;
|
||||
offset = offset || 0;
|
||||
|
||||
var imageW = layer.w/2;
|
||||
var imageH = layer.h;
|
||||
|
||||
var sourceX = layer.x + Math.floor(layer.w * rotation);
|
||||
var sourceY = layer.y
|
||||
var sourceW = Math.min(imageW, layer.x+layer.w-sourceX);
|
||||
var sourceH = imageH;
|
||||
|
||||
var destX = 0;
|
||||
var destY = offset;
|
||||
var destW = Math.floor(width * (sourceW/imageW));
|
||||
var destH = height;
|
||||
|
||||
ctx.drawImage(background, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
|
||||
if (sourceW < imageW)
|
||||
ctx.drawImage(background, layer.x, sourceY, imageW-sourceW, sourceH, destW-1, destY, width-destW, destH);
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
sprite: function(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY, clipY) {
|
||||
|
||||
// scale for projection AND relative to roadWidth (for tweakUI)
|
||||
var destW = (sprite.w * scale * width/2) * (SPRITES.SCALE * roadWidth);
|
||||
var destH = (sprite.h * scale * width/2) * (SPRITES.SCALE * roadWidth);
|
||||
|
||||
destX = destX + (destW * (offsetX || 0));
|
||||
destY = destY + (destH * (offsetY || 0));
|
||||
|
||||
var clipH = clipY ? Math.max(0, destY+destH-clipY) : 0;
|
||||
if (clipH < destH)
|
||||
ctx.drawImage(sprites, sprite.x, sprite.y, sprite.w, sprite.h - (sprite.h*clipH/destH), destX, destY, destW, destH - clipH);
|
||||
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
player: function(ctx, width, height, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown) {
|
||||
|
||||
var bounce = (1.5 * Math.random() * speedPercent * resolution) * Util.randomChoice([-1,1]);
|
||||
var sprite;
|
||||
if (steer < 0)
|
||||
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_LEFT : SPRITES.PLAYER_LEFT;
|
||||
else if (steer > 0)
|
||||
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_RIGHT : SPRITES.PLAYER_RIGHT;
|
||||
else
|
||||
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_STRAIGHT : SPRITES.PLAYER_STRAIGHT;
|
||||
|
||||
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY + bounce, -0.5, -1);
|
||||
},
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
fog: function(ctx, x, y, width, height, fog) {
|
||||
if (fog < 1) {
|
||||
ctx.globalAlpha = (1-fog)
|
||||
ctx.fillStyle = COLORS.FOG;
|
||||
ctx.fillRect(x, y, width, height);
|
||||
ctx.globalAlpha = 1;
|
||||
}
|
||||
},
|
||||
|
||||
rumbleWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(6, 2*lanes); },
|
||||
laneMarkerWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(32, 8*lanes); }
|
||||
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// RACING GAME CONSTANTS
|
||||
//=============================================================================
|
||||
|
||||
var KEY = {
|
||||
LEFT: 37,
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
A: 65,
|
||||
D: 68,
|
||||
S: 83,
|
||||
W: 87
|
||||
};
|
||||
|
||||
var COLORS = {
|
||||
SKY: '#72D7EE',
|
||||
TREE: '#005108',
|
||||
FOG: '#005108',
|
||||
LIGHT: { road: '#6B6B6B', grass: '#10AA10', rumble: '#555555', lane: '#CCCCCC' },
|
||||
DARK: { road: '#696969', grass: '#009A00', rumble: '#BBBBBB' },
|
||||
START: { road: 'white', grass: 'white', rumble: 'white' },
|
||||
FINISH: { road: 'black', grass: 'black', rumble: 'black' }
|
||||
};
|
||||
|
||||
var BACKGROUND = {
|
||||
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
|
||||
SKY: { x: 5, y: 495, w: 1280, h: 480 },
|
||||
TREES: { x: 5, y: 985, w: 1280, h: 480 }
|
||||
};
|
||||
|
||||
var SPRITES = {
|
||||
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
|
||||
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
|
||||
TREE1: { x: 625, y: 5, w: 360, h: 360 },
|
||||
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
|
||||
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
|
||||
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
|
||||
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
|
||||
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
|
||||
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
|
||||
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
|
||||
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
|
||||
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
|
||||
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
|
||||
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
|
||||
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
|
||||
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
|
||||
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
|
||||
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
|
||||
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
|
||||
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
|
||||
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
|
||||
STUMP: { x: 995, y: 330, w: 195, h: 140 },
|
||||
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
|
||||
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
|
||||
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
|
||||
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
|
||||
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
|
||||
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
|
||||
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
|
||||
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
|
||||
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
|
||||
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
|
||||
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
|
||||
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
|
||||
};
|
||||
|
||||
SPRITES.SCALE = 0.3 * (1/SPRITES.PLAYER_STRAIGHT.w) // the reference sprite width should be 1/3rd the (half-)roadWidth
|
||||
|
||||
SPRITES.BILLBOARDS = [SPRITES.BILLBOARD01, SPRITES.BILLBOARD02, SPRITES.BILLBOARD03, SPRITES.BILLBOARD04, SPRITES.BILLBOARD05, SPRITES.BILLBOARD06, SPRITES.BILLBOARD07, SPRITES.BILLBOARD08, SPRITES.BILLBOARD09];
|
||||
SPRITES.PLANTS = [SPRITES.TREE1, SPRITES.TREE2, SPRITES.DEAD_TREE1, SPRITES.DEAD_TREE2, SPRITES.PALM_TREE, SPRITES.BUSH1, SPRITES.BUSH2, SPRITES.CACTUS, SPRITES.STUMP, SPRITES.BOULDER1, SPRITES.BOULDER2, SPRITES.BOULDER3];
|
||||
SPRITES.CARS = [SPRITES.CAR01, SPRITES.CAR02, SPRITES.CAR03, SPRITES.CAR04, SPRITES.SEMI, SPRITES.TRUCK];
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
var BACKGROUND = {
|
||||
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
|
||||
SKY: { x: 5, y: 495, w: 1280, h: 480 },
|
||||
TREES: { x: 5, y: 985, w: 1280, h: 480 }
|
||||
};
|
||||
|
Before Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
@ -1,36 +0,0 @@
|
||||
var SPRITES = {
|
||||
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
|
||||
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
|
||||
TREE1: { x: 625, y: 5, w: 360, h: 360 },
|
||||
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
|
||||
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
|
||||
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
|
||||
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
|
||||
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
|
||||
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
|
||||
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
|
||||
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
|
||||
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
|
||||
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
|
||||
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
|
||||
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
|
||||
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
|
||||
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
|
||||
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
|
||||
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
|
||||
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
|
||||
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
|
||||
STUMP: { x: 995, y: 330, w: 195, h: 140 },
|
||||
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
|
||||
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
|
||||
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
|
||||
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
|
||||
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
|
||||
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
|
||||
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
|
||||
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
|
||||
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
|
||||
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
|
||||
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
|
||||
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
|
||||
};
|
||||
|
Before Width: | Height: | Size: 267 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 647 B |
|
Before Width: | Height: | Size: 908 B |
|
Before Width: | Height: | Size: 752 B |
|
Before Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 745 B |
|
Before Width: | Height: | Size: 742 B |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 751 B |
|
Before Width: | Height: | Size: 786 B |
|
Before Width: | Height: | Size: 758 B |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 973 B |
@ -1,18 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript Racer</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<ul>
|
||||
<li><a href='v1.straight.html'>Version 1 - Straight</a></li>
|
||||
<li><a href='v2.curves.html'>Version 2 - Curves</a></li>
|
||||
<li><a href='v3.hills.html'>Version 3 - Hills</a></li>
|
||||
<li><a href='v4.final.html'>Version 4 - Final</a></li>
|
||||
</ul>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@ -1,143 +0,0 @@
|
||||
/**
|
||||
* @author mrdoob / http://mrdoob.com/
|
||||
*/
|
||||
|
||||
var Stats = function () {
|
||||
|
||||
var startTime = Date.now(), prevTime = startTime;
|
||||
var ms = 0, msMin = 1000, msMax = 0;
|
||||
var fps = 0, fpsMin = 1000, fpsMax = 0;
|
||||
var frames = 0, mode = 0;mode
|
||||
var container = document.createElement( 'div' );
|
||||
container.id = 'stats';
|
||||
container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false );
|
||||
container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
|
||||
|
||||
var fpsDiv = document.createElement( 'div' );
|
||||
fpsDiv.id = 'fps';
|
||||
fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002';
|
||||
container.appendChild( fpsDiv );
|
||||
|
||||
var fpsText = document.createElement( 'div' );
|
||||
fpsText.id = 'fpsText';
|
||||
fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
|
||||
fpsText.innerHTML = 'FPS';
|
||||
fpsDiv.appendChild( fpsText );
|
||||
|
||||
var fpsGraph = document.createElement( 'div' );
|
||||
fpsGraph.id = 'fpsGraph';
|
||||
fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff';
|
||||
fpsDiv.appendChild( fpsGraph );
|
||||
|
||||
while ( fpsGraph.children.length < 74 ) {
|
||||
|
||||
var bar = document.createElement( 'span' );
|
||||
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113';
|
||||
fpsGraph.appendChild( bar );
|
||||
|
||||
}
|
||||
|
||||
var msDiv = document.createElement( 'div' );
|
||||
msDiv.id = 'ms';
|
||||
msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none';
|
||||
container.appendChild( msDiv );
|
||||
|
||||
var msText = document.createElement( 'div' );
|
||||
msText.id = 'msText';
|
||||
msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
|
||||
msText.innerHTML = 'MS';
|
||||
msDiv.appendChild( msText );
|
||||
|
||||
var msGraph = document.createElement( 'div' );
|
||||
msGraph.id = 'msGraph';
|
||||
msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
|
||||
msDiv.appendChild( msGraph );
|
||||
|
||||
while ( msGraph.children.length < 74 ) {
|
||||
|
||||
var bar = document.createElement( 'span' );
|
||||
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
|
||||
msGraph.appendChild( bar );
|
||||
|
||||
}
|
||||
|
||||
var setMode = function ( value ) {
|
||||
|
||||
mode = value;
|
||||
|
||||
switch ( mode ) {
|
||||
|
||||
case 0:
|
||||
fpsDiv.style.display = 'block';
|
||||
msDiv.style.display = 'none';
|
||||
break;
|
||||
case 1:
|
||||
fpsDiv.style.display = 'none';
|
||||
msDiv.style.display = 'block';
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var updateGraph = function ( dom, value ) {
|
||||
|
||||
var child = dom.appendChild( dom.firstChild );
|
||||
child.style.height = value + 'px';
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
domElement: container,
|
||||
|
||||
setMode: setMode,
|
||||
|
||||
current: function() { return fps; },
|
||||
|
||||
begin: function () {
|
||||
|
||||
startTime = Date.now();
|
||||
|
||||
},
|
||||
|
||||
end: function () {
|
||||
|
||||
var time = Date.now();
|
||||
|
||||
ms = time - startTime;
|
||||
msMin = Math.min( msMin, ms );
|
||||
msMax = Math.max( msMax, ms );
|
||||
|
||||
msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')';
|
||||
updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) );
|
||||
|
||||
frames ++;
|
||||
|
||||
if ( time > prevTime + 1000 ) {
|
||||
|
||||
fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
|
||||
fpsMin = Math.min( fpsMin, fps );
|
||||
fpsMax = Math.max( fpsMax, fps );
|
||||
|
||||
fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')';
|
||||
updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) );
|
||||
|
||||
prevTime = time;
|
||||
frames = 0;
|
||||
|
||||
}
|
||||
|
||||
return time;
|
||||
|
||||
},
|
||||
|
||||
update: function () {
|
||||
|
||||
startTime = this.end();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@ -1,314 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript Racer - v1 (straight)</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table id="controls">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href='v1.straight.html'>straight</a> |
|
||||
<a href='v2.curves.html'>curves</a> |
|
||||
<a href='v3.hills.html'>hills</a> |
|
||||
<a href='v4.final.html'>final</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||
<tr>
|
||||
<th><label for="resolution">Resolution :</label></th>
|
||||
<td>
|
||||
<select id="resolution" style="width:100%">
|
||||
<option value='fine'>Fine (1280x960)</option>
|
||||
<option selected value='high'>High (1024x768)</option>
|
||||
<option value='medium'>Medium (640x480)</option>
|
||||
<option value='low'>Low (480x360)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="lanes">Lanes :</label></th>
|
||||
<td>
|
||||
<select id="lanes">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option selected>3</option>
|
||||
<option>4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id='instructions'>
|
||||
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||
</div>
|
||||
|
||||
<div id="racer">
|
||||
<canvas id="canvas">
|
||||
Sorry, this example cannot be run because your browser does not support the <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>
|
||||
|
||||
@ -1,387 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript Racer - v2 (curves)</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table id="controls">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href='v1.straight.html'>straight</a> |
|
||||
<a href='v2.curves.html'>curves</a> |
|
||||
<a href='v3.hills.html'>hills</a> |
|
||||
<a href='v4.final.html'>final</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||
<tr>
|
||||
<th><label for="resolution">Resolution :</label></th>
|
||||
<td>
|
||||
<select id="resolution" style="width:100%">
|
||||
<option value='fine'>Fine (1280x960)</option>
|
||||
<option selected value='high'>High (1024x768)</option>
|
||||
<option value='medium'>Medium (640x480)</option>
|
||||
<option value='low'>Low (480x360)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="lanes">Lanes :</label></th>
|
||||
<td>
|
||||
<select id="lanes">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option selected>3</option>
|
||||
<option>4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id='instructions'>
|
||||
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||
</div>
|
||||
|
||||
<div id="racer">
|
||||
<canvas id="canvas">
|
||||
Sorry, this example cannot be run because your browser does not support the <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>
|
||||
|
||||
@ -1,419 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript Racer - v3 (hills)</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table id="controls">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href='v1.straight.html'>straight</a> |
|
||||
<a href='v2.curves.html'>curves</a> |
|
||||
<a href='v3.hills.html'>hills</a> |
|
||||
<a href='v4.final.html'>final</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||
<tr>
|
||||
<th><label for="resolution">Resolution :</label></th>
|
||||
<td>
|
||||
<select id="resolution" style="width:100%">
|
||||
<option value='fine'>Fine (1280x960)</option>
|
||||
<option selected value='high'>High (1024x768)</option>
|
||||
<option value='medium'>Medium (640x480)</option>
|
||||
<option value='low'>Low (480x360)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="lanes">Lanes :</label></th>
|
||||
<td>
|
||||
<select id="lanes">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option selected>3</option>
|
||||
<option>4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id='instructions'>
|
||||
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||
</div>
|
||||
|
||||
<div id="racer">
|
||||
<canvas id="canvas">
|
||||
Sorry, this example cannot be run because your browser does not support the <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>
|
||||
@ -1,688 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Javascript Racer - v4 (final)</title>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<table id="controls">
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<a href='v1.straight.html'>straight</a> |
|
||||
<a href='v2.curves.html'>curves</a> |
|
||||
<a href='v3.hills.html'>hills</a> |
|
||||
<a href='v4.final.html'>final</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||
<tr>
|
||||
<th><label for="resolution">Resolution :</label></th>
|
||||
<td>
|
||||
<select id="resolution" style="width:100%">
|
||||
<option value='fine'>Fine (1280x960)</option>
|
||||
<option selected value='high'>High (1024x768)</option>
|
||||
<option value='medium'>Medium (640x480)</option>
|
||||
<option value='low'>Low (480x360)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="lanes">Lanes :</label></th>
|
||||
<td>
|
||||
<select id="lanes">
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option selected>3</option>
|
||||
<option>4</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id='instructions'>
|
||||
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||
</div>
|
||||
|
||||
<div id="racer">
|
||||
<div id="hud">
|
||||
<span id="speed" class="hud"><span id="speed_value" class="value">0</span> mph</span>
|
||||
<span id="current_lap_time" class="hud">Time: <span id="current_lap_time_value" class="value">0.0</span></span>
|
||||
<span id="last_lap_time" class="hud">Last Lap: <span id="last_lap_time_value" class="value">0.0</span></span>
|
||||
<span id="fast_lap_time" class="hud">Fastest Lap: <span id="fast_lap_time_value" class="value">0.0</span></span>
|
||||
</div>
|
||||
<canvas id="canvas">
|
||||
Sorry, this example cannot be run because your browser does not support the <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>
|
||||
@ -1,3 +0,0 @@
|
||||
return {
|
||||
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
[
|
||||
{
|
||||
"enter": 1,
|
||||
"hold" : 1,
|
||||
"leave": 90,
|
||||
"curve": 9,
|
||||
"height": 80,
|
||||
"width" : 3000
|
||||
},
|
||||
{
|
||||
"enter": 25,
|
||||
"hold" : 25,
|
||||
"leave": 25,
|
||||
"curve": 0,
|
||||
"height": 0,
|
||||
"width" : 1000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 2,
|
||||
"height": 0,
|
||||
"width" : 2000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 2,
|
||||
"height": 20,
|
||||
"width" : 2000
|
||||
},
|
||||
{
|
||||
"enter": 100,
|
||||
"hold" : 100,
|
||||
"leave": 100,
|
||||
"curve": 4,
|
||||
"height": 40,
|
||||
"width" : 4000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 0,
|
||||
"height": 0,
|
||||
"width" : 1000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 6,
|
||||
"height": 60,
|
||||
"width" : 3000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 0,
|
||||
"height": 60,
|
||||
"width" : 2000
|
||||
},
|
||||
{
|
||||
"enter": 50,
|
||||
"hold" : 50,
|
||||
"leave": 50,
|
||||
"curve": 0,
|
||||
"height": 20,
|
||||
"width" : 3000
|
||||
},
|
||||
{
|
||||
"enter": 100,
|
||||
"hold" : 100,
|
||||
"leave": 100,
|
||||
"curve": 0,
|
||||
"height": -280,
|
||||
"width" : 4000
|
||||
}
|
||||
]
|
||||
@ -1,17 +0,0 @@
|
||||
[
|
||||
{
|
||||
"index" : 200,
|
||||
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/column.png",
|
||||
"offset" : -0.1
|
||||
},
|
||||
{
|
||||
"index" : 200,
|
||||
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/cactus.png",
|
||||
"offset" : 4
|
||||
},
|
||||
{
|
||||
"index" : 150,
|
||||
"texture" : "love_src/asset/image/sample/javascript-racer-master/images/sprites/stump.png",
|
||||
"offset" : 0
|
||||
}
|
||||
]
|
||||
@ -1,55 +0,0 @@
|
||||
local racing = require("love_src.src.system.racing_phy")
|
||||
|
||||
local entity = {}
|
||||
|
||||
entity.__index = entity
|
||||
|
||||
function entity.load(actor, finish)
|
||||
local self = setmetatable({}, entity)
|
||||
self.data = {
|
||||
pos = {0, 100, 0},
|
||||
color = {255/255, 255/255, 255/255},
|
||||
|
||||
actor = actor,
|
||||
|
||||
race = {
|
||||
speed = 0.0,
|
||||
accel = 10.0,
|
||||
},
|
||||
|
||||
finish = finish
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
function entity:update(dt)
|
||||
if (self.data.pos[1] > self.data.finish[1]) then
|
||||
self.data.pos[1] = 0
|
||||
self.data.race.speed = 0
|
||||
self.data.race.accel = 10.0
|
||||
end
|
||||
self.data.race.speed = racing.accelerate(dt, 1, self.data.race.speed, self.data.actor.data.max_speed, self.data.race.accel)
|
||||
-- self:accel(dt)
|
||||
self.data.pos[1] = self.data.pos[1] + self.data.race.speed * dt
|
||||
end
|
||||
|
||||
-- function entity:accel(dt)
|
||||
-- if (self.data.current_accel <= self.data.actor.data.accel) then
|
||||
-- self.data.current_accel = self.data.current_accel + dt
|
||||
-- end
|
||||
-- if (self.data.current_speed <= self.data.actor.data.max_speed) then
|
||||
-- self.data.current_speed = self.data.current_speed + self.data.current_accel * dt
|
||||
-- end
|
||||
-- end
|
||||
|
||||
function entity:draw()
|
||||
love.graphics.push()
|
||||
love.graphics.setColor(self.data.color[1], self.data.color[2], self.data.color[3])
|
||||
love.graphics.points(self.data.pos[1], self.data.pos[2])
|
||||
love.graphics.print(self.data.race.speed, self.data.pos[1], self.data.pos[2])
|
||||
-- love.graphics.print(string.format("Current Accel : %s", self.data.current_accel), 0, 40)
|
||||
love.graphics.pop()
|
||||
self.data.actor:draw()
|
||||
end
|
||||
|
||||
return entity
|
||||
@ -1,41 +0,0 @@
|
||||
local entity = {}
|
||||
|
||||
entity.__index = entity
|
||||
|
||||
function entity.load(actor)
|
||||
local self = setmetatable({}, entity)
|
||||
self.data = {
|
||||
actor = actor,
|
||||
|
||||
increase_max_speed = 1,
|
||||
increase_accel = 1
|
||||
}
|
||||
return self
|
||||
end
|
||||
|
||||
function entity:update(dt)
|
||||
|
||||
end
|
||||
|
||||
function entity:draw()
|
||||
love.graphics.push()
|
||||
love.graphics.print(string.format("[(a)-Max Speed+(d)] : %s", self.data.increase_max_speed), 0, 40)
|
||||
love.graphics.print(string.format("[(q)-Accel+(e)] : %s", self.data.increase_accel), 0, 60)
|
||||
love.graphics.pop()
|
||||
self.data.actor:draw()
|
||||
end
|
||||
|
||||
function entity:keyreleased(key, scancode)
|
||||
if (key == "a") then
|
||||
self.data.actor.data.max_speed = self.data.actor.data.max_speed - self.data.increase_max_speed
|
||||
elseif (key == "d") then
|
||||
self.data.actor.data.max_speed = self.data.actor.data.max_speed + self.data.increase_max_speed
|
||||
elseif (key == "q") then
|
||||
self.data.actor.data.accel = self.data.actor.data.accel - self.data.increase_accel
|
||||
elseif (key == "e") then
|
||||
self.data.actor.data.accel = self.data.actor.data.accel + self.data.increase_accel
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return entity
|
||||
@ -1,37 +0,0 @@
|
||||
local entity = {}
|
||||
|
||||
entity.__index = entity
|
||||
|
||||
_data = {
|
||||
name = "p1",
|
||||
|
||||
max_speed = 100.0,
|
||||
accel = 2.0,
|
||||
grip = 2.0,
|
||||
brake = 2.0,
|
||||
|
||||
ui = {0, 0}
|
||||
}
|
||||
|
||||
function entity.load(data)
|
||||
local self = setmetatable({}, entity)
|
||||
self.data = data or _data
|
||||
return self
|
||||
end
|
||||
|
||||
function entity:update(dt)
|
||||
end
|
||||
|
||||
function entity:draw()
|
||||
love.graphics.push()
|
||||
love.graphics.print(string.format("Name : %s", self.data.name, self.data.ui[1], self.data.ui[2]))
|
||||
love.graphics.print(string.format("Max Speed : %s", self.data.max_speed), self.data.ui[1], self.data.ui[2] + 20)
|
||||
love.graphics.print(string.format("Accel : %s", self.data.accel),self.data.ui[1], self.data.ui[2] + 40)
|
||||
love.graphics.print(string.format("Grip : %s", self.data.grip), self.data.ui[1], self.data.ui[2] + 60)
|
||||
love.graphics.print(string.format("Brake : %s", self.data.brake), self.data.ui[1], self.data.ui[2] + 80)
|
||||
love.graphics.pop()
|
||||
end
|
||||
|
||||
|
||||
|
||||
return entity
|
||||
@ -1,24 +0,0 @@
|
||||
local mode = {}
|
||||
|
||||
|
||||
function mode.load(player)
|
||||
|
||||
end
|
||||
|
||||
function mode.update(dt)
|
||||
end
|
||||
|
||||
function mode.draw()
|
||||
end
|
||||
|
||||
function mode.keyreleased(key, scancode)
|
||||
end
|
||||
|
||||
function mode.keypressed(key, scancode, isrepeat)
|
||||
end
|
||||
|
||||
function mode.mousereleased(x, y, button, istouch, presses)
|
||||
end
|
||||
|
||||
|
||||
return mode
|
||||
@ -1,80 +0,0 @@
|
||||
local slick = require("love_src.lib.slick")
|
||||
|
||||
local racer = require("love_src.src.entities.racing.racer")
|
||||
local player = require("love_src.src.entities.shared.actor").load()
|
||||
|
||||
local mode = {}
|
||||
|
||||
local finish = {100, 100, 0}
|
||||
|
||||
local time = 0
|
||||
|
||||
|
||||
local w, h = 800, 600
|
||||
|
||||
function mode:load()
|
||||
self.entities = {}
|
||||
self.level = {}
|
||||
self.world = slick.newWorld(w, h, {
|
||||
quadTreeX = 0,
|
||||
quadTreeY = 0
|
||||
})
|
||||
|
||||
local new_racer = racer.load(player, finish)
|
||||
|
||||
self.world:add(new_racer, 200, 500, slick.newCircleShape(0, 0, 16))
|
||||
|
||||
table.insert(self.entities, new_racer)
|
||||
|
||||
self.world:add(self.level, 0, 0, slick.newShapeGroup(
|
||||
-- Boxes surrounding the map
|
||||
slick.newRectangleShape(0, 0, w, 8), -- top
|
||||
slick.newRectangleShape(0, 0, 8, h), -- left
|
||||
slick.newRectangleShape(w - 8, 0, 8, h), -- right
|
||||
slick.newRectangleShape(0, h - 8, w, 8), -- bottom
|
||||
-- Triangles in corners
|
||||
slick.newPolygonShape({ 8, h - h / 8, w / 4, h - 8, 8, h - 8 }),
|
||||
slick.newPolygonShape({ w - w / 4, h, w - 8, h / 2, w - 8, h }),
|
||||
-- Convex shape
|
||||
slick.newPolygonMeshShape({ w / 2 + w / 4, h / 4, w / 2 + w / 4 + w / 8, h / 4 + h / 8, w / 2 + w / 4, h / 4 + h / 4, w / 2 + w / 4 + w / 16, h / 4 + h / 8 })
|
||||
))
|
||||
end
|
||||
|
||||
local function movePlayer(p, dt)
|
||||
local goalX, goalY = p.x + dt * p.velocityX, p.y + dt * p.velocityY
|
||||
p.x, p.y = world:move(p, goalX, goalY)
|
||||
end
|
||||
|
||||
function mode:update(dt)
|
||||
for _,e in pairs(self.entities) do
|
||||
e:update(dt)
|
||||
|
||||
-- local goalX, goalY = w / 2, -1000
|
||||
|
||||
-- local actualX, actualY, collisions, count = self.world:move(e, goalX, goalY, function(item, other, shape, otherShape)
|
||||
-- return "slide"
|
||||
-- end)
|
||||
-- print(actualX, actualY)
|
||||
-- movePlayer(e, dt)
|
||||
end
|
||||
time = time + dt
|
||||
end
|
||||
|
||||
function mode:draw()
|
||||
for _,e in pairs(self.entities) do
|
||||
e:draw()
|
||||
end
|
||||
slick.drawWorld(self.world)
|
||||
love.graphics.print(time, love.graphics.getWidth() / 2, love.graphics.getHeight()/ 2)
|
||||
end
|
||||
|
||||
function mode:keyreleased(key, scancode)
|
||||
end
|
||||
|
||||
function mode:keypressed(key, scancode, isrepeat)
|
||||
end
|
||||
|
||||
function mode:mousereleased(x, y, button, istouch, presses)
|
||||
end
|
||||
|
||||
return mode
|
||||
@ -1,31 +0,0 @@
|
||||
|
||||
local trainee = require("love_src.src.entities.raising.trainee")
|
||||
|
||||
local mode = {}
|
||||
|
||||
local entities = {}
|
||||
|
||||
function mode.load(player)
|
||||
entities.trainee = trainee.load(player)
|
||||
end
|
||||
|
||||
function mode.update(dt)
|
||||
entities.trainee:update(dt)
|
||||
end
|
||||
|
||||
function mode.draw()
|
||||
entities.trainee:draw()
|
||||
end
|
||||
|
||||
function mode.keyreleased(key, scancode)
|
||||
entities.trainee:keyreleased(key, scancode)
|
||||
end
|
||||
|
||||
function mode.keypressed(key, scancode, isrepeat)
|
||||
end
|
||||
|
||||
function mode.mousereleased(x, y, button, istouch, presses)
|
||||
end
|
||||
|
||||
|
||||
return mode
|
||||
61
game/love_src/src/new/shared/components/animation.lua
Normal file
@ -0,0 +1,61 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local sfx = require"love_src.wrapper.lappy.new.source".obj
|
||||
|
||||
local anim_math = require("love_src.src.system.animation")
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "animation")
|
||||
end
|
||||
|
||||
local function default_trigger()
|
||||
end
|
||||
|
||||
function world:load(e, data)
|
||||
self.data = {}
|
||||
|
||||
self.data.run_time = data.run_time or 0
|
||||
self.data.anim_direction = data.anim_direction or 1
|
||||
self.data.fps = data.fps or 12
|
||||
self.data.frames = data.frames or {}
|
||||
self.data.current_frame = data.current_frame or 1
|
||||
self.data.triggers = data.triggers or {}
|
||||
self.data.next_trigger = data.next_trigger or 1
|
||||
|
||||
self.data.triggers_fn = data.triggers_fn or {}
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
self.data.run_time = anim_math.update_time(
|
||||
self.data.run_time, dt, self.data.anim_direction
|
||||
)
|
||||
local new_index = anim_math.update_fps_time(
|
||||
self.data.run_time, self.data.fps
|
||||
)
|
||||
self.data.current_frame = anim_math.get_loop(
|
||||
new_index,
|
||||
#self.data.frames
|
||||
)
|
||||
|
||||
if (anim_math.is_trigger(
|
||||
self.data.triggers,
|
||||
self.data.next_trigger,
|
||||
self.data.current_frame
|
||||
)) then
|
||||
self.data.triggers_fn[self.data.current_frame]()
|
||||
self.data.next_trigger = anim_math.get_loop(
|
||||
self.data.next_trigger + 1, #self.data.triggers
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return world
|
||||
110
game/love_src/src/new/world/main_menu/components/button.lua
Normal file
@ -0,0 +1,110 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local sfx = require"love_src.wrapper.lappy.new.source".obj
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "button")
|
||||
end
|
||||
|
||||
function default_func()
|
||||
end
|
||||
|
||||
local default = {
|
||||
pos = vm.vec2(0, 0),
|
||||
wh = vm.vec2(0, 0),
|
||||
scale = 1,
|
||||
origin = vm.vec2(0, 0),
|
||||
label_origin = vm.vec2(0, 0),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 0, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = default_func,
|
||||
label = "skip",
|
||||
}
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data or default
|
||||
self.data.temp_color = self.data.bg_color
|
||||
self.mouse = "not click"
|
||||
self.sfx_highlight = sfx:load_to("love_src/asset/audio/sfx/book_flip.4.ogg", "love_src/asset/audio/sfx/book_flip.4.ogg", "static")
|
||||
self.sfx_click = sfx:load_to("love_src/asset/audio/sfx/book_flip.8.ogg", "love_src/asset/audio/sfx/book_flip.8.ogg", "static")
|
||||
self.sfx_non_highlight = sfx:load_to("love_src/asset/audio/sfx/book_flip.1.ogg", "love_src/asset/audio/sfx/book_flip.1.ogg", "static")
|
||||
end
|
||||
|
||||
function world:highlight()
|
||||
self.data.temp_color = self.data.highlight_color
|
||||
if (not self.inside) then
|
||||
self.sfx_highlight:play()
|
||||
self.inside = true
|
||||
end
|
||||
end
|
||||
|
||||
function world:non_highlight()
|
||||
if (self.inside) then
|
||||
self.inside = false
|
||||
self.data.temp_color = self.data.bg_color
|
||||
self.sfx_non_highlight:play()
|
||||
end
|
||||
end
|
||||
|
||||
function world:click()
|
||||
self.data.func()
|
||||
self.sfx_click:play()
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
local xm, ym = love.mouse.getPosition()
|
||||
local x, y, w, h, ox, oy, lox, loy, s = self.data.pos[1], self.data.pos[2],
|
||||
self.data.wh[1], self.data.wh[2],
|
||||
self.data.origin[1], self.data.origin[2],
|
||||
self.data.label_origin[1], self.data.label_origin[2],
|
||||
self.data.scale
|
||||
x, y, w, h, ox, oy, lox, loy = uib.transform(x, y, w, h, ox, oy, lox, loy, s)
|
||||
if (uib.is_inside(x, y, w, h, xm, ym)) then
|
||||
self:highlight()
|
||||
if (love.mouse.isDown(1)) then
|
||||
if (self.mouse == "not click") then
|
||||
self.mouse = "click"
|
||||
end
|
||||
else
|
||||
if (self.mouse == "click") then
|
||||
self.mouse = "not click"
|
||||
self:click()
|
||||
end
|
||||
end
|
||||
else
|
||||
self:non_highlight()
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_button(text, x, y, w, h, ox, oy, lox, loy, s, ct, cl, cb)
|
||||
x, y, w, h, ox, oy, lox, loy = uib.transform(x, y, w, h, ox, oy, lox, loy, s)
|
||||
test.draw_line_quad_start()
|
||||
uir.draw_box(x, y, w, h, cb)
|
||||
uir.draw_outline(x, y, w, h, cl)
|
||||
x = x + lox
|
||||
y = y + loy
|
||||
uir.draw_text(text, x, y, ct)
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
draw_button(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2], self.data.wh[1], self.data.wh[2],
|
||||
self.data.origin[1], self.data.origin[2],
|
||||
self.data.label_origin[1], self.data.label_origin[2],
|
||||
self.data.scale,
|
||||
self.data.label_color, self.data.line_color, self.data.temp_color
|
||||
)
|
||||
end
|
||||
|
||||
return world
|
||||
27
game/love_src/src/new/world/main_menu/components/label.lua
Normal file
@ -0,0 +1,27 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "button")
|
||||
end
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
uir.draw_text(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
end
|
||||
|
||||
return world
|
||||
50
game/love_src/src/new/world/main_menu/data/main_menu.lua
Normal file
@ -0,0 +1,50 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local wm = require"world_map"
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local function to_play()
|
||||
load_world(wm["town"])
|
||||
end
|
||||
|
||||
local wh = vm.vec2(120, 60)
|
||||
local l_wh = vm.vec2(50, 25)
|
||||
|
||||
return {
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_play",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2, lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_play,
|
||||
label = "Play",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "title",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.label"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2 - 100, 100),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
label = "Bibliotheca",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
36
game/love_src/src/new/world/main_menu/data/monastery.lua
Normal file
@ -0,0 +1,36 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local wm = require"world_map"
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local function to_track_choose()
|
||||
load_world(wm["track_choose"])
|
||||
end
|
||||
|
||||
local wh = vm.vec2(120, 60)
|
||||
local l_wh = vm.vec2(50, 25)
|
||||
|
||||
return {
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_race",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2, lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_track_choose,
|
||||
label = "Next Race",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
62
game/love_src/src/new/world/main_menu/data/town.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local wm = require"world_map"
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local function to_track_choose()
|
||||
load_world(wm["track_choose"])
|
||||
end
|
||||
|
||||
local function to_monastery()
|
||||
load_world(wm["monastery"])
|
||||
end
|
||||
|
||||
local wh = vm.vec2(120, 60)
|
||||
local l_wh = vm.vec2(50, 25)
|
||||
|
||||
return {
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_track_choose",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2 + wh[1], lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_track_choose,
|
||||
label = "Next Race",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_monastery",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2 - wh[1], lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_monastery,
|
||||
label = "Monastery",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
62
game/love_src/src/new/world/main_menu/data/track_choose.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local wm = require"world_map"
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local function to_track_1()
|
||||
load_world(wm["track_1"])
|
||||
end
|
||||
|
||||
local function to_track_2()
|
||||
load_world(wm["track_2"])
|
||||
end
|
||||
|
||||
local wh = vm.vec2(120, 60)
|
||||
local l_wh = vm.vec2(50, 25)
|
||||
|
||||
return {
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_track_1",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2 + wh[1], lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_track_1,
|
||||
label = "Track 1",
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_track_2",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2 - wh[1], lh/2),
|
||||
wh = wh,
|
||||
scale = 1,
|
||||
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
|
||||
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_track_2,
|
||||
label = "Track 2",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
101
game/love_src/src/new/world/main_menu/init.lua
Normal file
@ -0,0 +1,101 @@
|
||||
local wm = require"world_map"
|
||||
local vm = require"lib.vornmath"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "main_menu")
|
||||
self.data = {}
|
||||
self.entities_data = {}
|
||||
self.entities = {}
|
||||
end
|
||||
|
||||
local default = {
|
||||
|
||||
}
|
||||
|
||||
local function to_race()
|
||||
load_world(wm["race"])
|
||||
end
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local default_entities = {
|
||||
{
|
||||
type = "button",
|
||||
name = "button_to_race",
|
||||
components = {
|
||||
button = require"love_src.src.new.world.main_menu.components.button"
|
||||
},
|
||||
data = {
|
||||
button = {
|
||||
pos = vm.vec2(lw/2, lh/2),
|
||||
wh = vm.vec2(120, 60),
|
||||
scale = 1,
|
||||
origin = vm.vec2(-60, -30),
|
||||
label_origin = vm.vec2(25, 12.5),
|
||||
label_color = {255/255, 255/255, 255/255},
|
||||
bg_color = {0, 1, 0},
|
||||
line_color = {1, 0, 0},
|
||||
highlight_color = {0, 0, 1},
|
||||
func = to_race,
|
||||
label = "play",
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function world:setup()
|
||||
for _, d in pairs(self.entities_data) do
|
||||
local e = {
|
||||
type = d.type,
|
||||
name = d.name,
|
||||
components = {}
|
||||
}
|
||||
for t, c in pairs(d.components) do
|
||||
e.components[t] = c()
|
||||
e.components[t]:load(e, d.data[t])
|
||||
end
|
||||
table.insert(self.entities, e)
|
||||
end
|
||||
end
|
||||
|
||||
function world:load(data, entities_data)
|
||||
self.data = data
|
||||
self.entities_data = entities_data
|
||||
|
||||
self:setup()
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:update(dt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:draw()
|
||||
test.draw()
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:ui_draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return world
|
||||
52
game/love_src/src/new/world/race/components/actor.lua
Normal file
@ -0,0 +1,52 @@
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
local vm = require "lib.vornmath"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "actor")
|
||||
end
|
||||
|
||||
local default = {
|
||||
forward = 0.0,
|
||||
steer = 0.0
|
||||
}
|
||||
|
||||
function world:load(entity, data)
|
||||
self.e = entity
|
||||
self.data = data or default
|
||||
end
|
||||
|
||||
local function handle_input(v2, accel, brake, steer)
|
||||
local desire_forward = 0
|
||||
local desire_steer = 0
|
||||
if (v2[1] > 0) then
|
||||
desire_forward = accel
|
||||
elseif (v2[1] < 0) then
|
||||
desire_forward = - brake
|
||||
end
|
||||
if (v2[2] < 0) then
|
||||
desire_steer = steer
|
||||
elseif (v2[2] > 0) then
|
||||
desire_steer = - steer
|
||||
end
|
||||
return desire_forward, desire_steer
|
||||
end
|
||||
|
||||
local function plan()
|
||||
return vm.vec2(1.0, 1.0)
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
local accel, brake, steer = self.e.components.racer.data.accel, self.e.components.racer.data.brake, self.e.components.racer.data.steer
|
||||
local plan_v = plan()
|
||||
local desire_forward, desire_steer = handle_input(plan_v, accel, brake, steer)
|
||||
|
||||
self.data.forward = desire_forward
|
||||
self.data.steer = desire_steer
|
||||
end
|
||||
|
||||
return world
|
||||
46
game/love_src/src/new/world/race/components/checkpoints.lua
Normal file
@ -0,0 +1,46 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "checkpoints")
|
||||
end
|
||||
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data
|
||||
self.e = entity
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
-- self.data.label = self.e.components.animation.data.frames[self.e.components.animation.data.current_frame]
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
-- if (not self.disappear) then
|
||||
-- uir.draw_text(self.data.label,
|
||||
-- self.data.pos[1], self.data.pos[2],
|
||||
-- self.data.label_color
|
||||
-- )
|
||||
-- elseif (self.disappear and self.disappear > 0) then
|
||||
-- uir.draw_text(self.data.label,
|
||||
-- self.data.pos[1], self.data.pos[2],
|
||||
-- self.data.label_color
|
||||
-- )
|
||||
-- else
|
||||
-- self.data.label = ""
|
||||
-- uir.draw_text(self.data.label,
|
||||
-- self.data.pos[1], self.data.pos[2],
|
||||
-- self.data.label_color
|
||||
-- )
|
||||
-- end
|
||||
end
|
||||
|
||||
return world
|
||||
59
game/love_src/src/new/world/race/components/countdown.lua
Normal file
@ -0,0 +1,59 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "countdown")
|
||||
end
|
||||
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data
|
||||
self.e = entity
|
||||
end
|
||||
|
||||
function world:load_world_state(world_state)
|
||||
self.e.components.animation.data.triggers_fn[4] = function ()
|
||||
if (not self.disappear) then
|
||||
world_state.data.state = "start"
|
||||
self.e.components.animation.data.anim_direction = 0
|
||||
self.disappear = 2
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
if (self.disappear) then
|
||||
self.disappear = self.disappear - dt
|
||||
end
|
||||
self.data.label = self.e.components.animation.data.frames[self.e.components.animation.data.current_frame]
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
if (not self.disappear) then
|
||||
uir.draw_text(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
elseif (self.disappear and self.disappear > 0) then
|
||||
uir.draw_text(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
else
|
||||
self.data.label = ""
|
||||
uir.draw_text(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
return world
|
||||
37
game/love_src/src/new/world/race/components/map.lua
Normal file
@ -0,0 +1,37 @@
|
||||
local vm = require"lib.vornmath"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "map")
|
||||
end
|
||||
|
||||
local default = {
|
||||
drag_movement = 0.0,
|
||||
friction = 1.0,
|
||||
drag = vm.vec2(1.0, 0.0)
|
||||
}
|
||||
|
||||
function world:load(entity, data)
|
||||
self.e = entity
|
||||
self.data = data or default
|
||||
end
|
||||
|
||||
local function debug_data(x, y, r, g, b, to_debug)
|
||||
local font_ix = test.draw_font_start()
|
||||
test.draw_font_set_base_color(r, g, b)
|
||||
for k, v in pairs(to_debug) do
|
||||
y = y + test.draw_font(font_ix, string.format("%s : %s", k, v), x, y)
|
||||
end
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
-- debug_data(650, 800, 1.0, 0.0, 1.0, self.data)
|
||||
end
|
||||
|
||||
return world
|
||||
27
game/love_src/src/new/world/race/components/phase.lua
Normal file
@ -0,0 +1,27 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "phase")
|
||||
end
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
uir.draw_text(self.data.label,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
end
|
||||
|
||||
return world
|
||||
51
game/love_src/src/new/world/race/components/player.lua
Normal file
@ -0,0 +1,51 @@
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "player")
|
||||
end
|
||||
|
||||
local function handle_input(accel, brake, steer)
|
||||
local up = love.keyboard.isDown("up")
|
||||
local down = love.keyboard.isDown("down")
|
||||
local left = love.keyboard.isDown("left")
|
||||
local right = love.keyboard.isDown("right")
|
||||
|
||||
local desire_forward = 0
|
||||
local desire_steer = 0
|
||||
if (up) then
|
||||
desire_forward = accel
|
||||
elseif (down) then
|
||||
desire_forward = - brake
|
||||
end
|
||||
if (left) then
|
||||
desire_steer = steer
|
||||
elseif (right) then
|
||||
desire_steer = - steer
|
||||
end
|
||||
return desire_forward, desire_steer
|
||||
end
|
||||
|
||||
local default = {
|
||||
forward = 0.0,
|
||||
steer = 0.0
|
||||
}
|
||||
|
||||
function world:load(entity, data)
|
||||
self.e = entity
|
||||
self.data = data or default
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
local accel, brake, steer = self.e.components.racer.data.accel, self.e.components.racer.data.brake, self.e.components.racer.data.steer
|
||||
local desire_forward, desire_steer = handle_input(accel, brake, steer)
|
||||
|
||||
self.data.forward = desire_forward
|
||||
self.data.steer = desire_steer
|
||||
end
|
||||
|
||||
return world
|
||||
255
game/love_src/src/new/world/race/components/racer.lua
Normal file
@ -0,0 +1,255 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local racing_force = require("love_src.src.system.racing_force")
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local get_loop = require"love_src.src.system.animation".get_loop
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "racer")
|
||||
end
|
||||
|
||||
local default = {
|
||||
pos = vm.vec2(44.64, 10.87),
|
||||
|
||||
orientation = vm.vec2(0.0, 0.0),
|
||||
orientation_speed = vm.vec2(0.0, 0.0),
|
||||
min_orientation_speed = -1.0,
|
||||
max_orientation_speed = 1.0,
|
||||
|
||||
velocity = vm.vec2(0.0, 0.0),
|
||||
max_speed = 0.1,
|
||||
min_speed = -0.1,
|
||||
inertia = vm.vec2(0.0, 0.0),
|
||||
|
||||
accel = 0.1,
|
||||
brake = 0.1,
|
||||
grip = 0.1,
|
||||
steer = 0.1,
|
||||
mass = 0.1,
|
||||
streamline = 0.1
|
||||
}
|
||||
|
||||
local magic_w = {
|
||||
tire = 0.01,
|
||||
floor = 0.1,
|
||||
forward = 0.01,
|
||||
steer = 0.01,
|
||||
drag_boost = 0.01,
|
||||
drag_halt = 0.1,
|
||||
inertia = 0.0,
|
||||
centrifugal = 0.1,
|
||||
dampening = 0.1
|
||||
}
|
||||
|
||||
function world:load(entity, data)
|
||||
self.e = entity
|
||||
self.data = data or default
|
||||
self.map = {}
|
||||
|
||||
self.countdown = {}
|
||||
self.world_state = {}
|
||||
|
||||
self.late_timer = 0
|
||||
|
||||
self.time_attacks = {}
|
||||
self.lap = 1
|
||||
self.current_cp = 1
|
||||
self.prev_cp = 0
|
||||
|
||||
self.checkpoints = {}
|
||||
|
||||
self.is_finish = false
|
||||
|
||||
self.show_pov = false
|
||||
end
|
||||
|
||||
function get(s)
|
||||
return
|
||||
s.data.max_speed,
|
||||
s.data.min_speed,
|
||||
s.data.velocity,
|
||||
s.data.accel,
|
||||
s.data.brake,
|
||||
s.data.grip,
|
||||
s.data.steer,
|
||||
s.data.inertia,
|
||||
s.data.mass,
|
||||
s.data.streamline
|
||||
end
|
||||
|
||||
local function rotate_vector(x0, y0, theta)
|
||||
local x1 = x0 * math.cos(theta) - y0 * math.sin(theta)
|
||||
local y1 = x0 * math.sin(theta) + y0 * math.cos(theta)
|
||||
return vm.vec2(x1, y1)
|
||||
end
|
||||
|
||||
local function forward_velocity(dt, force, mass, velocity, max_speed, min_speed, dampening)
|
||||
if (mass == 0) then
|
||||
mass = 1
|
||||
end
|
||||
local a = force[1] * dt / mass
|
||||
local new_velocity = velocity[1] + a
|
||||
if (a == 0 and new_velocity > 0.1) then
|
||||
new_velocity = velocity[1] - dampening * dt / mass
|
||||
elseif (a == 0 and new_velocity < -0.1) then
|
||||
new_velocity = velocity[1] + dampening * dt / mass
|
||||
elseif (a == 0) then
|
||||
new_velocity = 0
|
||||
end
|
||||
new_velocity = vm.clamp(new_velocity, min_speed, max_speed)
|
||||
return new_velocity
|
||||
end
|
||||
|
||||
local function orientation_velocity(dt, force, mass, orientation_speed, orientation, max_orientation, min_orientation)
|
||||
if (mass == 0) then
|
||||
mass = 1
|
||||
end
|
||||
local a = force[2] / mass
|
||||
local new_speed = orientation_speed + a
|
||||
new_speed = vm.clamp(new_speed, min_orientation, max_orientation)
|
||||
local new_orientation = rotate_vector(orientation[1], orientation[2], new_speed)
|
||||
return new_orientation, new_speed
|
||||
end
|
||||
|
||||
function world:update_checkpoint(dt)
|
||||
local cp = self.checkpoints.data.check[self.current_cp]
|
||||
local is_inside_cp = uib.is_inside(cp.pos[1], cp.pos[2], cp.wh[1], cp.wh[2], self.data.pos[1], self.data.pos[2])
|
||||
if (is_inside_cp) then
|
||||
self.prev_cp = self.current_cp
|
||||
self.current_cp = get_loop(self.current_cp + 1, #self.checkpoints.data.check)
|
||||
if (self.current_cp < self.prev_cp) then
|
||||
self.lap = self.lap + 1
|
||||
end
|
||||
|
||||
if (self.lap > self.checkpoints.data.max_lap) then
|
||||
self.is_finish = true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:update_race(dt)
|
||||
local x, y = self.data.pos[1], self.data.pos[2]
|
||||
local or_x, or_y = self.data.orientation[1], self.data.orientation[2]
|
||||
|
||||
local max_speed, min_speed, velocity, accel, brake, grip, steer, inertia, mass, streamline = get(self)
|
||||
local friction, drag, drag_movement = self.map.data.friction, self.map.data.drag, self.map.data.drag_movement
|
||||
|
||||
local desire_forward, desire_steer = self.e.components.actor.data.forward, self.e.components.actor.data.steer
|
||||
|
||||
if (self.world_state.data.state == "start" and self.countdown.data.label == "go") then
|
||||
self.late_timer = self.late_timer + dt
|
||||
self.initial_boost = false
|
||||
end
|
||||
|
||||
local force = racing_force(
|
||||
desire_forward, desire_steer,
|
||||
grip, friction,
|
||||
drag, drag_movement, streamline,
|
||||
inertia,
|
||||
mass,
|
||||
magic_w.forward, magic_w.steer,
|
||||
magic_w.tire, magic_w.floor,
|
||||
magic_w.drag_boost, magic_w.drag_halt,
|
||||
magic_w.inertia, magic_w.centrifugal
|
||||
)
|
||||
local f_v = forward_velocity(dt, force, mass, velocity, max_speed, min_speed, magic_w.dampening)
|
||||
local o, o_v = orientation_velocity(dt, force, mass, self.data.orientation_speed, self.data.orientation, self.data.max_orientation_speed, self.data.min_orientation_speed)
|
||||
|
||||
if (self.world_state.data.state == "count down") then
|
||||
|
||||
elseif (not self.is_finish) then
|
||||
if (self.late_timer < 1 and not self.initial_boost and f_v > 0) then
|
||||
f_v = f_v + 0.1
|
||||
self.initial_boost = true
|
||||
end
|
||||
self.data.velocity[1] = f_v
|
||||
-- self.data.orientation_speed = o_v
|
||||
self.data.orientation = o
|
||||
-- if (self.data.velocity[1] ~= 0) then
|
||||
self.data.pos = self.data.pos + o * f_v
|
||||
-- end
|
||||
|
||||
self.data.inertia = force
|
||||
end
|
||||
end
|
||||
|
||||
function world:update_time_attack(dt)
|
||||
if (self.time_attacks[self.lap] == nil and not self.is_finish) then
|
||||
self.time_attacks[self.lap] = 0
|
||||
end
|
||||
if (self.world_state.data.state == "count down") then
|
||||
|
||||
elseif (not self.is_finish) then
|
||||
self.time_attacks[self.lap] = self.time_attacks[self.lap] + dt
|
||||
end
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
self:update_race(dt)
|
||||
self:update_time_attack(dt)
|
||||
self:update_checkpoint(dt)
|
||||
end
|
||||
|
||||
function world:draw()
|
||||
local x, y = self.data.pos[1], self.data.pos[2]
|
||||
local or_x, or_y = self.data.orientation[1], self.data.orientation[2]
|
||||
local vx, vy = self.data.orientation[1] * self.data.velocity[1], self.data.orientation[2] * self.data.velocity[1]
|
||||
|
||||
if (self.show_pov) then
|
||||
test.set_sphere(x, y, vx, vy)
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_time_attack(x, y, r, g, b, to_debug)
|
||||
local font_ix = test.draw_font_start()
|
||||
test.draw_font_set_base_color(r, g, b)
|
||||
for k, v in pairs(to_debug) do
|
||||
y = y + test.draw_font(font_ix, string.format("%s : %s seconds", k, v), x, y)
|
||||
end
|
||||
end
|
||||
|
||||
local function draw_finish(x, y, r, g, b)
|
||||
local font_ix = test.draw_font_start()
|
||||
test.draw_font_set_base_color(r, g, b)
|
||||
y = y + test.draw_font(font_ix, "FINISH", x, y)
|
||||
end
|
||||
|
||||
local function debug_data(x, y, r, g, b, to_debug)
|
||||
local font_ix = test.draw_font_start()
|
||||
test.draw_font_set_base_color(r, g, b)
|
||||
for k, v in pairs(to_debug) do
|
||||
y = y + test.draw_font(font_ix, string.format("%s : %s", k, v), x, y)
|
||||
end
|
||||
end
|
||||
|
||||
local function debug_vector(vx, vy)
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
test.draw_line_quad_start()
|
||||
test.draw_set_color(1.0, 0.0, 0.0) -- r, g, b (0.0 to 1.0)
|
||||
test.draw_line(lw/2, lh/2, lw/vx, lh/vy) -- x1, y1, x2, y2
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
if (self.show_pov) then
|
||||
draw_time_attack(lw/2, 20, 1.0, 0.0, 1.0, self.time_attacks)
|
||||
if (self.is_finish) then
|
||||
draw_finish(lw/2, lh/2, 1.0, 1.0, 0.0)
|
||||
end
|
||||
end
|
||||
-- debug_data(650, 0, 1.0, 0.0, 1.0, self.data)
|
||||
-- debug_data(650, 400, 1.0, 0.0, 1.0, magic_w)
|
||||
|
||||
local vx, vy = self.data.orientation[1], self.data.orientation[2]
|
||||
-- debug_vector(vx, vy)
|
||||
end
|
||||
|
||||
return world
|
||||
32
game/love_src/src/new/world/race/components/world_state.lua
Normal file
@ -0,0 +1,32 @@
|
||||
local vm = require"lib.vornmath"
|
||||
local uib = require"love_src.src.system.ui_behavior"
|
||||
local uir = require"love_src.src.system.ui_render"
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "phase")
|
||||
end
|
||||
|
||||
function world:load(entity, data)
|
||||
self.data = data
|
||||
self.e = entity
|
||||
end
|
||||
|
||||
function world:next_state(state)
|
||||
self.data.state = state
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
uir.draw_text(self.data.state,
|
||||
self.data.pos[1], self.data.pos[2],
|
||||
self.data.label_color
|
||||
)
|
||||
end
|
||||
|
||||
return world
|
||||
170
game/love_src/src/new/world/race/data/track1.lua
Normal file
@ -0,0 +1,170 @@
|
||||
local vm = require"lib.vornmath"
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
return {
|
||||
{
|
||||
components = {
|
||||
racer = require("love_src.src.new.world.race.components.racer"),
|
||||
actor = require("love_src.src.new.world.race.components.player"),
|
||||
},
|
||||
type = "racer",
|
||||
name = "racer 1",
|
||||
data = {
|
||||
racer = {
|
||||
pos = vm.vec2(44.64, 10.87),
|
||||
|
||||
orientation = vm.vec2(1.0, 0.0),
|
||||
orientation_speed = 0.0,
|
||||
min_orientation_speed = -1.0,
|
||||
max_orientation_speed = 1.0,
|
||||
|
||||
velocity = vm.vec2(0.0, 0.0),
|
||||
max_speed = 0.3,
|
||||
min_speed = -0.3,
|
||||
inertia = vm.vec2(0.0, 0.0),
|
||||
|
||||
accel = 0.1,
|
||||
brake = 0.1,
|
||||
grip = 0.1,
|
||||
steer = 0.01,
|
||||
mass = 0.1,
|
||||
streamline = 0.1
|
||||
},
|
||||
actor = {
|
||||
forward = 0.0,
|
||||
steer = 0.0,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
components = {
|
||||
racer = require("love_src.src.new.world.race.components.racer"),
|
||||
actor = require("love_src.src.new.world.race.components.actor"),
|
||||
},
|
||||
type = "racer",
|
||||
name = "racer 2",
|
||||
data = {
|
||||
racer = {
|
||||
pos = vm.vec2(44.64, 10.87),
|
||||
|
||||
orientation = vm.vec2(1.0, 0.0),
|
||||
orientation_speed = 0.0,
|
||||
min_orientation_speed = -1.0,
|
||||
max_orientation_speed = 1.0,
|
||||
|
||||
velocity = vm.vec2(0.0, 0.0),
|
||||
max_speed = 0.3,
|
||||
min_speed = -0.3,
|
||||
inertia = vm.vec2(0.0, 0.0),
|
||||
|
||||
accel = 0.1,
|
||||
brake = 0.1,
|
||||
grip = 0.1,
|
||||
steer = 0.01,
|
||||
mass = 0.1,
|
||||
streamline = 0.1
|
||||
},
|
||||
actor = {
|
||||
forward = 0.0,
|
||||
steer = 0.0,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
components = {
|
||||
map = require"love_src.src.new.world.race.components.map"
|
||||
},
|
||||
type = "map",
|
||||
name = "map 1",
|
||||
data = {
|
||||
map = {
|
||||
drag_movement = 0.1,
|
||||
friction = 0.1,
|
||||
drag = vm.vec2(0.1, 0.0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "world_state",
|
||||
name = "race_state",
|
||||
components = {
|
||||
world_state = require("love_src.src.new.world.race.components.world_state"),
|
||||
},
|
||||
data = {
|
||||
world_state = {
|
||||
state = "count down",
|
||||
pos = vm.vec2(lw/2, lh - 50),
|
||||
label_color = {1, 0, 1}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "countdown",
|
||||
name = "321go",
|
||||
components = {
|
||||
countdown = require("love_src.src.new.world.race.components.countdown"),
|
||||
animation = require("love_src.src.new.shared.components.animation"),
|
||||
},
|
||||
data = {
|
||||
countdown = {
|
||||
label = "",
|
||||
pos = vm.vec2(lw/2, lh/2),
|
||||
label_color = {1, 0, 1}
|
||||
},
|
||||
animation = {
|
||||
frames = {
|
||||
"3",
|
||||
"2",
|
||||
"1",
|
||||
"go",
|
||||
""
|
||||
},
|
||||
fps = 1,
|
||||
triggers = {4},
|
||||
triggers_fn = {
|
||||
[4] = function ()
|
||||
print("GO")
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "checkpoints",
|
||||
name = "checkpoints",
|
||||
components = {
|
||||
checkpoints = require"love_src.src.new.world.race.components.checkpoints"
|
||||
},
|
||||
data = {
|
||||
checkpoints = {
|
||||
label = "",
|
||||
pos = vm.vec2(lw - 50, 0),
|
||||
label_color = {1, 1, 0},
|
||||
check = {
|
||||
{
|
||||
pos = vm.vec2(80, 8),
|
||||
wh = vm.vec2(10, 10)
|
||||
},
|
||||
{
|
||||
pos = vm.vec2(115, 23),
|
||||
wh = vm.vec2(10, 10)
|
||||
},
|
||||
{
|
||||
pos = vm.vec2(12, 55),
|
||||
wh = vm.vec2(10, 10)
|
||||
},
|
||||
{
|
||||
pos = vm.vec2(-23, 28),
|
||||
wh = vm.vec2(10, 10)
|
||||
},
|
||||
{
|
||||
pos = vm.vec2(40, 7), -- finish line
|
||||
wh = vm.vec2(10, 10)
|
||||
}
|
||||
},
|
||||
max_lap = 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
251
game/love_src/src/new/world/race/init.lua
Normal file
@ -0,0 +1,251 @@
|
||||
local sfx = require"love_src.wrapper.lappy.new.source".obj
|
||||
local vm = require"lib.vornmath"
|
||||
local get_loop = require"love_src.src.system.animation".get_loop
|
||||
|
||||
local main_wrapper = require "love_src.wrapper.lappy.world"
|
||||
---@class wrappers.Concord.world : lappy.world
|
||||
local world = main_wrapper:extend()
|
||||
|
||||
local reap = require("lib.reap")
|
||||
local BASE = reap.base_path(...)
|
||||
|
||||
local default = {
|
||||
racers = {
|
||||
base = require "love_src.src.new.world.race.components.racer"
|
||||
}
|
||||
}
|
||||
|
||||
local lw, lh = love.graphics.getDimensions()
|
||||
|
||||
local entities_default = {
|
||||
{
|
||||
components = {
|
||||
racer = require("love_src.src.new.world.race.components.racer"),
|
||||
actor = require("love_src.src.new.world.race.components.actor"),
|
||||
},
|
||||
type = "racer",
|
||||
name = "racer 1",
|
||||
data = {
|
||||
racer = {
|
||||
pos = vm.vec2(44.64, 10.87),
|
||||
|
||||
orientation = vm.vec2(1.0, 0.0),
|
||||
orientation_speed = 0.0,
|
||||
min_orientation_speed = -1.0,
|
||||
max_orientation_speed = 1.0,
|
||||
|
||||
velocity = vm.vec2(0.0, 0.0),
|
||||
max_speed = 0.3,
|
||||
min_speed = -0.3,
|
||||
inertia = vm.vec2(0.0, 0.0),
|
||||
|
||||
accel = 0.1,
|
||||
brake = 0.1,
|
||||
grip = 0.1,
|
||||
steer = 0.01,
|
||||
mass = 0.1,
|
||||
streamline = 0.1
|
||||
},
|
||||
actor = {
|
||||
forward = 0.0,
|
||||
steer = 0.0,
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
components = {
|
||||
map = require"love_src.src.new.world.race.components.map"
|
||||
},
|
||||
type = "map",
|
||||
name = "map 1",
|
||||
data = {
|
||||
map = {
|
||||
drag_movement = 0.1,
|
||||
friction = 0.1,
|
||||
drag = vm.vec2(0.1, 0.0)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type = "countdown",
|
||||
name = "321go",
|
||||
components = {
|
||||
countdown = require("love_src.src.new.world.race.components.countdown"),
|
||||
animation = require("love_src.src.new.shared.components.animation"),
|
||||
},
|
||||
data = {
|
||||
countdown = {
|
||||
label = "",
|
||||
pos = vm.vec2(lw/2, lh/2),
|
||||
label_color = {1, 0, 1}
|
||||
},
|
||||
animation = {
|
||||
frames = {
|
||||
"3",
|
||||
"2",
|
||||
"1",
|
||||
"go",
|
||||
""
|
||||
},
|
||||
fps = 1,
|
||||
triggers = {4},
|
||||
triggers_fn = {
|
||||
[4] = function ()
|
||||
print("GO")
|
||||
end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function world:new()
|
||||
world.super.new(self, BASE, "race")
|
||||
self.is_loaded = false
|
||||
self.entities = {}
|
||||
end
|
||||
|
||||
function world:setup()
|
||||
local map
|
||||
local countdown
|
||||
self.checkpoints = {}
|
||||
self.world_state = {}
|
||||
self.racers = {}
|
||||
self.pov_index = 1
|
||||
for _, d in pairs(self.entities_data) do
|
||||
local e = {
|
||||
type = d.type,
|
||||
name = d.name,
|
||||
components = {}
|
||||
}
|
||||
for t, c in pairs(d.components) do
|
||||
e.components[t] = c()
|
||||
e.components[t]:load(e, d.data[t])
|
||||
end
|
||||
table.insert(self.entities, e)
|
||||
if (e.type == "map") then
|
||||
map = e
|
||||
elseif e.type == "world_state" then
|
||||
self.world_state = e
|
||||
elseif e.type == "countdown" then
|
||||
countdown = e
|
||||
elseif e.type == "checkpoints" then
|
||||
self.checkpoints = e
|
||||
elseif e.type == "racer" then
|
||||
table.insert(self.racers, e)
|
||||
if (e.components.actor.name == "player") then
|
||||
e.components.racer.show_pov = true
|
||||
end
|
||||
end
|
||||
end
|
||||
for _, e in ipairs(self.entities) do
|
||||
if (e.type == "racer") then
|
||||
e.components.racer.map = map.components.map
|
||||
e.components.racer.countdown = countdown.components.countdown
|
||||
e.components.racer.world_state = self.world_state.components.world_state
|
||||
e.components.racer.checkpoints = self.checkpoints.components.checkpoints
|
||||
elseif (e.type == "countdown") then
|
||||
e.components.countdown:load_world_state(self.world_state.components.world_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:load(data, entities_data)
|
||||
self.data = data or default
|
||||
self.is_loaded = true
|
||||
|
||||
self.entities_data = entities_data or entities_default
|
||||
|
||||
self:setup()
|
||||
end
|
||||
|
||||
local phases = {
|
||||
intro = {
|
||||
is_played = false,
|
||||
source = sfx:load_to("love_src/asset/audio/sfx/race/va/test1.mp3", "love_src/asset/audio/sfx/race/va/test1.mp3", "static"),
|
||||
},
|
||||
mid = {
|
||||
is_played = false,
|
||||
source = sfx:load_to("love_src/asset/audio/sfx/race/va/Test2.mp3", "love_src/asset/audio/sfx/race/va/Test2.mp3", "static"),
|
||||
},
|
||||
ed = {
|
||||
is_played = false,
|
||||
source = sfx:load_to("love_src/asset/audio/sfx/race/va/Test3.mp3", "love_src/asset/audio/sfx/race/va/Test3.mp3", "static")
|
||||
}
|
||||
}
|
||||
|
||||
local phase = "intro"
|
||||
|
||||
local x_is_down = false
|
||||
|
||||
local function percentage_clear(lap, max_lap, cp, max_cp)
|
||||
return lap + cp * (1/max_cp)
|
||||
end
|
||||
|
||||
function world:update_race_rank(dt)
|
||||
local rank = {}
|
||||
for k, v in pairs(self.racers) do
|
||||
local cp = v.components.racer.current_cp
|
||||
local lap = v.components.racer.lap
|
||||
local ts = v.components.racer.time_attacks
|
||||
local finish = v.components.racer.is_finish
|
||||
|
||||
local max_lap = self.checkpoints.components.checkpoints.data.max_lap
|
||||
local max_cp = #self.checkpoints.components.checkpoints.data.check
|
||||
|
||||
local percentage = percentage_clear(lap, max_lap, cp, max_cp)
|
||||
v.components.racer.percentage = percentage
|
||||
|
||||
if (finish) then
|
||||
local t = 0
|
||||
for _, lt in pairs(ts) do
|
||||
t = t + lt
|
||||
end
|
||||
local finish_time = t
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:update(dt)
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:update(dt)
|
||||
end
|
||||
end
|
||||
if (phases[phase]) then
|
||||
if (not phases[phase].source:isPlaying() and not phases[phase].is_played) then
|
||||
phases[phase].source:play()
|
||||
phases[phase].is_played = true
|
||||
end
|
||||
end
|
||||
|
||||
if (love.keyboard.isDown("x") and not x_is_down) then
|
||||
self.racers[self.pov_index].components.racer.show_pov = false
|
||||
self.pov_index = get_loop(self.pov_index + 1, #self.racers)
|
||||
self.racers[self.pov_index].components.racer.show_pov = true
|
||||
x_is_down = true
|
||||
elseif (not love.keyboard.isDown("x") and x_is_down == true) then
|
||||
x_is_down = false
|
||||
end
|
||||
|
||||
self:update_race_rank(dt)
|
||||
end
|
||||
|
||||
function world:draw()
|
||||
test.draw()
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function world:ui_draw()
|
||||
for _, e in ipairs(self.entities) do
|
||||
for t, c in pairs(e.components) do
|
||||
c:ui_draw()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return world
|
||||