User prompt
The pitch of eng sound ranges from 0.5 to 5
User prompt
can you make a sound mechanic where the pitch of the sound is increasing with the rpmneedle going up just like a cars enginesound where the engine sounds pitch is increased by the rpm
User prompt
make all the things (rpmgauge, rpmneedle, speedometer, gear indicator,) closer to the race track so player can watch the cars race and concentrate on shifting at the same time
User prompt
the max speed for each gear is reached when the rpm reaches its max and the car goes to rev limiter
User prompt
speed up the rpms (the cars power)
User prompt
the car speeds up as the rpm goes higher
User prompt
make the rpm going up slower with each gear shift like in a real car
User prompt
player onşy control the green car and playersshifting only affects green car. enemys shifting doesnt affect green car
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'call')' in or related to this line: 'Car.prototype.update.call(self);' Line Number: 107
Code edit (1 edits merged)
Please save this source code
User prompt
Perfect Shift - Drag Race Championship
Initial prompt
a pixel art drag race sim where you need to shift gears perfeclty in time in order to win
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Car = Container.expand(function (carType) { var self = Container.call(this); var carBody = self.attachAsset(carType, { anchorX: 0.5, anchorY: 0.5 }); self.speed = 0; self.maxSpeed = 200; self.acceleration = 0; self.rpm = 1000; self.gear = 1; self.maxGear = 6; self.perfectShiftZone = { min: 6000, max: 6500 }; self.redline = 7000; self.isRevving = false; self.raceStarted = false; self.raceFinished = false; self.startRevving = function () { self.isRevving = true; }; self.stopRevving = function () { self.isRevving = false; }; self.shift = function () { if (self.gear < self.maxGear && self.raceStarted && !self.raceFinished) { var perfect = self.rpm >= self.perfectShiftZone.min && self.rpm <= self.perfectShiftZone.max; var fabulous = self.rpm >= 6200 && self.rpm <= 6300; // Smaller zone within perfect zone if (fabulous) { self.gear++; self.rpm = 2000; self.acceleration = 1.2; // Higher acceleration for fabulous shift self.speed += 15; // Direct speed boost for fabulous shift LK.getSound('fabulousShift').play(); LK.effects.flashScreen(0x00BFFF, 300); // Blue flash for fabulous shift // Blue NOS effect on car tween(self, { tint: 0x00BFFF }, { duration: 500, onFinish: function onFinish() { tween(self, { tint: 0xFFFFFF }, { duration: 300 }); } }); } else if (perfect) { self.gear++; self.rpm = 2000; self.acceleration = 0.8; LK.getSound('perfectShift').play(); LK.effects.flashScreen(0x4CAF50, 200); } else { var badShift = self.rpm <= 5000 || self.rpm >= 7000; // Wider bad zone if (badShift) { self.gear++; self.rpm = 2000; self.acceleration = 0.2; // Lower acceleration for bad shift self.speed -= 10; // Speed penalty for bad shift if (self.speed < 0) self.speed = 0; // Prevent negative speed LK.getSound('badShift').play(); LK.effects.flashScreen(0xF44336, 200); } else { // Regular shift (not perfect, not fabulous, not bad) self.gear++; self.rpm = 2000; self.acceleration = 0.3; LK.getSound('badShift').play(); LK.effects.flashScreen(0xF44336, 200); } } } }; self.update = function () { if (self.isRevving && !self.raceStarted) { self.rpm += 160; if (self.rpm > self.redline) { self.rpm = self.redline; } } else if (!self.isRevving && !self.raceStarted) { self.rpm -= 40; if (self.rpm < 1000) { self.rpm = 1000; } } if (self.raceStarted) { var rpmIncrease = 120 / self.gear; // RPM increases slower in higher gears self.rpm += rpmIncrease; if (self.rpm > self.redline) { self.rpm = self.redline; self.acceleration = 0.1; } // Calculate target speed based on RPM and gear var rpmRatio = (self.rpm - 1000) / (self.redline - 1000); // Normalize RPM to 0-1 var gearMaxSpeed = self.maxSpeed * (self.gear / self.maxGear); // Max speed for current gear var targetSpeed = rpmRatio * gearMaxSpeed; // Rev limiter: when at redline, speed is capped at gear's max speed if (self.rpm >= self.redline) { targetSpeed = gearMaxSpeed; } // Gradually adjust speed towards target speed if (targetSpeed > self.speed) { self.speed += self.acceleration; } else { self.speed = targetSpeed; } if (self.speed > self.maxSpeed) { self.speed = self.maxSpeed; } self.x += self.speed * 0.15; self.acceleration *= 0.95; } }; return self; }); var OpponentCar = Car.expand(function () { var self = Car.call(this, 'opponentCar'); self.aiShiftTimer = 0; self.aiShiftDelay = 90 + Math.random() * 30; self.difficultyLevel = 'easy'; // Will be updated based on current race self.baseLaunchAcceleration = 0.5; // Base launch performance self.baseShiftAcceleration = 0.4; // Base shift performance self.update = function () { // Call parent update manually since we're using expand() if (self.isRevving && !self.raceStarted) { self.rpm += 160; if (self.rpm > self.redline) { self.rpm = self.redline; } } else if (!self.isRevving && !self.raceStarted) { self.rpm -= 40; if (self.rpm < 1000) { self.rpm = 1000; } } if (self.raceStarted) { var rpmIncrease = 120 / self.gear; // RPM increases slower in higher gears self.rpm += rpmIncrease; if (self.rpm > self.redline) { self.rpm = self.redline; self.acceleration = 0.1; } // Calculate target speed based on RPM and gear var rpmRatio = (self.rpm - 1000) / (self.redline - 1000); // Normalize RPM to 0-1 var gearMaxSpeed = self.maxSpeed * (self.gear / self.maxGear); // Max speed for current gear var targetSpeed = rpmRatio * gearMaxSpeed; // Rev limiter: when at redline, speed is capped at gear's max speed if (self.rpm >= self.redline) { targetSpeed = gearMaxSpeed; } // Gradually adjust speed towards target speed if (targetSpeed > self.speed) { self.speed += self.acceleration; } else { self.speed = targetSpeed; } if (self.speed > self.maxSpeed) { self.speed = self.maxSpeed; } self.x += self.speed * 0.15; self.acceleration *= 0.95; } if (self.raceStarted && !self.raceFinished) { self.aiShiftTimer++; // Difficulty-based shift timing and performance var shiftDelay, minAcceleration, maxAcceleration; if (self.difficultyLevel === 'easy') { shiftDelay = 100 + Math.random() * 60; // Slower shifts: 100-160 frames minAcceleration = 0.4; maxAcceleration = 0.6; } else if (self.difficultyLevel === 'medium') { shiftDelay = 80 + Math.random() * 40; // Medium shifts: 80-120 frames minAcceleration = 0.6; maxAcceleration = 0.75; } else if (currentRace === 5) { // hard (race 5) shiftDelay = 60 + Math.random() * 20; // Fast shifts: 60-80 frames minAcceleration = 0.75; maxAcceleration = 0.9; } else { // race 6 - slightly slower but more consistent for perfect requirement shiftDelay = 65 + Math.random() * 15; // Slightly slower shifts: 65-80 frames minAcceleration = 0.72; maxAcceleration = 0.87; } if (self.aiShiftTimer >= self.aiShiftDelay && self.gear < self.maxGear) { // Opponent car shifts automatically with difficulty-based performance self.gear++; self.rpm = 2000; self.acceleration = minAcceleration + Math.random() * (maxAcceleration - minAcceleration); self.aiShiftTimer = 0; self.aiShiftDelay = shiftDelay; } } }; return self; }); var WinPopup = Container.expand(function () { var self = Container.call(this); // Semi-transparent background overlay var overlay = self.attachAsset('rpmGauge', { anchorX: 0.5, anchorY: 0.5, scaleX: 10, scaleY: 15, alpha: 0.95, x: 1024, y: 1366 }); overlay.tint = 0x000000; // Win text var winText = new Text2('YOU WON!', { size: 120, fill: 0xFFD700 }); winText.anchor.set(0.5, 0.5); winText.x = 1024; winText.y = 1200; self.addChild(winText); // Next race button background var buttonBg = self.attachAsset('rpmGauge', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 0.8, x: 1024, y: 1500 }); buttonBg.tint = 0x4CAF50; // Next race button text var buttonText = new Text2('NEXT RACE', { size: 60, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); buttonText.x = 1024; buttonText.y = 1500; self.addChild(buttonText); self.down = function (x, y, obj) { // Check if click is on button area if (x > 824 && x < 1224 && y > 1420 && y < 1580) { self.onNextRace(); } }; self.onNextRace = function () { // This will be set from outside }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var background = game.attachAsset('background', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); var currentRace = 1; var maxRaces = 6; var raceState = 'countdown'; var countdownTimer = 0; var startLightTimer = 0; var perfectStartWindow = false; var lightSequenceStep = 0; var lightSequenceTimer = 0; var currentGearSound = null; var revSound = null; var lastGear = 1; var isCurrentlyRevving = false; var track = game.attachAsset('track', { anchorX: 0, anchorY: 0.5, x: 0, y: 1366 }); // Create audience above track var upperAudience = []; for (var i = 0; i < 10; i++) { var audienceGroup = new Container(); var audienceBody = audienceGroup.attachAsset('audience', { anchorX: 0.5, anchorY: 1 }); // Add multiple heads on each audience section for (var j = 0; j < 8; j++) { var head = audienceGroup.attachAsset('audienceHead', { anchorX: 0.5, anchorY: 0.5, x: -75 + j * 20 + (Math.random() * 10 - 5), y: -60 + (Math.random() * 20 - 10) }); // Randomize head colors var headColors = [0xFFDBB3, 0xF1C27D, 0xE0AC69, 0xC68642]; head.tint = headColors[Math.floor(Math.random() * headColors.length)]; } audienceGroup.x = 200 + i * 180; audienceGroup.y = 1216; game.addChild(audienceGroup); upperAudience.push(audienceGroup); } // Create audience below track var lowerAudience = []; for (var i = 0; i < 10; i++) { var audienceGroup = new Container(); var audienceBody = audienceGroup.attachAsset('audience', { anchorX: 0.5, anchorY: 0 }); // Add multiple heads on each audience section for (var j = 0; j < 8; j++) { var head = audienceGroup.attachAsset('audienceHead', { anchorX: 0.5, anchorY: 0.5, x: -75 + j * 20 + (Math.random() * 10 - 5), y: 60 + (Math.random() * 20 - 10) }); // Randomize head colors var headColors = [0xFFDBB3, 0xF1C27D, 0xE0AC69, 0xC68642]; head.tint = headColors[Math.floor(Math.random() * headColors.length)]; } audienceGroup.x = 200 + i * 180; audienceGroup.y = 1516; game.addChild(audienceGroup); lowerAudience.push(audienceGroup); } var startLine = game.attachAsset('startLine', { anchorX: 0.5, anchorY: 0.5, x: 100, y: 1366 }); var finishLine = game.attachAsset('finishLine', { anchorX: 0.5, anchorY: 0.5, x: 1948, y: 1366 }); var playerCar = game.addChild(new Car('playerCar')); playerCar.x = 50; playerCar.y = 1320; var opponentCar = game.addChild(new OpponentCar()); opponentCar.x = 50; opponentCar.y = 1420; var rpmGauge = game.attachAsset('rpmGauge', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1600 }); var badZone = game.attachAsset('badZone', { anchorX: 0.5, anchorY: 0.5, x: 1180, y: 1600 }); var perfectZone = game.attachAsset('perfectZone', { anchorX: 0.5, anchorY: 0.5, x: 1180, y: 1600 }); var fabulousZone = game.attachAsset('fabulousZone', { anchorX: 0.5, anchorY: 0.5, x: 1180, y: 1600 }); var rpmNeedle = game.attachAsset('rpmNeedle', { anchorX: 0.5, anchorY: 0.5, x: 824, y: 1600 }); var redLight1 = game.attachAsset('redLight1', { anchorX: 0.5, anchorY: 0.5, x: 824, y: 600, alpha: 0.3 }); var redLight2 = game.attachAsset('redLight2', { anchorX: 0.5, anchorY: 0.5, x: 924, y: 600, alpha: 0.3 }); var redLight3 = game.attachAsset('redLight3', { anchorX: 0.5, anchorY: 0.5, x: 1124, y: 600, alpha: 0.3 }); var redLight4 = game.attachAsset('redLight4', { anchorX: 0.5, anchorY: 0.5, x: 1224, y: 600, alpha: 0.3 }); var redLight = game.attachAsset('redLight', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 600 }); var greenLight = game.attachAsset('greenLight', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 600, alpha: 0 }); var speedText = new Text2('0 MPH', { size: 80, fill: 0x000000 }); speedText.anchor.set(0.5, 0); speedText.x = 1024; speedText.y = 1700; game.addChild(speedText); var gearBackground = game.attachAsset('gearBackground', { anchorX: 0.5, anchorY: 0.5, x: 1400, y: 1620 }); var gearText = new Text2('1', { size: 120, fill: 0xFFFFFF }); gearText.anchor.set(0.5, 0); gearText.x = 1400; gearText.y = 1550; game.addChild(gearText); var raceText = new Text2('RACE ' + currentRace, { size: 100, fill: 0x000000 }); raceText.anchor.set(0.5, 0.5); raceText.x = 1024; raceText.y = 300; game.addChild(raceText); var instructionText = new Text2('HOLD TO REV', { size: 80, fill: 0x000000 }); instructionText.anchor.set(0.5, 0.5); instructionText.x = 1024; instructionText.y = 800; game.addChild(instructionText); var difficultyText = new Text2('EASY', { size: 120, fill: 0x4CAF50, stroke: 0x000000, strokeThickness: 8 }); difficultyText.anchor.set(1, 0); difficultyText.x = 1948; difficultyText.y = 150; game.addChild(difficultyText); var winPopup = null; var displayedSpeed = 0; // Current displayed speed value var targetSpeed = 0; // Target speed value for smooth transition // Play background music LK.playMusic('back'); function showWinPopup() { // Stop all sounds when popup appears if (currentGearSound) { currentGearSound.stop(); currentGearSound = null; } if (revSound) { revSound.stop(); revSound = null; } isCurrentlyRevving = false; if (!winPopup) { winPopup = new WinPopup(); winPopup.onNextRace = function () { hideWinPopup(); currentRace++; if (currentRace > maxRaces) { currentRace = 1; } resetRace(); }; game.addChild(winPopup); } } function hideWinPopup() { if (winPopup) { game.removeChild(winPopup); winPopup = null; } } function startCountdown() { raceState = 'countdown'; countdownTimer = 0; startLightTimer = 0; lightSequenceStep = 0; lightSequenceTimer = 0; // Reset all lights to dim state redLight1.alpha = 0.3; redLight2.alpha = 0.3; redLight3.alpha = 0.3; redLight4.alpha = 0.3; redLight.alpha = 0; greenLight.alpha = 0; // Start light sequence after 1 second LK.setTimeout(function () { // Light 1 turns on tween(redLight1, { alpha: 1 }, { duration: 200 }); LK.setTimeout(function () { // Light 2 turns on tween(redLight2, { alpha: 1 }, { duration: 200 }); LK.setTimeout(function () { // Light 3 turns on tween(redLight3, { alpha: 1 }, { duration: 200 }); LK.setTimeout(function () { // Light 4 turns on tween(redLight4, { alpha: 1 }, { duration: 200 }); LK.setTimeout(function () { // All red lights turn off and green lights turn on tween(redLight1, { alpha: 0.3 }, { duration: 100 }); tween(redLight2, { alpha: 0.3 }, { duration: 100 }); tween(redLight3, { alpha: 0.3 }, { duration: 100 }); tween(redLight4, { alpha: 0.3 }, { duration: 100 }); tween(greenLight, { alpha: 1 }, { duration: 200 }); perfectStartWindow = true; instructionText.setText('TAP TO SHIFT IN GREEN ZONE'); LK.getSound('raceStart').play(); LK.setTimeout(function () { raceState = 'racing'; playerCar.raceStarted = true; opponentCar.raceStarted = true; var launchQuality = 0.5; if (playerCar.rpm >= 4000 && playerCar.rpm <= 5000) { launchQuality = 1.0; } else if (playerCar.rpm >= 3000 && playerCar.rpm <= 6000) { launchQuality = 0.8; } playerCar.acceleration = launchQuality; // Use difficulty-based launch acceleration with some randomness var launchVariation = Math.random() * 0.15; // ±0.15 variation opponentCar.acceleration = opponentCar.baseLaunchAcceleration + launchVariation; perfectStartWindow = false; // Reset RPM needle position to starting position playerCar.rpm = 1000; opponentCar.rpm = 1000; }, 500); }, 800); }, 800); }, 800); }, 800); }, 1000); } function updateOpponentDifficulty() { if (currentRace >= 1 && currentRace <= 2) { opponentCar.difficultyLevel = 'easy'; opponentCar.baseLaunchAcceleration = 0.5; opponentCar.baseShiftAcceleration = 0.4; } else if (currentRace >= 3 && currentRace <= 4) { opponentCar.difficultyLevel = 'medium'; opponentCar.baseLaunchAcceleration = 0.7; opponentCar.baseShiftAcceleration = 0.6; } else if (currentRace === 5) { // race 5 - hard opponentCar.difficultyLevel = 'hard'; opponentCar.baseLaunchAcceleration = 0.85; opponentCar.baseShiftAcceleration = 0.75; } else { // race 6 - slightly reduced difficulty to require perfect play opponentCar.difficultyLevel = 'hard'; opponentCar.baseLaunchAcceleration = 0.82; opponentCar.baseShiftAcceleration = 0.72; } } function resetRace() { raceState = 'countdown'; playerCar.x = 50; playerCar.y = 1320; playerCar.speed = 0; playerCar.rpm = 1000; playerCar.gear = 1; playerCar.acceleration = 0; playerCar.raceStarted = false; playerCar.raceFinished = false; playerCar.isRevving = false; opponentCar.x = 50; opponentCar.y = 1420; opponentCar.speed = 0; opponentCar.rpm = 1000; opponentCar.gear = 1; opponentCar.acceleration = 0; opponentCar.raceStarted = false; opponentCar.raceFinished = false; opponentCar.aiShiftTimer = 0; updateOpponentDifficulty(); // Update difficulty based on current race lightSequenceStep = 0; lightSequenceTimer = 0; // Reset all lights to dim state redLight1.alpha = 0.3; redLight2.alpha = 0.3; redLight3.alpha = 0.3; redLight4.alpha = 0.3; greenLight.alpha = 0; raceText.setText('RACE ' + currentRace); instructionText.setText('HOLD TO REV'); // Update difficulty text based on current race if (currentRace >= 1 && currentRace <= 2) { difficultyText.setText('EASY'); difficultyText.fill = 0x4CAF50; // Green } else if (currentRace >= 3 && currentRace <= 4) { difficultyText.setText('MEDIUM'); difficultyText.fill = 0xFFFF00; // Yellow } else if (currentRace === 5) { difficultyText.setText('HARD'); difficultyText.fill = 0xFF0000; // Red } else { difficultyText.setText('EXPERT'); difficultyText.fill = 0xFF0000; // Red } // Stop any existing gear sound if (currentGearSound) { currentGearSound.stop(); currentGearSound = null; } // Stop revving sound if (revSound) { revSound.stop(); revSound = null; } isCurrentlyRevving = false; displayedSpeed = 0; targetSpeed = 0; // Create loading screen var loadingText = new Text2('LOADING...', { size: 120, fill: 0x000000 }); loadingText.anchor.set(0.5, 0.5); loadingText.x = 1024; loadingText.y = 1366; game.addChild(loadingText); // Wait for assets to load then start countdown LK.setTimeout(function () { // Remove loading text game.removeChild(loadingText); // Start the game startCountdown(); }, 1000); } game.down = function (x, y, obj) { if (raceState === 'countdown') { playerCar.startRevving(); LK.getSound('engineRev').play(); } else if (raceState === 'racing') { // Only player car shifting is controlled by player input playerCar.shift(); } }; game.up = function (x, y, obj) { if (raceState === 'countdown') { playerCar.stopRevving(); } }; game.update = function () { var needlePosition = 824 + (playerCar.rpm - 1000) / 6000 * 400; if (needlePosition > 1224) needlePosition = 1224; if (needlePosition < 824) needlePosition = 824; rpmNeedle.x = needlePosition; // Calculate new target speed for display (multiply by 3 for speedometer effect) var newTargetSpeed = Math.floor(playerCar.speed * 3); // Only start tween if target speed has changed significantly if (Math.abs(newTargetSpeed - targetSpeed) > 1) { targetSpeed = newTargetSpeed; // Stop any existing speed tween tween.stop({ speed: displayedSpeed }, { speed: true }); // Smoothly tween to new target speed over 200ms for faster updates tween({ speed: displayedSpeed }, { speed: targetSpeed }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { displayedSpeed = targetSpeed; } }); } // Update displayed speed during tween speedText.setText(Math.floor(displayedSpeed) + ' MPH'); gearText.setText(playerCar.gear); // Sound control - play individual gear sounds or revving sound if (playerCar.isRevving && !playerCar.raceStarted) { // Handle revving before race starts if (!isCurrentlyRevving) { // Stop any current gear sound if (currentGearSound) { currentGearSound.stop(); currentGearSound = null; } // Start revving sound revSound = LK.getSound('revSound'); revSound.play(); isCurrentlyRevving = true; } } else if (playerCar.raceStarted) { // Handle gear sounds during racing if (isCurrentlyRevving) { // Stop revving sound if (revSound) { revSound.stop(); revSound = null; } isCurrentlyRevving = false; } // Check if gear changed if (playerCar.gear !== lastGear) { // Stop current gear sound if (currentGearSound) { currentGearSound.stop(); currentGearSound = null; } // Start new gear sound var gearSoundId = 'gear' + playerCar.gear + 'Sound'; currentGearSound = LK.getSound(gearSoundId); currentGearSound.play(); lastGear = playerCar.gear; } } else { // Stop all sounds when not racing or revving if (currentGearSound) { currentGearSound.stop(); currentGearSound = null; } if (revSound) { revSound.stop(); revSound = null; } isCurrentlyRevving = false; } if (raceState === 'racing') { if (playerCar.x >= 1900 && !playerCar.raceFinished) { playerCar.raceFinished = true; opponentCar.raceFinished = true; LK.setTimeout(function () { showWinPopup(); }, 1000); } else if (opponentCar.x >= 1900 && !opponentCar.raceFinished) { playerCar.raceFinished = true; opponentCar.raceFinished = true; LK.showGameOver(); } } }; startCountdown();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Car = Container.expand(function (carType) {
var self = Container.call(this);
var carBody = self.attachAsset(carType, {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 0;
self.maxSpeed = 200;
self.acceleration = 0;
self.rpm = 1000;
self.gear = 1;
self.maxGear = 6;
self.perfectShiftZone = {
min: 6000,
max: 6500
};
self.redline = 7000;
self.isRevving = false;
self.raceStarted = false;
self.raceFinished = false;
self.startRevving = function () {
self.isRevving = true;
};
self.stopRevving = function () {
self.isRevving = false;
};
self.shift = function () {
if (self.gear < self.maxGear && self.raceStarted && !self.raceFinished) {
var perfect = self.rpm >= self.perfectShiftZone.min && self.rpm <= self.perfectShiftZone.max;
var fabulous = self.rpm >= 6200 && self.rpm <= 6300; // Smaller zone within perfect zone
if (fabulous) {
self.gear++;
self.rpm = 2000;
self.acceleration = 1.2; // Higher acceleration for fabulous shift
self.speed += 15; // Direct speed boost for fabulous shift
LK.getSound('fabulousShift').play();
LK.effects.flashScreen(0x00BFFF, 300); // Blue flash for fabulous shift
// Blue NOS effect on car
tween(self, {
tint: 0x00BFFF
}, {
duration: 500,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 300
});
}
});
} else if (perfect) {
self.gear++;
self.rpm = 2000;
self.acceleration = 0.8;
LK.getSound('perfectShift').play();
LK.effects.flashScreen(0x4CAF50, 200);
} else {
var badShift = self.rpm <= 5000 || self.rpm >= 7000; // Wider bad zone
if (badShift) {
self.gear++;
self.rpm = 2000;
self.acceleration = 0.2; // Lower acceleration for bad shift
self.speed -= 10; // Speed penalty for bad shift
if (self.speed < 0) self.speed = 0; // Prevent negative speed
LK.getSound('badShift').play();
LK.effects.flashScreen(0xF44336, 200);
} else {
// Regular shift (not perfect, not fabulous, not bad)
self.gear++;
self.rpm = 2000;
self.acceleration = 0.3;
LK.getSound('badShift').play();
LK.effects.flashScreen(0xF44336, 200);
}
}
}
};
self.update = function () {
if (self.isRevving && !self.raceStarted) {
self.rpm += 160;
if (self.rpm > self.redline) {
self.rpm = self.redline;
}
} else if (!self.isRevving && !self.raceStarted) {
self.rpm -= 40;
if (self.rpm < 1000) {
self.rpm = 1000;
}
}
if (self.raceStarted) {
var rpmIncrease = 120 / self.gear; // RPM increases slower in higher gears
self.rpm += rpmIncrease;
if (self.rpm > self.redline) {
self.rpm = self.redline;
self.acceleration = 0.1;
}
// Calculate target speed based on RPM and gear
var rpmRatio = (self.rpm - 1000) / (self.redline - 1000); // Normalize RPM to 0-1
var gearMaxSpeed = self.maxSpeed * (self.gear / self.maxGear); // Max speed for current gear
var targetSpeed = rpmRatio * gearMaxSpeed;
// Rev limiter: when at redline, speed is capped at gear's max speed
if (self.rpm >= self.redline) {
targetSpeed = gearMaxSpeed;
}
// Gradually adjust speed towards target speed
if (targetSpeed > self.speed) {
self.speed += self.acceleration;
} else {
self.speed = targetSpeed;
}
if (self.speed > self.maxSpeed) {
self.speed = self.maxSpeed;
}
self.x += self.speed * 0.15;
self.acceleration *= 0.95;
}
};
return self;
});
var OpponentCar = Car.expand(function () {
var self = Car.call(this, 'opponentCar');
self.aiShiftTimer = 0;
self.aiShiftDelay = 90 + Math.random() * 30;
self.difficultyLevel = 'easy'; // Will be updated based on current race
self.baseLaunchAcceleration = 0.5; // Base launch performance
self.baseShiftAcceleration = 0.4; // Base shift performance
self.update = function () {
// Call parent update manually since we're using expand()
if (self.isRevving && !self.raceStarted) {
self.rpm += 160;
if (self.rpm > self.redline) {
self.rpm = self.redline;
}
} else if (!self.isRevving && !self.raceStarted) {
self.rpm -= 40;
if (self.rpm < 1000) {
self.rpm = 1000;
}
}
if (self.raceStarted) {
var rpmIncrease = 120 / self.gear; // RPM increases slower in higher gears
self.rpm += rpmIncrease;
if (self.rpm > self.redline) {
self.rpm = self.redline;
self.acceleration = 0.1;
}
// Calculate target speed based on RPM and gear
var rpmRatio = (self.rpm - 1000) / (self.redline - 1000); // Normalize RPM to 0-1
var gearMaxSpeed = self.maxSpeed * (self.gear / self.maxGear); // Max speed for current gear
var targetSpeed = rpmRatio * gearMaxSpeed;
// Rev limiter: when at redline, speed is capped at gear's max speed
if (self.rpm >= self.redline) {
targetSpeed = gearMaxSpeed;
}
// Gradually adjust speed towards target speed
if (targetSpeed > self.speed) {
self.speed += self.acceleration;
} else {
self.speed = targetSpeed;
}
if (self.speed > self.maxSpeed) {
self.speed = self.maxSpeed;
}
self.x += self.speed * 0.15;
self.acceleration *= 0.95;
}
if (self.raceStarted && !self.raceFinished) {
self.aiShiftTimer++;
// Difficulty-based shift timing and performance
var shiftDelay, minAcceleration, maxAcceleration;
if (self.difficultyLevel === 'easy') {
shiftDelay = 100 + Math.random() * 60; // Slower shifts: 100-160 frames
minAcceleration = 0.4;
maxAcceleration = 0.6;
} else if (self.difficultyLevel === 'medium') {
shiftDelay = 80 + Math.random() * 40; // Medium shifts: 80-120 frames
minAcceleration = 0.6;
maxAcceleration = 0.75;
} else if (currentRace === 5) {
// hard (race 5)
shiftDelay = 60 + Math.random() * 20; // Fast shifts: 60-80 frames
minAcceleration = 0.75;
maxAcceleration = 0.9;
} else {
// race 6 - slightly slower but more consistent for perfect requirement
shiftDelay = 65 + Math.random() * 15; // Slightly slower shifts: 65-80 frames
minAcceleration = 0.72;
maxAcceleration = 0.87;
}
if (self.aiShiftTimer >= self.aiShiftDelay && self.gear < self.maxGear) {
// Opponent car shifts automatically with difficulty-based performance
self.gear++;
self.rpm = 2000;
self.acceleration = minAcceleration + Math.random() * (maxAcceleration - minAcceleration);
self.aiShiftTimer = 0;
self.aiShiftDelay = shiftDelay;
}
}
};
return self;
});
var WinPopup = Container.expand(function () {
var self = Container.call(this);
// Semi-transparent background overlay
var overlay = self.attachAsset('rpmGauge', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 15,
alpha: 0.95,
x: 1024,
y: 1366
});
overlay.tint = 0x000000;
// Win text
var winText = new Text2('YOU WON!', {
size: 120,
fill: 0xFFD700
});
winText.anchor.set(0.5, 0.5);
winText.x = 1024;
winText.y = 1200;
self.addChild(winText);
// Next race button background
var buttonBg = self.attachAsset('rpmGauge', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.8,
x: 1024,
y: 1500
});
buttonBg.tint = 0x4CAF50;
// Next race button text
var buttonText = new Text2('NEXT RACE', {
size: 60,
fill: 0xFFFFFF
});
buttonText.anchor.set(0.5, 0.5);
buttonText.x = 1024;
buttonText.y = 1500;
self.addChild(buttonText);
self.down = function (x, y, obj) {
// Check if click is on button area
if (x > 824 && x < 1224 && y > 1420 && y < 1580) {
self.onNextRace();
}
};
self.onNextRace = function () {
// This will be set from outside
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var background = game.attachAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
var currentRace = 1;
var maxRaces = 6;
var raceState = 'countdown';
var countdownTimer = 0;
var startLightTimer = 0;
var perfectStartWindow = false;
var lightSequenceStep = 0;
var lightSequenceTimer = 0;
var currentGearSound = null;
var revSound = null;
var lastGear = 1;
var isCurrentlyRevving = false;
var track = game.attachAsset('track', {
anchorX: 0,
anchorY: 0.5,
x: 0,
y: 1366
});
// Create audience above track
var upperAudience = [];
for (var i = 0; i < 10; i++) {
var audienceGroup = new Container();
var audienceBody = audienceGroup.attachAsset('audience', {
anchorX: 0.5,
anchorY: 1
});
// Add multiple heads on each audience section
for (var j = 0; j < 8; j++) {
var head = audienceGroup.attachAsset('audienceHead', {
anchorX: 0.5,
anchorY: 0.5,
x: -75 + j * 20 + (Math.random() * 10 - 5),
y: -60 + (Math.random() * 20 - 10)
});
// Randomize head colors
var headColors = [0xFFDBB3, 0xF1C27D, 0xE0AC69, 0xC68642];
head.tint = headColors[Math.floor(Math.random() * headColors.length)];
}
audienceGroup.x = 200 + i * 180;
audienceGroup.y = 1216;
game.addChild(audienceGroup);
upperAudience.push(audienceGroup);
}
// Create audience below track
var lowerAudience = [];
for (var i = 0; i < 10; i++) {
var audienceGroup = new Container();
var audienceBody = audienceGroup.attachAsset('audience', {
anchorX: 0.5,
anchorY: 0
});
// Add multiple heads on each audience section
for (var j = 0; j < 8; j++) {
var head = audienceGroup.attachAsset('audienceHead', {
anchorX: 0.5,
anchorY: 0.5,
x: -75 + j * 20 + (Math.random() * 10 - 5),
y: 60 + (Math.random() * 20 - 10)
});
// Randomize head colors
var headColors = [0xFFDBB3, 0xF1C27D, 0xE0AC69, 0xC68642];
head.tint = headColors[Math.floor(Math.random() * headColors.length)];
}
audienceGroup.x = 200 + i * 180;
audienceGroup.y = 1516;
game.addChild(audienceGroup);
lowerAudience.push(audienceGroup);
}
var startLine = game.attachAsset('startLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 100,
y: 1366
});
var finishLine = game.attachAsset('finishLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 1948,
y: 1366
});
var playerCar = game.addChild(new Car('playerCar'));
playerCar.x = 50;
playerCar.y = 1320;
var opponentCar = game.addChild(new OpponentCar());
opponentCar.x = 50;
opponentCar.y = 1420;
var rpmGauge = game.attachAsset('rpmGauge', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1600
});
var badZone = game.attachAsset('badZone', {
anchorX: 0.5,
anchorY: 0.5,
x: 1180,
y: 1600
});
var perfectZone = game.attachAsset('perfectZone', {
anchorX: 0.5,
anchorY: 0.5,
x: 1180,
y: 1600
});
var fabulousZone = game.attachAsset('fabulousZone', {
anchorX: 0.5,
anchorY: 0.5,
x: 1180,
y: 1600
});
var rpmNeedle = game.attachAsset('rpmNeedle', {
anchorX: 0.5,
anchorY: 0.5,
x: 824,
y: 1600
});
var redLight1 = game.attachAsset('redLight1', {
anchorX: 0.5,
anchorY: 0.5,
x: 824,
y: 600,
alpha: 0.3
});
var redLight2 = game.attachAsset('redLight2', {
anchorX: 0.5,
anchorY: 0.5,
x: 924,
y: 600,
alpha: 0.3
});
var redLight3 = game.attachAsset('redLight3', {
anchorX: 0.5,
anchorY: 0.5,
x: 1124,
y: 600,
alpha: 0.3
});
var redLight4 = game.attachAsset('redLight4', {
anchorX: 0.5,
anchorY: 0.5,
x: 1224,
y: 600,
alpha: 0.3
});
var redLight = game.attachAsset('redLight', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600
});
var greenLight = game.attachAsset('greenLight', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600,
alpha: 0
});
var speedText = new Text2('0 MPH', {
size: 80,
fill: 0x000000
});
speedText.anchor.set(0.5, 0);
speedText.x = 1024;
speedText.y = 1700;
game.addChild(speedText);
var gearBackground = game.attachAsset('gearBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 1400,
y: 1620
});
var gearText = new Text2('1', {
size: 120,
fill: 0xFFFFFF
});
gearText.anchor.set(0.5, 0);
gearText.x = 1400;
gearText.y = 1550;
game.addChild(gearText);
var raceText = new Text2('RACE ' + currentRace, {
size: 100,
fill: 0x000000
});
raceText.anchor.set(0.5, 0.5);
raceText.x = 1024;
raceText.y = 300;
game.addChild(raceText);
var instructionText = new Text2('HOLD TO REV', {
size: 80,
fill: 0x000000
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 1024;
instructionText.y = 800;
game.addChild(instructionText);
var difficultyText = new Text2('EASY', {
size: 120,
fill: 0x4CAF50,
stroke: 0x000000,
strokeThickness: 8
});
difficultyText.anchor.set(1, 0);
difficultyText.x = 1948;
difficultyText.y = 150;
game.addChild(difficultyText);
var winPopup = null;
var displayedSpeed = 0; // Current displayed speed value
var targetSpeed = 0; // Target speed value for smooth transition
// Play background music
LK.playMusic('back');
function showWinPopup() {
// Stop all sounds when popup appears
if (currentGearSound) {
currentGearSound.stop();
currentGearSound = null;
}
if (revSound) {
revSound.stop();
revSound = null;
}
isCurrentlyRevving = false;
if (!winPopup) {
winPopup = new WinPopup();
winPopup.onNextRace = function () {
hideWinPopup();
currentRace++;
if (currentRace > maxRaces) {
currentRace = 1;
}
resetRace();
};
game.addChild(winPopup);
}
}
function hideWinPopup() {
if (winPopup) {
game.removeChild(winPopup);
winPopup = null;
}
}
function startCountdown() {
raceState = 'countdown';
countdownTimer = 0;
startLightTimer = 0;
lightSequenceStep = 0;
lightSequenceTimer = 0;
// Reset all lights to dim state
redLight1.alpha = 0.3;
redLight2.alpha = 0.3;
redLight3.alpha = 0.3;
redLight4.alpha = 0.3;
redLight.alpha = 0;
greenLight.alpha = 0;
// Start light sequence after 1 second
LK.setTimeout(function () {
// Light 1 turns on
tween(redLight1, {
alpha: 1
}, {
duration: 200
});
LK.setTimeout(function () {
// Light 2 turns on
tween(redLight2, {
alpha: 1
}, {
duration: 200
});
LK.setTimeout(function () {
// Light 3 turns on
tween(redLight3, {
alpha: 1
}, {
duration: 200
});
LK.setTimeout(function () {
// Light 4 turns on
tween(redLight4, {
alpha: 1
}, {
duration: 200
});
LK.setTimeout(function () {
// All red lights turn off and green lights turn on
tween(redLight1, {
alpha: 0.3
}, {
duration: 100
});
tween(redLight2, {
alpha: 0.3
}, {
duration: 100
});
tween(redLight3, {
alpha: 0.3
}, {
duration: 100
});
tween(redLight4, {
alpha: 0.3
}, {
duration: 100
});
tween(greenLight, {
alpha: 1
}, {
duration: 200
});
perfectStartWindow = true;
instructionText.setText('TAP TO SHIFT IN GREEN ZONE');
LK.getSound('raceStart').play();
LK.setTimeout(function () {
raceState = 'racing';
playerCar.raceStarted = true;
opponentCar.raceStarted = true;
var launchQuality = 0.5;
if (playerCar.rpm >= 4000 && playerCar.rpm <= 5000) {
launchQuality = 1.0;
} else if (playerCar.rpm >= 3000 && playerCar.rpm <= 6000) {
launchQuality = 0.8;
}
playerCar.acceleration = launchQuality;
// Use difficulty-based launch acceleration with some randomness
var launchVariation = Math.random() * 0.15; // ±0.15 variation
opponentCar.acceleration = opponentCar.baseLaunchAcceleration + launchVariation;
perfectStartWindow = false;
// Reset RPM needle position to starting position
playerCar.rpm = 1000;
opponentCar.rpm = 1000;
}, 500);
}, 800);
}, 800);
}, 800);
}, 800);
}, 1000);
}
function updateOpponentDifficulty() {
if (currentRace >= 1 && currentRace <= 2) {
opponentCar.difficultyLevel = 'easy';
opponentCar.baseLaunchAcceleration = 0.5;
opponentCar.baseShiftAcceleration = 0.4;
} else if (currentRace >= 3 && currentRace <= 4) {
opponentCar.difficultyLevel = 'medium';
opponentCar.baseLaunchAcceleration = 0.7;
opponentCar.baseShiftAcceleration = 0.6;
} else if (currentRace === 5) {
// race 5 - hard
opponentCar.difficultyLevel = 'hard';
opponentCar.baseLaunchAcceleration = 0.85;
opponentCar.baseShiftAcceleration = 0.75;
} else {
// race 6 - slightly reduced difficulty to require perfect play
opponentCar.difficultyLevel = 'hard';
opponentCar.baseLaunchAcceleration = 0.82;
opponentCar.baseShiftAcceleration = 0.72;
}
}
function resetRace() {
raceState = 'countdown';
playerCar.x = 50;
playerCar.y = 1320;
playerCar.speed = 0;
playerCar.rpm = 1000;
playerCar.gear = 1;
playerCar.acceleration = 0;
playerCar.raceStarted = false;
playerCar.raceFinished = false;
playerCar.isRevving = false;
opponentCar.x = 50;
opponentCar.y = 1420;
opponentCar.speed = 0;
opponentCar.rpm = 1000;
opponentCar.gear = 1;
opponentCar.acceleration = 0;
opponentCar.raceStarted = false;
opponentCar.raceFinished = false;
opponentCar.aiShiftTimer = 0;
updateOpponentDifficulty(); // Update difficulty based on current race
lightSequenceStep = 0;
lightSequenceTimer = 0;
// Reset all lights to dim state
redLight1.alpha = 0.3;
redLight2.alpha = 0.3;
redLight3.alpha = 0.3;
redLight4.alpha = 0.3;
greenLight.alpha = 0;
raceText.setText('RACE ' + currentRace);
instructionText.setText('HOLD TO REV');
// Update difficulty text based on current race
if (currentRace >= 1 && currentRace <= 2) {
difficultyText.setText('EASY');
difficultyText.fill = 0x4CAF50; // Green
} else if (currentRace >= 3 && currentRace <= 4) {
difficultyText.setText('MEDIUM');
difficultyText.fill = 0xFFFF00; // Yellow
} else if (currentRace === 5) {
difficultyText.setText('HARD');
difficultyText.fill = 0xFF0000; // Red
} else {
difficultyText.setText('EXPERT');
difficultyText.fill = 0xFF0000; // Red
}
// Stop any existing gear sound
if (currentGearSound) {
currentGearSound.stop();
currentGearSound = null;
}
// Stop revving sound
if (revSound) {
revSound.stop();
revSound = null;
}
isCurrentlyRevving = false;
displayedSpeed = 0;
targetSpeed = 0;
// Create loading screen
var loadingText = new Text2('LOADING...', {
size: 120,
fill: 0x000000
});
loadingText.anchor.set(0.5, 0.5);
loadingText.x = 1024;
loadingText.y = 1366;
game.addChild(loadingText);
// Wait for assets to load then start countdown
LK.setTimeout(function () {
// Remove loading text
game.removeChild(loadingText);
// Start the game
startCountdown();
}, 1000);
}
game.down = function (x, y, obj) {
if (raceState === 'countdown') {
playerCar.startRevving();
LK.getSound('engineRev').play();
} else if (raceState === 'racing') {
// Only player car shifting is controlled by player input
playerCar.shift();
}
};
game.up = function (x, y, obj) {
if (raceState === 'countdown') {
playerCar.stopRevving();
}
};
game.update = function () {
var needlePosition = 824 + (playerCar.rpm - 1000) / 6000 * 400;
if (needlePosition > 1224) needlePosition = 1224;
if (needlePosition < 824) needlePosition = 824;
rpmNeedle.x = needlePosition;
// Calculate new target speed for display (multiply by 3 for speedometer effect)
var newTargetSpeed = Math.floor(playerCar.speed * 3);
// Only start tween if target speed has changed significantly
if (Math.abs(newTargetSpeed - targetSpeed) > 1) {
targetSpeed = newTargetSpeed;
// Stop any existing speed tween
tween.stop({
speed: displayedSpeed
}, {
speed: true
});
// Smoothly tween to new target speed over 200ms for faster updates
tween({
speed: displayedSpeed
}, {
speed: targetSpeed
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
displayedSpeed = targetSpeed;
}
});
}
// Update displayed speed during tween
speedText.setText(Math.floor(displayedSpeed) + ' MPH');
gearText.setText(playerCar.gear);
// Sound control - play individual gear sounds or revving sound
if (playerCar.isRevving && !playerCar.raceStarted) {
// Handle revving before race starts
if (!isCurrentlyRevving) {
// Stop any current gear sound
if (currentGearSound) {
currentGearSound.stop();
currentGearSound = null;
}
// Start revving sound
revSound = LK.getSound('revSound');
revSound.play();
isCurrentlyRevving = true;
}
} else if (playerCar.raceStarted) {
// Handle gear sounds during racing
if (isCurrentlyRevving) {
// Stop revving sound
if (revSound) {
revSound.stop();
revSound = null;
}
isCurrentlyRevving = false;
}
// Check if gear changed
if (playerCar.gear !== lastGear) {
// Stop current gear sound
if (currentGearSound) {
currentGearSound.stop();
currentGearSound = null;
}
// Start new gear sound
var gearSoundId = 'gear' + playerCar.gear + 'Sound';
currentGearSound = LK.getSound(gearSoundId);
currentGearSound.play();
lastGear = playerCar.gear;
}
} else {
// Stop all sounds when not racing or revving
if (currentGearSound) {
currentGearSound.stop();
currentGearSound = null;
}
if (revSound) {
revSound.stop();
revSound = null;
}
isCurrentlyRevving = false;
}
if (raceState === 'racing') {
if (playerCar.x >= 1900 && !playerCar.raceFinished) {
playerCar.raceFinished = true;
opponentCar.raceFinished = true;
LK.setTimeout(function () {
showWinPopup();
}, 1000);
} else if (opponentCar.x >= 1900 && !opponentCar.raceFinished) {
playerCar.raceFinished = true;
opponentCar.raceFinished = true;
LK.showGameOver();
}
}
};
startCountdown();
engineSound
Sound effect
gear1Sound
Sound effect
gear2Sound
Sound effect
gear3Sound
Sound effect
gear4Sound
Sound effect
gear5Sound
Sound effect
gear6Sound
Sound effect
revSound
Sound effect
perfectShift
Sound effect
badShift
Sound effect
raceStart
Sound effect
back
Music
fabulousShift
Sound effect