User prompt
buat layar jadi statik ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
buat efek meledak dan flash merah saat musuh mati ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
tower mampu menyrang dari seluruh mata angin
User prompt
tower non stop menyerang saat musuh datang
User prompt
tower bisa menyerang dari 4 arah sekaligus
User prompt
perbaiki display game
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'toGlobal')' in or related to this line: 'var localPos = gameWorld.toLocal(obj.parent.toGlobal(obj.position));' Line Number: 320
Code edit (1 edits merged)
Please save this source code
User prompt
Spirit Guard: Village Defense
Initial prompt
buat game side crolling gulir ke seluruh arah. game bergenre tower defense melindungi desa dari serangan roh roh jahat
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Projectile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('projectile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.target = null; self.direction = null; self.damage = 20; self.update = function () { if (self.target && !self.target.destroyed) { // Move towards target var dx = self.target.x - self.x; var dy = self.target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 20) { self.target.takeDamage(self.damage); self.destroy(); return; } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } } else if (self.direction) { // Move in fixed direction self.x += self.direction.x * self.speed; self.y += self.direction.y * self.speed; // Check if we hit any spirit along the way for (var i = 0; i < spirits.length; i++) { var spirit = spirits[i]; var dx = spirit.x - self.x; var dy = spirit.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { spirit.takeDamage(self.damage); self.destroy(); return; } } // Destroy if projectile goes too far if (Math.abs(self.x) > 1000 || Math.abs(self.y) > 1000) { self.destroy(); return; } } else { // No target and no direction, destroy self.destroy(); return; } }; self.destroy = function () { for (var i = projectiles.length - 1; i >= 0; i--) { if (projectiles[i] === self) { projectiles.splice(i, 1); break; } } if (self.parent) { self.parent.removeChild(self); } }; return self; }); var Spirit = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('spirit', { anchorX: 0.5, anchorY: 0.5 }); self.health = 50; self.maxHealth = 50; self.speed = 1; self.gold = 10; self.targetX = villageX; self.targetY = villageY; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 5) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } else { self.reachVillage(); } }; self.takeDamage = function (damage) { self.health -= damage; LK.getSound('spirit_hit').play(); if (self.health <= 0) { self.die(); } }; self.die = function () { gold += self.gold; goldText.setText('Gold: ' + gold); LK.getSound('spirit_death').play(); // Create explosion effect - scale up and fade out tween(self, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 300, easing: tween.easeOut, onFinish: function onFinish() { // Remove from spirits array and destroy after explosion for (var i = spirits.length - 1; i >= 0; i--) { if (spirits[i] === self) { spirits.splice(i, 1); break; } } self.destroy(); } }); // Flash red effect tween(self, { tint: 0xFF0000 }, { duration: 100 }); tween(self, { tint: 0xFFFFFF }, { duration: 200 }); // Flash screen red LK.effects.flashScreen(0xFF0000, 200); }; self.reachVillage = function () { villageHealth -= 10; healthText.setText('Health: ' + villageHealth); if (villageHealth <= 0) { LK.showGameOver(); } for (var i = spirits.length - 1; i >= 0; i--) { if (spirits[i] === self) { spirits.splice(i, 1); break; } } self.destroy(); }; return self; }); var Tower = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('tower', { anchorX: 0.5, anchorY: 0.5 }); self.range = 150; self.damage = 20; self.fireRate = 60; // frames between shots self.lastShot = 0; self.level = 1; self.update = function () { self.lastShot++; if (self.lastShot >= self.fireRate) { // Shoot continuously if there are any spirits on the map if (spirits.length > 0) { self.shoot(null); // Pass null since we're shooting in all directions self.lastShot = 0; } } }; self.findTarget = function () { var closestSpirit = null; var closestDistance = self.range; for (var i = 0; i < spirits.length; i++) { var spirit = spirits[i]; var dx = spirit.x - self.x; var dy = spirit.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < closestDistance) { closestDistance = distance; closestSpirit = spirit; } } return closestSpirit; }; self.shoot = function (target) { // Fire projectiles in all 8 compass directions var directions = [{ x: 0, y: -1 }, // North { x: 0.707, y: -0.707 }, // Northeast { x: 1, y: 0 }, // East { x: 0.707, y: 0.707 }, // Southeast { x: 0, y: 1 }, // South { x: -0.707, y: 0.707 }, // Southwest { x: -1, y: 0 }, // West { x: -0.707, y: -0.707 } // Northwest ]; for (var d = 0; d < directions.length; d++) { var projectile = new Projectile(); projectile.x = self.x; projectile.y = self.y; // Always find closest spirit in this direction for targeting var dirTarget = self.findTargetInDirection(directions[d]); if (dirTarget) { projectile.target = dirTarget; } else { // Always shoot in the direction even if no specific target projectile.direction = directions[d]; } projectile.damage = self.damage; projectiles.push(projectile); gameWorld.addChild(projectile); } }; self.findTargetInDirection = function (direction) { var bestTarget = null; var bestDistance = self.range; for (var i = 0; i < spirits.length; i++) { var spirit = spirits[i]; var dx = spirit.x - self.x; var dy = spirit.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if spirit is within range if (distance <= self.range && distance < bestDistance) { // Check if spirit is roughly in the desired direction var normalizedDx = dx / distance; var normalizedDy = dy / distance; // Calculate dot product to see if spirit aligns with direction var dot = normalizedDx * direction.x + normalizedDy * direction.y; // If dot product > 0.3, spirit is in this general direction (wider angle for 8 directions) if (dot > 0.3) { bestDistance = distance; bestTarget = spirit; } } } return bestTarget; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2F4F2F }); /**** * Game Code ****/ var gameWorld = new Container(); game.addChild(gameWorld); var gameWorldX = 1024; // Center horizontally (2048/2) var gameWorldY = 1366; // Center vertically (2732/2) gameWorld.x = gameWorldX; gameWorld.y = gameWorldY; var isDragging = false; var lastDragX = 0; var lastDragY = 0; var villageX = 0; var villageY = 0; var villageHealth = 100; var gold = 100; var wave = 1; var spiritsToSpawn = 5; var spiritsSpawned = 0; var spawnTimer = 0; var towers = []; var spirits = []; var projectiles = []; var placementMode = false; var placementPreview = null; // Create village at center var village = gameWorld.attachAsset('village', { anchorX: 0.5, anchorY: 0.5, x: villageX, y: villageY }); // UI Elements var goldText = new Text2('Gold: ' + gold, { size: 80, fill: 0xFFD700 }); goldText.anchor.set(1, 0); goldText.x = -20; goldText.y = 120; LK.gui.topRight.addChild(goldText); var healthText = new Text2('Health: ' + villageHealth, { size: 80, fill: 0xFF0000 }); healthText.anchor.set(1, 0); healthText.x = -20; healthText.y = 220; LK.gui.topRight.addChild(healthText); var waveText = new Text2('Wave: ' + wave, { size: 100, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); waveText.y = 120; LK.gui.top.addChild(waveText); var buildButton = new Text2('Build Tower (50g)', { size: 80, fill: 0x00FF00 }); buildButton.anchor.set(0.5, 1); buildButton.y = -50; LK.gui.bottom.addChild(buildButton); function canPlaceTower(x, y) { // Check distance from village var dx = x - villageX; var dy = y - villageY; var distanceFromVillage = Math.sqrt(dx * dx + dy * dy); if (distanceFromVillage < 150) { return false; } // Check distance from other towers for (var i = 0; i < towers.length; i++) { var tower = towers[i]; var tdx = x - tower.x; var tdy = y - tower.y; var distanceFromTower = Math.sqrt(tdx * tdx + tdy * tdy); if (distanceFromTower < 80) { return false; } } return true; } function spawnSpirit() { if (spiritsSpawned >= spiritsToSpawn) { return; } var spirit = new Spirit(); // Spawn from random edge var edge = Math.floor(Math.random() * 4); var mapSize = 1500; switch (edge) { case 0: // Top spirit.x = Math.random() * mapSize - mapSize / 2; spirit.y = -mapSize / 2; break; case 1: // Right spirit.x = mapSize / 2; spirit.y = Math.random() * mapSize - mapSize / 2; break; case 2: // Bottom spirit.x = Math.random() * mapSize - mapSize / 2; spirit.y = mapSize / 2; break; case 3: // Left spirit.x = -mapSize / 2; spirit.y = Math.random() * mapSize - mapSize / 2; break; } spirits.push(spirit); gameWorld.addChild(spirit); spiritsSpawned++; } function nextWave() { if (spirits.length === 0 && spiritsSpawned >= spiritsToSpawn) { wave++; spiritsToSpawn = 5 + wave * 2; spiritsSpawned = 0; spawnTimer = 0; waveText.setText('Wave: ' + wave); // Bonus gold for completing wave gold += 20; goldText.setText('Gold: ' + gold); } } game.down = function (x, y, obj) { var localPos = gameWorld.toLocal(game.toGlobal({ x: x, y: y })); if (placementMode) { if (canPlaceTower(localPos.x, localPos.y) && gold >= 50) { var tower = new Tower(); tower.x = localPos.x; tower.y = localPos.y; towers.push(tower); gameWorld.addChild(tower); gold -= 50; goldText.setText('Gold: ' + gold); LK.getSound('place_tower').play(); placementMode = false; if (placementPreview) { placementPreview.destroy(); placementPreview = null; } } } else { isDragging = true; lastDragX = x; lastDragY = y; } }; game.move = function (x, y, obj) { var localPos = gameWorld.toLocal(game.toGlobal({ x: x, y: y })); if (placementMode) { if (placementPreview) { placementPreview.destroy(); } if (canPlaceTower(localPos.x, localPos.y)) { placementPreview = gameWorld.attachAsset('validPlacement', { anchorX: 0.5, anchorY: 0.5, x: localPos.x, y: localPos.y, alpha: 0.5 }); } else { placementPreview = gameWorld.attachAsset('invalidPlacement', { anchorX: 0.5, anchorY: 0.5, x: localPos.x, y: localPos.y, alpha: 0.5 }); } } else if (isDragging) { var deltaX = x - lastDragX; var deltaY = y - lastDragY; gameWorldX += deltaX; gameWorldY += deltaY; // Limit scrolling gameWorldX = Math.max(-1000, Math.min(1000, gameWorldX)); gameWorldY = Math.max(-1000, Math.min(1000, gameWorldY)); gameWorld.x = gameWorldX; gameWorld.y = gameWorldY; lastDragX = x; lastDragY = y; } }; game.up = function (x, y, obj) { isDragging = false; }; buildButton.down = function (x, y, obj) { if (gold >= 50) { placementMode = !placementMode; if (!placementMode && placementPreview) { placementPreview.destroy(); placementPreview = null; } } }; game.update = function () { // Spawn spirits spawnTimer++; if (spawnTimer >= 120) { // Spawn every 2 seconds spawnSpirit(); spawnTimer = 0; } // Check for next wave nextWave(); // Update all game objects for (var i = 0; i < towers.length; i++) { towers[i].update(); } for (var i = 0; i < spirits.length; i++) { spirits[i].update(); } for (var i = 0; i < projectiles.length; i++) { projectiles[i].update(); } };
===================================================================
--- original.js
+++ change.js
@@ -103,15 +103,40 @@
self.die = function () {
gold += self.gold;
goldText.setText('Gold: ' + gold);
LK.getSound('spirit_death').play();
- for (var i = spirits.length - 1; i >= 0; i--) {
- if (spirits[i] === self) {
- spirits.splice(i, 1);
- break;
+ // Create explosion effect - scale up and fade out
+ tween(self, {
+ scaleX: 3,
+ scaleY: 3,
+ alpha: 0
+ }, {
+ duration: 300,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Remove from spirits array and destroy after explosion
+ for (var i = spirits.length - 1; i >= 0; i--) {
+ if (spirits[i] === self) {
+ spirits.splice(i, 1);
+ break;
+ }
+ }
+ self.destroy();
}
- }
- self.destroy();
+ });
+ // Flash red effect
+ tween(self, {
+ tint: 0xFF0000
+ }, {
+ duration: 100
+ });
+ tween(self, {
+ tint: 0xFFFFFF
+ }, {
+ duration: 200
+ });
+ // Flash screen red
+ LK.effects.flashScreen(0xFF0000, 200);
};
self.reachVillage = function () {
villageHealth -= 10;
healthText.setText('Health: ' + villageHealth);
image top down hutan pinus luas yang ditengahnya terdapat kerajaan peri daun. 2d anime. In-Game asset. 2d. High contrast. No shadows
kesatria peri pria seluruh badan bercahaya melayang dengan tangan merengggang keatas. In-Game asset. 2d. High contrast. No shadows
roh jahat berbentuk orb gelap. In-Game asset. 2d. High contrast. No shadows
roh jahat bentuk orb api. In-Game asset. 2d. High contrast. No shadows