Compare commits
No commits in common. "5bf9e1841a3e92c45c82ebda388fca1a9faea9f6" and "afd7edd93dc61774d14398cd42d690aa50189326" have entirely different histories.
5bf9e1841a
...
afd7edd93d
@ -26,7 +26,7 @@ function love.conf(t)
|
|||||||
t.window.width = 1024
|
t.window.width = 1024
|
||||||
t.window.height = 1024
|
t.window.height = 1024
|
||||||
t.window.borderless = false
|
t.window.borderless = false
|
||||||
t.window.resizable = false
|
t.window.resizable = true
|
||||||
t.window.minwidth = 10
|
t.window.minwidth = 10
|
||||||
t.window.minheight = 9
|
t.window.minheight = 9
|
||||||
t.window.fullscreen = false
|
t.window.fullscreen = false
|
||||||
|
|||||||
BIN
game/love_src/asset/image/book/StoryMode.png
Normal file
|
After Width: | Height: | Size: 280 B |
BIN
game/love_src/asset/image/book/StoryModeBook.png
Normal file
|
After Width: | Height: | Size: 496 B |
BIN
game/love_src/asset/image/book/StoryModeBook11.png
Normal file
|
After Width: | Height: | Size: 557 B |
BIN
game/love_src/asset/image/book/StoryModeBook16.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
game/love_src/asset/image/book/StoryModeBook2.png
Normal file
|
After Width: | Height: | Size: 575 B |
BIN
game/love_src/asset/image/book/StoryModeBook3.png
Normal file
|
After Width: | Height: | Size: 639 B |
BIN
game/love_src/asset/image/book/StoryModeBook4.png
Normal file
|
After Width: | Height: | Size: 643 B |
BIN
game/love_src/asset/image/book/StoryModeBook5.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
game/love_src/asset/image/book/StoryModeBook6.png
Normal file
|
After Width: | Height: | Size: 719 B |
BIN
game/love_src/asset/image/book/StoryModeBook7.png
Normal file
|
After Width: | Height: | Size: 653 B |
BIN
game/love_src/asset/image/book/StoryModeBook8.png
Normal file
|
After Width: | Height: | Size: 609 B |
BIN
game/love_src/asset/image/book/StoryModeBook9.png
Normal file
|
After Width: | Height: | Size: 557 B |
@ -0,0 +1,22 @@
|
|||||||
|
Copyright (c) 2012, 2013, 2014, 2015, 2016 Jake Gordon and contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
Javascript Pseudo 3D Racer
|
||||||
|
==========================
|
||||||
|
|
||||||
|
An Outrun-style pseudo-3d racing game in HTML5 and Javascript
|
||||||
|
|
||||||
|
* [play the game](https://jakesgordon.com/games/racer/)
|
||||||
|
* view the [source](https://github.com/jakesgordon/javascript-racer)
|
||||||
|
* read about [how it works](https://jakesgordon.com/writing/javascript-racer/)
|
||||||
|
|
||||||
|
Incrementally built up in 4 parts:
|
||||||
|
|
||||||
|
* play the [straight road demo](https://jakesgordon.com/games/racer/v1-straight/)
|
||||||
|
* play the [curves demo](https://jakesgordon.com/games/racer/v2-curves/)
|
||||||
|
* play the [hills demo](https://jakesgordon.com/games/racer/v3-hills/)
|
||||||
|
* play the [final version](https://jakesgordon.com/games/racer/)
|
||||||
|
|
||||||
|
With detailed descriptions of how each part works:
|
||||||
|
|
||||||
|
* read more about [v1 - straight roads](https://jakesgordon.com/writing/javascript-racer-v1-straight/)
|
||||||
|
* read more about [v2 - curves](https://jakesgordon.com/writing/javascript-racer-v2-curves/)
|
||||||
|
* read more about [v3 - hills](https://jakesgordon.com/writing/javascript-racer-v3-hills/)
|
||||||
|
* read more about v4 - final (coming soon)
|
||||||
|
|
||||||
|
A note on performance
|
||||||
|
=====================
|
||||||
|
|
||||||
|
The performance of this game is **very** machine/browser dependent. It works quite well in modern
|
||||||
|
browsers, especially those with GPU canvas acceleration, but a bad graphics driver can kill it stone
|
||||||
|
dead. So your mileage may vary. There are controls provided to change the rendering resolution
|
||||||
|
and the draw distance to scale to fit your machine.
|
||||||
|
|
||||||
|
Currently supported browsers include:
|
||||||
|
|
||||||
|
* Firefox (v12+) works great, 60fps at high res - Nice!
|
||||||
|
* Chrome (v19+) works great, 60fps at high res... provided you dont have a bad GPU driver
|
||||||
|
* IE9 - ok, 30fps at medium res... not great, but at least it works
|
||||||
|
|
||||||
|
The current state of mobile browser performance is pretty dismal. Dont expect this to be playable on
|
||||||
|
any mobile device.
|
||||||
|
|
||||||
|
>> _NOTE: I havent actually spent anytime optimizing for performance yet. So it might be possible to
|
||||||
|
make it play well on older browsers, but that's not really what this project is about._
|
||||||
|
|
||||||
|
A note on code structure
|
||||||
|
========================
|
||||||
|
|
||||||
|
This project happens to be implemented in javascript (because its easy for prototyping) but
|
||||||
|
is not intended to demonstrate javascript techniques or best practices. In fact, in order to
|
||||||
|
keep it simple to understand it embeds the javascript for each example directly in the HTML
|
||||||
|
page (horror!) and, even worse, uses global variables and functions (OMG!).
|
||||||
|
|
||||||
|
If I was building a real game I would have much more structure and organization to the
|
||||||
|
code, but since its just a racing game tech demo, I have elected to [KISS](http://en.wikipedia.org/wiki/KISS_principle).
|
||||||
|
|
||||||
|
FUTURE
|
||||||
|
======
|
||||||
|
|
||||||
|
It's quite astounding what it takes to actually [finish](https://jakesgordon.com/writing/defining-finished/)
|
||||||
|
a game, even a simple one. And this is not a project that I plan on polishing into a finished state. It should
|
||||||
|
really be considered just how to get started with a pseudo-3d racing game.
|
||||||
|
|
||||||
|
If we were to try to turn it into a real game we would have to consider:
|
||||||
|
|
||||||
|
* car sound fx
|
||||||
|
* better synchronized music
|
||||||
|
* full screen mode
|
||||||
|
* HUD fx (flash on fastest lap, confetti, color coded speedometer, etc)
|
||||||
|
* more accurate sprite collision
|
||||||
|
* better car AI (steering, braking etc)
|
||||||
|
* an actual crash when colliding at high speed
|
||||||
|
* more bounce when car is off road
|
||||||
|
* screen shake when off-road or collision
|
||||||
|
* throw up dirt particles when off road
|
||||||
|
* more dynamic camera (lower at faster speed, swoop over hills etc)
|
||||||
|
* automatic resolution & drawDistance detection
|
||||||
|
* projection based curves ? x,y rotation
|
||||||
|
* sub-pixel aliasing artifacts on curves
|
||||||
|
* smarter fog to cover sprites (blue against sky, cover sprites)
|
||||||
|
* multiple stages, different maps
|
||||||
|
* a lap map, with current position indicator
|
||||||
|
* road splits and joins
|
||||||
|
* day/night cycle
|
||||||
|
* weather effects
|
||||||
|
* tunnels, bridges, clouds, walls, buildings
|
||||||
|
* city, desert, ocean
|
||||||
|
* add city of seattle and space needle to background
|
||||||
|
* 'bad guys' - add some competetor drivers to race against as well as the 'traffic'
|
||||||
|
* different game modes - fastest lap, 1-on-1 racing, collect coins ? shoot bad guys ?
|
||||||
|
* a whole lot of gameplay tuning
|
||||||
|
* ...
|
||||||
|
* ...
|
||||||
|
|
||||||
|
Related Links
|
||||||
|
=============
|
||||||
|
|
||||||
|
* [Lou's Pseudo-3d Page](http://www.extentofthejam.com/pseudo/) - high level how-to guide
|
||||||
|
* [Racer 10k](https://github.com/onaluf/RacerJS) - another javascript racing game
|
||||||
|
|
||||||
|
License
|
||||||
|
=======
|
||||||
|
|
||||||
|
[MIT](http://en.wikipedia.org/wiki/MIT_License) license.
|
||||||
|
|
||||||
|
>> NOTE: the music tracks included in this project are royalty free resources paid for and licensed
|
||||||
|
from [Lucky Lion Studios](http://luckylionstudios.com/). They are licensed ONLY for use in this
|
||||||
|
project and should not be reproduced.
|
||||||
|
|
||||||
|
>> NOTE: the sprite graphics are placeholder graphics [borrowed](http://pixel.garoux.net/game/44) from the old
|
||||||
|
genesis version of outrun and used here as teaching examples. If there are any pixel artists out there who want to
|
||||||
|
provide original art to turn this into a real game please get in touch!
|
||||||
|
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
desc 'recreate sprite sheets'
|
||||||
|
task 'resprite' do
|
||||||
|
require 'sprite_factory'
|
||||||
|
|
||||||
|
SpriteFactory.run!('images/sprites', :layout => :packed, :output_style => 'images/sprites.js', :margin => 5, :nocomments => true) do |images|
|
||||||
|
SpriteHelper.javascript_style("SPRITES", images)
|
||||||
|
end
|
||||||
|
|
||||||
|
SpriteFactory.run!('images/background', :layout => :vertical, :output_style => 'images/background.js', :margin => 5, :nocomments => true) do |images|
|
||||||
|
SpriteHelper.javascript_style("BACKGROUND", images)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
module SpriteHelper
|
||||||
|
|
||||||
|
# slightly unusual use of sprite-factory to generate a javascript object structure instead of CSS attributes...
|
||||||
|
def self.javascript_style(variable, images)
|
||||||
|
maxname = images.keys.inject(0) {|n,key| [n,key.length].max }
|
||||||
|
rules = []
|
||||||
|
images.each do |name, i|
|
||||||
|
name = name.upcase
|
||||||
|
whitespace = ' '*(maxname-name.length)
|
||||||
|
x = '%4d' % i[:cssx]
|
||||||
|
y = '%4d' % i[:cssy]
|
||||||
|
w = '%4d' % i[:cssw]
|
||||||
|
h = '%4d' % i[:cssh]
|
||||||
|
rules << " #{name}: #{whitespace}{ x: #{x}, y: #{y}, w: #{w}, h: #{h} }"
|
||||||
|
end
|
||||||
|
"var #{variable} = {\n#{rules.join(",\n")}\n};"
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
|
||||||
|
/****************************************/
|
||||||
|
/* common styles used for v1 through v4 */
|
||||||
|
/****************************************/
|
||||||
|
|
||||||
|
body { font-family: Arial, Helvetica, sans-serif; }
|
||||||
|
#stats { border: 2px solid black; }
|
||||||
|
#controls { width: 28em; float: left; padding: 1em; font-size: 0.7em; }
|
||||||
|
#controls th { text-align: right; vertical-align: middle; }
|
||||||
|
#instructions { clear: left; float: left; width: 17em; padding: 1em; border: 1px solid black; box-shadow: 0 0 5px black; }
|
||||||
|
#racer { position: relative; z-index: 0; width: 640px; height: 480px; margin-left: 20em; border: 2px solid black; }
|
||||||
|
#canvas { position: absolute; z-index: 0; width: 640px; height: 480px; z-index: 0; background-color: #72D7EE; }
|
||||||
|
#mute { background-position: 0px 0px; width: 32px; height: 32px; background: url(images/mute.png); display: inline-block; cursor: pointer; position: absolute; margin-left: 20em; }
|
||||||
|
#mute.on { background-position: -32px 0px; }
|
||||||
|
|
||||||
|
/**************************************************/
|
||||||
|
/* rudimentary heads up display (only used in v4) */
|
||||||
|
/**************************************************/
|
||||||
|
|
||||||
|
#hud { position: absolute; z-index: 1; width: 640px; padding: 5px 0; font-family: Verdana, Geneva, sans-serif; font-size: 0.8em; background-color: rgba(255,0,0,0.4); color: black; border-bottom: 2px solid black; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; }
|
||||||
|
#hud .hud { background-color: rgba(255,255,255,0.6); padding: 5px; border: 1px solid black; margin: 0 5px; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
|
||||||
|
#hud #speed { float: right; }
|
||||||
|
#hud #current_lap_time { float: left; }
|
||||||
|
#hud #last_lap_time { float: left; display: none; }
|
||||||
|
#hud #fast_lap_time { display: block; width: 12em; margin: 0 auto; text-align: center; transition-property: background-color; transition-duration: 2s; -webkit-transition-property: background-color; -webkit-transition-duration: 2s; }
|
||||||
|
#hud .value { color: black; font-weight: bold; }
|
||||||
|
#hud .fastest { background-color: rgba(255,215,0,0.5); }
|
||||||
|
|
||||||
@ -0,0 +1,414 @@
|
|||||||
|
//=========================================================================
|
||||||
|
// minimalist DOM helpers
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
var Dom = {
|
||||||
|
|
||||||
|
get: function(id) { return ((id instanceof HTMLElement) || (id === document)) ? id : document.getElementById(id); },
|
||||||
|
set: function(id, html) { Dom.get(id).innerHTML = html; },
|
||||||
|
on: function(ele, type, fn, capture) { Dom.get(ele).addEventListener(type, fn, capture); },
|
||||||
|
un: function(ele, type, fn, capture) { Dom.get(ele).removeEventListener(type, fn, capture); },
|
||||||
|
show: function(ele, type) { Dom.get(ele).style.display = (type || 'block'); },
|
||||||
|
blur: function(ev) { ev.target.blur(); },
|
||||||
|
|
||||||
|
addClassName: function(ele, name) { Dom.toggleClassName(ele, name, true); },
|
||||||
|
removeClassName: function(ele, name) { Dom.toggleClassName(ele, name, false); },
|
||||||
|
toggleClassName: function(ele, name, on) {
|
||||||
|
ele = Dom.get(ele);
|
||||||
|
var classes = ele.className.split(' ');
|
||||||
|
var n = classes.indexOf(name);
|
||||||
|
on = (typeof on == 'undefined') ? (n < 0) : on;
|
||||||
|
if (on && (n < 0))
|
||||||
|
classes.push(name);
|
||||||
|
else if (!on && (n >= 0))
|
||||||
|
classes.splice(n, 1);
|
||||||
|
ele.className = classes.join(' ');
|
||||||
|
},
|
||||||
|
|
||||||
|
storage: window.localStorage || {}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// general purpose helpers (mostly math)
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
var Util = {
|
||||||
|
|
||||||
|
timestamp: function() { return new Date().getTime(); },
|
||||||
|
toInt: function(obj, def) { if (obj !== null) { var x = parseInt(obj, 10); if (!isNaN(x)) return x; } return Util.toInt(def, 0); },
|
||||||
|
toFloat: function(obj, def) { if (obj !== null) { var x = parseFloat(obj); if (!isNaN(x)) return x; } return Util.toFloat(def, 0.0); },
|
||||||
|
limit: function(value, min, max) { return Math.max(min, Math.min(value, max)); },
|
||||||
|
randomInt: function(min, max) { return Math.round(Util.interpolate(min, max, Math.random())); },
|
||||||
|
randomChoice: function(options) { return options[Util.randomInt(0, options.length-1)]; },
|
||||||
|
percentRemaining: function(n, total) { return (n%total)/total; },
|
||||||
|
accelerate: function(v, accel, dt) { return v + (accel * dt); },
|
||||||
|
interpolate: function(a,b,percent) { return a + (b-a)*percent },
|
||||||
|
easeIn: function(a,b,percent) { return a + (b-a)*Math.pow(percent,2); },
|
||||||
|
easeOut: function(a,b,percent) { return a + (b-a)*(1-Math.pow(1-percent,2)); },
|
||||||
|
easeInOut: function(a,b,percent) { return a + (b-a)*((-Math.cos(percent*Math.PI)/2) + 0.5); },
|
||||||
|
exponentialFog: function(distance, density) { return 1 / (Math.pow(Math.E, (distance * distance * density))); },
|
||||||
|
|
||||||
|
increase: function(start, increment, max) { // with looping
|
||||||
|
var result = start + increment;
|
||||||
|
while (result >= max)
|
||||||
|
result -= max;
|
||||||
|
while (result < 0)
|
||||||
|
result += max;
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
project: function(p, cameraX, cameraY, cameraZ, cameraDepth, width, height, roadWidth) {
|
||||||
|
p.camera.x = (p.world.x || 0) - cameraX;
|
||||||
|
p.camera.y = (p.world.y || 0) - cameraY;
|
||||||
|
p.camera.z = (p.world.z || 0) - cameraZ;
|
||||||
|
p.screen.scale = cameraDepth/p.camera.z;
|
||||||
|
p.screen.x = Math.round((width/2) + (p.screen.scale * p.camera.x * width/2));
|
||||||
|
p.screen.y = Math.round((height/2) - (p.screen.scale * p.camera.y * height/2));
|
||||||
|
p.screen.w = Math.round( (p.screen.scale * roadWidth * width/2));
|
||||||
|
},
|
||||||
|
|
||||||
|
overlap: function(x1, w1, x2, w2, percent) {
|
||||||
|
var half = (percent || 1)/2;
|
||||||
|
var min1 = x1 - (w1*half);
|
||||||
|
var max1 = x1 + (w1*half);
|
||||||
|
var min2 = x2 - (w2*half);
|
||||||
|
var max2 = x2 + (w2*half);
|
||||||
|
return ! ((max1 < min2) || (min1 > max2));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// POLYFILL for requestAnimationFrame
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
if (!window.requestAnimationFrame) { // http://paulirish.com/2011/requestanimationframe-for-smart-animating/
|
||||||
|
window.requestAnimationFrame = window.webkitRequestAnimationFrame ||
|
||||||
|
window.mozRequestAnimationFrame ||
|
||||||
|
window.oRequestAnimationFrame ||
|
||||||
|
window.msRequestAnimationFrame ||
|
||||||
|
function(callback, element) {
|
||||||
|
window.setTimeout(callback, 1000 / 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// GAME LOOP helpers
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
var Game = { // a modified version of the game loop from my previous boulderdash game - see https://jakesgordon.com/writing/javascript-boulderdash/#gameloop
|
||||||
|
|
||||||
|
run: function(options) {
|
||||||
|
|
||||||
|
Game.loadImages(options.images, function(images) {
|
||||||
|
|
||||||
|
options.ready(images); // tell caller to initialize itself because images are loaded and we're ready to rumble
|
||||||
|
|
||||||
|
Game.setKeyListener(options.keys);
|
||||||
|
|
||||||
|
var canvas = options.canvas, // canvas render target is provided by caller
|
||||||
|
update = options.update, // method to update game logic is provided by caller
|
||||||
|
render = options.render, // method to render the game is provided by caller
|
||||||
|
step = options.step, // fixed frame step (1/fps) is specified by caller
|
||||||
|
stats = options.stats, // stats instance is provided by caller
|
||||||
|
now = null,
|
||||||
|
last = Util.timestamp(),
|
||||||
|
dt = 0,
|
||||||
|
gdt = 0;
|
||||||
|
|
||||||
|
function frame() {
|
||||||
|
now = Util.timestamp();
|
||||||
|
dt = Math.min(1, (now - last) / 1000); // using requestAnimationFrame have to be able to handle large delta's caused when it 'hibernates' in a background or non-visible tab
|
||||||
|
gdt = gdt + dt;
|
||||||
|
while (gdt > step) {
|
||||||
|
gdt = gdt - step;
|
||||||
|
update(step);
|
||||||
|
}
|
||||||
|
render();
|
||||||
|
stats.update();
|
||||||
|
last = now;
|
||||||
|
requestAnimationFrame(frame, canvas);
|
||||||
|
}
|
||||||
|
frame(); // lets get this party started
|
||||||
|
Game.playMusic();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
loadImages: function(names, callback) { // load multiple images and callback when ALL images have loaded
|
||||||
|
var result = [];
|
||||||
|
var count = names.length;
|
||||||
|
|
||||||
|
var onload = function() {
|
||||||
|
if (--count == 0)
|
||||||
|
callback(result);
|
||||||
|
};
|
||||||
|
|
||||||
|
for(var n = 0 ; n < names.length ; n++) {
|
||||||
|
var name = names[n];
|
||||||
|
result[n] = document.createElement('img');
|
||||||
|
Dom.on(result[n], 'load', onload);
|
||||||
|
result[n].src = "images/" + name + ".png";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
setKeyListener: function(keys) {
|
||||||
|
var onkey = function(keyCode, mode) {
|
||||||
|
var n, k;
|
||||||
|
for(n = 0 ; n < keys.length ; n++) {
|
||||||
|
k = keys[n];
|
||||||
|
k.mode = k.mode || 'up';
|
||||||
|
if ((k.key == keyCode) || (k.keys && (k.keys.indexOf(keyCode) >= 0))) {
|
||||||
|
if (k.mode == mode) {
|
||||||
|
k.action.call();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Dom.on(document, 'keydown', function(ev) { onkey(ev.keyCode, 'down'); } );
|
||||||
|
Dom.on(document, 'keyup', function(ev) { onkey(ev.keyCode, 'up'); } );
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
stats: function(parentId, id) { // construct mr.doobs FPS counter - along with friendly good/bad/ok message box
|
||||||
|
|
||||||
|
var result = new Stats();
|
||||||
|
result.domElement.id = id || 'stats';
|
||||||
|
Dom.get(parentId).appendChild(result.domElement);
|
||||||
|
|
||||||
|
var msg = document.createElement('div');
|
||||||
|
msg.style.cssText = "border: 2px solid gray; padding: 5px; margin-top: 5px; text-align: left; font-size: 1.15em; text-align: right;";
|
||||||
|
msg.innerHTML = "Your canvas performance is ";
|
||||||
|
Dom.get(parentId).appendChild(msg);
|
||||||
|
|
||||||
|
var value = document.createElement('span');
|
||||||
|
value.innerHTML = "...";
|
||||||
|
msg.appendChild(value);
|
||||||
|
|
||||||
|
setInterval(function() {
|
||||||
|
var fps = result.current();
|
||||||
|
var ok = (fps > 50) ? 'good' : (fps < 30) ? 'bad' : 'ok';
|
||||||
|
var color = (fps > 50) ? 'green' : (fps < 30) ? 'red' : 'gray';
|
||||||
|
value.innerHTML = ok;
|
||||||
|
value.style.color = color;
|
||||||
|
msg.style.borderColor = color;
|
||||||
|
}, 5000);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
playMusic: function() {
|
||||||
|
var music = Dom.get('music');
|
||||||
|
music.loop = true;
|
||||||
|
music.volume = 0.05; // shhhh! annoying music!
|
||||||
|
music.muted = (Dom.storage.muted === "true");
|
||||||
|
music.play();
|
||||||
|
Dom.toggleClassName('mute', 'on', music.muted);
|
||||||
|
Dom.on('mute', 'click', function() {
|
||||||
|
Dom.storage.muted = music.muted = !music.muted;
|
||||||
|
Dom.toggleClassName('mute', 'on', music.muted);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// canvas rendering helpers
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
var Render = {
|
||||||
|
|
||||||
|
polygon: function(ctx, x1, y1, x2, y2, x3, y3, x4, y4, color) {
|
||||||
|
ctx.fillStyle = color;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x1, y1);
|
||||||
|
ctx.lineTo(x2, y2);
|
||||||
|
ctx.lineTo(x3, y3);
|
||||||
|
ctx.lineTo(x4, y4);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
segment: function(ctx, width, lanes, x1, y1, w1, x2, y2, w2, fog, color) {
|
||||||
|
|
||||||
|
var r1 = Render.rumbleWidth(w1, lanes),
|
||||||
|
r2 = Render.rumbleWidth(w2, lanes),
|
||||||
|
l1 = Render.laneMarkerWidth(w1, lanes),
|
||||||
|
l2 = Render.laneMarkerWidth(w2, lanes),
|
||||||
|
lanew1, lanew2, lanex1, lanex2, lane;
|
||||||
|
|
||||||
|
ctx.fillStyle = color.grass;
|
||||||
|
ctx.fillRect(0, y2, width, y1 - y2);
|
||||||
|
|
||||||
|
Render.polygon(ctx, x1-w1-r1, y1, x1-w1, y1, x2-w2, y2, x2-w2-r2, y2, color.rumble);
|
||||||
|
Render.polygon(ctx, x1+w1+r1, y1, x1+w1, y1, x2+w2, y2, x2+w2+r2, y2, color.rumble);
|
||||||
|
Render.polygon(ctx, x1-w1, y1, x1+w1, y1, x2+w2, y2, x2-w2, y2, color.road);
|
||||||
|
|
||||||
|
if (color.lane) {
|
||||||
|
lanew1 = w1*2/lanes;
|
||||||
|
lanew2 = w2*2/lanes;
|
||||||
|
lanex1 = x1 - w1 + lanew1;
|
||||||
|
lanex2 = x2 - w2 + lanew2;
|
||||||
|
for(lane = 1 ; lane < lanes ; lanex1 += lanew1, lanex2 += lanew2, lane++)
|
||||||
|
Render.polygon(ctx, lanex1 - l1/2, y1, lanex1 + l1/2, y1, lanex2 + l2/2, y2, lanex2 - l2/2, y2, color.lane);
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.fog(ctx, 0, y1, width, y2-y1, fog);
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
background: function(ctx, background, width, height, layer, rotation, offset) {
|
||||||
|
|
||||||
|
rotation = rotation || 0;
|
||||||
|
offset = offset || 0;
|
||||||
|
|
||||||
|
var imageW = layer.w/2;
|
||||||
|
var imageH = layer.h;
|
||||||
|
|
||||||
|
var sourceX = layer.x + Math.floor(layer.w * rotation);
|
||||||
|
var sourceY = layer.y
|
||||||
|
var sourceW = Math.min(imageW, layer.x+layer.w-sourceX);
|
||||||
|
var sourceH = imageH;
|
||||||
|
|
||||||
|
var destX = 0;
|
||||||
|
var destY = offset;
|
||||||
|
var destW = Math.floor(width * (sourceW/imageW));
|
||||||
|
var destH = height;
|
||||||
|
|
||||||
|
ctx.drawImage(background, sourceX, sourceY, sourceW, sourceH, destX, destY, destW, destH);
|
||||||
|
if (sourceW < imageW)
|
||||||
|
ctx.drawImage(background, layer.x, sourceY, imageW-sourceW, sourceH, destW-1, destY, width-destW, destH);
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
sprite: function(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY, offsetX, offsetY, clipY) {
|
||||||
|
|
||||||
|
// scale for projection AND relative to roadWidth (for tweakUI)
|
||||||
|
var destW = (sprite.w * scale * width/2) * (SPRITES.SCALE * roadWidth);
|
||||||
|
var destH = (sprite.h * scale * width/2) * (SPRITES.SCALE * roadWidth);
|
||||||
|
|
||||||
|
destX = destX + (destW * (offsetX || 0));
|
||||||
|
destY = destY + (destH * (offsetY || 0));
|
||||||
|
|
||||||
|
var clipH = clipY ? Math.max(0, destY+destH-clipY) : 0;
|
||||||
|
if (clipH < destH)
|
||||||
|
ctx.drawImage(sprites, sprite.x, sprite.y, sprite.w, sprite.h - (sprite.h*clipH/destH), destX, destY, destW, destH - clipH);
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
player: function(ctx, width, height, resolution, roadWidth, sprites, speedPercent, scale, destX, destY, steer, updown) {
|
||||||
|
|
||||||
|
var bounce = (1.5 * Math.random() * speedPercent * resolution) * Util.randomChoice([-1,1]);
|
||||||
|
var sprite;
|
||||||
|
if (steer < 0)
|
||||||
|
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_LEFT : SPRITES.PLAYER_LEFT;
|
||||||
|
else if (steer > 0)
|
||||||
|
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_RIGHT : SPRITES.PLAYER_RIGHT;
|
||||||
|
else
|
||||||
|
sprite = (updown > 0) ? SPRITES.PLAYER_UPHILL_STRAIGHT : SPRITES.PLAYER_STRAIGHT;
|
||||||
|
|
||||||
|
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite, scale, destX, destY + bounce, -0.5, -1);
|
||||||
|
},
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
fog: function(ctx, x, y, width, height, fog) {
|
||||||
|
if (fog < 1) {
|
||||||
|
ctx.globalAlpha = (1-fog)
|
||||||
|
ctx.fillStyle = COLORS.FOG;
|
||||||
|
ctx.fillRect(x, y, width, height);
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
rumbleWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(6, 2*lanes); },
|
||||||
|
laneMarkerWidth: function(projectedRoadWidth, lanes) { return projectedRoadWidth/Math.max(32, 8*lanes); }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// RACING GAME CONSTANTS
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
var KEY = {
|
||||||
|
LEFT: 37,
|
||||||
|
UP: 38,
|
||||||
|
RIGHT: 39,
|
||||||
|
DOWN: 40,
|
||||||
|
A: 65,
|
||||||
|
D: 68,
|
||||||
|
S: 83,
|
||||||
|
W: 87
|
||||||
|
};
|
||||||
|
|
||||||
|
var COLORS = {
|
||||||
|
SKY: '#72D7EE',
|
||||||
|
TREE: '#005108',
|
||||||
|
FOG: '#005108',
|
||||||
|
LIGHT: { road: '#6B6B6B', grass: '#10AA10', rumble: '#555555', lane: '#CCCCCC' },
|
||||||
|
DARK: { road: '#696969', grass: '#009A00', rumble: '#BBBBBB' },
|
||||||
|
START: { road: 'white', grass: 'white', rumble: 'white' },
|
||||||
|
FINISH: { road: 'black', grass: 'black', rumble: 'black' }
|
||||||
|
};
|
||||||
|
|
||||||
|
var BACKGROUND = {
|
||||||
|
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
|
||||||
|
SKY: { x: 5, y: 495, w: 1280, h: 480 },
|
||||||
|
TREES: { x: 5, y: 985, w: 1280, h: 480 }
|
||||||
|
};
|
||||||
|
|
||||||
|
var SPRITES = {
|
||||||
|
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
|
||||||
|
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
|
||||||
|
TREE1: { x: 625, y: 5, w: 360, h: 360 },
|
||||||
|
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
|
||||||
|
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
|
||||||
|
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
|
||||||
|
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
|
||||||
|
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
|
||||||
|
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
|
||||||
|
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
|
||||||
|
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
|
||||||
|
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
|
||||||
|
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
|
||||||
|
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
|
||||||
|
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
|
||||||
|
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
|
||||||
|
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
|
||||||
|
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
|
||||||
|
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
|
||||||
|
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
|
||||||
|
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
|
||||||
|
STUMP: { x: 995, y: 330, w: 195, h: 140 },
|
||||||
|
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
|
||||||
|
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
|
||||||
|
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
|
||||||
|
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
|
||||||
|
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
|
||||||
|
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
|
||||||
|
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
|
||||||
|
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
|
||||||
|
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
|
||||||
|
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
|
||||||
|
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
|
||||||
|
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
|
||||||
|
};
|
||||||
|
|
||||||
|
SPRITES.SCALE = 0.3 * (1/SPRITES.PLAYER_STRAIGHT.w) // the reference sprite width should be 1/3rd the (half-)roadWidth
|
||||||
|
|
||||||
|
SPRITES.BILLBOARDS = [SPRITES.BILLBOARD01, SPRITES.BILLBOARD02, SPRITES.BILLBOARD03, SPRITES.BILLBOARD04, SPRITES.BILLBOARD05, SPRITES.BILLBOARD06, SPRITES.BILLBOARD07, SPRITES.BILLBOARD08, SPRITES.BILLBOARD09];
|
||||||
|
SPRITES.PLANTS = [SPRITES.TREE1, SPRITES.TREE2, SPRITES.DEAD_TREE1, SPRITES.DEAD_TREE2, SPRITES.PALM_TREE, SPRITES.BUSH1, SPRITES.BUSH2, SPRITES.CACTUS, SPRITES.STUMP, SPRITES.BOULDER1, SPRITES.BOULDER2, SPRITES.BOULDER3];
|
||||||
|
SPRITES.CARS = [SPRITES.CAR01, SPRITES.CAR02, SPRITES.CAR03, SPRITES.CAR04, SPRITES.SEMI, SPRITES.TRUCK];
|
||||||
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
var BACKGROUND = {
|
||||||
|
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
|
||||||
|
SKY: { x: 5, y: 495, w: 1280, h: 480 },
|
||||||
|
TREES: { x: 5, y: 985, w: 1280, h: 480 }
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@ -0,0 +1,36 @@
|
|||||||
|
var SPRITES = {
|
||||||
|
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
|
||||||
|
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
|
||||||
|
TREE1: { x: 625, y: 5, w: 360, h: 360 },
|
||||||
|
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
|
||||||
|
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
|
||||||
|
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
|
||||||
|
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
|
||||||
|
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
|
||||||
|
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
|
||||||
|
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
|
||||||
|
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
|
||||||
|
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
|
||||||
|
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
|
||||||
|
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
|
||||||
|
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
|
||||||
|
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
|
||||||
|
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
|
||||||
|
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
|
||||||
|
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
|
||||||
|
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
|
||||||
|
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
|
||||||
|
STUMP: { x: 995, y: 330, w: 195, h: 140 },
|
||||||
|
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
|
||||||
|
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
|
||||||
|
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
|
||||||
|
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
|
||||||
|
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
|
||||||
|
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
|
||||||
|
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
|
||||||
|
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
|
||||||
|
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
|
||||||
|
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
|
||||||
|
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
|
||||||
|
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
|
||||||
|
};
|
||||||
|
After Width: | Height: | Size: 267 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 647 B |
|
After Width: | Height: | Size: 908 B |
|
After Width: | Height: | Size: 752 B |
|
After Width: | Height: | Size: 790 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 745 B |
|
After Width: | Height: | Size: 742 B |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 751 B |
|
After Width: | Height: | Size: 786 B |
|
After Width: | Height: | Size: 758 B |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 973 B |
@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Javascript Racer</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href='v1.straight.html'>Version 1 - Straight</a></li>
|
||||||
|
<li><a href='v2.curves.html'>Version 2 - Curves</a></li>
|
||||||
|
<li><a href='v3.hills.html'>Version 3 - Hills</a></li>
|
||||||
|
<li><a href='v4.final.html'>Version 4 - Final</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
* @author mrdoob / http://mrdoob.com/
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Stats = function () {
|
||||||
|
|
||||||
|
var startTime = Date.now(), prevTime = startTime;
|
||||||
|
var ms = 0, msMin = 1000, msMax = 0;
|
||||||
|
var fps = 0, fpsMin = 1000, fpsMax = 0;
|
||||||
|
var frames = 0, mode = 0;mode
|
||||||
|
var container = document.createElement( 'div' );
|
||||||
|
container.id = 'stats';
|
||||||
|
container.addEventListener( 'mousedown', function ( event ) { event.preventDefault(); setMode( ++ mode % 2 ) }, false );
|
||||||
|
container.style.cssText = 'width:80px;opacity:0.9;cursor:pointer';
|
||||||
|
|
||||||
|
var fpsDiv = document.createElement( 'div' );
|
||||||
|
fpsDiv.id = 'fps';
|
||||||
|
fpsDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#002';
|
||||||
|
container.appendChild( fpsDiv );
|
||||||
|
|
||||||
|
var fpsText = document.createElement( 'div' );
|
||||||
|
fpsText.id = 'fpsText';
|
||||||
|
fpsText.style.cssText = 'color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
|
||||||
|
fpsText.innerHTML = 'FPS';
|
||||||
|
fpsDiv.appendChild( fpsText );
|
||||||
|
|
||||||
|
var fpsGraph = document.createElement( 'div' );
|
||||||
|
fpsGraph.id = 'fpsGraph';
|
||||||
|
fpsGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0ff';
|
||||||
|
fpsDiv.appendChild( fpsGraph );
|
||||||
|
|
||||||
|
while ( fpsGraph.children.length < 74 ) {
|
||||||
|
|
||||||
|
var bar = document.createElement( 'span' );
|
||||||
|
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#113';
|
||||||
|
fpsGraph.appendChild( bar );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var msDiv = document.createElement( 'div' );
|
||||||
|
msDiv.id = 'ms';
|
||||||
|
msDiv.style.cssText = 'padding:0 0 3px 3px;text-align:left;background-color:#020;display:none';
|
||||||
|
container.appendChild( msDiv );
|
||||||
|
|
||||||
|
var msText = document.createElement( 'div' );
|
||||||
|
msText.id = 'msText';
|
||||||
|
msText.style.cssText = 'color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px';
|
||||||
|
msText.innerHTML = 'MS';
|
||||||
|
msDiv.appendChild( msText );
|
||||||
|
|
||||||
|
var msGraph = document.createElement( 'div' );
|
||||||
|
msGraph.id = 'msGraph';
|
||||||
|
msGraph.style.cssText = 'position:relative;width:74px;height:30px;background-color:#0f0';
|
||||||
|
msDiv.appendChild( msGraph );
|
||||||
|
|
||||||
|
while ( msGraph.children.length < 74 ) {
|
||||||
|
|
||||||
|
var bar = document.createElement( 'span' );
|
||||||
|
bar.style.cssText = 'width:1px;height:30px;float:left;background-color:#131';
|
||||||
|
msGraph.appendChild( bar );
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var setMode = function ( value ) {
|
||||||
|
|
||||||
|
mode = value;
|
||||||
|
|
||||||
|
switch ( mode ) {
|
||||||
|
|
||||||
|
case 0:
|
||||||
|
fpsDiv.style.display = 'block';
|
||||||
|
msDiv.style.display = 'none';
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
fpsDiv.style.display = 'none';
|
||||||
|
msDiv.style.display = 'block';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var updateGraph = function ( dom, value ) {
|
||||||
|
|
||||||
|
var child = dom.appendChild( dom.firstChild );
|
||||||
|
child.style.height = value + 'px';
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
domElement: container,
|
||||||
|
|
||||||
|
setMode: setMode,
|
||||||
|
|
||||||
|
current: function() { return fps; },
|
||||||
|
|
||||||
|
begin: function () {
|
||||||
|
|
||||||
|
startTime = Date.now();
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
end: function () {
|
||||||
|
|
||||||
|
var time = Date.now();
|
||||||
|
|
||||||
|
ms = time - startTime;
|
||||||
|
msMin = Math.min( msMin, ms );
|
||||||
|
msMax = Math.max( msMax, ms );
|
||||||
|
|
||||||
|
msText.textContent = ms + ' MS (' + msMin + '-' + msMax + ')';
|
||||||
|
updateGraph( msGraph, Math.min( 30, 30 - ( ms / 200 ) * 30 ) );
|
||||||
|
|
||||||
|
frames ++;
|
||||||
|
|
||||||
|
if ( time > prevTime + 1000 ) {
|
||||||
|
|
||||||
|
fps = Math.round( ( frames * 1000 ) / ( time - prevTime ) );
|
||||||
|
fpsMin = Math.min( fpsMin, fps );
|
||||||
|
fpsMax = Math.max( fpsMax, fps );
|
||||||
|
|
||||||
|
fpsText.textContent = fps + ' FPS (' + fpsMin + '-' + fpsMax + ')';
|
||||||
|
updateGraph( fpsGraph, Math.min( 30, 30 - ( fps / 100 ) * 30 ) );
|
||||||
|
|
||||||
|
prevTime = time;
|
||||||
|
frames = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return time;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
update: function () {
|
||||||
|
|
||||||
|
startTime = this.end();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
@ -0,0 +1,314 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Javascript Racer - v1 (straight)</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table id="controls">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href='v1.straight.html'>straight</a> |
|
||||||
|
<a href='v2.curves.html'>curves</a> |
|
||||||
|
<a href='v3.hills.html'>hills</a> |
|
||||||
|
<a href='v4.final.html'>final</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="resolution">Resolution :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="resolution" style="width:100%">
|
||||||
|
<option value='fine'>Fine (1280x960)</option>
|
||||||
|
<option selected value='high'>High (1024x768)</option>
|
||||||
|
<option value='medium'>Medium (640x480)</option>
|
||||||
|
<option value='low'>Low (480x360)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="lanes">Lanes :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="lanes">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option selected>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||||
|
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||||
|
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||||
|
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||||
|
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||||
|
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id='instructions'>
|
||||||
|
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="racer">
|
||||||
|
<canvas id="canvas">
|
||||||
|
Sorry, this example cannot be run because your browser does not support the <canvas> element
|
||||||
|
</canvas>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id='music'>
|
||||||
|
<source src="music/racer.ogg">
|
||||||
|
<source src="music/racer.mp3">
|
||||||
|
</audio>
|
||||||
|
<span id="mute"></span>
|
||||||
|
|
||||||
|
<script src="stats.js"></script>
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var fps = 60; // how many 'update' frames per second
|
||||||
|
var step = 1/fps; // how long is each frame (in seconds)
|
||||||
|
var width = 1024; // logical canvas width
|
||||||
|
var height = 768; // logical canvas height
|
||||||
|
var segments = []; // array of road segments
|
||||||
|
var stats = Game.stats('fps'); // mr.doobs FPS counter
|
||||||
|
var canvas = Dom.get('canvas'); // our canvas...
|
||||||
|
var ctx = canvas.getContext('2d'); // ...and its drawing context
|
||||||
|
var background = null; // our background image (loaded below)
|
||||||
|
var sprites = null; // our spritesheet (loaded below)
|
||||||
|
var resolution = null; // scaling factor to provide resolution independence (computed)
|
||||||
|
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
|
||||||
|
var segmentLength = 200; // length of a single segment
|
||||||
|
var rumbleLength = 3; // number of segments per red/white rumble strip
|
||||||
|
var trackLength = null; // z length of entire track (computed)
|
||||||
|
var lanes = 3; // number of lanes
|
||||||
|
var fieldOfView = 100; // angle (degrees) for field of view
|
||||||
|
var cameraHeight = 1000; // z height of camera
|
||||||
|
var cameraDepth = null; // z distance camera is from screen (computed)
|
||||||
|
var drawDistance = 300; // number of segments to draw
|
||||||
|
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
|
||||||
|
var playerZ = null; // player relative z distance from camera (computed)
|
||||||
|
var fogDensity = 5; // exponential fog density
|
||||||
|
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
|
||||||
|
var speed = 0; // current speed
|
||||||
|
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
|
||||||
|
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
|
||||||
|
var breaking = -maxSpeed; // deceleration rate when braking
|
||||||
|
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
|
||||||
|
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
|
||||||
|
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
|
||||||
|
|
||||||
|
var keyLeft = false;
|
||||||
|
var keyRight = false;
|
||||||
|
var keyFaster = false;
|
||||||
|
var keySlower = false;
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// UPDATE THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function update(dt) {
|
||||||
|
|
||||||
|
position = Util.increase(position, dt * speed, trackLength);
|
||||||
|
|
||||||
|
var dx = dt * 2 * (speed/maxSpeed); // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
|
||||||
|
|
||||||
|
if (keyLeft)
|
||||||
|
playerX = playerX - dx;
|
||||||
|
else if (keyRight)
|
||||||
|
playerX = playerX + dx;
|
||||||
|
|
||||||
|
if (keyFaster)
|
||||||
|
speed = Util.accelerate(speed, accel, dt);
|
||||||
|
else if (keySlower)
|
||||||
|
speed = Util.accelerate(speed, breaking, dt);
|
||||||
|
else
|
||||||
|
speed = Util.accelerate(speed, decel, dt);
|
||||||
|
|
||||||
|
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
|
||||||
|
speed = Util.accelerate(speed, offRoadDecel, dt);
|
||||||
|
|
||||||
|
playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of bounds
|
||||||
|
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// RENDER THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
|
||||||
|
var baseSegment = findSegment(position);
|
||||||
|
var maxy = height;
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.SKY);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.HILLS);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.TREES);
|
||||||
|
|
||||||
|
var n, segment;
|
||||||
|
|
||||||
|
for(n = 0 ; n < drawDistance ; n++) {
|
||||||
|
|
||||||
|
segment = segments[(baseSegment.index + n) % segments.length];
|
||||||
|
segment.looped = segment.index < baseSegment.index;
|
||||||
|
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
|
||||||
|
|
||||||
|
Util.project(segment.p1, (playerX * roadWidth), cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
Util.project(segment.p2, (playerX * roadWidth), cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
|
||||||
|
if ((segment.p1.camera.z <= cameraDepth) || // behind us
|
||||||
|
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Render.segment(ctx, width, lanes,
|
||||||
|
segment.p1.screen.x,
|
||||||
|
segment.p1.screen.y,
|
||||||
|
segment.p1.screen.w,
|
||||||
|
segment.p2.screen.x,
|
||||||
|
segment.p2.screen.y,
|
||||||
|
segment.p2.screen.w,
|
||||||
|
segment.fog,
|
||||||
|
segment.color);
|
||||||
|
|
||||||
|
maxy = segment.p2.screen.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
|
||||||
|
cameraDepth/playerZ,
|
||||||
|
width/2,
|
||||||
|
height,
|
||||||
|
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// BUILD ROAD GEOMETRY
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function resetRoad() {
|
||||||
|
segments = [];
|
||||||
|
for(var n = 0 ; n < 500 ; n++) {
|
||||||
|
segments.push({
|
||||||
|
index: n,
|
||||||
|
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
|
||||||
|
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
|
||||||
|
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
segments[findSegment(playerZ).index + 2].color = COLORS.START;
|
||||||
|
segments[findSegment(playerZ).index + 3].color = COLORS.START;
|
||||||
|
for(var n = 0 ; n < rumbleLength ; n++)
|
||||||
|
segments[segments.length-1-n].color = COLORS.FINISH;
|
||||||
|
|
||||||
|
trackLength = segments.length * segmentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSegment(z) {
|
||||||
|
return segments[Math.floor(z/segmentLength) % segments.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// THE GAME LOOP
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Game.run({
|
||||||
|
canvas: canvas, render: render, update: update, stats: stats, step: step,
|
||||||
|
images: ["background", "sprites"],
|
||||||
|
keys: [
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
|
||||||
|
],
|
||||||
|
ready: function(images) {
|
||||||
|
background = images[0];
|
||||||
|
sprites = images[1];
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset(options) {
|
||||||
|
options = options || {};
|
||||||
|
canvas.width = width = Util.toInt(options.width, width);
|
||||||
|
canvas.height = height = Util.toInt(options.height, height);
|
||||||
|
lanes = Util.toInt(options.lanes, lanes);
|
||||||
|
roadWidth = Util.toInt(options.roadWidth, roadWidth);
|
||||||
|
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
|
||||||
|
drawDistance = Util.toInt(options.drawDistance, drawDistance);
|
||||||
|
fogDensity = Util.toInt(options.fogDensity, fogDensity);
|
||||||
|
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
|
||||||
|
segmentLength = Util.toInt(options.segmentLength, segmentLength);
|
||||||
|
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
|
||||||
|
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
|
||||||
|
playerZ = (cameraHeight * cameraDepth);
|
||||||
|
resolution = height/480;
|
||||||
|
refreshTweakUI();
|
||||||
|
|
||||||
|
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
|
||||||
|
resetRoad(); // only rebuild road when necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// TWEAK UI HANDLERS
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Dom.on('resolution', 'change', function(ev) {
|
||||||
|
var w, h, ratio;
|
||||||
|
switch(ev.target.options[ev.target.selectedIndex].value) {
|
||||||
|
case 'fine': w = 1280; h = 960; ratio=w/width; break;
|
||||||
|
case 'high': w = 1024; h = 768; ratio=w/width; break;
|
||||||
|
case 'medium': w = 640; h = 480; ratio=w/width; break;
|
||||||
|
case 'low': w = 480; h = 360; ratio=w/width; break;
|
||||||
|
}
|
||||||
|
reset({ width: w, height: h })
|
||||||
|
Dom.blur(ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
|
||||||
|
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
|
||||||
|
function refreshTweakUI() {
|
||||||
|
Dom.get('lanes').selectedIndex = lanes-1;
|
||||||
|
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
|
||||||
|
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
|
||||||
|
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
|
||||||
|
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
|
||||||
|
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
@ -0,0 +1,387 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Javascript Racer - v2 (curves)</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table id="controls">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href='v1.straight.html'>straight</a> |
|
||||||
|
<a href='v2.curves.html'>curves</a> |
|
||||||
|
<a href='v3.hills.html'>hills</a> |
|
||||||
|
<a href='v4.final.html'>final</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="resolution">Resolution :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="resolution" style="width:100%">
|
||||||
|
<option value='fine'>Fine (1280x960)</option>
|
||||||
|
<option selected value='high'>High (1024x768)</option>
|
||||||
|
<option value='medium'>Medium (640x480)</option>
|
||||||
|
<option value='low'>Low (480x360)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="lanes">Lanes :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="lanes">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option selected>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||||
|
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||||
|
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||||
|
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||||
|
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||||
|
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id='instructions'>
|
||||||
|
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="racer">
|
||||||
|
<canvas id="canvas">
|
||||||
|
Sorry, this example cannot be run because your browser does not support the <canvas> element
|
||||||
|
</canvas>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id='music'>
|
||||||
|
<source src="music/racer.ogg">
|
||||||
|
<source src="music/racer.mp3">
|
||||||
|
</audio>
|
||||||
|
<span id="mute"></span>
|
||||||
|
|
||||||
|
<script src="stats.js"></script>
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var fps = 60; // how many 'update' frames per second
|
||||||
|
var step = 1/fps; // how long is each frame (in seconds)
|
||||||
|
var width = 1024; // logical canvas width
|
||||||
|
var height = 768; // logical canvas height
|
||||||
|
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
|
||||||
|
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
|
||||||
|
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
|
||||||
|
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
|
||||||
|
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
|
||||||
|
var skyOffset = 0; // current sky scroll offset
|
||||||
|
var hillOffset = 0; // current hill scroll offset
|
||||||
|
var treeOffset = 0; // current tree scroll offset
|
||||||
|
var segments = []; // array of road segments
|
||||||
|
var stats = Game.stats('fps'); // mr.doobs FPS counter
|
||||||
|
var canvas = Dom.get('canvas'); // our canvas...
|
||||||
|
var ctx = canvas.getContext('2d'); // ...and its drawing context
|
||||||
|
var background = null; // our background image (loaded below)
|
||||||
|
var sprites = null; // our spritesheet (loaded below)
|
||||||
|
var resolution = null; // scaling factor to provide resolution independence (computed)
|
||||||
|
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
|
||||||
|
var segmentLength = 200; // length of a single segment
|
||||||
|
var rumbleLength = 3; // number of segments per red/white rumble strip
|
||||||
|
var trackLength = null; // z length of entire track (computed)
|
||||||
|
var lanes = 3; // number of lanes
|
||||||
|
var fieldOfView = 100; // angle (degrees) for field of view
|
||||||
|
var cameraHeight = 1000; // z height of camera
|
||||||
|
var cameraDepth = null; // z distance camera is from screen (computed)
|
||||||
|
var drawDistance = 300; // number of segments to draw
|
||||||
|
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
|
||||||
|
var playerZ = null; // player relative z distance from camera (computed)
|
||||||
|
var fogDensity = 5; // exponential fog density
|
||||||
|
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
|
||||||
|
var speed = 0; // current speed
|
||||||
|
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
|
||||||
|
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
|
||||||
|
var breaking = -maxSpeed; // deceleration rate when braking
|
||||||
|
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
|
||||||
|
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
|
||||||
|
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
|
||||||
|
|
||||||
|
var keyLeft = false;
|
||||||
|
var keyRight = false;
|
||||||
|
var keyFaster = false;
|
||||||
|
var keySlower = false;
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// UPDATE THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function update(dt) {
|
||||||
|
|
||||||
|
var playerSegment = findSegment(position+playerZ);
|
||||||
|
var speedPercent = speed/maxSpeed;
|
||||||
|
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to +1) in 1 second
|
||||||
|
|
||||||
|
position = Util.increase(position, dt * speed, trackLength);
|
||||||
|
|
||||||
|
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
|
||||||
|
if (keyLeft)
|
||||||
|
playerX = playerX - dx;
|
||||||
|
else if (keyRight)
|
||||||
|
playerX = playerX + dx;
|
||||||
|
|
||||||
|
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
|
||||||
|
|
||||||
|
if (keyFaster)
|
||||||
|
speed = Util.accelerate(speed, accel, dt);
|
||||||
|
else if (keySlower)
|
||||||
|
speed = Util.accelerate(speed, breaking, dt);
|
||||||
|
else
|
||||||
|
speed = Util.accelerate(speed, decel, dt);
|
||||||
|
|
||||||
|
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
|
||||||
|
speed = Util.accelerate(speed, offRoadDecel, dt);
|
||||||
|
|
||||||
|
playerX = Util.limit(playerX, -2, 2); // dont ever let player go too far out of bounds
|
||||||
|
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// RENDER THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
|
||||||
|
var baseSegment = findSegment(position);
|
||||||
|
var basePercent = Util.percentRemaining(position, segmentLength);
|
||||||
|
var maxy = height;
|
||||||
|
|
||||||
|
var x = 0;
|
||||||
|
var dx = - (baseSegment.curve * basePercent);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset);
|
||||||
|
|
||||||
|
var n, segment;
|
||||||
|
|
||||||
|
for(n = 0 ; n < drawDistance ; n++) {
|
||||||
|
|
||||||
|
segment = segments[(baseSegment.index + n) % segments.length];
|
||||||
|
segment.looped = segment.index < baseSegment.index;
|
||||||
|
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
|
||||||
|
|
||||||
|
Util.project(segment.p1, (playerX * roadWidth) - x, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
Util.project(segment.p2, (playerX * roadWidth) - x - dx, cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
|
||||||
|
x = x + dx;
|
||||||
|
dx = dx + segment.curve;
|
||||||
|
|
||||||
|
if ((segment.p1.camera.z <= cameraDepth) || // behind us
|
||||||
|
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Render.segment(ctx, width, lanes,
|
||||||
|
segment.p1.screen.x,
|
||||||
|
segment.p1.screen.y,
|
||||||
|
segment.p1.screen.w,
|
||||||
|
segment.p2.screen.x,
|
||||||
|
segment.p2.screen.y,
|
||||||
|
segment.p2.screen.w,
|
||||||
|
segment.fog,
|
||||||
|
segment.color);
|
||||||
|
|
||||||
|
maxy = segment.p2.screen.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
|
||||||
|
cameraDepth/playerZ,
|
||||||
|
width/2,
|
||||||
|
height,
|
||||||
|
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// BUILD ROAD GEOMETRY
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function addSegment(curve) {
|
||||||
|
var n = segments.length;
|
||||||
|
segments.push({
|
||||||
|
index: n,
|
||||||
|
p1: { world: { z: n *segmentLength }, camera: {}, screen: {} },
|
||||||
|
p2: { world: { z: (n+1)*segmentLength }, camera: {}, screen: {} },
|
||||||
|
curve: curve,
|
||||||
|
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRoad(enter, hold, leave, curve) {
|
||||||
|
var n;
|
||||||
|
for(n = 0 ; n < enter ; n++)
|
||||||
|
addSegment(Util.easeIn(0, curve, n/enter));
|
||||||
|
for(n = 0 ; n < hold ; n++)
|
||||||
|
addSegment(curve);
|
||||||
|
for(n = 0 ; n < leave ; n++)
|
||||||
|
addSegment(Util.easeInOut(curve, 0, n/leave));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ROAD = {
|
||||||
|
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
|
||||||
|
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
|
||||||
|
};
|
||||||
|
|
||||||
|
function addStraight(num) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
addRoad(num, num, num, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCurve(num, curve) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
curve = curve || ROAD.CURVE.MEDIUM;
|
||||||
|
addRoad(num, num, num, curve);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSCurves() {
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRoad() {
|
||||||
|
segments = [];
|
||||||
|
|
||||||
|
addStraight(ROAD.LENGTH.SHORT/4);
|
||||||
|
addSCurves();
|
||||||
|
addStraight(ROAD.LENGTH.LONG);
|
||||||
|
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM);
|
||||||
|
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addSCurves();
|
||||||
|
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM);
|
||||||
|
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addSCurves();
|
||||||
|
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.EASY);
|
||||||
|
|
||||||
|
segments[findSegment(playerZ).index + 2].color = COLORS.START;
|
||||||
|
segments[findSegment(playerZ).index + 3].color = COLORS.START;
|
||||||
|
for(var n = 0 ; n < rumbleLength ; n++)
|
||||||
|
segments[segments.length-1-n].color = COLORS.FINISH;
|
||||||
|
|
||||||
|
trackLength = segments.length * segmentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSegment(z) {
|
||||||
|
return segments[Math.floor(z/segmentLength) % segments.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// THE GAME LOOP
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Game.run({
|
||||||
|
canvas: canvas, render: render, update: update, stats: stats, step: step,
|
||||||
|
images: ["background", "sprites"],
|
||||||
|
keys: [
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
|
||||||
|
],
|
||||||
|
ready: function(images) {
|
||||||
|
background = images[0];
|
||||||
|
sprites = images[1];
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset(options) {
|
||||||
|
options = options || {};
|
||||||
|
canvas.width = width = Util.toInt(options.width, width);
|
||||||
|
canvas.height = height = Util.toInt(options.height, height);
|
||||||
|
lanes = Util.toInt(options.lanes, lanes);
|
||||||
|
roadWidth = Util.toInt(options.roadWidth, roadWidth);
|
||||||
|
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
|
||||||
|
drawDistance = Util.toInt(options.drawDistance, drawDistance);
|
||||||
|
fogDensity = Util.toInt(options.fogDensity, fogDensity);
|
||||||
|
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
|
||||||
|
segmentLength = Util.toInt(options.segmentLength, segmentLength);
|
||||||
|
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
|
||||||
|
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
|
||||||
|
playerZ = (cameraHeight * cameraDepth);
|
||||||
|
resolution = height/480;
|
||||||
|
refreshTweakUI();
|
||||||
|
|
||||||
|
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
|
||||||
|
resetRoad(); // only rebuild road when necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// TWEAK UI HANDLERS
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Dom.on('resolution', 'change', function(ev) {
|
||||||
|
var w, h, ratio;
|
||||||
|
switch(ev.target.options[ev.target.selectedIndex].value) {
|
||||||
|
case 'fine': w = 1280; h = 960; ratio=w/width; break;
|
||||||
|
case 'high': w = 1024; h = 768; ratio=w/width; break;
|
||||||
|
case 'medium': w = 640; h = 480; ratio=w/width; break;
|
||||||
|
case 'low': w = 480; h = 360; ratio=w/width; break;
|
||||||
|
}
|
||||||
|
reset({ width: w, height: h })
|
||||||
|
Dom.blur(ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
|
||||||
|
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
|
||||||
|
function refreshTweakUI() {
|
||||||
|
Dom.get('lanes').selectedIndex = lanes-1;
|
||||||
|
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
|
||||||
|
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
|
||||||
|
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
|
||||||
|
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
|
||||||
|
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
@ -0,0 +1,419 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Javascript Racer - v3 (hills)</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table id="controls">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href='v1.straight.html'>straight</a> |
|
||||||
|
<a href='v2.curves.html'>curves</a> |
|
||||||
|
<a href='v3.hills.html'>hills</a> |
|
||||||
|
<a href='v4.final.html'>final</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="resolution">Resolution :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="resolution" style="width:100%">
|
||||||
|
<option value='fine'>Fine (1280x960)</option>
|
||||||
|
<option selected value='high'>High (1024x768)</option>
|
||||||
|
<option value='medium'>Medium (640x480)</option>
|
||||||
|
<option value='low'>Low (480x360)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="lanes">Lanes :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="lanes">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option selected>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||||
|
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||||
|
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||||
|
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||||
|
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||||
|
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id='instructions'>
|
||||||
|
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="racer">
|
||||||
|
<canvas id="canvas">
|
||||||
|
Sorry, this example cannot be run because your browser does not support the <canvas> element
|
||||||
|
</canvas>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id='music'>
|
||||||
|
<source src="music/racer.ogg">
|
||||||
|
<source src="music/racer.mp3">
|
||||||
|
</audio>
|
||||||
|
<span id="mute"></span>
|
||||||
|
|
||||||
|
<script src="stats.js"></script>
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var fps = 60; // how many 'update' frames per second
|
||||||
|
var step = 1/fps; // how long is each frame (in seconds)
|
||||||
|
var width = 1024; // logical canvas width
|
||||||
|
var height = 768; // logical canvas height
|
||||||
|
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
|
||||||
|
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
|
||||||
|
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
|
||||||
|
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
|
||||||
|
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
|
||||||
|
var skyOffset = 0; // current sky scroll offset
|
||||||
|
var hillOffset = 0; // current hill scroll offset
|
||||||
|
var treeOffset = 0; // current tree scroll offset
|
||||||
|
var segments = []; // array of road segments
|
||||||
|
var stats = Game.stats('fps'); // mr.doobs FPS counter
|
||||||
|
var canvas = Dom.get('canvas'); // our canvas...
|
||||||
|
var ctx = canvas.getContext('2d'); // ...and its drawing context
|
||||||
|
var background = null; // our background image (loaded below)
|
||||||
|
var sprites = null; // our spritesheet (loaded below)
|
||||||
|
var resolution = null; // scaling factor to provide resolution independence (computed)
|
||||||
|
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
|
||||||
|
var segmentLength = 200; // length of a single segment
|
||||||
|
var rumbleLength = 3; // number of segments per red/white rumble strip
|
||||||
|
var trackLength = null; // z length of entire track (computed)
|
||||||
|
var lanes = 3; // number of lanes
|
||||||
|
var fieldOfView = 100; // angle (degrees) for field of view
|
||||||
|
var cameraHeight = 1000; // z height of camera
|
||||||
|
var cameraDepth = null; // z distance camera is from screen (computed)
|
||||||
|
var drawDistance = 300; // number of segments to draw
|
||||||
|
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
|
||||||
|
var playerZ = null; // player relative z distance from camera (computed)
|
||||||
|
var fogDensity = 5; // exponential fog density
|
||||||
|
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
|
||||||
|
var speed = 0; // current speed
|
||||||
|
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
|
||||||
|
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
|
||||||
|
var breaking = -maxSpeed; // deceleration rate when braking
|
||||||
|
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
|
||||||
|
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
|
||||||
|
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
|
||||||
|
|
||||||
|
var keyLeft = false;
|
||||||
|
var keyRight = false;
|
||||||
|
var keyFaster = false;
|
||||||
|
var keySlower = false;
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// UPDATE THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function update(dt) {
|
||||||
|
|
||||||
|
var playerSegment = findSegment(position+playerZ);
|
||||||
|
var speedPercent = speed/maxSpeed;
|
||||||
|
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
|
||||||
|
|
||||||
|
position = Util.increase(position, dt * speed, trackLength);
|
||||||
|
|
||||||
|
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * speedPercent, 1);
|
||||||
|
|
||||||
|
if (keyLeft)
|
||||||
|
playerX = playerX - dx;
|
||||||
|
else if (keyRight)
|
||||||
|
playerX = playerX + dx;
|
||||||
|
|
||||||
|
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
|
||||||
|
|
||||||
|
if (keyFaster)
|
||||||
|
speed = Util.accelerate(speed, accel, dt);
|
||||||
|
else if (keySlower)
|
||||||
|
speed = Util.accelerate(speed, breaking, dt);
|
||||||
|
else
|
||||||
|
speed = Util.accelerate(speed, decel, dt);
|
||||||
|
|
||||||
|
if (((playerX < -1) || (playerX > 1)) && (speed > offRoadLimit))
|
||||||
|
speed = Util.accelerate(speed, offRoadDecel, dt);
|
||||||
|
|
||||||
|
playerX = Util.limit(playerX, -2, 2); // dont ever let it go too far out of bounds
|
||||||
|
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// RENDER THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
|
||||||
|
var baseSegment = findSegment(position);
|
||||||
|
var basePercent = Util.percentRemaining(position, segmentLength);
|
||||||
|
var playerSegment = findSegment(position+playerZ);
|
||||||
|
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
|
||||||
|
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
|
||||||
|
var maxy = height;
|
||||||
|
|
||||||
|
var x = 0;
|
||||||
|
var dx = - (baseSegment.curve * basePercent);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset, resolution * hillSpeed * playerY);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset, resolution * treeSpeed * playerY);
|
||||||
|
|
||||||
|
var n, segment;
|
||||||
|
|
||||||
|
for(n = 0 ; n < drawDistance ; n++) {
|
||||||
|
|
||||||
|
segment = segments[(baseSegment.index + n) % segments.length];
|
||||||
|
segment.looped = segment.index < baseSegment.index;
|
||||||
|
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
|
||||||
|
|
||||||
|
Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
|
||||||
|
x = x + dx;
|
||||||
|
dx = dx + segment.curve;
|
||||||
|
|
||||||
|
if ((segment.p1.camera.z <= cameraDepth) || // behind us
|
||||||
|
(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
|
||||||
|
(segment.p2.screen.y >= maxy)) // clip by (already rendered) segment
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Render.segment(ctx, width, lanes,
|
||||||
|
segment.p1.screen.x,
|
||||||
|
segment.p1.screen.y,
|
||||||
|
segment.p1.screen.w,
|
||||||
|
segment.p2.screen.x,
|
||||||
|
segment.p2.screen.y,
|
||||||
|
segment.p2.screen.w,
|
||||||
|
segment.fog,
|
||||||
|
segment.color);
|
||||||
|
|
||||||
|
maxy = segment.p2.screen.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
|
||||||
|
cameraDepth/playerZ,
|
||||||
|
width/2,
|
||||||
|
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * height/2),
|
||||||
|
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
|
||||||
|
playerSegment.p2.world.y - playerSegment.p1.world.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// BUILD ROAD GEOMETRY
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y; }
|
||||||
|
|
||||||
|
function addSegment(curve, y) {
|
||||||
|
var n = segments.length;
|
||||||
|
segments.push({
|
||||||
|
index: n,
|
||||||
|
p1: { world: { y: lastY(), z: n *segmentLength }, camera: {}, screen: {} },
|
||||||
|
p2: { world: { y: y, z: (n+1)*segmentLength }, camera: {}, screen: {} },
|
||||||
|
curve: curve,
|
||||||
|
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRoad(enter, hold, leave, curve, y) {
|
||||||
|
var startY = lastY();
|
||||||
|
var endY = startY + (Util.toInt(y, 0) * segmentLength);
|
||||||
|
var n, total = enter + hold + leave;
|
||||||
|
for(n = 0 ; n < enter ; n++)
|
||||||
|
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
|
||||||
|
for(n = 0 ; n < hold ; n++)
|
||||||
|
addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
|
||||||
|
for(n = 0 ; n < leave ; n++)
|
||||||
|
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY, (enter+hold+n)/total));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ROAD = {
|
||||||
|
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
|
||||||
|
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
|
||||||
|
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
|
||||||
|
};
|
||||||
|
|
||||||
|
function addStraight(num) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHill(num, height) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
height = height || ROAD.HILL.MEDIUM;
|
||||||
|
addRoad(num, num, num, 0, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCurve(num, curve, height) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
curve = curve || ROAD.CURVE.MEDIUM;
|
||||||
|
height = height || ROAD.HILL.NONE;
|
||||||
|
addRoad(num, num, num, curve, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLowRollingHills(num, height) {
|
||||||
|
num = num || ROAD.LENGTH.SHORT;
|
||||||
|
height = height || ROAD.HILL.LOW;
|
||||||
|
addRoad(num, num, num, 0, height/2);
|
||||||
|
addRoad(num, num, num, 0, -height);
|
||||||
|
addRoad(num, num, num, 0, height);
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
addRoad(num, num, num, 0, height/2);
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSCurves() {
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.NONE);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, -ROAD.HILL.LOW);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.MEDIUM);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, -ROAD.HILL.MEDIUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDownhillToEnd(num) {
|
||||||
|
num = num || 200;
|
||||||
|
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRoad() {
|
||||||
|
segments = [];
|
||||||
|
|
||||||
|
addStraight(ROAD.LENGTH.SHORT/2);
|
||||||
|
addHill(ROAD.LENGTH.SHORT, ROAD.HILL.LOW);
|
||||||
|
addLowRollingHills();
|
||||||
|
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
|
||||||
|
addLowRollingHills();
|
||||||
|
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
|
||||||
|
addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
|
||||||
|
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
|
||||||
|
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addDownhillToEnd();
|
||||||
|
|
||||||
|
segments[findSegment(playerZ).index + 2].color = COLORS.START;
|
||||||
|
segments[findSegment(playerZ).index + 3].color = COLORS.START;
|
||||||
|
for(var n = 0 ; n < rumbleLength ; n++)
|
||||||
|
segments[segments.length-1-n].color = COLORS.FINISH;
|
||||||
|
|
||||||
|
trackLength = segments.length * segmentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSegment(z) {
|
||||||
|
return segments[Math.floor(z/segmentLength) % segments.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// THE GAME LOOP
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Game.run({
|
||||||
|
canvas: canvas, render: render, update: update, stats: stats, step: step,
|
||||||
|
images: ["background", "sprites"],
|
||||||
|
keys: [
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
|
||||||
|
],
|
||||||
|
ready: function(images) {
|
||||||
|
background = images[0];
|
||||||
|
sprites = images[1];
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset(options) {
|
||||||
|
options = options || {};
|
||||||
|
canvas.width = width = Util.toInt(options.width, width);
|
||||||
|
canvas.height = height = Util.toInt(options.height, height);
|
||||||
|
lanes = Util.toInt(options.lanes, lanes);
|
||||||
|
roadWidth = Util.toInt(options.roadWidth, roadWidth);
|
||||||
|
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
|
||||||
|
drawDistance = Util.toInt(options.drawDistance, drawDistance);
|
||||||
|
fogDensity = Util.toInt(options.fogDensity, fogDensity);
|
||||||
|
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
|
||||||
|
segmentLength = Util.toInt(options.segmentLength, segmentLength);
|
||||||
|
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
|
||||||
|
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
|
||||||
|
playerZ = (cameraHeight * cameraDepth);
|
||||||
|
resolution = height/480;
|
||||||
|
refreshTweakUI();
|
||||||
|
|
||||||
|
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
|
||||||
|
resetRoad(); // only rebuild road when necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// TWEAK UI HANDLERS
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Dom.on('resolution', 'change', function(ev) {
|
||||||
|
var w, h, ratio;
|
||||||
|
switch(ev.target.options[ev.target.selectedIndex].value) {
|
||||||
|
case 'fine': w = 1280; h = 960; ratio=w/width; break;
|
||||||
|
case 'high': w = 1024; h = 768; ratio=w/width; break;
|
||||||
|
case 'medium': w = 640; h = 480; ratio=w/width; break;
|
||||||
|
case 'low': w = 480; h = 360; ratio=w/width; break;
|
||||||
|
}
|
||||||
|
reset({ width: w, height: h })
|
||||||
|
Dom.blur(ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
|
||||||
|
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
|
||||||
|
function refreshTweakUI() {
|
||||||
|
Dom.get('lanes').selectedIndex = lanes-1;
|
||||||
|
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
|
||||||
|
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
|
||||||
|
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
|
||||||
|
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
|
||||||
|
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
@ -0,0 +1,688 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Javascript Racer - v4 (final)</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
|
||||||
|
<link href="common.css" rel="stylesheet" type="text/css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<table id="controls">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<a href='v1.straight.html'>straight</a> |
|
||||||
|
<a href='v2.curves.html'>curves</a> |
|
||||||
|
<a href='v3.hills.html'>hills</a> |
|
||||||
|
<a href='v4.final.html'>final</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td id="fps" colspan="2" align="right"></td></tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="resolution">Resolution :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="resolution" style="width:100%">
|
||||||
|
<option value='fine'>Fine (1280x960)</option>
|
||||||
|
<option selected value='high'>High (1024x768)</option>
|
||||||
|
<option value='medium'>Medium (640x480)</option>
|
||||||
|
<option value='low'>Low (480x360)</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="lanes">Lanes :</label></th>
|
||||||
|
<td>
|
||||||
|
<select id="lanes">
|
||||||
|
<option>1</option>
|
||||||
|
<option>2</option>
|
||||||
|
<option selected>3</option>
|
||||||
|
<option>4</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="roadWidth">Road Width (<span id="currentRoadWidth"></span>) :</label></th>
|
||||||
|
<td><input id="roadWidth" type='range' min='500' max='3000' title="integer (500-3000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="cameraHeight">CameraHeight (<span id="currentCameraHeight"></span>) :</label></th>
|
||||||
|
<td><input id="cameraHeight" type='range' min='500' max='5000' title="integer (500-5000)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="drawDistance">Draw Distance (<span id="currentDrawDistance"></span>) :</label></th>
|
||||||
|
<td><input id="drawDistance" type='range' min='100' max='500' title="integer (100-500)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fieldOfView">Field of View (<span id="currentFieldOfView"></span>) :</label></th>
|
||||||
|
<td><input id="fieldOfView" type='range' min='80' max='140' title="integer (80-140)"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th><label for="fogDensity">Fog Density (<span id="currentFogDensity"></span>) :</label></th>
|
||||||
|
<td><input id="fogDensity" type='range' min='0' max='50' title="integer (0-50)"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div id='instructions'>
|
||||||
|
<p>Use the <b>arrow keys</b> to drive the car.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="racer">
|
||||||
|
<div id="hud">
|
||||||
|
<span id="speed" class="hud"><span id="speed_value" class="value">0</span> mph</span>
|
||||||
|
<span id="current_lap_time" class="hud">Time: <span id="current_lap_time_value" class="value">0.0</span></span>
|
||||||
|
<span id="last_lap_time" class="hud">Last Lap: <span id="last_lap_time_value" class="value">0.0</span></span>
|
||||||
|
<span id="fast_lap_time" class="hud">Fastest Lap: <span id="fast_lap_time_value" class="value">0.0</span></span>
|
||||||
|
</div>
|
||||||
|
<canvas id="canvas">
|
||||||
|
Sorry, this example cannot be run because your browser does not support the <canvas> element
|
||||||
|
</canvas>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id='music'>
|
||||||
|
<source src="music/racer.ogg">
|
||||||
|
<source src="music/racer.mp3">
|
||||||
|
</audio>
|
||||||
|
<span id="mute"></span>
|
||||||
|
|
||||||
|
<script src="stats.js"></script>
|
||||||
|
<script src="common.js"></script>
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var fps = 60; // how many 'update' frames per second
|
||||||
|
var step = 1/fps; // how long is each frame (in seconds)
|
||||||
|
var width = 1024; // logical canvas width
|
||||||
|
var height = 768; // logical canvas height
|
||||||
|
var centrifugal = 0.3; // centrifugal force multiplier when going around curves
|
||||||
|
var offRoadDecel = 0.99; // speed multiplier when off road (e.g. you lose 2% speed each update frame)
|
||||||
|
var skySpeed = 0.001; // background sky layer scroll speed when going around curve (or up hill)
|
||||||
|
var hillSpeed = 0.002; // background hill layer scroll speed when going around curve (or up hill)
|
||||||
|
var treeSpeed = 0.003; // background tree layer scroll speed when going around curve (or up hill)
|
||||||
|
var skyOffset = 0; // current sky scroll offset
|
||||||
|
var hillOffset = 0; // current hill scroll offset
|
||||||
|
var treeOffset = 0; // current tree scroll offset
|
||||||
|
var segments = []; // array of road segments
|
||||||
|
var cars = []; // array of cars on the road
|
||||||
|
var stats = Game.stats('fps'); // mr.doobs FPS counter
|
||||||
|
var canvas = Dom.get('canvas'); // our canvas...
|
||||||
|
var ctx = canvas.getContext('2d'); // ...and its drawing context
|
||||||
|
var background = null; // our background image (loaded below)
|
||||||
|
var sprites = null; // our spritesheet (loaded below)
|
||||||
|
var resolution = null; // scaling factor to provide resolution independence (computed)
|
||||||
|
var roadWidth = 2000; // actually half the roads width, easier math if the road spans from -roadWidth to +roadWidth
|
||||||
|
var segmentLength = 200; // length of a single segment
|
||||||
|
var rumbleLength = 3; // number of segments per red/white rumble strip
|
||||||
|
var trackLength = null; // z length of entire track (computed)
|
||||||
|
var lanes = 3; // number of lanes
|
||||||
|
var fieldOfView = 100; // angle (degrees) for field of view
|
||||||
|
var cameraHeight = 1000; // z height of camera
|
||||||
|
var cameraDepth = null; // z distance camera is from screen (computed)
|
||||||
|
var drawDistance = 300; // number of segments to draw
|
||||||
|
var playerX = 0; // player x offset from center of road (-1 to 1 to stay independent of roadWidth)
|
||||||
|
var playerZ = null; // player relative z distance from camera (computed)
|
||||||
|
var fogDensity = 5; // exponential fog density
|
||||||
|
var position = 0; // current camera Z position (add playerZ to get player's absolute Z position)
|
||||||
|
var speed = 0; // current speed
|
||||||
|
var maxSpeed = segmentLength/step; // top speed (ensure we can't move more than 1 segment in a single frame to make collision detection easier)
|
||||||
|
var accel = maxSpeed/5; // acceleration rate - tuned until it 'felt' right
|
||||||
|
var breaking = -maxSpeed; // deceleration rate when braking
|
||||||
|
var decel = -maxSpeed/5; // 'natural' deceleration rate when neither accelerating, nor braking
|
||||||
|
var offRoadDecel = -maxSpeed/2; // off road deceleration is somewhere in between
|
||||||
|
var offRoadLimit = maxSpeed/4; // limit when off road deceleration no longer applies (e.g. you can always go at least this speed even when off road)
|
||||||
|
var totalCars = 200; // total number of cars on the road
|
||||||
|
var currentLapTime = 0; // current lap time
|
||||||
|
var lastLapTime = null; // last lap time
|
||||||
|
|
||||||
|
var keyLeft = false;
|
||||||
|
var keyRight = false;
|
||||||
|
var keyFaster = false;
|
||||||
|
var keySlower = false;
|
||||||
|
|
||||||
|
var hud = {
|
||||||
|
speed: { value: null, dom: Dom.get('speed_value') },
|
||||||
|
current_lap_time: { value: null, dom: Dom.get('current_lap_time_value') },
|
||||||
|
last_lap_time: { value: null, dom: Dom.get('last_lap_time_value') },
|
||||||
|
fast_lap_time: { value: null, dom: Dom.get('fast_lap_time_value') }
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// UPDATE THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function update(dt) {
|
||||||
|
|
||||||
|
var n, car, carW, sprite, spriteW;
|
||||||
|
var playerSegment = findSegment(position+playerZ);
|
||||||
|
var playerW = SPRITES.PLAYER_STRAIGHT.w * SPRITES.SCALE;
|
||||||
|
var speedPercent = speed/maxSpeed;
|
||||||
|
var dx = dt * 2 * speedPercent; // at top speed, should be able to cross from left to right (-1 to 1) in 1 second
|
||||||
|
var startPosition = position;
|
||||||
|
|
||||||
|
updateCars(dt, playerSegment, playerW);
|
||||||
|
|
||||||
|
position = Util.increase(position, dt * speed, trackLength);
|
||||||
|
|
||||||
|
if (keyLeft)
|
||||||
|
playerX = playerX - dx;
|
||||||
|
else if (keyRight)
|
||||||
|
playerX = playerX + dx;
|
||||||
|
|
||||||
|
playerX = playerX - (dx * speedPercent * playerSegment.curve * centrifugal);
|
||||||
|
|
||||||
|
if (keyFaster)
|
||||||
|
speed = Util.accelerate(speed, accel, dt);
|
||||||
|
else if (keySlower)
|
||||||
|
speed = Util.accelerate(speed, breaking, dt);
|
||||||
|
else
|
||||||
|
speed = Util.accelerate(speed, decel, dt);
|
||||||
|
|
||||||
|
|
||||||
|
if ((playerX < -1) || (playerX > 1)) {
|
||||||
|
|
||||||
|
if (speed > offRoadLimit)
|
||||||
|
speed = Util.accelerate(speed, offRoadDecel, dt);
|
||||||
|
|
||||||
|
for(n = 0 ; n < playerSegment.sprites.length ; n++) {
|
||||||
|
sprite = playerSegment.sprites[n];
|
||||||
|
spriteW = sprite.source.w * SPRITES.SCALE;
|
||||||
|
if (Util.overlap(playerX, playerW, sprite.offset + spriteW/2 * (sprite.offset > 0 ? 1 : -1), spriteW)) {
|
||||||
|
speed = maxSpeed/5;
|
||||||
|
position = Util.increase(playerSegment.p1.world.z, -playerZ, trackLength); // stop in front of sprite (at front of segment)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n = 0 ; n < playerSegment.cars.length ; n++) {
|
||||||
|
car = playerSegment.cars[n];
|
||||||
|
carW = car.sprite.w * SPRITES.SCALE;
|
||||||
|
if (speed > car.speed) {
|
||||||
|
if (Util.overlap(playerX, playerW, car.offset, carW, 0.8)) {
|
||||||
|
speed = car.speed * (car.speed/speed);
|
||||||
|
position = Util.increase(car.z, -playerZ, trackLength);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
playerX = Util.limit(playerX, -3, 3); // dont ever let it go too far out of bounds
|
||||||
|
speed = Util.limit(speed, 0, maxSpeed); // or exceed maxSpeed
|
||||||
|
|
||||||
|
skyOffset = Util.increase(skyOffset, skySpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
|
||||||
|
hillOffset = Util.increase(hillOffset, hillSpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
|
||||||
|
treeOffset = Util.increase(treeOffset, treeSpeed * playerSegment.curve * (position-startPosition)/segmentLength, 1);
|
||||||
|
|
||||||
|
if (position > playerZ) {
|
||||||
|
if (currentLapTime && (startPosition < playerZ)) {
|
||||||
|
lastLapTime = currentLapTime;
|
||||||
|
currentLapTime = 0;
|
||||||
|
if (lastLapTime <= Util.toFloat(Dom.storage.fast_lap_time)) {
|
||||||
|
Dom.storage.fast_lap_time = lastLapTime;
|
||||||
|
updateHud('fast_lap_time', formatTime(lastLapTime));
|
||||||
|
Dom.addClassName('fast_lap_time', 'fastest');
|
||||||
|
Dom.addClassName('last_lap_time', 'fastest');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Dom.removeClassName('fast_lap_time', 'fastest');
|
||||||
|
Dom.removeClassName('last_lap_time', 'fastest');
|
||||||
|
}
|
||||||
|
updateHud('last_lap_time', formatTime(lastLapTime));
|
||||||
|
Dom.show('last_lap_time');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
currentLapTime += dt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateHud('speed', 5 * Math.round(speed/500));
|
||||||
|
updateHud('current_lap_time', formatTime(currentLapTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function updateCars(dt, playerSegment, playerW) {
|
||||||
|
var n, car, oldSegment, newSegment;
|
||||||
|
for(n = 0 ; n < cars.length ; n++) {
|
||||||
|
car = cars[n];
|
||||||
|
oldSegment = findSegment(car.z);
|
||||||
|
car.offset = car.offset + updateCarOffset(car, oldSegment, playerSegment, playerW);
|
||||||
|
car.z = Util.increase(car.z, dt * car.speed, trackLength);
|
||||||
|
car.percent = Util.percentRemaining(car.z, segmentLength); // useful for interpolation during rendering phase
|
||||||
|
newSegment = findSegment(car.z);
|
||||||
|
if (oldSegment != newSegment) {
|
||||||
|
index = oldSegment.cars.indexOf(car);
|
||||||
|
oldSegment.cars.splice(index, 1);
|
||||||
|
newSegment.cars.push(car);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCarOffset(car, carSegment, playerSegment, playerW) {
|
||||||
|
|
||||||
|
var i, j, dir, segment, otherCar, otherCarW, lookahead = 20, carW = car.sprite.w * SPRITES.SCALE;
|
||||||
|
|
||||||
|
// optimization, dont bother steering around other cars when 'out of sight' of the player
|
||||||
|
if ((carSegment.index - playerSegment.index) > drawDistance)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for(i = 1 ; i < lookahead ; i++) {
|
||||||
|
segment = segments[(carSegment.index+i)%segments.length];
|
||||||
|
|
||||||
|
if ((segment === playerSegment) && (car.speed > speed) && (Util.overlap(playerX, playerW, car.offset, carW, 1.2))) {
|
||||||
|
if (playerX > 0.5)
|
||||||
|
dir = -1;
|
||||||
|
else if (playerX < -0.5)
|
||||||
|
dir = 1;
|
||||||
|
else
|
||||||
|
dir = (car.offset > playerX) ? 1 : -1;
|
||||||
|
return dir * 1/i * (car.speed-speed)/maxSpeed; // the closer the cars (smaller i) and the greated the speed ratio, the larger the offset
|
||||||
|
}
|
||||||
|
|
||||||
|
for(j = 0 ; j < segment.cars.length ; j++) {
|
||||||
|
otherCar = segment.cars[j];
|
||||||
|
otherCarW = otherCar.sprite.w * SPRITES.SCALE;
|
||||||
|
if ((car.speed > otherCar.speed) && Util.overlap(car.offset, carW, otherCar.offset, otherCarW, 1.2)) {
|
||||||
|
if (otherCar.offset > 0.5)
|
||||||
|
dir = -1;
|
||||||
|
else if (otherCar.offset < -0.5)
|
||||||
|
dir = 1;
|
||||||
|
else
|
||||||
|
dir = (car.offset > otherCar.offset) ? 1 : -1;
|
||||||
|
return dir * 1/i * (car.speed-otherCar.speed)/maxSpeed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no cars ahead, but I have somehow ended up off road, then steer back on
|
||||||
|
if (car.offset < -0.9)
|
||||||
|
return 0.1;
|
||||||
|
else if (car.offset > 0.9)
|
||||||
|
return -0.1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function updateHud(key, value) { // accessing DOM can be slow, so only do it if value has changed
|
||||||
|
if (hud[key].value !== value) {
|
||||||
|
hud[key].value = value;
|
||||||
|
Dom.set(hud[key].dom, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(dt) {
|
||||||
|
var minutes = Math.floor(dt/60);
|
||||||
|
var seconds = Math.floor(dt - (minutes * 60));
|
||||||
|
var tenths = Math.floor(10 * (dt - Math.floor(dt)));
|
||||||
|
if (minutes > 0)
|
||||||
|
return minutes + "." + (seconds < 10 ? "0" : "") + seconds + "." + tenths;
|
||||||
|
else
|
||||||
|
return seconds + "." + tenths;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// RENDER THE GAME WORLD
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
|
||||||
|
var baseSegment = findSegment(position);
|
||||||
|
var basePercent = Util.percentRemaining(position, segmentLength);
|
||||||
|
var playerSegment = findSegment(position+playerZ);
|
||||||
|
var playerPercent = Util.percentRemaining(position+playerZ, segmentLength);
|
||||||
|
var playerY = Util.interpolate(playerSegment.p1.world.y, playerSegment.p2.world.y, playerPercent);
|
||||||
|
var maxy = height;
|
||||||
|
|
||||||
|
var x = 0;
|
||||||
|
var dx = - (baseSegment.curve * basePercent);
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.SKY, skyOffset, resolution * skySpeed * playerY);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.HILLS, hillOffset, resolution * hillSpeed * playerY);
|
||||||
|
Render.background(ctx, background, width, height, BACKGROUND.TREES, treeOffset, resolution * treeSpeed * playerY);
|
||||||
|
|
||||||
|
var n, i, segment, car, sprite, spriteScale, spriteX, spriteY;
|
||||||
|
|
||||||
|
for(n = 0 ; n < drawDistance ; n++) {
|
||||||
|
|
||||||
|
segment = segments[(baseSegment.index + n) % segments.length];
|
||||||
|
segment.looped = segment.index < baseSegment.index;
|
||||||
|
segment.fog = Util.exponentialFog(n/drawDistance, fogDensity);
|
||||||
|
segment.clip = maxy;
|
||||||
|
|
||||||
|
Util.project(segment.p1, (playerX * roadWidth) - x, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
Util.project(segment.p2, (playerX * roadWidth) - x - dx, playerY + cameraHeight, position - (segment.looped ? trackLength : 0), cameraDepth, width, height, roadWidth);
|
||||||
|
|
||||||
|
x = x + dx;
|
||||||
|
dx = dx + segment.curve;
|
||||||
|
|
||||||
|
if ((segment.p1.camera.z <= cameraDepth) || // behind us
|
||||||
|
(segment.p2.screen.y >= segment.p1.screen.y) || // back face cull
|
||||||
|
(segment.p2.screen.y >= maxy)) // clip by (already rendered) hill
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Render.segment(ctx, width, lanes,
|
||||||
|
segment.p1.screen.x,
|
||||||
|
segment.p1.screen.y,
|
||||||
|
segment.p1.screen.w,
|
||||||
|
segment.p2.screen.x,
|
||||||
|
segment.p2.screen.y,
|
||||||
|
segment.p2.screen.w,
|
||||||
|
segment.fog,
|
||||||
|
segment.color);
|
||||||
|
|
||||||
|
maxy = segment.p1.screen.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n = (drawDistance-1) ; n > 0 ; n--) {
|
||||||
|
segment = segments[(baseSegment.index + n) % segments.length];
|
||||||
|
|
||||||
|
for(i = 0 ; i < segment.cars.length ; i++) {
|
||||||
|
car = segment.cars[i];
|
||||||
|
sprite = car.sprite;
|
||||||
|
spriteScale = Util.interpolate(segment.p1.screen.scale, segment.p2.screen.scale, car.percent);
|
||||||
|
spriteX = Util.interpolate(segment.p1.screen.x, segment.p2.screen.x, car.percent) + (spriteScale * car.offset * roadWidth * width/2);
|
||||||
|
spriteY = Util.interpolate(segment.p1.screen.y, segment.p2.screen.y, car.percent);
|
||||||
|
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, car.sprite, spriteScale, spriteX, spriteY, -0.5, -1, segment.clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(i = 0 ; i < segment.sprites.length ; i++) {
|
||||||
|
sprite = segment.sprites[i];
|
||||||
|
spriteScale = segment.p1.screen.scale;
|
||||||
|
spriteX = segment.p1.screen.x + (spriteScale * sprite.offset * roadWidth * width/2);
|
||||||
|
spriteY = segment.p1.screen.y;
|
||||||
|
Render.sprite(ctx, width, height, resolution, roadWidth, sprites, sprite.source, spriteScale, spriteX, spriteY, (sprite.offset < 0 ? -1 : 0), -1, segment.clip);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (segment == playerSegment) {
|
||||||
|
Render.player(ctx, width, height, resolution, roadWidth, sprites, speed/maxSpeed,
|
||||||
|
cameraDepth/playerZ,
|
||||||
|
width/2,
|
||||||
|
(height/2) - (cameraDepth/playerZ * Util.interpolate(playerSegment.p1.camera.y, playerSegment.p2.camera.y, playerPercent) * height/2),
|
||||||
|
speed * (keyLeft ? -1 : keyRight ? 1 : 0),
|
||||||
|
playerSegment.p2.world.y - playerSegment.p1.world.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSegment(z) {
|
||||||
|
return segments[Math.floor(z/segmentLength) % segments.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// BUILD ROAD GEOMETRY
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
function lastY() { return (segments.length == 0) ? 0 : segments[segments.length-1].p2.world.y; }
|
||||||
|
|
||||||
|
function addSegment(curve, y) {
|
||||||
|
var n = segments.length;
|
||||||
|
segments.push({
|
||||||
|
index: n,
|
||||||
|
p1: { world: { y: lastY(), z: n *segmentLength }, camera: {}, screen: {} },
|
||||||
|
p2: { world: { y: y, z: (n+1)*segmentLength }, camera: {}, screen: {} },
|
||||||
|
curve: curve,
|
||||||
|
sprites: [],
|
||||||
|
cars: [],
|
||||||
|
color: Math.floor(n/rumbleLength)%2 ? COLORS.DARK : COLORS.LIGHT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSprite(n, sprite, offset) {
|
||||||
|
segments[n].sprites.push({ source: sprite, offset: offset });
|
||||||
|
}
|
||||||
|
|
||||||
|
function addRoad(enter, hold, leave, curve, y) {
|
||||||
|
var startY = lastY();
|
||||||
|
var endY = startY + (Util.toInt(y, 0) * segmentLength);
|
||||||
|
var n, total = enter + hold + leave;
|
||||||
|
for(n = 0 ; n < enter ; n++)
|
||||||
|
addSegment(Util.easeIn(0, curve, n/enter), Util.easeInOut(startY, endY, n/total));
|
||||||
|
for(n = 0 ; n < hold ; n++)
|
||||||
|
addSegment(curve, Util.easeInOut(startY, endY, (enter+n)/total));
|
||||||
|
for(n = 0 ; n < leave ; n++)
|
||||||
|
addSegment(Util.easeInOut(curve, 0, n/leave), Util.easeInOut(startY, endY, (enter+hold+n)/total));
|
||||||
|
}
|
||||||
|
|
||||||
|
var ROAD = {
|
||||||
|
LENGTH: { NONE: 0, SHORT: 25, MEDIUM: 50, LONG: 100 },
|
||||||
|
HILL: { NONE: 0, LOW: 20, MEDIUM: 40, HIGH: 60 },
|
||||||
|
CURVE: { NONE: 0, EASY: 2, MEDIUM: 4, HARD: 6 }
|
||||||
|
};
|
||||||
|
|
||||||
|
function addStraight(num) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addHill(num, height) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
height = height || ROAD.HILL.MEDIUM;
|
||||||
|
addRoad(num, num, num, 0, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCurve(num, curve, height) {
|
||||||
|
num = num || ROAD.LENGTH.MEDIUM;
|
||||||
|
curve = curve || ROAD.CURVE.MEDIUM;
|
||||||
|
height = height || ROAD.HILL.NONE;
|
||||||
|
addRoad(num, num, num, curve, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addLowRollingHills(num, height) {
|
||||||
|
num = num || ROAD.LENGTH.SHORT;
|
||||||
|
height = height || ROAD.HILL.LOW;
|
||||||
|
addRoad(num, num, num, 0, height/2);
|
||||||
|
addRoad(num, num, num, 0, -height);
|
||||||
|
addRoad(num, num, num, ROAD.CURVE.EASY, height);
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
addRoad(num, num, num, -ROAD.CURVE.EASY, height/2);
|
||||||
|
addRoad(num, num, num, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addSCurves() {
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.NONE);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.CURVE.EASY, -ROAD.HILL.LOW);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.EASY, ROAD.HILL.MEDIUM);
|
||||||
|
addRoad(ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, ROAD.LENGTH.MEDIUM, -ROAD.CURVE.MEDIUM, -ROAD.HILL.MEDIUM);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addBumps() {
|
||||||
|
addRoad(10, 10, 10, 0, 5);
|
||||||
|
addRoad(10, 10, 10, 0, -2);
|
||||||
|
addRoad(10, 10, 10, 0, -5);
|
||||||
|
addRoad(10, 10, 10, 0, 8);
|
||||||
|
addRoad(10, 10, 10, 0, 5);
|
||||||
|
addRoad(10, 10, 10, 0, -7);
|
||||||
|
addRoad(10, 10, 10, 0, 5);
|
||||||
|
addRoad(10, 10, 10, 0, -2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addDownhillToEnd(num) {
|
||||||
|
num = num || 200;
|
||||||
|
addRoad(num, num, num, -ROAD.CURVE.EASY, -lastY()/segmentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetRoad() {
|
||||||
|
segments = [];
|
||||||
|
|
||||||
|
addStraight(ROAD.LENGTH.SHORT);
|
||||||
|
addLowRollingHills();
|
||||||
|
addSCurves();
|
||||||
|
addCurve(ROAD.LENGTH.MEDIUM, ROAD.CURVE.MEDIUM, ROAD.HILL.LOW);
|
||||||
|
addBumps();
|
||||||
|
addLowRollingHills();
|
||||||
|
addCurve(ROAD.LENGTH.LONG*2, ROAD.CURVE.MEDIUM, ROAD.HILL.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addHill(ROAD.LENGTH.MEDIUM, ROAD.HILL.HIGH);
|
||||||
|
addSCurves();
|
||||||
|
addCurve(ROAD.LENGTH.LONG, -ROAD.CURVE.MEDIUM, ROAD.HILL.NONE);
|
||||||
|
addHill(ROAD.LENGTH.LONG, ROAD.HILL.HIGH);
|
||||||
|
addCurve(ROAD.LENGTH.LONG, ROAD.CURVE.MEDIUM, -ROAD.HILL.LOW);
|
||||||
|
addBumps();
|
||||||
|
addHill(ROAD.LENGTH.LONG, -ROAD.HILL.MEDIUM);
|
||||||
|
addStraight();
|
||||||
|
addSCurves();
|
||||||
|
addDownhillToEnd();
|
||||||
|
|
||||||
|
resetSprites();
|
||||||
|
resetCars();
|
||||||
|
|
||||||
|
segments[findSegment(playerZ).index + 2].color = COLORS.START;
|
||||||
|
segments[findSegment(playerZ).index + 3].color = COLORS.START;
|
||||||
|
for(var n = 0 ; n < rumbleLength ; n++)
|
||||||
|
segments[segments.length-1-n].color = COLORS.FINISH;
|
||||||
|
|
||||||
|
trackLength = segments.length * segmentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetSprites() {
|
||||||
|
var n, i;
|
||||||
|
|
||||||
|
addSprite(20, SPRITES.BILLBOARD07, -1);
|
||||||
|
addSprite(40, SPRITES.BILLBOARD06, -1);
|
||||||
|
addSprite(60, SPRITES.BILLBOARD08, -1);
|
||||||
|
addSprite(80, SPRITES.BILLBOARD09, -1);
|
||||||
|
addSprite(100, SPRITES.BILLBOARD01, -1);
|
||||||
|
addSprite(120, SPRITES.BILLBOARD02, -1);
|
||||||
|
addSprite(140, SPRITES.BILLBOARD03, -1);
|
||||||
|
addSprite(160, SPRITES.BILLBOARD04, -1);
|
||||||
|
addSprite(180, SPRITES.BILLBOARD05, -1);
|
||||||
|
|
||||||
|
addSprite(240, SPRITES.BILLBOARD07, -1.2);
|
||||||
|
addSprite(240, SPRITES.BILLBOARD06, 1.2);
|
||||||
|
addSprite(segments.length - 25, SPRITES.BILLBOARD07, -1.2);
|
||||||
|
addSprite(segments.length - 25, SPRITES.BILLBOARD06, 1.2);
|
||||||
|
|
||||||
|
for(n = 10 ; n < 200 ; n += 4 + Math.floor(n/100)) {
|
||||||
|
addSprite(n, SPRITES.PALM_TREE, 0.5 + Math.random()*0.5);
|
||||||
|
addSprite(n, SPRITES.PALM_TREE, 1 + Math.random()*2);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n = 250 ; n < 1000 ; n += 5) {
|
||||||
|
addSprite(n, SPRITES.COLUMN, 1.1);
|
||||||
|
addSprite(n + Util.randomInt(0,5), SPRITES.TREE1, -1 - (Math.random() * 2));
|
||||||
|
addSprite(n + Util.randomInt(0,5), SPRITES.TREE2, -1 - (Math.random() * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(n = 200 ; n < segments.length ; n += 3) {
|
||||||
|
addSprite(n, Util.randomChoice(SPRITES.PLANTS), Util.randomChoice([1,-1]) * (2 + Math.random() * 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
var side, sprite, offset;
|
||||||
|
for(n = 1000 ; n < (segments.length-50) ; n += 100) {
|
||||||
|
side = Util.randomChoice([1, -1]);
|
||||||
|
addSprite(n + Util.randomInt(0, 50), Util.randomChoice(SPRITES.BILLBOARDS), -side);
|
||||||
|
for(i = 0 ; i < 20 ; i++) {
|
||||||
|
sprite = Util.randomChoice(SPRITES.PLANTS);
|
||||||
|
offset = side * (1.5 + Math.random());
|
||||||
|
addSprite(n + Util.randomInt(0, 50), sprite, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetCars() {
|
||||||
|
cars = [];
|
||||||
|
var n, car, segment, offset, z, sprite, speed;
|
||||||
|
for (var n = 0 ; n < totalCars ; n++) {
|
||||||
|
offset = Math.random() * Util.randomChoice([-0.8, 0.8]);
|
||||||
|
z = Math.floor(Math.random() * segments.length) * segmentLength;
|
||||||
|
sprite = Util.randomChoice(SPRITES.CARS);
|
||||||
|
speed = maxSpeed/4 + Math.random() * maxSpeed/(sprite == SPRITES.SEMI ? 4 : 2);
|
||||||
|
car = { offset: offset, z: z, sprite: sprite, speed: speed };
|
||||||
|
segment = findSegment(car.z);
|
||||||
|
segment.cars.push(car);
|
||||||
|
cars.push(car);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// THE GAME LOOP
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Game.run({
|
||||||
|
canvas: canvas, render: render, update: update, stats: stats, step: step,
|
||||||
|
images: ["background", "sprites"],
|
||||||
|
keys: [
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'down', action: function() { keyLeft = true; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'down', action: function() { keyRight = true; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'down', action: function() { keyFaster = true; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'down', action: function() { keySlower = true; } },
|
||||||
|
{ keys: [KEY.LEFT, KEY.A], mode: 'up', action: function() { keyLeft = false; } },
|
||||||
|
{ keys: [KEY.RIGHT, KEY.D], mode: 'up', action: function() { keyRight = false; } },
|
||||||
|
{ keys: [KEY.UP, KEY.W], mode: 'up', action: function() { keyFaster = false; } },
|
||||||
|
{ keys: [KEY.DOWN, KEY.S], mode: 'up', action: function() { keySlower = false; } }
|
||||||
|
],
|
||||||
|
ready: function(images) {
|
||||||
|
background = images[0];
|
||||||
|
sprites = images[1];
|
||||||
|
reset();
|
||||||
|
Dom.storage.fast_lap_time = Dom.storage.fast_lap_time || 180;
|
||||||
|
updateHud('fast_lap_time', formatTime(Util.toFloat(Dom.storage.fast_lap_time)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function reset(options) {
|
||||||
|
options = options || {};
|
||||||
|
canvas.width = width = Util.toInt(options.width, width);
|
||||||
|
canvas.height = height = Util.toInt(options.height, height);
|
||||||
|
lanes = Util.toInt(options.lanes, lanes);
|
||||||
|
roadWidth = Util.toInt(options.roadWidth, roadWidth);
|
||||||
|
cameraHeight = Util.toInt(options.cameraHeight, cameraHeight);
|
||||||
|
drawDistance = Util.toInt(options.drawDistance, drawDistance);
|
||||||
|
fogDensity = Util.toInt(options.fogDensity, fogDensity);
|
||||||
|
fieldOfView = Util.toInt(options.fieldOfView, fieldOfView);
|
||||||
|
segmentLength = Util.toInt(options.segmentLength, segmentLength);
|
||||||
|
rumbleLength = Util.toInt(options.rumbleLength, rumbleLength);
|
||||||
|
cameraDepth = 1 / Math.tan((fieldOfView/2) * Math.PI/180);
|
||||||
|
playerZ = (cameraHeight * cameraDepth);
|
||||||
|
resolution = height/480;
|
||||||
|
refreshTweakUI();
|
||||||
|
|
||||||
|
if ((segments.length==0) || (options.segmentLength) || (options.rumbleLength))
|
||||||
|
resetRoad(); // only rebuild road when necessary
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// TWEAK UI HANDLERS
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
Dom.on('resolution', 'change', function(ev) {
|
||||||
|
var w, h, ratio;
|
||||||
|
switch(ev.target.options[ev.target.selectedIndex].value) {
|
||||||
|
case 'fine': w = 1280; h = 960; ratio=w/width; break;
|
||||||
|
case 'high': w = 1024; h = 768; ratio=w/width; break;
|
||||||
|
case 'medium': w = 640; h = 480; ratio=w/width; break;
|
||||||
|
case 'low': w = 480; h = 360; ratio=w/width; break;
|
||||||
|
}
|
||||||
|
reset({ width: w, height: h })
|
||||||
|
Dom.blur(ev);
|
||||||
|
});
|
||||||
|
|
||||||
|
Dom.on('lanes', 'change', function(ev) { Dom.blur(ev); reset({ lanes: ev.target.options[ev.target.selectedIndex].value }); });
|
||||||
|
Dom.on('roadWidth', 'change', function(ev) { Dom.blur(ev); reset({ roadWidth: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('cameraHeight', 'change', function(ev) { Dom.blur(ev); reset({ cameraHeight: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('drawDistance', 'change', function(ev) { Dom.blur(ev); reset({ drawDistance: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fieldOfView', 'change', function(ev) { Dom.blur(ev); reset({ fieldOfView: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
Dom.on('fogDensity', 'change', function(ev) { Dom.blur(ev); reset({ fogDensity: Util.limit(Util.toInt(ev.target.value), Util.toInt(ev.target.getAttribute('min')), Util.toInt(ev.target.getAttribute('max'))) }); });
|
||||||
|
|
||||||
|
function refreshTweakUI() {
|
||||||
|
Dom.get('lanes').selectedIndex = lanes-1;
|
||||||
|
Dom.get('currentRoadWidth').innerHTML = Dom.get('roadWidth').value = roadWidth;
|
||||||
|
Dom.get('currentCameraHeight').innerHTML = Dom.get('cameraHeight').value = cameraHeight;
|
||||||
|
Dom.get('currentDrawDistance').innerHTML = Dom.get('drawDistance').value = drawDistance;
|
||||||
|
Dom.get('currentFieldOfView').innerHTML = Dom.get('fieldOfView').value = fieldOfView;
|
||||||
|
Dom.get('currentFogDensity').innerHTML = Dom.get('fogDensity').value = fogDensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
game/love_src/asset/video/1_intro.ogv
Normal file
3
game/love_src/data/character/light_novel.lua
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
82
game/love_src/data/map/segment.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"enter": 1,
|
||||||
|
"hold" : 1,
|
||||||
|
"leave": 90,
|
||||||
|
"curve": 9,
|
||||||
|
"height": 80,
|
||||||
|
"width" : 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 25,
|
||||||
|
"hold" : 25,
|
||||||
|
"leave": 25,
|
||||||
|
"curve": 0,
|
||||||
|
"height": 0,
|
||||||
|
"width" : 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 2,
|
||||||
|
"height": 0,
|
||||||
|
"width" : 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 2,
|
||||||
|
"height": 20,
|
||||||
|
"width" : 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 100,
|
||||||
|
"hold" : 100,
|
||||||
|
"leave": 100,
|
||||||
|
"curve": 4,
|
||||||
|
"height": 40,
|
||||||
|
"width" : 4000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 0,
|
||||||
|
"height": 0,
|
||||||
|
"width" : 1000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 6,
|
||||||
|
"height": 60,
|
||||||
|
"width" : 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 0,
|
||||||
|
"height": 60,
|
||||||
|
"width" : 2000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 50,
|
||||||
|
"hold" : 50,
|
||||||
|
"leave": 50,
|
||||||
|
"curve": 0,
|
||||||
|
"height": 20,
|
||||||
|
"width" : 3000
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enter": 100,
|
||||||
|
"hold" : 100,
|
||||||
|
"leave": 100,
|
||||||
|
"curve": 0,
|
||||||
|
"height": -280,
|
||||||
|
"width" : 4000
|
||||||
|
}
|
||||||
|
]
|
||||||
17
game/love_src/data/map/sprites.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
55
game/love_src/src/entities/racing/racer.lua
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
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
|
||||||
41
game/love_src/src/entities/raising/trainee.lua
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
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
|
||||||
37
game/love_src/src/entities/shared/actor.lua
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
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
|
||||||
24
game/love_src/src/modes/mode.lua
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
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
|
||||||
80
game/love_src/src/modes/racing.lua
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
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
|
||||||
31
game/love_src/src/modes/raising_sim.lua
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
|
||||||
|
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
|
||||||
@ -1,61 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,110 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,50 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,62 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,52 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,46 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,255 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
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
|
|
||||||
@ -1,170 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,251 +0,0 @@
|
|||||||
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
|
|
||||||