User prompt
Every time your enemy car spawns randomly, the color is different
User prompt
Let the shield effect rotate around itself ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The animations of the car are broken and the health bar is not visible, fix them ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Fix it when the car slipped up
User prompt
Let the car have animations ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Don't let the shield effect rotate too much
User prompt
Add rotation effect to shield
User prompt
Slow down the speed from 250 points to 400 points, then decrease the speed
User prompt
After 250 points, it accelerates a little more up to 400 points, and at 400 points it returns to normal speed.
User prompt
Remove speed inscription
User prompt
Put an inscription showing how fast the car is and place it somewhere on the screen
User prompt
Let the fireball pass through the car while there is a shield
User prompt
Let's get the hellbox while there's a shield
User prompt
Don't let hellbox come out after 70 points
User prompt
After 200 points, only the barriers come out. Let the barriers spawn at the same time in 2 lanes after 200 points
User prompt
Add points to the fireball as enemies, and the points increase when you pass by the fireballs
User prompt
After 70 points, the score will continue to increase. Let fireballs earn points
User prompt
Let's get points from fireballs
User prompt
Don't let there be a fireball before 60 points
User prompt
After 70 points, the game will return to its normal speed
User prompt
After 70 points, only the fireball will come out
User prompt
GameLet the acceleration of the game start at 20 points, but not too much until 70 points
User prompt
When we say Hellboxa, let the car health increase a little
User prompt
Let the Hellbox spawn in random places, like a spell bottle
User prompt
Update and handle HellBox collisions in main game update loop
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Enemy Car Class
var EnemyCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('enemyCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 1;
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Fireball Class
var Fireball = Container.expand(function () {
var self = Container.call(this);
var fireball = self.attachAsset('Fireball', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 28;
self.lane = 1;
// --- Health bar for Fireball (ace) ---
self.maxLife = 3;
self.life = self.maxLife;
// Health bar background
self.lifeBarBg = self.attachAsset('lifeBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 220,
height: 220,
y: -90,
alpha: 0.92
});
// Health bar border
self.lifeBarBorder = self.attachAsset('lifeBarBorder', {
anchorX: 0.5,
anchorY: 0.5,
width: 260,
height: 260,
y: -90,
alpha: 1
});
// Health bar fill
self.lifeBar = self.attachAsset('lifeBar', {
anchorX: 0.0,
anchorY: 0.5,
width: 180,
height: 60,
x: -90,
y: -90,
alpha: 1
});
// Heart icon
self.lifeBarHeart = self.attachAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 70,
x: -130,
y: -90,
alpha: 1
});
// Helper to update fireball health bar UI
self.updateLifeBar = function () {
// Show only if not full and not dead
var show = self.life < self.maxLife && self.life > 0;
self.lifeBarBg.visible = show;
self.lifeBar.visible = show;
self.lifeBarBorder.visible = show;
self.lifeBarHeart.visible = show;
// Set width proportional to life
var minWidth = 24;
var w = self.life > 0 ? Math.max(minWidth, Math.round(180 * (self.life / self.maxLife))) : 0;
self.lifeBar.width = w;
// Hide if dead
if (self.life <= 0) {
self.lifeBarBg.visible = false;
self.lifeBar.visible = false;
self.lifeBarBorder.visible = false;
self.lifeBarHeart.visible = false;
}
};
// Initialize bar state
self.updateLifeBar();
self.update = function () {
// Move fireball down the screen
self.y += self.speed * gameSpeed;
// Keep health bar above fireball
self.lifeBarBg.y = -90;
self.lifeBarBorder.y = -90;
self.lifeBar.y = -90;
self.lifeBarHeart.y = -90;
self.lifeBar.x = -90;
self.lifeBarHeart.x = -130;
self.updateLifeBar();
};
return self;
});
// Lane Divider Class
var LaneDivider = Container.expand(function () {
var self = Container.call(this);
var div = self.attachAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Obstacle Class
var Obstacle = Container.expand(function () {
var self = Container.call(this);
var obs = self.attachAsset('obstacle', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 1;
self.speed = 18;
self.update = function () {
self.y += self.speed * gameSpeed;
};
return self;
});
// Player Car Class
var PlayerCar = Container.expand(function () {
var self = Container.call(this);
var car = self.attachAsset('playerCar', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = 1; // 0: left, 1: center, 2: right
self.life = playerMaxLife; // Add life property to player car
self.setLane = function (laneIdx) {
self.lane = laneIdx;
// Animate to new lane position
var targetX = lanes[self.lane];
// Squash/stretch effect: squash horizontally, stretch vertically, then restore
tween.stop(self, {
scaleX: true,
scaleY: true
});
tween(self, {
scaleX: 1.18,
scaleY: 0.88
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 80,
easing: tween.cubicOut
});
}
});
tween(self, {
x: targetX
}, {
duration: 120,
easing: tween.cubicOut,
onUpdate: function onUpdate() {
updateLifeBar();
}
});
updateLifeBar();
};
// Idle bounce animation for player car
self.idleBounce = function () {
// Cancel any previous bounce
tween.stop(self, {
y: true
});
// Animate up
tween(self, {
y: self.y - 18
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Animate down
tween(self, {
y: self.y + 18
}, {
duration: 320,
easing: tween.sineInOut,
onFinish: function onFinish() {
// Loop bounce
self.idleBounce();
}
});
}
});
};
// Start idle bounce when created
self.idleBounce();
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// Music (background)
// Sound for lane change
// Sound for crash
// Road lane divider
// Obstacle (barrier)
// Enemy car
// Car (player)
// Lane positions (3 lanes)
// When the player picks up the magic bottle, the car bursts into flames (flame burst effect)
// Spell bottle asset (example: blue bottle, 120x180)
// Health bar assets
// Use spellBottle as heart
var laneCount = 3;
var laneWidth = 520; // Increased lane width for more space between lanes
var lanes = [2048 / 2 - laneWidth,
// left
2048 / 2,
// center
2048 / 2 + laneWidth // right
];
// Game variables
var playerCar;
var enemyCars = [];
var obstacles = [];
var laneDividers = [];
var score = 0;
var highScore = storage.highScore || 0;
var scoreTxt;
var highScoreTxt;
var gameSpeed = 1;
var ticksSinceStart = 0;
var swipeStartX = null;
var swipeStartY = null;
var swipeActive = false;
var lastLane = 1;
var spawnTick = 0;
var dividerSpacing = 320;
var dragNode = null;
// --- LIFE SYSTEM ---
var playerMaxLife = 5;
var playerLife = playerMaxLife;
// Health bar UI will be handled by a new, clear, and consistent system below
// Score display
scoreTxt = new Text2('0', {
size: 120,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(scoreTxt);
// Shield timer display
var shieldTxt = new Text2('', {
size: 80,
fill: 0x2BD5E6
});
shieldTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(shieldTxt);
// High Score display
highScoreTxt = new Text2('HI: ' + highScore, {
size: 60,
fill: 0xFFFF00
});
highScoreTxt.anchor.set(0.5, 0.5);
LK.gui.top.addChild(highScoreTxt);
// Add swipe left gesture to high score text
var hiSwipeStartX = null;
var hiSwipeStartY = null;
var hiSwipeActive = false;
highScoreTxt.down = function (x, y, obj) {
hiSwipeStartX = x;
hiSwipeStartY = y;
hiSwipeActive = true;
};
highScoreTxt.move = function (x, y, obj) {
if (!hiSwipeActive) return;
var dx = x - hiSwipeStartX;
var dy = y - hiSwipeStartY;
// Only consider horizontal swipes, ignore vertical
if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy)) {
if (dx < 0) {
// Swipe left detected on high score text
// Flash the high score text white for feedback
tween(highScoreTxt, {
tint: 0xffffff
}, {
duration: 80,
yoyo: true,
repeat: 1,
onComplete: function onComplete() {
highScoreTxt.tint = 0xFFFF00;
}
});
hiSwipeActive = false;
}
}
};
highScoreTxt.up = function (x, y, obj) {
hiSwipeActive = false;
};
// Start music
LK.playMusic('bgmusic');
// Create player car
playerCar = new PlayerCar();
playerCar.x = lanes[1];
playerCar.y = 2732 - 500;
playerCar.setLane(1);
// Shield properties
playerCar.shieldActive = false;
playerCar.shieldTicks = 0;
// Add shield effect asset (invisible by default)
var shieldEffect = LK.getAsset('centerCircle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 1.2,
x: playerCar.x,
y: playerCar.y,
alpha: 0.5,
tint: 0xffffff
});
shieldEffect.visible = false;
game.addChild(shieldEffect);
game.addChild(playerCar);
// Add two vertical side stripes (left and right) to the play area
var sideStripeWidth = 60;
var sideStripeHeight = 2732;
var leftStripe = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0,
width: sideStripeWidth,
height: sideStripeHeight,
x: sideStripeWidth / 2,
y: 0
});
var rightStripe = LK.getAsset('laneDivider', {
anchorX: 0.5,
anchorY: 0,
width: sideStripeWidth,
height: sideStripeHeight,
x: 2048 - sideStripeWidth / 2,
y: 0
});
game.addChild(leftStripe);
game.addChild(rightStripe);
// Create lane dividers (vertical lines for each lane)
// For 3 lanes, we want to copy the center lane divider and place it to the right and left of the center stripe, without adding extra lines
// The center divider is between lane 0 and lane 1, and between lane 1 and lane 2
// So, for 3 lanes, we want to draw the two dividers: one between lane 0 and 1, and one between lane 1 and 2
var dividerOffsets = [];
// Find the X positions for the two dividers (between lanes)
for (var l = 1; l < laneCount; l++) {
dividerOffsets.push((lanes[l - 1] + lanes[l]) / 2);
}
// Now, for each divider, draw a set of stripes down the screen
for (var d = 0; d < dividerOffsets.length; d++) {
for (var i = 0; i < 10; i++) {
// Center LaneDivider
var divider = new LaneDivider();
divider.x = dividerOffsets[d];
divider.y = i * dividerSpacing;
laneDividers.push(divider);
game.addChild(divider);
// Left copy
var dividerLeft = new LaneDivider();
dividerLeft.x = dividerOffsets[d] - laneWidth;
dividerLeft.y = i * dividerSpacing;
laneDividers.push(dividerLeft);
game.addChild(dividerLeft);
// Right copy
var dividerRight = new LaneDivider();
dividerRight.x = dividerOffsets[d] + laneWidth;
dividerRight.y = i * dividerSpacing;
laneDividers.push(dividerRight);
game.addChild(dividerRight);
}
}
// Prepare for dynamic spell bottle spawning
var spellBottle = null;
var spellBottleActive = false;
var spellBottleLane = 1;
// Add: track last spell bottle spawn tick to limit spawn rate
var lastSpellBottleSpawnTick = -1000;
// Helper: spawn enemy car, obstacle, or fireball
function spawnObstacleOrEnemy() {
var y = -300;
if (score > 200) {
// After 200 points, only spawn barriers (obstacles), and spawn in 2 lanes at once
// Pick two different lanes
var lanesToUse = [];
while (lanesToUse.length < 2) {
var idx = Math.floor(Math.random() * laneCount);
if (lanesToUse.indexOf(idx) === -1) lanesToUse.push(idx);
}
for (var i = 0; i < lanesToUse.length; i++) {
var obs = new Obstacle();
obs.lane = lanesToUse[i];
obs.x = lanes[lanesToUse[i]];
obs.y = y;
obstacles.push(obs);
game.addChild(obs);
}
} else if (score >= 70) {
// Only spawn fireballs after 70 points
if (!window.fireballs) window.fireballs = [];
if (window.fireballs.length < 2) {
var laneIdx = Math.floor(Math.random() * laneCount);
var fireball = new Fireball();
fireball.lane = laneIdx;
fireball.x = lanes[laneIdx];
fireball.y = y;
window.fireballs.push(fireball);
game.addChild(fireball);
}
} else {
// Randomly decide: 60% enemy car, 25% obstacle, 15% fireball
var laneIdx = Math.floor(Math.random() * laneCount);
var rand = Math.random();
if (rand < 0.6) {
var enemy = new EnemyCar();
enemy.lane = laneIdx;
enemy.x = lanes[laneIdx];
enemy.y = y;
enemyCars.push(enemy);
game.addChild(enemy);
} else if (rand < 0.85) {
var obs = new Obstacle();
obs.lane = laneIdx;
obs.x = lanes[laneIdx];
obs.y = y;
obstacles.push(obs);
game.addChild(obs);
} else {
// Prevent fireballs from spawning before 60 points
if (score >= 60) {
// Limit fireballs: only spawn if fewer than 2 are on screen
if (!window.fireballs) window.fireballs = [];
if (window.fireballs.length < 2) {
var fireball = new Fireball();
fireball.lane = laneIdx;
fireball.x = lanes[laneIdx];
fireball.y = y;
window.fireballs.push(fireball);
game.addChild(fireball);
}
}
}
}
}
// Helper: update score
function updateScore(val) {
score = val;
scoreTxt.setText(score);
if (score > highScore) {
highScore = score;
highScoreTxt.setText('HI: ' + highScore);
storage.highScore = highScore;
}
}
// Helper: update player health bar UI (global, for use in PlayerCar.setLane and elsewhere)
function updateLifeBar() {
// Always show the health bar unless dead
var showBar = playerLife > 0;
if (window.playerHealthBarBg) window.playerHealthBarBg.visible = showBar;
if (window.playerHealthBarFill) window.playerHealthBarFill.visible = showBar;
if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = showBar;
// Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = showBar;
// Position above player car
if (playerCar && window.playerHealthBarBg && window.playerHealthBarFill && window.playerHealthBarBorder) {
var barY = playerCar.y - 140;
var barX = playerCar.x;
window.playerHealthBarBg.x = barX;
window.playerHealthBarBg.y = barY;
window.playerHealthBarFill.x = barX - 240;
window.playerHealthBarFill.y = barY;
window.playerHealthBarBorder.x = barX;
window.playerHealthBarBorder.y = barY;
// Removed: window.playerHealthBarHeart.x = barX - 320;
// Removed: window.playerHealthBarHeart.y = barY;
// Set width proportional to life, always show a minimum width if not dead
var minWidth = 38;
var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0;
window.playerHealthBarFill.width = fillW;
}
// Hide if dead
if (playerLife <= 0) {
if (window.playerHealthBarBg) window.playerHealthBarBg.visible = false;
if (window.playerHealthBarFill) window.playerHealthBarFill.visible = false;
if (window.playerHealthBarBorder) window.playerHealthBarBorder.visible = false;
// Removed: if (window.playerHealthBarHeart) window.playerHealthBarHeart.visible = false;
}
}
// Touch/drag/swipe handling
game.down = function (x, y, obj) {
swipeStartX = x;
swipeStartY = y;
swipeActive = true;
dragNode = playerCar;
};
game.move = function (x, y, obj) {
// Only handle swipe if active
if (!swipeActive) return;
if (!dragNode) return;
var dx = x - swipeStartX;
var dy = y - swipeStartY;
// Only consider horizontal swipes, ignore vertical
if (Math.abs(dx) > 80 && Math.abs(dx) > Math.abs(dy)) {
var newLane = playerCar.lane;
if (dx < 0 && playerCar.lane > 0) {
newLane = playerCar.lane - 1;
} else if (dx > 0 && playerCar.lane < laneCount - 1) {
newLane = playerCar.lane + 1;
}
if (newLane !== playerCar.lane) {
playerCar.setLane(newLane);
LK.getSound('swipe').play();
swipeActive = false;
dragNode = null;
}
}
};
game.up = function (x, y, obj) {
swipeActive = false;
dragNode = null;
};
// Main game update loop
game.update = function () {
ticksSinceStart++;
// Adjust acceleration: start at 20 points, increase slowly until 70 points, then ramp up
if (score >= 400) {
// After 400 points, decrease the speed gradually
if (ticksSinceStart % 60 === 0 && gameSpeed > 1) {
gameSpeed -= 0.08;
if (gameSpeed < 1) gameSpeed = 1;
}
} else if (score >= 250) {
// Between 250 and 400, slow down the speed a little (decelerate)
if (ticksSinceStart % 60 === 0 && gameSpeed > 1.2) {
gameSpeed -= 0.04;
if (gameSpeed < 1.2) gameSpeed = 1.2;
}
} else if (score >= 70) {
// After 70, only fireballs spawn and game returns to normal speed
gameSpeed = 1;
} else if (score >= 20) {
// Between 20 and 70, increase very slowly
if (ticksSinceStart % 90 === 0 && gameSpeed < 2) {
gameSpeed += 0.04;
if (gameSpeed > 2) gameSpeed = 2;
}
} else {
gameSpeed = 1;
}
// Move lane dividers, loop to top
for (var i = 0; i < laneDividers.length; i++) {
var div = laneDividers[i];
div.update();
if (div.y > 2732 + 60) {
div.y -= dividerSpacing * 10;
}
}
// Spawn or move spell bottle
if (!spellBottleActive) {
// Determine spawn cooldown and chance based on score
var bottleCooldown = score >= 50 ? 300 : 600; // 5s if score>=50, else 10s
var bottleChance = score >= 50 ? 1 / 60 : 1 / 120; // 1/60 per frame if score>=50, else 1/120
// Only allow spawn if enough time has passed since last spawn
if (ticksSinceStart - lastSpellBottleSpawnTick > bottleCooldown) {
// Try to spawn with the appropriate chance
if (Math.random() < bottleChance) {
spellBottleLane = Math.floor(Math.random() * laneCount);
var bottleX = lanes[spellBottleLane];
// Place the bottle at a fixed Y position (e.g., 900px from the top)
var bottleY = 900;
spellBottle = LK.getAsset('spellBottle', {
anchorX: 0.5,
anchorY: 1.0,
x: bottleX,
y: bottleY
});
game.addChild(spellBottle);
spellBottleActive = true;
lastSpellBottleSpawnTick = ticksSinceStart;
}
}
} else if (spellBottle) {
// Move the spell bottle down the screen
spellBottle.y += 18 * gameSpeed;
// Off screen? Remove (if it has scrolled above the visible area)
if (spellBottle.y < -200 || spellBottle.y > 2732 + 200) {
spellBottle.destroy();
spellBottle = null;
spellBottleActive = false;
}
// Check collision with playerCar for shield pickup
if (spellBottle && !playerCar.shieldActive && spellBottle.intersects(playerCar)) {
// Grant shield for 3 seconds
playerCar.shieldActive = true;
playerCar.shieldTicks = 180; // 3 seconds at 60fps
// Add flame burst effect: flashObject with orange/yellow color, then white
// Growing fire effect: overlay a fire image that grows in scale for 1s, then fades out
var fireMagic = LK.getAsset('spellBottle', {
anchorX: 0.5,
anchorY: 0.5,
x: playerCar.x,
y: playerCar.y,
scaleX: 1,
scaleY: 1,
alpha: 0.85,
tint: 0xff6600
});
game.addChild(fireMagic);
// Animate fire to grow and fade out
tween(fireMagic, {
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
}, {
duration: 1000,
easing: tween.cubicOut,
onUpdate: function onUpdate() {
fireMagic.x = playerCar.x;
fireMagic.y = playerCar.y;
},
onComplete: function onComplete() {
fireMagic.destroy();
}
});
// Also flash the car for extra effect
LK.effects.flashObject(playerCar, 0xffa500, 350);
LK.setTimeout(function () {
LK.effects.flashObject(playerCar, 0xffff00, 350);
}, 350);
spellBottle.destroy();
spellBottle = null;
spellBottleActive = false;
}
}
// Spawn enemies/obstacles every 40-60 ticks, randomize
spawnTick++;
var spawnInterval = 40 + Math.floor(Math.random() * 20);
if (spawnTick > spawnInterval) {
spawnObstacleOrEnemy();
spawnTick = 0;
}
// Update enemy cars
for (var i = enemyCars.length - 1; i >= 0; i--) {
var enemy = enemyCars[i];
enemy.update();
// Off screen
if (enemy.y > 2732 + 300) {
enemy.destroy();
enemyCars.splice(i, 1);
updateScore(score + 1);
continue;
}
// Collision with spell bottle
if (spellBottle && enemy.intersects(spellBottle)) {
spellBottle.destroy();
spellBottle = null;
spellBottleActive = false;
}
// Collision with player
if (enemy.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through enemy car while shield is active
continue;
} else {
// --- LIFE SYSTEM: lose 1 life, give 1 to enemy, show bar ---
if (playerLife > 0) {
playerLife--;
playerCar.life = playerLife;
// Give 1 life to enemy (could be used for enemy powerup, here just for demo)
if (!enemy.life) enemy.life = 0;
enemy.life++;
// If enemy is a fireball, update its health bar
if (typeof enemy.updateLifeBar === "function") {
enemy.updateLifeBar();
}
updateLifeBar();
LK.effects.flashObject(playerCar, 0xff0000, 400);
if (playerLife <= 0) {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
// Remove enemy on hit
enemy.destroy();
enemyCars.splice(i, 1);
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
}
}
}
// Update obstacles
for (var j = obstacles.length - 1; j >= 0; j--) {
var obs = obstacles[j];
obs.update();
// Off screen
if (obs.y > 2732 + 200) {
obs.destroy();
obstacles.splice(j, 1);
updateScore(score + 1);
continue;
}
// Collision with player
if (obs.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through obstacle while shield is active
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff0000, 800);
// Explosion effect at player car position
LK.effects.flashObject(playerCar, 0xffffff, 700);
LK.showGameOver();
return;
}
}
}
// Update fireballs
if (!window.fireballs) window.fireballs = [];
for (var f = window.fireballs.length - 1; f >= 0; f--) {
var fireball = window.fireballs[f];
fireball.update();
// Track lastY for fireball to detect when it leaves the screen
if (typeof fireball.lastY === "undefined") fireball.lastY = fireball.y;
// Track if player has already passed this fireball for scoring
if (typeof fireball.passedByPlayer === "undefined") fireball.passedByPlayer = false;
// Award points if player passes by (dodges) the fireball
// Player "passes by" if fireball moves below the player car, and they did not collide
if (!fireball.passedByPlayer && fireball.lastY <= playerCar.y && fireball.y > playerCar.y && fireball.life > 0 // Only if not destroyed
) {
updateScore(score + 2); // Award 2 points for dodging a fireball
fireball.passedByPlayer = true;
}
// Off screen
if (fireball.y > 2732 + 200) {
fireball.destroy();
window.fireballs.splice(f, 1);
continue;
}
// Award points if fireball is destroyed (life reaches 0)
if (typeof fireball.lastLife === "undefined") fireball.lastLife = fireball.life;
if (fireball.lastLife > 0 && fireball.life <= 0) {
// Fireball was just destroyed this frame
// Always award points for destroying a fireball, even after 70 points
updateScore(score + 3); // Award 3 points for destroying a fireball
fireball.destroy();
window.fireballs.splice(f, 1);
continue;
}
fireball.lastLife = fireball.life;
// Collision with player
if (fireball.intersects(playerCar)) {
if (playerCar.shieldActive) {
// Ignore collision, let player pass through fireball while shield is active
// Do NOT destroy the fireball, just let it continue
continue;
} else {
LK.getSound('crash').play();
LK.effects.flashScreen(0xff3300, 900);
LK.effects.flashObject(playerCar, 0xff6600, 700);
LK.showGameOver();
return;
}
}
fireball.lastY = fireball.y;
}
// --- HELLBOX SPAWNING & COLLISION HANDLING ---
// Find all HellBoxes in the game (if any)
if (!window.hellBoxes) window.hellBoxes = [];
if (typeof window.lastHellBoxSpawnTick === "undefined") window.lastHellBoxSpawnTick = -1000;
if (typeof window.hellBoxActive === "undefined") window.hellBoxActive = false;
if (typeof window.hellBoxObj === "undefined") window.hellBoxObj = null;
// HellBox spawn logic (like spell bottle)
// Prevent HellBox from spawning after 70 points
if (!window.hellBoxActive && score <= 70) {
// Only allow spawn if enough time has passed since last spawn
var hellBoxCooldown = score >= 50 ? 400 : 800; // 6.6s if score>=50, else 13s
var hellBoxChance = score >= 50 ? 1 / 90 : 1 / 180; // 1/90 per frame if score>=50, else 1/180
if (ticksSinceStart - window.lastHellBoxSpawnTick > hellBoxCooldown) {
if (Math.random() < hellBoxChance) {
var hellBoxLane = Math.floor(Math.random() * laneCount);
var hellBoxX = lanes[hellBoxLane];
// Place the HellBox at a random Y between 700 and 1200
var hellBoxY = 700 + Math.floor(Math.random() * 500);
var hellBox = LK.getAsset('HellBox', {
anchorX: 0.5,
anchorY: 1.0,
x: hellBoxX,
y: hellBoxY
});
game.addChild(hellBox);
window.hellBoxes.push(hellBox);
window.hellBoxActive = true;
window.hellBoxObj = hellBox;
window.lastHellBoxSpawnTick = ticksSinceStart;
}
}
} else if (window.hellBoxObj) {
// Move the HellBox down the screen
window.hellBoxObj.y += 18 * gameSpeed;
// Off screen? Remove
if (window.hellBoxObj.y < -200 || window.hellBoxObj.y > 2732 + 200) {
window.hellBoxObj.destroy();
var idx = window.hellBoxes.indexOf(window.hellBoxObj);
if (idx !== -1) window.hellBoxes.splice(idx, 1);
window.hellBoxObj = null;
window.hellBoxActive = false;
}
}
// HellBox collision and cleanup
for (var h = window.hellBoxes.length - 1; h >= 0; h--) {
var hellBox = window.hellBoxes[h];
if (!hellBox) continue;
// Update position if needed (if HellBox has .update)
if (typeof hellBox.update === "function") hellBox.update();
// Off screen? Remove
if (hellBox.y > 2732 + 200) {
hellBox.destroy();
window.hellBoxes.splice(h, 1);
if (window.hellBoxObj === hellBox) {
window.hellBoxObj = null;
window.hellBoxActive = false;
}
continue;
}
// Collision with player
if (hellBox.intersects(playerCar)) {
// Player can collect HellBox if shield is active
// Always heal and destroy HellBox on collision, regardless of shield
var healAmount = 1;
if (playerLife < playerMaxLife) {
playerLife = Math.min(playerLife + healAmount, playerMaxLife);
playerCar.life = playerLife;
updateLifeBar();
LK.effects.flashObject(playerCar, 0x00ff00, 400);
}
hellBox.destroy();
window.hellBoxes.splice(h, 1);
if (window.hellBoxObj === hellBox) {
window.hellBoxObj = null;
window.hellBoxActive = false;
}
continue;
}
}
;
// Shield timer logic
if (playerCar.shieldActive) {
playerCar.shieldTicks--;
// Show shield seconds left (rounded up)
var shieldSeconds = Math.ceil(playerCar.shieldTicks / 60);
shieldTxt.setText("Shield: " + shieldSeconds + "s");
// Show shield effect
shieldEffect.visible = true;
// Keep shield effect centered on playerCar
shieldEffect.x = playerCar.x;
shieldEffect.y = playerCar.y;
// Add rotation effect to shield
if (typeof shieldEffect.rotation === "undefined") shieldEffect.rotation = 0;
// Limit rotation to a small range (e.g., oscillate between -0.25 and 0.25 radians)
if (typeof shieldEffect._rotDir === "undefined") shieldEffect._rotDir = 1;
var maxRot = 0.25;
var minRot = -0.25;
var rotStep = 0.018; // much slower, gentle oscillation
shieldEffect.rotation += rotStep * shieldEffect._rotDir;
if (shieldEffect.rotation > maxRot) {
shieldEffect.rotation = maxRot;
shieldEffect._rotDir = -1;
} else if (shieldEffect.rotation < minRot) {
shieldEffect.rotation = minRot;
shieldEffect._rotDir = 1;
}
if (playerCar.shieldTicks <= 0) {
playerCar.shieldActive = false;
playerCar.shieldTicks = 0;
shieldTxt.setText('');
shieldEffect.visible = false;
}
} else {
shieldTxt.setText('');
shieldEffect.visible = false;
}
// --- LIFE BAR UI update ---
// Draw a simple, always-visible horizontal health bar above the player car
if (!window.playerHealthBar) {
// Bar background (expanded, taller)
window.playerHealthBarBg = LK.getAsset('lifeBarBg', {
anchorX: 0.5,
anchorY: 0.5,
width: 520,
height: 120,
//{4H} // Increased height for taller bar
alpha: 0.85
});
// Bar fill (expanded, taller)
window.playerHealthBarFill = LK.getAsset('lifeBar', {
anchorX: 0.0,
anchorY: 0.5,
width: 480,
height: 90,
// Increased height for taller bar
x: -240,
alpha: 1
});
// Bar border (expanded, taller)
window.playerHealthBarBorder = LK.getAsset('lifeBarBorder', {
anchorX: 0.5,
anchorY: 0.5,
width: 560,
height: 130,
// Increased height for taller border
alpha: 1
});
// Heart icon (expanded)
// Removed the magic thing (spell bottle) to the left of the health bar by not adding any extra icon here
window.playerHealthBarHeart = LK.getAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80,
x: -320,
alpha: 1
});
// Add to game
game.addChild(window.playerHealthBarBg);
game.addChild(window.playerHealthBarFill);
game.addChild(window.playerHealthBarBorder);
// Removed: game.addChild(window.playerHealthBarHeart);
window.playerHealthBar = true;
}
// Always show the health bar unless dead
var showBar = playerLife > 0;
window.playerHealthBarBg.visible = showBar;
window.playerHealthBarFill.visible = showBar;
window.playerHealthBarBorder.visible = showBar;
// Removed: window.playerHealthBarHeart.visible = showBar;
// Position above player car
if (playerCar) {
var barY = playerCar.y - 140;
var barX = playerCar.x;
window.playerHealthBarBg.x = barX;
window.playerHealthBarBg.y = barY;
window.playerHealthBarFill.x = barX - 240;
window.playerHealthBarFill.y = barY;
window.playerHealthBarBorder.x = barX;
window.playerHealthBarBorder.y = barY;
// Removed: window.playerHealthBarHeart.x = barX - 320;
// Removed: window.playerHealthBarHeart.y = barY;
// Set width proportional to life, always show a minimum width if not dead
var minWidth = 38;
var fillW = playerLife > 0 ? Math.max(minWidth, Math.round(480 * (playerLife / playerMaxLife))) : 0;
window.playerHealthBarFill.width = fillW;
}
// Hide if dead
if (playerLife <= 0) {
window.playerHealthBarBg.visible = false;
window.playerHealthBarFill.visible = false;
window.playerHealthBarBorder.visible = false;
// Removed: window.playerHealthBarHeart.visible = false;
}
};
// Place high score text to the left of the score text, both horizontally aligned
highScoreTxt.x = LK.gui.top.width / 2 - 220;
highScoreTxt.y = scoreTxt.height / 2 + 10;
scoreTxt.x = LK.gui.top.width / 2 - 60;
scoreTxt.y = scoreTxt.height / 2 + 10;
// Position shield timer text directly below score, moved a little to the left
shieldTxt.x = LK.gui.top.width / 2 - 80;
shieldTxt.y = scoreTxt.y + scoreTxt.height / 2 + shieldTxt.height / 2 + 10;
; ===================================================================
--- original.js
+++ change.js
@@ -146,9 +146,30 @@
self.setLane = function (laneIdx) {
self.lane = laneIdx;
// Animate to new lane position
var targetX = lanes[self.lane];
+ // Squash/stretch effect: squash horizontally, stretch vertically, then restore
+ tween.stop(self, {
+ scaleX: true,
+ scaleY: true
+ });
tween(self, {
+ scaleX: 1.18,
+ scaleY: 0.88
+ }, {
+ duration: 60,
+ easing: tween.cubicOut,
+ onFinish: function onFinish() {
+ tween(self, {
+ scaleX: 1,
+ scaleY: 1
+ }, {
+ duration: 80,
+ easing: tween.cubicOut
+ });
+ }
+ });
+ tween(self, {
x: targetX
}, {
duration: 120,
easing: tween.cubicOut,
@@ -157,8 +178,37 @@
}
});
updateLifeBar();
};
+ // Idle bounce animation for player car
+ self.idleBounce = function () {
+ // Cancel any previous bounce
+ tween.stop(self, {
+ y: true
+ });
+ // Animate up
+ tween(self, {
+ y: self.y - 18
+ }, {
+ duration: 320,
+ easing: tween.sineInOut,
+ onFinish: function onFinish() {
+ // Animate down
+ tween(self, {
+ y: self.y + 18
+ }, {
+ duration: 320,
+ easing: tween.sineInOut,
+ onFinish: function onFinish() {
+ // Loop bounce
+ self.idleBounce();
+ }
+ });
+ }
+ });
+ };
+ // Start idle bounce when created
+ self.idleBounce();
return self;
});
/****