Compare commits

...

36 Commits

Author SHA1 Message Date
5bf9e1841a Merge remote-tracking branch 'origin/main' into ui 2026-03-23 03:10:21 +09:00
92174baf7b Add percentage_clear on racers 2026-03-23 03:00:30 +09:00
8957b4427c add multiple racers 2026-03-23 02:23:59 +09:00
c84cf41222 add finish 2026-03-23 02:02:44 +09:00
a9f4d30803 Add checkpoint check 2026-03-23 01:49:34 +09:00
e916cf63fa add time attacks 2026-03-23 01:31:16 +09:00
6a300c5b8f add initial boost 2026-03-23 00:48:21 +09:00
5c2aa31153 add countdown and track1 data 2026-03-23 00:19:20 +09:00
3e9f16dfbd Add countdown 2026-03-22 23:42:13 +09:00
7398450404 Add title label 2026-03-22 21:32:56 +09:00
7f0b9101b3 Cleanup unnecessary files 2026-03-22 20:58:49 +09:00
0d8c45e621 add sound effects 2026-03-22 19:57:25 +09:00
5dbf8ce1d0 Add UI and navigation 2026-03-22 19:21:32 +09:00
a7fd31a83f add button 2026-03-22 17:53:58 +09:00
eef53745f3 delete unnecessary print 2026-03-22 15:00:47 +09:00
9373054ebe add auto control 2026-03-22 14:59:16 +09:00
91faa6f8de update number and weight 2026-03-22 14:49:46 +09:00
eae97741d3 add dampening, fix weight 2026-03-22 14:44:17 +09:00
03c0f489b8 kinda work movement 2026-03-22 14:13:12 +09:00
ca11966ff4 add velocity_flow 2026-03-22 13:33:58 +09:00
6bba132f3e reafactor race world 2026-03-22 13:05:40 +09:00
0a40f6554e add fix eye -Nan, add font color 2026-03-22 09:39:11 +09:00
7899b1444c add min_speed 2026-03-22 09:24:00 +09:00
11bfd4404e add ui draw, add debug ui 2026-03-22 01:49:56 +09:00
6f1b083076 fix movement 2026-03-22 00:56:19 +09:00
08521535fe Update racer system 2026-03-22 00:13:43 +09:00
8f67a4de8b Merge remote-tracking branch 'origin/integration' into integration 2026-03-21 23:43:08 +09:00
f85339131a temporary setup 2026-03-21 23:40:45 +09:00
e032650030 add force sum 2026-03-21 23:38:34 +09:00
3cd0a1f8ae lua_api: accept velocity as a vector 2026-03-21 09:36:00 -05:00
29c701ce6a fix infinite drift 2026-03-21 12:01:06 +09:00
391010b4c4 fix magic number 2026-03-21 11:36:46 +09:00
aa87bdf016 Merge remote-tracking branch 'origin/sphere-hack' into integration 2026-03-21 11:26:54 +09:00
9cee838411 Merge remote-tracking branch 'origin/integration' into integration 2026-03-21 11:23:16 +09:00
1c2fad5a8a hacked together sphere positioning from lua 2026-03-20 21:19:57 -05:00
dfb114b5fc update love-demo2 code to latest version 2026-03-20 20:22:45 -05:00
140 changed files with 1672 additions and 7242 deletions

View File

