/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
carsUnlocked: 1
});
/****
* Classes
****/
var Car = Container.expand(function () {
var self = Container.call(this);
// Properties
self.velocity = {
x: 0,
y: 0
};
self.speed = 0;
self.maxSpeed = 15;
self.acceleration = 0.2;
self.deceleration = 0.1;
self.direction = 0; // Angle in radians
self.drifting = false;
self.driftDirection = 0;
self.driftPower = 0.5;
self.driftFactor = 0;
self.steeringPower = 0.05;
self.turningRadius = 0.04;
// Graphics
var carGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
// Trail container to hold drift trails
self.trailContainer = new Container();
self.addChild(self.trailContainer);
// Drift trail particles
self.trailParticles = [];
// Methods
self.accelerate = function () {
if (self.speed < self.maxSpeed) {
self.speed += self.acceleration;
if (self.speed > self.maxSpeed) {
self.speed = self.maxSpeed;
}
}
};
self.decelerate = function () {
if (self.speed > 0) {
self.speed -= self.deceleration;
if (self.speed < 0) {
self.speed = 0;
}
}
};
self.startDrift = function (direction) {
self.drifting = true;
self.driftDirection = direction;
LK.getSound('drift').play();
};
self.stopDrift = function () {
self.drifting = false;
self.driftFactor = 0;
};
self.addTrailParticle = function () {
if (self.speed > 1 && self.drifting) {
var trail = LK.getAsset('driftTrail', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
trail.x = 0;
trail.y = 0;
self.trailContainer.addChild(trail);
// Store the global position for this trail particle
var globalPos = self.trailContainer.toGlobal({
x: 0,
y: 0
});
trail.globalX = globalPos.x;
trail.globalY = globalPos.y;
self.trailParticles.push(trail);
// Fade out the trail particle
tween(trail, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
var index = self.trailParticles.indexOf(trail);
if (index !== -1) {
self.trailParticles.splice(index, 1);
}
}
});
}
};
self.update = function () {
// Apply physics
if (self.drifting) {
self.driftFactor = Math.min(1, self.driftFactor + 0.05);
self.direction += self.driftDirection * self.driftPower * self.driftFactor;
// Add trail particles at a rate based on speed
if (LK.ticks % Math.max(1, Math.floor(10 / self.speed)) === 0) {
self.addTrailParticle();
}
} else {
// Normal steering
self.direction += self.driftDirection * self.steeringPower;
}
// Update velocity based on speed and direction
self.velocity.x = Math.sin(self.direction) * self.speed;
self.velocity.y = -Math.cos(self.direction) * self.speed;
// Apply velocity to position
self.x += self.velocity.x;
self.y += self.velocity.y;
// Set rotation to match direction
carGraphics.rotation = self.direction;
// Natural speed decay
self.decelerate();
// Update position of trail container
self.trailContainer.x = -self.x;
self.trailContainer.y = -self.y;
};
return self;
});
var ScorePopup = Container.expand(function (value, x, y) {
var self = Container.call(this);
// Create the popup
var popup = self.attachAsset('scorePopup', {
anchorX: 0.5,
anchorY: 0.5
});
// Add score text
var scoreText = new Text2("+" + value, {
size: 40,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0.5);
self.addChild(scoreText);
// Set initial position
self.x = x;
self.y = y;
// Animate the popup
tween(self, {
y: y - 100,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
return self;
});
var Track = Container.expand(function () {
var self = Container.call(this);
// Track background
var trackBackground = self.attachAsset('track', {
anchorX: 0.5,
anchorY: 0.5
});
// Track elements
self.borders = [];
self.checkpoints = [];
self.corners = [];
// Create track layout
self.createTrack = function (level) {
// Clear existing track elements
for (var i = 0; i < self.borders.length; i++) {
self.removeChild(self.borders[i]);
}
self.borders = [];
for (var i = 0; i < self.checkpoints.length; i++) {
self.removeChild(self.checkpoints[i]);
}
self.checkpoints = [];
for (var i = 0; i < self.corners.length; i++) {
self.removeChild(self.corners[i]);
}
self.corners = [];
// Track width and height
var trackWidth = trackBackground.width;
var trackHeight = trackBackground.height;
// Create border walls
// Top border
var topBorder = self.createBorder(0, -trackHeight / 2, trackWidth, 50);
// Bottom border
var bottomBorder = self.createBorder(0, trackHeight / 2, trackWidth, 50);
// Left border
var leftBorder = self.createBorder(-trackWidth / 2, 0, 50, trackHeight);
// Right border
var rightBorder = self.createBorder(trackWidth / 2, 0, 50, trackHeight);
// Add obstacles based on level
if (level >= 1) {
// Center obstacle
self.createBorder(0, 0, 500, 100);
// Create checkpoints
self.createCheckpoint(-trackWidth / 4, -trackHeight / 4, 0, 200, 50);
self.createCheckpoint(trackWidth / 4, trackHeight / 4, Math.PI / 2, 200, 50);
// Create corners for drift points
self.createCorner(-trackWidth / 4, trackHeight / 4, 300);
self.createCorner(trackWidth / 4, -trackHeight / 4, 300);
}
if (level >= 2) {
// Add more obstacles
self.createBorder(-trackWidth / 4, -trackHeight / 3, 100, 400);
self.createBorder(trackWidth / 4, trackHeight / 3, 100, 400);
// Additional checkpoints
self.createCheckpoint(0, trackHeight / 3, Math.PI, 200, 50);
self.createCheckpoint(0, -trackHeight / 3, 0, 200, 50);
// More corners
self.createCorner(-trackWidth / 3, 0, 250);
self.createCorner(trackWidth / 3, 0, 250);
}
};
self.createBorder = function (x, y, width, height) {
var border = LK.getAsset('trackBorder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 50,
scaleY: height / 50
});
border.x = x;
border.y = y;
border.width = width;
border.height = height;
self.addChild(border);
self.borders.push(border);
return border;
};
self.createCheckpoint = function (x, y, rotation, width, height) {
var checkpoint = LK.getAsset('checkpoint', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 200,
scaleY: height / 50,
alpha: 0.7
});
checkpoint.x = x;
checkpoint.y = y;
checkpoint.rotation = rotation;
checkpoint.width = width;
checkpoint.height = height;
checkpoint.passed = false;
self.addChild(checkpoint);
self.checkpoints.push(checkpoint);
return checkpoint;
};
self.createCorner = function (x, y, size) {
var corner = LK.getAsset('corner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size / 300,
scaleY: size / 300,
alpha: 0.3
});
corner.x = x;
corner.y = y;
corner.size = size;
self.addChild(corner);
self.corners.push(corner);
return corner;
};
// Check collision between car and track elements
self.checkCollisions = function (car) {
// Check border collisions
for (var i = 0; i < self.borders.length; i++) {
if (car.intersects(self.borders[i])) {
return {
type: 'border',
element: self.borders[i]
};
}
}
// Check checkpoint collisions
for (var i = 0; i < self.checkpoints.length; i++) {
if (!self.checkpoints[i].passed && car.intersects(self.checkpoints[i])) {
self.checkpoints[i].passed = true;
return {
type: 'checkpoint',
element: self.checkpoints[i]
};
}
}
// Check if in corner for drift points
for (var i = 0; i < self.corners.length; i++) {
if (car.intersects(self.corners[i])) {
return {
type: 'corner',
element: self.corners[i]
};
}
}
return null;
};
// Check if all checkpoints have been passed
self.allCheckpointsPassed = function () {
for (var i = 0; i < self.checkpoints.length; i++) {
if (!self.checkpoints[i].passed) {
return false;
}
}
return self.checkpoints.length > 0;
};
// Reset checkpoints
self.resetCheckpoints = function () {
for (var i = 0; i < self.checkpoints.length; i++) {
self.checkpoints[i].passed = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x008080
});
/****
* Game Code
****/
// Music
// Sounds
// Game feedback
// Drift trail
// Track elements
// Car
// Game state
var gameState = 'ready'; // ready, playing, gameover
var currentLevel = 1;
var score = 0;
var highScore = storage.highScore || 0;
var driftScore = 0;
var driftMultiplier = 1;
var driftCombo = 0;
var lastDriftTime = 0;
var checkpointValue = 100;
var car;
var track;
var swipeStartX = 0;
var swipeStartY = 0;
var touchStart = false;
// UI Elements
var scoreText = new Text2("SCORE: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -200;
scoreText.y = 20;
var highScoreText = new Text2("BEST: " + highScore, {
size: 30,
fill: 0xCCCCCC
});
highScoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreText);
highScoreText.x = -200;
highScoreText.y = 80;
var multiplierText = new Text2("x1", {
size: 60,
fill: 0xFFCC00
});
multiplierText.anchor.set(0.5, 0.5);
multiplierText.alpha = 0;
LK.gui.center.addChild(multiplierText);
var instructionText = new Text2("TAP to accelerate\nSWIPE to drift", {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
instructionText.y = 300;
// Initialize the game
function initGame() {
// Reset game state
gameState = 'ready';
score = 0;
driftScore = 0;
driftMultiplier = 1;
driftCombo = 0;
// Update UI
updateScoreText();
multiplierText.alpha = 0;
instructionText.alpha = 1;
// Create track
if (!track) {
track = new Track();
game.addChild(track);
}
track.createTrack(currentLevel);
// Create car
if (!car) {
car = new Car();
game.addChild(car);
}
// Reset car position
car.x = 0;
car.y = 0;
car.speed = 0;
car.direction = 0;
car.drifting = false;
car.driftDirection = 0;
car.driftFactor = 0;
// Clear all trail particles
for (var i = 0; i < car.trailParticles.length; i++) {
if (car.trailParticles[i].parent) {
car.trailParticles[i].parent.removeChild(car.trailParticles[i]);
}
}
car.trailParticles = [];
// Play music
LK.playMusic('bgmusic');
}
// Update the score display
function updateScoreText() {
scoreText.setText("SCORE: " + score);
highScoreText.setText("BEST: " + highScore);
}
// Create a score popup
function createScorePopup(value, x, y) {
var popup = new ScorePopup(value, x, y);
game.addChild(popup);
}
// Handle drift scoring
function handleDriftPoints() {
// Check if still drifting
if (car.drifting && car.speed > 5) {
// Check time since last drift point
var currentTime = Date.now();
var timeDiff = currentTime - lastDriftTime;
// Add drift points every 500ms while drifting
if (timeDiff >= 500) {
lastDriftTime = currentTime;
// Calculate points based on speed and drift factor
var points = Math.floor(car.speed * car.driftFactor * driftMultiplier);
// Bonus for drifting in a corner
var collision = track.checkCollisions(car);
if (collision && collision.type === 'corner') {
points *= 2;
}
// Add points to score
if (points > 0) {
score += points;
createScorePopup(points, car.x, car.y);
// Increase combo
driftCombo++;
// Update multiplier based on combo
driftMultiplier = Math.min(5, 1 + Math.floor(driftCombo / 3));
// Show multiplier
multiplierText.setText("x" + driftMultiplier);
multiplierText.alpha = 1;
tween.stop(multiplierText);
tween(multiplierText, {
scale: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(multiplierText, {
scale: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
} else if (!car.drifting && driftCombo > 0) {
// Reset combo when not drifting
driftCombo = 0;
driftMultiplier = 1;
// Hide multiplier
tween(multiplierText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle checkpoint scoring
function handleCheckpoint() {
var points = checkpointValue * driftMultiplier;
score += points;
createScorePopup(points, car.x, car.y);
LK.getSound('checkpoint').play();
// Flash the checkpoint text
var checkpointScoreText = new Text2("CHECKPOINT: +" + points, {
size: 60,
fill: 0xFFCC00
});
checkpointScoreText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(checkpointScoreText);
checkpointScoreText.y = -200;
tween(checkpointScoreText, {
y: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(checkpointScoreText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (checkpointScoreText.parent) {
checkpointScoreText.parent.removeChild(checkpointScoreText);
}
}
});
}
});
// Check if all checkpoints are passed
if (track.allCheckpointsPassed()) {
// Level complete!
var levelCompleteText = new Text2("LEVEL COMPLETE!", {
size: 80,
fill: 0xFFCC00
});
levelCompleteText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(levelCompleteText);
// Animate the level complete text
tween(levelCompleteText, {
scale: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelCompleteText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (levelCompleteText.parent) {
levelCompleteText.parent.removeChild(levelCompleteText);
}
// Advance to next level
currentLevel++;
track.createTrack(currentLevel);
track.resetCheckpoints();
}
});
}
});
}
}
// Handle game over
function gameOver() {
gameState = 'gameover';
// Update high score if necessary
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
updateScoreText();
}
// Show game over
LK.showGameOver();
}
// Game initialization
initGame();
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'ready') {
gameState = 'playing';
instructionText.alpha = 0;
}
if (gameState === 'playing') {
// Start acceleration
car.accelerate();
// Store start position for swipe detection
swipeStartX = x;
swipeStartY = y;
touchStart = true;
// Play engine sound
LK.getSound('engine').play();
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
// Stop drifting when releasing
car.stopDrift();
touchStart = false;
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing' && touchStart) {
// Calculate swipe direction and magnitude
var deltaX = x - swipeStartX;
var deltaY = y - swipeStartY;
var swipeMagnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Only register as a swipe if the magnitude is significant
if (swipeMagnitude > 30) {
// Determine drift direction based on swipe direction
var swipeDirection = Math.atan2(deltaY, deltaX);
// Simplified direction determination (left or right)
var driftDir = 0;
// Compare swipe angle to car direction to determine if it's a left or right drift
var angleDiff = swipeDirection - car.direction;
// Normalize angle to -PI to PI
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
if (angleDiff > 0) {
driftDir = 1; // Right drift
} else {
driftDir = -1; // Left drift
}
// Start drifting in the determined direction
car.startDrift(driftDir);
// Reset drift time tracking
lastDriftTime = Date.now();
}
}
};
// Main game loop
game.update = function () {
if (gameState === 'playing') {
// Update car physics
car.update();
// Check collisions
var collision = track.checkCollisions(car);
if (collision) {
if (collision.type === 'border') {
// Hit a wall - game over
gameOver();
} else if (collision.type === 'checkpoint' && !collision.element.passed) {
// Passed checkpoint
handleCheckpoint();
}
// Corner collisions are handled in the drift scoring
}
// Handle drift scoring
handleDriftPoints();
// Update score display
updateScoreText();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highScore: 0,
carsUnlocked: 1
});
/****
* Classes
****/
var Car = Container.expand(function () {
var self = Container.call(this);
// Properties
self.velocity = {
x: 0,
y: 0
};
self.speed = 0;
self.maxSpeed = 15;
self.acceleration = 0.2;
self.deceleration = 0.1;
self.direction = 0; // Angle in radians
self.drifting = false;
self.driftDirection = 0;
self.driftPower = 0.5;
self.driftFactor = 0;
self.steeringPower = 0.05;
self.turningRadius = 0.04;
// Graphics
var carGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
// Trail container to hold drift trails
self.trailContainer = new Container();
self.addChild(self.trailContainer);
// Drift trail particles
self.trailParticles = [];
// Methods
self.accelerate = function () {
if (self.speed < self.maxSpeed) {
self.speed += self.acceleration;
if (self.speed > self.maxSpeed) {
self.speed = self.maxSpeed;
}
}
};
self.decelerate = function () {
if (self.speed > 0) {
self.speed -= self.deceleration;
if (self.speed < 0) {
self.speed = 0;
}
}
};
self.startDrift = function (direction) {
self.drifting = true;
self.driftDirection = direction;
LK.getSound('drift').play();
};
self.stopDrift = function () {
self.drifting = false;
self.driftFactor = 0;
};
self.addTrailParticle = function () {
if (self.speed > 1 && self.drifting) {
var trail = LK.getAsset('driftTrail', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.7
});
trail.x = 0;
trail.y = 0;
self.trailContainer.addChild(trail);
// Store the global position for this trail particle
var globalPos = self.trailContainer.toGlobal({
x: 0,
y: 0
});
trail.globalX = globalPos.x;
trail.globalY = globalPos.y;
self.trailParticles.push(trail);
// Fade out the trail particle
tween(trail, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
var index = self.trailParticles.indexOf(trail);
if (index !== -1) {
self.trailParticles.splice(index, 1);
}
}
});
}
};
self.update = function () {
// Apply physics
if (self.drifting) {
self.driftFactor = Math.min(1, self.driftFactor + 0.05);
self.direction += self.driftDirection * self.driftPower * self.driftFactor;
// Add trail particles at a rate based on speed
if (LK.ticks % Math.max(1, Math.floor(10 / self.speed)) === 0) {
self.addTrailParticle();
}
} else {
// Normal steering
self.direction += self.driftDirection * self.steeringPower;
}
// Update velocity based on speed and direction
self.velocity.x = Math.sin(self.direction) * self.speed;
self.velocity.y = -Math.cos(self.direction) * self.speed;
// Apply velocity to position
self.x += self.velocity.x;
self.y += self.velocity.y;
// Set rotation to match direction
carGraphics.rotation = self.direction;
// Natural speed decay
self.decelerate();
// Update position of trail container
self.trailContainer.x = -self.x;
self.trailContainer.y = -self.y;
};
return self;
});
var ScorePopup = Container.expand(function (value, x, y) {
var self = Container.call(this);
// Create the popup
var popup = self.attachAsset('scorePopup', {
anchorX: 0.5,
anchorY: 0.5
});
// Add score text
var scoreText = new Text2("+" + value, {
size: 40,
fill: 0x000000
});
scoreText.anchor.set(0.5, 0.5);
self.addChild(scoreText);
// Set initial position
self.x = x;
self.y = y;
// Animate the popup
tween(self, {
y: y - 100,
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.parent) {
self.parent.removeChild(self);
}
}
});
return self;
});
var Track = Container.expand(function () {
var self = Container.call(this);
// Track background
var trackBackground = self.attachAsset('track', {
anchorX: 0.5,
anchorY: 0.5
});
// Track elements
self.borders = [];
self.checkpoints = [];
self.corners = [];
// Create track layout
self.createTrack = function (level) {
// Clear existing track elements
for (var i = 0; i < self.borders.length; i++) {
self.removeChild(self.borders[i]);
}
self.borders = [];
for (var i = 0; i < self.checkpoints.length; i++) {
self.removeChild(self.checkpoints[i]);
}
self.checkpoints = [];
for (var i = 0; i < self.corners.length; i++) {
self.removeChild(self.corners[i]);
}
self.corners = [];
// Track width and height
var trackWidth = trackBackground.width;
var trackHeight = trackBackground.height;
// Create border walls
// Top border
var topBorder = self.createBorder(0, -trackHeight / 2, trackWidth, 50);
// Bottom border
var bottomBorder = self.createBorder(0, trackHeight / 2, trackWidth, 50);
// Left border
var leftBorder = self.createBorder(-trackWidth / 2, 0, 50, trackHeight);
// Right border
var rightBorder = self.createBorder(trackWidth / 2, 0, 50, trackHeight);
// Add obstacles based on level
if (level >= 1) {
// Center obstacle
self.createBorder(0, 0, 500, 100);
// Create checkpoints
self.createCheckpoint(-trackWidth / 4, -trackHeight / 4, 0, 200, 50);
self.createCheckpoint(trackWidth / 4, trackHeight / 4, Math.PI / 2, 200, 50);
// Create corners for drift points
self.createCorner(-trackWidth / 4, trackHeight / 4, 300);
self.createCorner(trackWidth / 4, -trackHeight / 4, 300);
}
if (level >= 2) {
// Add more obstacles
self.createBorder(-trackWidth / 4, -trackHeight / 3, 100, 400);
self.createBorder(trackWidth / 4, trackHeight / 3, 100, 400);
// Additional checkpoints
self.createCheckpoint(0, trackHeight / 3, Math.PI, 200, 50);
self.createCheckpoint(0, -trackHeight / 3, 0, 200, 50);
// More corners
self.createCorner(-trackWidth / 3, 0, 250);
self.createCorner(trackWidth / 3, 0, 250);
}
};
self.createBorder = function (x, y, width, height) {
var border = LK.getAsset('trackBorder', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 50,
scaleY: height / 50
});
border.x = x;
border.y = y;
border.width = width;
border.height = height;
self.addChild(border);
self.borders.push(border);
return border;
};
self.createCheckpoint = function (x, y, rotation, width, height) {
var checkpoint = LK.getAsset('checkpoint', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: width / 200,
scaleY: height / 50,
alpha: 0.7
});
checkpoint.x = x;
checkpoint.y = y;
checkpoint.rotation = rotation;
checkpoint.width = width;
checkpoint.height = height;
checkpoint.passed = false;
self.addChild(checkpoint);
self.checkpoints.push(checkpoint);
return checkpoint;
};
self.createCorner = function (x, y, size) {
var corner = LK.getAsset('corner', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size / 300,
scaleY: size / 300,
alpha: 0.3
});
corner.x = x;
corner.y = y;
corner.size = size;
self.addChild(corner);
self.corners.push(corner);
return corner;
};
// Check collision between car and track elements
self.checkCollisions = function (car) {
// Check border collisions
for (var i = 0; i < self.borders.length; i++) {
if (car.intersects(self.borders[i])) {
return {
type: 'border',
element: self.borders[i]
};
}
}
// Check checkpoint collisions
for (var i = 0; i < self.checkpoints.length; i++) {
if (!self.checkpoints[i].passed && car.intersects(self.checkpoints[i])) {
self.checkpoints[i].passed = true;
return {
type: 'checkpoint',
element: self.checkpoints[i]
};
}
}
// Check if in corner for drift points
for (var i = 0; i < self.corners.length; i++) {
if (car.intersects(self.corners[i])) {
return {
type: 'corner',
element: self.corners[i]
};
}
}
return null;
};
// Check if all checkpoints have been passed
self.allCheckpointsPassed = function () {
for (var i = 0; i < self.checkpoints.length; i++) {
if (!self.checkpoints[i].passed) {
return false;
}
}
return self.checkpoints.length > 0;
};
// Reset checkpoints
self.resetCheckpoints = function () {
for (var i = 0; i < self.checkpoints.length; i++) {
self.checkpoints[i].passed = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x008080
});
/****
* Game Code
****/
// Music
// Sounds
// Game feedback
// Drift trail
// Track elements
// Car
// Game state
var gameState = 'ready'; // ready, playing, gameover
var currentLevel = 1;
var score = 0;
var highScore = storage.highScore || 0;
var driftScore = 0;
var driftMultiplier = 1;
var driftCombo = 0;
var lastDriftTime = 0;
var checkpointValue = 100;
var car;
var track;
var swipeStartX = 0;
var swipeStartY = 0;
var touchStart = false;
// UI Elements
var scoreText = new Text2("SCORE: 0", {
size: 50,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(scoreText);
scoreText.x = -200;
scoreText.y = 20;
var highScoreText = new Text2("BEST: " + highScore, {
size: 30,
fill: 0xCCCCCC
});
highScoreText.anchor.set(0, 0);
LK.gui.topRight.addChild(highScoreText);
highScoreText.x = -200;
highScoreText.y = 80;
var multiplierText = new Text2("x1", {
size: 60,
fill: 0xFFCC00
});
multiplierText.anchor.set(0.5, 0.5);
multiplierText.alpha = 0;
LK.gui.center.addChild(multiplierText);
var instructionText = new Text2("TAP to accelerate\nSWIPE to drift", {
size: 50,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(instructionText);
instructionText.y = 300;
// Initialize the game
function initGame() {
// Reset game state
gameState = 'ready';
score = 0;
driftScore = 0;
driftMultiplier = 1;
driftCombo = 0;
// Update UI
updateScoreText();
multiplierText.alpha = 0;
instructionText.alpha = 1;
// Create track
if (!track) {
track = new Track();
game.addChild(track);
}
track.createTrack(currentLevel);
// Create car
if (!car) {
car = new Car();
game.addChild(car);
}
// Reset car position
car.x = 0;
car.y = 0;
car.speed = 0;
car.direction = 0;
car.drifting = false;
car.driftDirection = 0;
car.driftFactor = 0;
// Clear all trail particles
for (var i = 0; i < car.trailParticles.length; i++) {
if (car.trailParticles[i].parent) {
car.trailParticles[i].parent.removeChild(car.trailParticles[i]);
}
}
car.trailParticles = [];
// Play music
LK.playMusic('bgmusic');
}
// Update the score display
function updateScoreText() {
scoreText.setText("SCORE: " + score);
highScoreText.setText("BEST: " + highScore);
}
// Create a score popup
function createScorePopup(value, x, y) {
var popup = new ScorePopup(value, x, y);
game.addChild(popup);
}
// Handle drift scoring
function handleDriftPoints() {
// Check if still drifting
if (car.drifting && car.speed > 5) {
// Check time since last drift point
var currentTime = Date.now();
var timeDiff = currentTime - lastDriftTime;
// Add drift points every 500ms while drifting
if (timeDiff >= 500) {
lastDriftTime = currentTime;
// Calculate points based on speed and drift factor
var points = Math.floor(car.speed * car.driftFactor * driftMultiplier);
// Bonus for drifting in a corner
var collision = track.checkCollisions(car);
if (collision && collision.type === 'corner') {
points *= 2;
}
// Add points to score
if (points > 0) {
score += points;
createScorePopup(points, car.x, car.y);
// Increase combo
driftCombo++;
// Update multiplier based on combo
driftMultiplier = Math.min(5, 1 + Math.floor(driftCombo / 3));
// Show multiplier
multiplierText.setText("x" + driftMultiplier);
multiplierText.alpha = 1;
tween.stop(multiplierText);
tween(multiplierText, {
scale: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(multiplierText, {
scale: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
} else if (!car.drifting && driftCombo > 0) {
// Reset combo when not drifting
driftCombo = 0;
driftMultiplier = 1;
// Hide multiplier
tween(multiplierText, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut
});
}
}
// Handle checkpoint scoring
function handleCheckpoint() {
var points = checkpointValue * driftMultiplier;
score += points;
createScorePopup(points, car.x, car.y);
LK.getSound('checkpoint').play();
// Flash the checkpoint text
var checkpointScoreText = new Text2("CHECKPOINT: +" + points, {
size: 60,
fill: 0xFFCC00
});
checkpointScoreText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(checkpointScoreText);
checkpointScoreText.y = -200;
tween(checkpointScoreText, {
y: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(checkpointScoreText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (checkpointScoreText.parent) {
checkpointScoreText.parent.removeChild(checkpointScoreText);
}
}
});
}
});
// Check if all checkpoints are passed
if (track.allCheckpointsPassed()) {
// Level complete!
var levelCompleteText = new Text2("LEVEL COMPLETE!", {
size: 80,
fill: 0xFFCC00
});
levelCompleteText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(levelCompleteText);
// Animate the level complete text
tween(levelCompleteText, {
scale: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(levelCompleteText, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (levelCompleteText.parent) {
levelCompleteText.parent.removeChild(levelCompleteText);
}
// Advance to next level
currentLevel++;
track.createTrack(currentLevel);
track.resetCheckpoints();
}
});
}
});
}
}
// Handle game over
function gameOver() {
gameState = 'gameover';
// Update high score if necessary
if (score > highScore) {
highScore = score;
storage.highScore = highScore;
updateScoreText();
}
// Show game over
LK.showGameOver();
}
// Game initialization
initGame();
// Event handlers
game.down = function (x, y, obj) {
if (gameState === 'ready') {
gameState = 'playing';
instructionText.alpha = 0;
}
if (gameState === 'playing') {
// Start acceleration
car.accelerate();
// Store start position for swipe detection
swipeStartX = x;
swipeStartY = y;
touchStart = true;
// Play engine sound
LK.getSound('engine').play();
}
};
game.up = function (x, y, obj) {
if (gameState === 'playing') {
// Stop drifting when releasing
car.stopDrift();
touchStart = false;
}
};
game.move = function (x, y, obj) {
if (gameState === 'playing' && touchStart) {
// Calculate swipe direction and magnitude
var deltaX = x - swipeStartX;
var deltaY = y - swipeStartY;
var swipeMagnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
// Only register as a swipe if the magnitude is significant
if (swipeMagnitude > 30) {
// Determine drift direction based on swipe direction
var swipeDirection = Math.atan2(deltaY, deltaX);
// Simplified direction determination (left or right)
var driftDir = 0;
// Compare swipe angle to car direction to determine if it's a left or right drift
var angleDiff = swipeDirection - car.direction;
// Normalize angle to -PI to PI
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
if (angleDiff > 0) {
driftDir = 1; // Right drift
} else {
driftDir = -1; // Left drift
}
// Start drifting in the determined direction
car.startDrift(driftDir);
// Reset drift time tracking
lastDriftTime = Date.now();
}
}
};
// Main game loop
game.update = function () {
if (gameState === 'playing') {
// Update car physics
car.update();
// Check collisions
var collision = track.checkCollisions(car);
if (collision) {
if (collision.type === 'border') {
// Hit a wall - game over
gameOver();
} else if (collision.type === 'checkpoint' && !collision.element.passed) {
// Passed checkpoint
handleCheckpoint();
}
// Corner collisions are handled in the drift scoring
}
// Handle drift scoring
handleDriftPoints();
// Update score display
updateScoreText();
}
};