@ -26,7 +26,7 @@ function love.conf(t)
t.window.width = 1024
t.window.height = 1024
t.window.borderless = false
t.window.resizable = true
t.window.resizable = false
t.window.minwidth = 10
t.window.minheight = 9
t.window.fullscreen = false

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 639 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 643 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 739 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 653 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 609 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 557 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +0,0 @@
var BACKGROUND = {
HILLS: { x: 5, y: 5, w: 1280, h: 480 },
SKY: { x: 5, y: 495, w: 1280, h: 480 },
TREES: { x: 5, y: 985, w: 1280, h: 480 }
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -1,36 +0,0 @@
var SPRITES = {
PALM_TREE: { x: 5, y: 5, w: 215, h: 540 },
BILLBOARD08: { x: 230, y: 5, w: 385, h: 265 },
TREE1: { x: 625, y: 5, w: 360, h: 360 },
DEAD_TREE1: { x: 5, y: 555, w: 135, h: 332 },
BILLBOARD09: { x: 150, y: 555, w: 328, h: 282 },
BOULDER3: { x: 230, y: 280, w: 320, h: 220 },
COLUMN: { x: 995, y: 5, w: 200, h: 315 },
BILLBOARD01: { x: 625, y: 375, w: 300, h: 170 },
BILLBOARD06: { x: 488, y: 555, w: 298, h: 190 },
BILLBOARD05: { x: 5, y: 897, w: 298, h: 190 },
BILLBOARD07: { x: 313, y: 897, w: 298, h: 190 },
BOULDER2: { x: 621, y: 897, w: 298, h: 140 },
TREE2: { x: 1205, y: 5, w: 282, h: 295 },
BILLBOARD04: { x: 1205, y: 310, w: 268, h: 170 },
DEAD_TREE2: { x: 1205, y: 490, w: 150, h: 260 },
BOULDER1: { x: 1205, y: 760, w: 168, h: 248 },
BUSH1: { x: 5, y: 1097, w: 240, h: 155 },
CACTUS: { x: 929, y: 897, w: 235, h: 118 },
BUSH2: { x: 255, y: 1097, w: 232, h: 152 },
BILLBOARD03: { x: 5, y: 1262, w: 230, h: 220 },
BILLBOARD02: { x: 245, y: 1262, w: 215, h: 220 },
STUMP: { x: 995, y: 330, w: 195, h: 140 },
SEMI: { x: 1365, y: 490, w: 122, h: 144 },
TRUCK: { x: 1365, y: 644, w: 100, h: 78 },
CAR03: { x: 1383, y: 760, w: 88, h: 55 },
CAR02: { x: 1383, y: 825, w: 80, h: 59 },
CAR04: { x: 1383, y: 894, w: 80, h: 57 },
CAR01: { x: 1205, y: 1018, w: 80, h: 56 },
PLAYER_UPHILL_LEFT: { x: 1383, y: 961, w: 80, h: 45 },
PLAYER_UPHILL_STRAIGHT: { x: 1295, y: 1018, w: 80, h: 45 },
PLAYER_UPHILL_RIGHT: { x: 1385, y: 1018, w: 80, h: 45 },
PLAYER_LEFT: { x: 995, y: 480, w: 80, h: 41 },
PLAYER_STRAIGHT: { x: 1085, y: 480, w: 80, h: 41 },
PLAYER_RIGHT: { x: 995, y: 531, w: 80, h: 41 }
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 647 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 752 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 742 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 973 B

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,61 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local sfx = require"love_src.wrapper.lappy.new.source".obj
local anim_math = require("love_src.src.system.animation")
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "animation")
end
local function default_trigger()
end
function world:load(e, data)
self.data = {}
self.data.run_time = data.run_time or 0
self.data.anim_direction = data.anim_direction or 1
self.data.fps = data.fps or 12
self.data.frames = data.frames or {}
self.data.current_frame = data.current_frame or 1
self.data.triggers = data.triggers or {}
self.data.next_trigger = data.next_trigger or 1
self.data.triggers_fn = data.triggers_fn or {}
end
function world:update(dt)
self.data.run_time = anim_math.update_time(
self.data.run_time, dt, self.data.anim_direction
)
local new_index = anim_math.update_fps_time(
self.data.run_time, self.data.fps
)
self.data.current_frame = anim_math.get_loop(
new_index,
#self.data.frames
)
if (anim_math.is_trigger(
self.data.triggers,
self.data.next_trigger,
self.data.current_frame
)) then
self.data.triggers_fn[self.data.current_frame]()
self.data.next_trigger = anim_math.get_loop(
self.data.next_trigger + 1, #self.data.triggers
)
end
end
return world

View File

@ -0,0 +1,110 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local sfx = require"love_src.wrapper.lappy.new.source".obj
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "button")
end
function default_func()
end
local default = {
pos = vm.vec2(0, 0),
wh = vm.vec2(0, 0),
scale = 1,
origin = vm.vec2(0, 0),
label_origin = vm.vec2(0, 0),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 0, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = default_func,
label = "skip",
}
function world:load(entity, data)
self.data = data or default
self.data.temp_color = self.data.bg_color
self.mouse = "not click"
self.sfx_highlight = sfx:load_to("love_src/asset/audio/sfx/book_flip.4.ogg", "love_src/asset/audio/sfx/book_flip.4.ogg", "static")
self.sfx_click = sfx:load_to("love_src/asset/audio/sfx/book_flip.8.ogg", "love_src/asset/audio/sfx/book_flip.8.ogg", "static")
self.sfx_non_highlight = sfx:load_to("love_src/asset/audio/sfx/book_flip.1.ogg", "love_src/asset/audio/sfx/book_flip.1.ogg", "static")
end
function world:highlight()
self.data.temp_color = self.data.highlight_color
if (not self.inside) then
self.sfx_highlight:play()
self.inside = true
end
end
function world:non_highlight()
if (self.inside) then
self.inside = false
self.data.temp_color = self.data.bg_color
self.sfx_non_highlight:play()
end
end
function world:click()
self.data.func()
self.sfx_click:play()
end
function world:update(dt)
local xm, ym = love.mouse.getPosition()
local x, y, w, h, ox, oy, lox, loy, s = self.data.pos[1], self.data.pos[2],
self.data.wh[1], self.data.wh[2],
self.data.origin[1], self.data.origin[2],
self.data.label_origin[1], self.data.label_origin[2],
self.data.scale
x, y, w, h, ox, oy, lox, loy = uib.transform(x, y, w, h, ox, oy, lox, loy, s)
if (uib.is_inside(x, y, w, h, xm, ym)) then
self:highlight()
if (love.mouse.isDown(1)) then
if (self.mouse == "not click") then
self.mouse = "click"
end
else
if (self.mouse == "click") then
self.mouse = "not click"
self:click()
end
end
else
self:non_highlight()
end
end
local function draw_button(text, x, y, w, h, ox, oy, lox, loy, s, ct, cl, cb)
x, y, w, h, ox, oy, lox, loy = uib.transform(x, y, w, h, ox, oy, lox, loy, s)
test.draw_line_quad_start()
uir.draw_box(x, y, w, h, cb)
uir.draw_outline(x, y, w, h, cl)
x = x + lox
y = y + loy
uir.draw_text(text, x, y, ct)
end
function world:ui_draw()
draw_button(self.data.label,
self.data.pos[1], self.data.pos[2], self.data.wh[1], self.data.wh[2],
self.data.origin[1], self.data.origin[2],
self.data.label_origin[1], self.data.label_origin[2],
self.data.scale,
self.data.label_color, self.data.line_color, self.data.temp_color
)
end
return world

View File

@ -0,0 +1,27 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "button")
end
function world:load(entity, data)
self.data = data
end
function world:ui_draw()
uir.draw_text(self.data.label,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
end
return world

View File

@ -0,0 +1,50 @@
local vm = require"lib.vornmath"
local wm = require"world_map"
local lw, lh = love.graphics.getDimensions()
local function to_play()
load_world(wm["town"])
end
local wh = vm.vec2(120, 60)
local l_wh = vm.vec2(50, 25)
return {
{
type = "button",
name = "button_to_play",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2, lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_play,
label = "Play",
}
},
},
{
type = "label",
name = "title",
components = {
button = require"love_src.src.new.world.main_menu.components.label"
},
data = {
button = {
pos = vm.vec2(lw/2 - 100, 100),
label_color = {255/255, 255/255, 255/255},
label = "Bibliotheca",
}
},
}
}

View File

@ -0,0 +1,36 @@
local vm = require"lib.vornmath"
local wm = require"world_map"
local lw, lh = love.graphics.getDimensions()
local function to_track_choose()
load_world(wm["track_choose"])
end
local wh = vm.vec2(120, 60)
local l_wh = vm.vec2(50, 25)
return {
{
type = "button",
name = "button_to_race",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2, lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_track_choose,
label = "Next Race",
}
},
}
}

View File

@ -0,0 +1,62 @@
local vm = require"lib.vornmath"
local wm = require"world_map"
local lw, lh = love.graphics.getDimensions()
local function to_track_choose()
load_world(wm["track_choose"])
end
local function to_monastery()
load_world(wm["monastery"])
end
local wh = vm.vec2(120, 60)
local l_wh = vm.vec2(50, 25)
return {
{
type = "button",
name = "button_to_track_choose",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2 + wh[1], lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_track_choose,
label = "Next Race",
}
},
},
{
type = "button",
name = "button_to_monastery",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2 - wh[1], lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_monastery,
label = "Monastery",
}
},
}
}

View File

@ -0,0 +1,62 @@
local vm = require"lib.vornmath"
local wm = require"world_map"
local lw, lh = love.graphics.getDimensions()
local function to_track_1()
load_world(wm["track_1"])
end
local function to_track_2()
load_world(wm["track_2"])
end
local wh = vm.vec2(120, 60)
local l_wh = vm.vec2(50, 25)
return {
{
type = "button",
name = "button_to_track_1",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2 + wh[1], lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_track_1,
label = "Track 1",
}
},
},
{
type = "button",
name = "button_to_track_2",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2 - wh[1], lh/2),
wh = wh,
scale = 1,
origin = vm.vec2(-wh[1]/2, -wh[2]/2),
label_origin = vm.vec2(l_wh[1]/2, l_wh[2]/2),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_track_2,
label = "Track 2",
}
},
}
}

View File

@ -0,0 +1,101 @@
local wm = require"world_map"
local vm = require"lib.vornmath"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "main_menu")
self.data = {}
self.entities_data = {}
self.entities = {}
end
local default = {
}
local function to_race()
load_world(wm["race"])
end
local lw, lh = love.graphics.getDimensions()
local default_entities = {
{
type = "button",
name = "button_to_race",
components = {
button = require"love_src.src.new.world.main_menu.components.button"
},
data = {
button = {
pos = vm.vec2(lw/2, lh/2),
wh = vm.vec2(120, 60),
scale = 1,
origin = vm.vec2(-60, -30),
label_origin = vm.vec2(25, 12.5),
label_color = {255/255, 255/255, 255/255},
bg_color = {0, 1, 0},
line_color = {1, 0, 0},
highlight_color = {0, 0, 1},
func = to_race,
label = "play",
}
},
}
}
function world:setup()
for _, d in pairs(self.entities_data) do
local e = {
type = d.type,
name = d.name,
components = {}
}
for t, c in pairs(d.components) do
e.components[t] = c()
e.components[t]:load(e, d.data[t])
end
table.insert(self.entities, e)
end
end
function world:load(data, entities_data)
self.data = data
self.entities_data = entities_data
self:setup()
end
function world:update(dt)
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:update(dt)
end
end
end
function world:draw()
test.draw()
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:draw()
end
end
end
function world:ui_draw()
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:ui_draw()
end
end
end
return world

View File

@ -0,0 +1,52 @@
local main_wrapper = require "love_src.wrapper.lappy.world"
local vm = require "lib.vornmath"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "actor")
end
local default = {
forward = 0.0,
steer = 0.0
}
function world:load(entity, data)
self.e = entity
self.data = data or default
end
local function handle_input(v2, accel, brake, steer)
local desire_forward = 0
local desire_steer = 0
if (v2[1] > 0) then
desire_forward = accel
elseif (v2[1] < 0) then
desire_forward = - brake
end
if (v2[2] < 0) then
desire_steer = steer
elseif (v2[2] > 0) then
desire_steer = - steer
end
return desire_forward, desire_steer
end
local function plan()
return vm.vec2(1.0, 1.0)
end
function world:update(dt)
local accel, brake, steer = self.e.components.racer.data.accel, self.e.components.racer.data.brake, self.e.components.racer.data.steer
local plan_v = plan()
local desire_forward, desire_steer = handle_input(plan_v, accel, brake, steer)
self.data.forward = desire_forward
self.data.steer = desire_steer
end
return world

View File

@ -0,0 +1,46 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "checkpoints")
end
function world:load(entity, data)
self.data = data
self.e = entity
end
function world:update(dt)
-- self.data.label = self.e.components.animation.data.frames[self.e.components.animation.data.current_frame]
end
function world:ui_draw()
-- if (not self.disappear) then
-- uir.draw_text(self.data.label,
-- self.data.pos[1], self.data.pos[2],
-- self.data.label_color
-- )
-- elseif (self.disappear and self.disappear > 0) then
-- uir.draw_text(self.data.label,
-- self.data.pos[1], self.data.pos[2],
-- self.data.label_color
-- )
-- else
-- self.data.label = ""
-- uir.draw_text(self.data.label,
-- self.data.pos[1], self.data.pos[2],
-- self.data.label_color
-- )
-- end
end
return world

View File

@ -0,0 +1,59 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "countdown")
end
function world:load(entity, data)
self.data = data
self.e = entity
end
function world:load_world_state(world_state)
self.e.components.animation.data.triggers_fn[4] = function ()
if (not self.disappear) then
world_state.data.state = "start"
self.e.components.animation.data.anim_direction = 0
self.disappear = 2
end
end
end
function world:update(dt)
if (self.disappear) then
self.disappear = self.disappear - dt
end
self.data.label = self.e.components.animation.data.frames[self.e.components.animation.data.current_frame]
end
function world:ui_draw()
if (not self.disappear) then
uir.draw_text(self.data.label,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
elseif (self.disappear and self.disappear > 0) then
uir.draw_text(self.data.label,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
else
self.data.label = ""
uir.draw_text(self.data.label,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
end
end
return world

View File

@ -0,0 +1,37 @@
local vm = require"lib.vornmath"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "map")
end
local default = {
drag_movement = 0.0,
friction = 1.0,
drag = vm.vec2(1.0, 0.0)
}
function world:load(entity, data)
self.e = entity
self.data = data or default
end
local function debug_data(x, y, r, g, b, to_debug)
local font_ix = test.draw_font_start()
test.draw_font_set_base_color(r, g, b)
for k, v in pairs(to_debug) do
y = y + test.draw_font(font_ix, string.format("%s : %s", k, v), x, y)
end
end
function world:ui_draw()
-- debug_data(650, 800, 1.0, 0.0, 1.0, self.data)
end
return world

View File

@ -0,0 +1,27 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "phase")
end
function world:load(entity, data)
self.data = data
end
function world:ui_draw()
uir.draw_text(self.data.label,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
end
return world

View File

@ -0,0 +1,51 @@
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "player")
end
local function handle_input(accel, brake, steer)
local up = love.keyboard.isDown("up")
local down = love.keyboard.isDown("down")
local left = love.keyboard.isDown("left")
local right = love.keyboard.isDown("right")
local desire_forward = 0
local desire_steer = 0
if (up) then
desire_forward = accel
elseif (down) then
desire_forward = - brake
end
if (left) then
desire_steer = steer
elseif (right) then
desire_steer = - steer
end
return desire_forward, desire_steer
end
local default = {
forward = 0.0,
steer = 0.0
}
function world:load(entity, data)
self.e = entity
self.data = data or default
end
function world:update(dt)
local accel, brake, steer = self.e.components.racer.data.accel, self.e.components.racer.data.brake, self.e.components.racer.data.steer
local desire_forward, desire_steer = handle_input(accel, brake, steer)
self.data.forward = desire_forward
self.data.steer = desire_steer
end
return world

View File

@ -0,0 +1,255 @@
local vm = require"lib.vornmath"
local racing_force = require("love_src.src.system.racing_force")
local uib = require"love_src.src.system.ui_behavior"
local get_loop = require"love_src.src.system.animation".get_loop
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "racer")
end
local default = {
pos = vm.vec2(44.64, 10.87),
orientation = vm.vec2(0.0, 0.0),
orientation_speed = vm.vec2(0.0, 0.0),
min_orientation_speed = -1.0,
max_orientation_speed = 1.0,
velocity = vm.vec2(0.0, 0.0),
max_speed = 0.1,
min_speed = -0.1,
inertia = vm.vec2(0.0, 0.0),
accel = 0.1,
brake = 0.1,
grip = 0.1,
steer = 0.1,
mass = 0.1,
streamline = 0.1
}
local magic_w = {
tire = 0.01,
floor = 0.1,
forward = 0.01,
steer = 0.01,
drag_boost = 0.01,
drag_halt = 0.1,
inertia = 0.0,
centrifugal = 0.1,
dampening = 0.1
}
function world:load(entity, data)
self.e = entity
self.data = data or default
self.map = {}
self.countdown = {}
self.world_state = {}
self.late_timer = 0
self.time_attacks = {}
self.lap = 1
self.current_cp = 1
self.prev_cp = 0
self.checkpoints = {}
self.is_finish = false
self.show_pov = false
end
function get(s)
return
s.data.max_speed,
s.data.min_speed,
s.data.velocity,
s.data.accel,
s.data.brake,
s.data.grip,
s.data.steer,
s.data.inertia,
s.data.mass,
s.data.streamline
end
local function rotate_vector(x0, y0, theta)
local x1 = x0 * math.cos(theta) - y0 * math.sin(theta)
local y1 = x0 * math.sin(theta) + y0 * math.cos(theta)
return vm.vec2(x1, y1)
end
local function forward_velocity(dt, force, mass, velocity, max_speed, min_speed, dampening)
if (mass == 0) then
mass = 1
end
local a = force[1] * dt / mass
local new_velocity = velocity[1] + a
if (a == 0 and new_velocity > 0.1) then
new_velocity = velocity[1] - dampening * dt / mass
elseif (a == 0 and new_velocity < -0.1) then
new_velocity = velocity[1] + dampening * dt / mass
elseif (a == 0) then
new_velocity = 0
end
new_velocity = vm.clamp(new_velocity, min_speed, max_speed)
return new_velocity
end
local function orientation_velocity(dt, force, mass, orientation_speed, orientation, max_orientation, min_orientation)
if (mass == 0) then
mass = 1
end
local a = force[2] / mass
local new_speed = orientation_speed + a
new_speed = vm.clamp(new_speed, min_orientation, max_orientation)
local new_orientation = rotate_vector(orientation[1], orientation[2], new_speed)
return new_orientation, new_speed
end
function world:update_checkpoint(dt)
local cp = self.checkpoints.data.check[self.current_cp]
local is_inside_cp = uib.is_inside(cp.pos[1], cp.pos[2], cp.wh[1], cp.wh[2], self.data.pos[1], self.data.pos[2])
if (is_inside_cp) then
self.prev_cp = self.current_cp
self.current_cp = get_loop(self.current_cp + 1, #self.checkpoints.data.check)
if (self.current_cp < self.prev_cp) then
self.lap = self.lap + 1
end
if (self.lap > self.checkpoints.data.max_lap) then
self.is_finish = true
end
end
end
function world:update_race(dt)
local x, y = self.data.pos[1], self.data.pos[2]
local or_x, or_y = self.data.orientation[1], self.data.orientation[2]
local max_speed, min_speed, velocity, accel, brake, grip, steer, inertia, mass, streamline = get(self)
local friction, drag, drag_movement = self.map.data.friction, self.map.data.drag, self.map.data.drag_movement
local desire_forward, desire_steer = self.e.components.actor.data.forward, self.e.components.actor.data.steer
if (self.world_state.data.state == "start" and self.countdown.data.label == "go") then
self.late_timer = self.late_timer + dt
self.initial_boost = false
end
local force = racing_force(
desire_forward, desire_steer,
grip, friction,
drag, drag_movement, streamline,
inertia,
mass,
magic_w.forward, magic_w.steer,
magic_w.tire, magic_w.floor,
magic_w.drag_boost, magic_w.drag_halt,
magic_w.inertia, magic_w.centrifugal
)
local f_v = forward_velocity(dt, force, mass, velocity, max_speed, min_speed, magic_w.dampening)
local o, o_v = orientation_velocity(dt, force, mass, self.data.orientation_speed, self.data.orientation, self.data.max_orientation_speed, self.data.min_orientation_speed)
if (self.world_state.data.state == "count down") then
elseif (not self.is_finish) then
if (self.late_timer < 1 and not self.initial_boost and f_v > 0) then
f_v = f_v + 0.1
self.initial_boost = true
end
self.data.velocity[1] = f_v
-- self.data.orientation_speed = o_v
self.data.orientation = o
-- if (self.data.velocity[1] ~= 0) then
self.data.pos = self.data.pos + o * f_v
-- end
self.data.inertia = force
end
end
function world:update_time_attack(dt)
if (self.time_attacks[self.lap] == nil and not self.is_finish) then
self.time_attacks[self.lap] = 0
end
if (self.world_state.data.state == "count down") then
elseif (not self.is_finish) then
self.time_attacks[self.lap] = self.time_attacks[self.lap] + dt
end
end
function world:update(dt)
self:update_race(dt)
self:update_time_attack(dt)
self:update_checkpoint(dt)
end
function world:draw()
local x, y = self.data.pos[1], self.data.pos[2]
local or_x, or_y = self.data.orientation[1], self.data.orientation[2]
local vx, vy = self.data.orientation[1] * self.data.velocity[1], self.data.orientation[2] * self.data.velocity[1]
if (self.show_pov) then
test.set_sphere(x, y, vx, vy)
end
end
local function draw_time_attack(x, y, r, g, b, to_debug)
local font_ix = test.draw_font_start()
test.draw_font_set_base_color(r, g, b)
for k, v in pairs(to_debug) do
y = y + test.draw_font(font_ix, string.format("%s : %s seconds", k, v), x, y)
end
end
local function draw_finish(x, y, r, g, b)
local font_ix = test.draw_font_start()
test.draw_font_set_base_color(r, g, b)
y = y + test.draw_font(font_ix, "FINISH", x, y)
end
local function debug_data(x, y, r, g, b, to_debug)
local font_ix = test.draw_font_start()
test.draw_font_set_base_color(r, g, b)
for k, v in pairs(to_debug) do
y = y + test.draw_font(font_ix, string.format("%s : %s", k, v), x, y)
end
end
local function debug_vector(vx, vy)
local lw, lh = love.graphics.getDimensions()
test.draw_line_quad_start()
test.draw_set_color(1.0, 0.0, 0.0) -- r, g, b (0.0 to 1.0)
test.draw_line(lw/2, lh/2, lw/vx, lh/vy) -- x1, y1, x2, y2
end
function world:ui_draw()
local lw, lh = love.graphics.getDimensions()
if (self.show_pov) then
draw_time_attack(lw/2, 20, 1.0, 0.0, 1.0, self.time_attacks)
if (self.is_finish) then
draw_finish(lw/2, lh/2, 1.0, 1.0, 0.0)
end
end
-- debug_data(650, 0, 1.0, 0.0, 1.0, self.data)
-- debug_data(650, 400, 1.0, 0.0, 1.0, magic_w)
local vx, vy = self.data.orientation[1], self.data.orientation[2]
-- debug_vector(vx, vy)
end
return world

View File

@ -0,0 +1,32 @@
local vm = require"lib.vornmath"
local uib = require"love_src.src.system.ui_behavior"
local uir = require"love_src.src.system.ui_render"
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
function world:new()
world.super.new(self, BASE, "phase")
end
function world:load(entity, data)
self.data = data
self.e = entity
end
function world:next_state(state)
self.data.state = state
end
function world:ui_draw()
uir.draw_text(self.data.state,
self.data.pos[1], self.data.pos[2],
self.data.label_color
)
end
return world

View File

@ -0,0 +1,170 @@
local vm = require"lib.vornmath"
local lw, lh = love.graphics.getDimensions()
return {
{
components = {
racer = require("love_src.src.new.world.race.components.racer"),
actor = require("love_src.src.new.world.race.components.player"),
},
type = "racer",
name = "racer 1",
data = {
racer = {
pos = vm.vec2(44.64, 10.87),
orientation = vm.vec2(1.0, 0.0),
orientation_speed = 0.0,
min_orientation_speed = -1.0,
max_orientation_speed = 1.0,
velocity = vm.vec2(0.0, 0.0),
max_speed = 0.3,
min_speed = -0.3,
inertia = vm.vec2(0.0, 0.0),
accel = 0.1,
brake = 0.1,
grip = 0.1,
steer = 0.01,
mass = 0.1,
streamline = 0.1
},
actor = {
forward = 0.0,
steer = 0.0,
}
}
},
{
components = {
racer = require("love_src.src.new.world.race.components.racer"),
actor = require("love_src.src.new.world.race.components.actor"),
},
type = "racer",
name = "racer 2",
data = {
racer = {
pos = vm.vec2(44.64, 10.87),
orientation = vm.vec2(1.0, 0.0),
orientation_speed = 0.0,
min_orientation_speed = -1.0,
max_orientation_speed = 1.0,
velocity = vm.vec2(0.0, 0.0),
max_speed = 0.3,
min_speed = -0.3,
inertia = vm.vec2(0.0, 0.0),
accel = 0.1,
brake = 0.1,
grip = 0.1,
steer = 0.01,
mass = 0.1,
streamline = 0.1
},
actor = {
forward = 0.0,
steer = 0.0,
}
}
},
{
components = {
map = require"love_src.src.new.world.race.components.map"
},
type = "map",
name = "map 1",
data = {
map = {
drag_movement = 0.1,
friction = 0.1,
drag = vm.vec2(0.1, 0.0)
}
}
},
{
type = "world_state",
name = "race_state",
components = {
world_state = require("love_src.src.new.world.race.components.world_state"),
},
data = {
world_state = {
state = "count down",
pos = vm.vec2(lw/2, lh - 50),
label_color = {1, 0, 1}
}
}
},
{
type = "countdown",
name = "321go",
components = {
countdown = require("love_src.src.new.world.race.components.countdown"),
animation = require("love_src.src.new.shared.components.animation"),
},
data = {
countdown = {
label = "",
pos = vm.vec2(lw/2, lh/2),
label_color = {1, 0, 1}
},
animation = {
frames = {
"3",
"2",
"1",
"go",
""
},
fps = 1,
triggers = {4},
triggers_fn = {
[4] = function ()
print("GO")
end
}
}
}
},
{
type = "checkpoints",
name = "checkpoints",
components = {
checkpoints = require"love_src.src.new.world.race.components.checkpoints"
},
data = {
checkpoints = {
label = "",
pos = vm.vec2(lw - 50, 0),
label_color = {1, 1, 0},
check = {
{
pos = vm.vec2(80, 8),
wh = vm.vec2(10, 10)
},
{
pos = vm.vec2(115, 23),
wh = vm.vec2(10, 10)
},
{
pos = vm.vec2(12, 55),
wh = vm.vec2(10, 10)
},
{
pos = vm.vec2(-23, 28),
wh = vm.vec2(10, 10)
},
{
pos = vm.vec2(40, 7), -- finish line
wh = vm.vec2(10, 10)
}
},
max_lap = 3
}
}
}
}

View File

@ -0,0 +1,251 @@
local sfx = require"love_src.wrapper.lappy.new.source".obj
local vm = require"lib.vornmath"
local get_loop = require"love_src.src.system.animation".get_loop
local main_wrapper = require "love_src.wrapper.lappy.world"
---@class wrappers.Concord.world : lappy.world
local world = main_wrapper:extend()
local reap = require("lib.reap")
local BASE = reap.base_path(...)
local default = {
racers = {
base = require "love_src.src.new.world.race.components.racer"
}
}
local lw, lh = love.graphics.getDimensions()
local entities_default = {
{
components = {
racer = require("love_src.src.new.world.race.components.racer"),
actor = require("love_src.src.new.world.race.components.actor"),
},
type = "racer",
name = "racer 1",
data = {
racer = {
pos = vm.vec2(44.64, 10.87),
orientation = vm.vec2(1.0, 0.0),
orientation_speed = 0.0,
min_orientation_speed = -1.0,
max_orientation_speed = 1.0,
velocity = vm.vec2(0.0, 0.0),
max_speed = 0.3,
min_speed = -0.3,
inertia = vm.vec2(0.0, 0.0),
accel = 0.1,
brake = 0.1,
grip = 0.1,
steer = 0.01,
mass = 0.1,
streamline = 0.1
},
actor = {
forward = 0.0,
steer = 0.0,
}
}
},
{
components = {
map = require"love_src.src.new.world.race.components.map"
},
type = "map",
name = "map 1",
data = {
map = {
drag_movement = 0.1,
friction = 0.1,
drag = vm.vec2(0.1, 0.0)
}
}
},
{
type = "countdown",
name = "321go",
components = {
countdown = require("love_src.src.new.world.race.components.countdown"),
animation = require("love_src.src.new.shared.components.animation"),
},
data = {
countdown = {
label = "",
pos = vm.vec2(lw/2, lh/2),
label_color = {1, 0, 1}
},
animation = {
frames = {
"3",
"2",
"1",
"go",
""
},
fps = 1,
triggers = {4},
triggers_fn = {
[4] = function ()
print("GO")
end
}
}
}
}
}
function world:new()
world.super.new(self, BASE, "race")
self.is_loaded = false
self.entities = {}
end
function world:setup()
local map
local countdown
self.checkpoints = {}
self.world_state = {}
self.racers = {}
self.pov_index = 1
for _, d in pairs(self.entities_data) do
local e = {
type = d.type,
name = d.name,
components = {}
}
for t, c in pairs(d.components) do
e.components[t] = c()
e.components[t]:load(e, d.data[t])
end
table.insert(self.entities, e)
if (e.type == "map") then
map = e
elseif e.type == "world_state" then
self.world_state = e
elseif e.type == "countdown" then
countdown = e
elseif e.type == "checkpoints" then
self.checkpoints = e
elseif e.type == "racer" then
table.insert(self.racers, e)
if (e.components.actor.name == "player") then
e.components.racer.show_pov = true
end
end
end
for _, e in ipairs(self.entities) do
if (e.type == "racer") then
e.components.racer.map = map.components.map
e.components.racer.countdown = countdown.components.countdown
e.components.racer.world_state = self.world_state.components.world_state
e.components.racer.checkpoints = self.checkpoints.components.checkpoints
elseif (e.type == "countdown") then
e.components.countdown:load_world_state(self.world_state.components.world_state)
end
end
end
function world:load(data, entities_data)
self.data = data or default
self.is_loaded = true
self.entities_data = entities_data or entities_default
self:setup()
end
local phases = {
intro = {
is_played = false,
source = sfx:load_to("love_src/asset/audio/sfx/race/va/test1.mp3", "love_src/asset/audio/sfx/race/va/test1.mp3", "static"),
},
mid = {
is_played = false,
source = sfx:load_to("love_src/asset/audio/sfx/race/va/Test2.mp3", "love_src/asset/audio/sfx/race/va/Test2.mp3", "static"),
},
ed = {
is_played = false,
source = sfx:load_to("love_src/asset/audio/sfx/race/va/Test3.mp3", "love_src/asset/audio/sfx/race/va/Test3.mp3", "static")
}
}
local phase = "intro"
local x_is_down = false
local function percentage_clear(lap, max_lap, cp, max_cp)
return lap + cp * (1/max_cp)
end
function world:update_race_rank(dt)
local rank = {}
for k, v in pairs(self.racers) do
local cp = v.components.racer.current_cp
local lap = v.components.racer.lap
local ts = v.components.racer.time_attacks
local finish = v.components.racer.is_finish
local max_lap = self.checkpoints.components.checkpoints.data.max_lap
local max_cp = #self.checkpoints.components.checkpoints.data.check
local percentage = percentage_clear(lap, max_lap, cp, max_cp)
v.components.racer.percentage = percentage
if (finish) then
local t = 0
for _, lt in pairs(ts) do
t = t + lt
end
local finish_time = t
end
end
end
function world:update(dt)
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:update(dt)
end
end
if (phases[phase]) then
if (not phases[phase].source:isPlaying() and not phases[phase].is_played) then
phases[phase].source:play()
phases[phase].is_played = true
end
end
if (love.keyboard.isDown("x") and not x_is_down) then
self.racers[self.pov_index].components.racer.show_pov = false
self.pov_index = get_loop(self.pov_index + 1, #self.racers)
self.racers[self.pov_index].components.racer.show_pov = true
x_is_down = true
elseif (not love.keyboard.isDown("x") and x_is_down == true) then
x_is_down = false
end
self:update_race_rank(dt)
end
function world:draw()
test.draw()
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:draw()
end
end
end
function world:ui_draw()
for _, e in ipairs(self.entities) do
for t, c in pairs(e.components) do
c:ui_draw()
end
end
end
return world

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