User prompt
When choosing a plugin, creatures should not cause damage, and add more plugins, add plugins that give area damage, etc., and add a background model, I will change it. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
They die without touching me. Add a bullet model and when choosing an add-on, the creatures should not cause damage.
User prompt
There is a level up tab, add it as a model, I will change it and the bullet model should go directly to the man, not as a straight png, but straight
User prompt
add a level screen to show how many levels we have or how much of the other level is left and add an assets screen compatible with the police
User prompt
The issue is that when we get a skill, it should not go to living beings, it should come only to us, and also to living beings with weapons in their hands, and it should slow down the very fast speeds of the living beings a little.
User prompt
Reduce the number of living creatures coming, but increase it by 1 click after 3 minutes, so that it constantly uses weapons, and the gun barrel points to living creatures.
User prompt
Let the gun barrel also look at the creatures, and while you buy the add-ons, the game should pause, and the creatures should be a little smarter, and the creatures should come in stages.
User prompt
Let the gun be visible in our hand and have a bullet model when firing
User prompt
Let the creatures come a little later and give us a weapon model
User prompt
Please fix the bug: 'prepareWave is not defined' in or related to this line: 'prepareWave();' Line Number: 758
User prompt
Knk, look, creatures should behave a little differently from each other because when creatures are gathered somewhere and revolve around them, they cannot do anything. Also, they should come continuously, not in waves, but continuously and without stopping. Also, the game should wait like this while purchasing add-ons.
User prompt
Knk, look, the creatures should behave a little differently from each other because when you gather the creatures somewhere, for example, they cannot do anything when they revolve around you. Also, they should come continuously, not in waves, but continuously and without stopping. Also, the game should wait like this while purchasing add-ons.
User prompt
Let it be a continuous live stream and add something like artificial intelligence to the creatures so that it is not so stupid
User prompt
Don't let the creatures come all at once, let them come continuously and expand the playing area a little.
User prompt
Don't let other creatures come so much, let them increase over time, and make sure that almost no bullets miss. Make a better weapon's mechanic to turn into creatures.
Code edit (1 edits merged)
Please save this source code
Initial prompt
Beast Survivors
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Creature projectile class with unique behaviors
var CreatureProjectile = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('creatureProj', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 32;
self.speed = 32;
self.angle = 0;
self.damage = 1;
// Assign a unique behavior type to each projectile
var behaviorTypes = ['homing', 'zigzag', 'boomerang', 'spiral', 'dash'];
self.behavior = behaviorTypes[Math.floor(Math.random() * behaviorTypes.length)];
self.behaviorSeed = Math.random() * 10000;
self.behaviorTick = 0;
self.originX = self.x;
self.originY = self.y;
self.maxDistance = 1200 + Math.random() * 400;
self.update = function () {
self.behaviorTick++;
// --- Unique behaviors ---
if (self.behavior === 'homing') {
// Homing: as before, but with a little more erratic movement
var nearest = null;
var minDist = 999999;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
nearest = e;
}
}
if (nearest && minDist > 1) {
var lead = 0.18 + Math.random() * 0.08;
var ex = nearest.x,
ey = nearest.y;
if (typeof nearest.lastX === "number" && typeof nearest.lastY === "number") {
var vx = nearest.x - nearest.lastX;
var vy = nearest.y - nearest.lastY;
ex += vx * lead * (minDist / 64);
ey += vy * lead * (minDist / 64);
}
var targetAngle = Math.atan2(ey - self.y, ex - self.x);
// Add a little random wiggle
targetAngle += Math.sin(self.behaviorTick * 0.13 + self.behaviorSeed) * 0.12;
// Smoothly turn toward target
var da = targetAngle - self.angle;
while (da > Math.PI) da -= Math.PI * 2;
while (da < -Math.PI) da += Math.PI * 2;
var turnRate = 0.18 + Math.min(0.32, minDist / 400 * 0.25);
self.angle += da * turnRate;
}
// Move forward
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
} else if (self.behavior === 'zigzag') {
// Zigzag: moves forward, but oscillates side to side
var freq = 0.18 + self.behaviorSeed % 0.2;
var amp = 48 + self.behaviorSeed % 32;
var zig = Math.sin(self.behaviorTick * freq + self.behaviorSeed) * amp;
self.x += Math.cos(self.angle) * self.speed + Math.cos(self.angle + Math.PI / 2) * zig * 0.08;
self.y += Math.sin(self.angle) * self.speed + Math.sin(self.angle + Math.PI / 2) * zig * 0.08;
} else if (self.behavior === 'boomerang') {
// Boomerang: flies out, then returns to player
var t = self.behaviorTick;
var outTime = 32 + self.behaviorSeed % 32;
if (t < outTime) {
self.x += Math.cos(self.angle) * self.speed;
self.y += Math.sin(self.angle) * self.speed;
} else {
// Return to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed * 1.2;
self.y += dy / dist * self.speed * 1.2;
}
}
} else if (self.behavior === 'spiral') {
// Spiral: moves in a spiral pattern
var spiralSpeed = self.speed * 0.7;
var spiralRadius = 80 + self.behaviorSeed % 40;
var spiralAngle = self.angle + self.behaviorTick * 0.13;
self.x += Math.cos(self.angle) * spiralSpeed + Math.cos(spiralAngle) * spiralRadius * 0.04;
self.y += Math.sin(self.angle) * spiralSpeed + Math.sin(spiralAngle) * spiralRadius * 0.04;
} else if (self.behavior === 'dash') {
// Dash: moves very fast in a straight line, then slows and homes
if (self.behaviorTick < 18) {
self.x += Math.cos(self.angle) * self.speed * 2.2;
self.y += Math.sin(self.angle) * self.speed * 2.2;
} else {
// Home in after dash
var nearest = null;
var minDist = 999999;
for (var i = 0; i < enemies.length; i++) {
var e = enemies[i];
var dx = e.x - self.x;
var dy = e.y - self.y;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
nearest = e;
}
}
if (nearest && minDist > 1) {
var targetAngle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
var da = targetAngle - self.angle;
while (da > Math.PI) da -= Math.PI * 2;
while (da < -Math.PI) da += Math.PI * 2;
self.angle += da * 0.22;
}
self.x += Math.cos(self.angle) * self.speed * 0.8;
self.y += Math.sin(self.angle) * self.speed * 0.8;
}
}
// Save last position for AI
self.lastX = self.x;
self.lastY = self.y;
// Remove projectile if it has traveled too far from its origin
var dx0 = self.x - self.originX;
var dy0 = self.y - self.originY;
if (dx0 * dx0 + dy0 * dy0 > self.maxDistance * self.maxDistance) {
self.destroy();
}
};
return self;
});
// Creature weapon class (auto-attacks for player)
var CreatureWeapon = Container.expand(function () {
var self = Container.call(this);
// Attach asset (box, yellow)
var asset = self.attachAsset('creature', {
anchorX: 0.5,
anchorY: 0.5
});
// Weapon stats
self.damage = 1;
self.speed = 32;
self.cooldown = 60; // frames between shots
self.timer = 0;
self.projectileCount = 1;
self.spread = 0.2; // radians
// Update method: fires projectiles if timer is up
self.update = function () {
if (self.timer > 0) {
self.timer--;
} else {
self.fire();
self.timer = self.cooldown;
}
};
// Fire projectiles in a spread
self.fire = function () {
// Projectiles orbit and launch from around the player
var now = Date.now();
for (var i = 0; i < self.projectileCount; i++) {
// Orbit offset for each projectile
var orbitAngle = 2 * Math.PI / self.projectileCount * i + now % 1000 / 1000 * Math.PI * 2;
var launchDist = 120 + Math.random() * 30;
var px = player.x + Math.cos(orbitAngle) * launchDist;
var py = player.y + Math.sin(orbitAngle) * launchDist;
// Clamp projectile spawn to play area
if (px < 0) px = 0;
if (px > GAME_WIDTH) px = GAME_WIDTH;
if (py < 0) py = 0;
if (py > GAME_HEIGHT) py = GAME_HEIGHT;
// Aim at nearest enemy if possible
var target = null;
var minDist = 999999;
for (var j = 0; j < enemies.length; j++) {
var e = enemies[j];
var dx = e.x - px;
var dy = e.y - py;
var d = Math.sqrt(dx * dx + dy * dy);
if (d < minDist) {
minDist = d;
target = e;
}
}
var angle;
if (target) {
angle = Math.atan2(target.y - py, target.x - px);
} else {
angle = orbitAngle;
}
var proj = new CreatureProjectile();
proj.x = px;
proj.y = py;
proj.angle = angle;
proj.speed = self.speed;
proj.damage = self.damage;
projectiles.push(proj);
game.addChild(proj);
}
};
// Upgrade weapon
self.upgrade = function (type) {
if (type === 'damage') self.damage++;
if (type === 'speed') self.speed += 6;
if (type === 'cooldown' && self.cooldown > 20) self.cooldown -= 8;
if (type === 'projectile') self.projectileCount++;
if (type === 'spread') self.spread += 0.1;
};
return self;
});
// Enemy class
var Enemy = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 48;
self.speed = 6 + Math.random() * 3;
self.hp = 2 + Math.floor(wave * 0.5);
self.maxHp = self.hp;
// For movement
self.update = function () {
// Save last position for AI
self.lastX = self.x;
self.lastY = self.y;
// Move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
};
// Take damage
self.takeDamage = function (amount) {
self.hp -= amount;
LK.effects.flashObject(self, 0xffff00, 200);
if (self.hp <= 0) {
spawnExp(self.x, self.y, 1 + Math.floor(wave * 0.2));
self.destroy();
return true;
}
return false;
};
return self;
});
// Experience gem class
var ExpGem = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('expGem', {
anchorX: 0.5,
anchorY: 0.5
});
self.radius = 28;
self.value = 1;
self.update = function () {
// If close to player, move towards player
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 300) {
self.x += dx / dist * 18;
self.y += dy / dist * 18;
}
};
return self;
});
// Player character class
var Player = Container.expand(function () {
var self = Container.call(this);
// Attach player asset (ellipse, green)
var playerAsset = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Set up player stats
self.radius = 60; // for collision
self.speed = 18; // movement speed per frame
self.level = 1;
self.exp = 0;
self.expToLevel = 10;
self.hp = 3;
self.maxHp = 3;
// For movement smoothing
self.targetX = GAME_WIDTH / 2;
self.targetY = GAME_HEIGHT / 2;
// For invulnerability after hit
self.invulnTicks = 0;
// Update method
self.update = function () {
// Smoothly move towards target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.speed) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
} else {
self.x = self.targetX;
self.y = self.targetY;
}
// Clamp to game area
if (self.x < self.radius) self.x = self.radius;
if (self.x > GAME_WIDTH - self.radius) self.x = GAME_WIDTH - self.radius;
if (self.y < self.radius) self.y = self.radius;
if (self.y > GAME_HEIGHT - self.radius) self.y = GAME_HEIGHT - self.radius;
// Invulnerability timer
if (self.invulnTicks > 0) {
self.invulnTicks--;
playerAsset.alpha = self.invulnTicks % 10 < 5 ? 0.5 : 1;
} else {
playerAsset.alpha = 1;
}
};
// Level up
self.gainExp = function (amount) {
self.exp += amount;
while (self.exp >= self.expToLevel) {
self.exp -= self.expToLevel;
self.level++;
self.expToLevel = Math.floor(self.expToLevel * 1.3 + 5);
showLevelUp();
}
updateExpBar();
};
// Take damage
self.takeDamage = function () {
if (self.invulnTicks > 0) return;
self.hp--;
updateHpBar();
self.invulnTicks = 60;
LK.effects.flashObject(self, 0xff0000, 500);
if (self.hp <= 0) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
// --- Global variables ---
var player;
var creatureWeapon;
var projectiles = [];
var enemies = [];
var expGems = [];
var wave = 1; // still used for scaling difficulty
var dragNode = null;
// --- GUI Elements ---
var scoreTxt = new Text2('0', {
size: 100,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var expBar = new Text2('', {
size: 60,
fill: 0xAAFF00
});
expBar.anchor.set(0.5, 0);
LK.gui.top.addChild(expBar);
var hpBar = new Text2('', {
size: 60,
fill: 0xFF4444
});
hpBar.anchor.set(0.5, 0);
LK.gui.top.addChild(hpBar);
function updateExpBar() {
expBar.setText('LV ' + player.level + ' EXP: ' + player.exp + '/' + player.expToLevel);
}
function updateHpBar() {
hpBar.setText('HP: ' + player.hp + '/' + player.maxHp);
}
function updateScore() {
scoreTxt.setText('Wave: ' + wave);
}
// --- Asset initialization (shapes) ---
// --- Game setup ---
player = new Player();
// Expanded play area
var GAME_WIDTH = 3072;
var GAME_HEIGHT = 4096;
player.x = GAME_WIDTH / 2;
player.y = GAME_HEIGHT / 2;
game.addChild(player);
creatureWeapon = new CreatureWeapon();
game.addChild(creatureWeapon);
updateExpBar();
updateHpBar();
updateScore();
// --- Level up popup ---
var levelUpPopup = null;
function showLevelUp() {
if (levelUpPopup) return; // Already showing
// Randomly pick 3 upgrades
var upgrades = [{
type: 'damage',
label: 'Creature Damage +'
}, {
type: 'speed',
label: 'Creature Speed +'
}, {
type: 'cooldown',
label: 'Faster Attacks'
}, {
type: 'projectile',
label: 'More Projectiles'
}, {
type: 'spread',
label: 'Wider Spread'
}];
// Shuffle and pick 3
for (var i = upgrades.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var t = upgrades[i];
upgrades[i] = upgrades[j];
upgrades[j] = t;
}
var options = upgrades.slice(0, 3);
// Popup container
levelUpPopup = new Container();
var bg = LK.getAsset('levelUpBg', {
width: 900,
height: 700,
color: 0x222244,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
levelUpPopup.addChild(bg);
bg.x = 0;
bg.y = 0;
var title = new Text2('LEVEL UP!', {
size: 120,
fill: "#fff"
});
title.anchor.set(0.5, 0);
title.x = 0;
title.y = -300;
levelUpPopup.addChild(title);
// Option buttons
for (var i = 0; i < 3; i++) {
(function (idx) {
var opt = options[idx];
var btn = LK.getAsset('levelUpBtn' + idx, {
width: 700,
height: 140,
color: 0x4444aa,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
btn.x = 0;
btn.y = -80 + idx * 180;
levelUpPopup.addChild(btn);
var txt = new Text2(opt.label, {
size: 70,
fill: "#fff"
});
txt.anchor.set(0.5, 0.5);
txt.x = 0;
txt.y = btn.y;
levelUpPopup.addChild(txt);
btn.down = function (x, y, obj) {
creatureWeapon.upgrade(opt.type);
// Remove popup
LK.gui.center.removeChild(levelUpPopup);
levelUpPopup = null;
};
})(i);
}
levelUpPopup.x = 0;
levelUpPopup.y = 0;
LK.gui.center.addChild(levelUpPopup);
}
// --- Spawn experience gem ---
function spawnExp(x, y, value) {
var gem = new ExpGem();
gem.x = x;
gem.y = y;
gem.value = value;
expGems.push(gem);
game.addChild(gem);
}
// --- Continuous enemy spawn system ---
var spawnDelay = 32; // frames between spawns, will decrease as wave increases
var spawnDelayTimer = 0;
function updateSpawnRate() {
// Increase spawn rate over time, but clamp to a minimum
spawnDelay = Math.max(4, 32 - Math.floor(wave * 1.2));
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn at random edge
var edge = Math.floor(Math.random() * 4);
if (edge === 0) {
// top
enemy.x = Math.random() * GAME_WIDTH;
enemy.y = -100;
} else if (edge === 1) {
// bottom
enemy.x = Math.random() * GAME_WIDTH;
enemy.y = GAME_HEIGHT + 100;
} else if (edge === 2) {
// left
enemy.x = -100;
enemy.y = Math.random() * GAME_HEIGHT;
} else {
// right
enemy.x = GAME_WIDTH + 100;
enemy.y = Math.random() * GAME_HEIGHT;
}
enemies.push(enemy);
game.addChild(enemy);
}
// --- Game move handler (touch/mouse drag to move player) ---
function handleMove(x, y, obj) {
// Don't allow movement if level up popup is open
if (levelUpPopup) return;
// Clamp to avoid top left menu
if (x < 100) x = 100;
if (y < 0) y = 0;
player.targetX = x;
player.targetY = y;
}
game.move = handleMove;
game.down = function (x, y, obj) {
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
// No dragNode needed, movement is always allowed
};
// --- Main game update loop ---
game.update = function () {
// Pause all game logic except GUI and popup when level up popup is open
if (levelUpPopup) {
// Only update GUI, skip all gameplay logic
return;
}
// Update player
player.update();
// Update weapon
creatureWeapon.update();
// Update projectiles
for (var i = projectiles.length - 1; i >= 0; i--) {
var proj = projectiles[i];
proj.update();
// Remove if out of bounds
if (proj.x < -100 || proj.x > GAME_WIDTH + 100 || proj.y < -100 || proj.y > GAME_HEIGHT + 100) {
proj.destroy();
projectiles.splice(i, 1);
continue;
}
// Check collision with enemies
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
var dx = proj.x - enemy.x;
var dy = proj.y - enemy.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < proj.radius + enemy.radius) {
var killed = enemy.takeDamage(proj.damage);
proj.destroy();
projectiles.splice(i, 1);
if (killed) {
enemies.splice(j, 1);
}
break;
}
}
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.update();
// Check collision with player
var dx = enemy.x - player.x;
var dy = enemy.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < enemy.radius + player.radius) {
player.takeDamage();
// Knockback enemy
var angle = Math.atan2(dy, dx);
enemy.x += Math.cos(angle) * 120;
enemy.y += Math.sin(angle) * 120;
}
}
// Update exp gems
for (var i = expGems.length - 1; i >= 0; i--) {
var gem = expGems[i];
gem.update();
// Collect if close to player
var dx = gem.x - player.x;
var dy = gem.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < gem.radius + player.radius) {
player.gainExp(gem.value);
gem.destroy();
expGems.splice(i, 1);
}
}
// Continuous enemy spawn (no waves)
if (!levelUpPopup) {
spawnDelayTimer++;
if (spawnDelayTimer >= spawnDelay) {
spawnEnemy();
spawnDelayTimer = 0;
// Increase difficulty over time
if (LK.ticks % (60 * 10) === 0) {
// every 10 seconds
wave++;
updateScore();
updateSpawnRate();
}
}
}
};
// --- GUI positioning ---
scoreTxt.x = GAME_WIDTH / 2;
scoreTxt.y = 20;
expBar.x = GAME_WIDTH / 2;
expBar.y = 140;
hpBar.x = GAME_WIDTH / 2;
hpBar.y = 220;
// --- Start first wave ---
prepareWave(); ===================================================================
--- original.js
+++ change.js
@@ -5,9 +5,9 @@
/****
* Classes
****/
-// Creature projectile class
+// Creature projectile class with unique behaviors
var CreatureProjectile = Container.expand(function () {
var self = Container.call(this);
var asset = self.attachAsset('creatureProj', {
anchorX: 0.5,
@@ -16,57 +16,126 @@
self.radius = 32;
self.speed = 32;
self.angle = 0;
self.damage = 1;
+ // Assign a unique behavior type to each projectile
+ var behaviorTypes = ['homing', 'zigzag', 'boomerang', 'spiral', 'dash'];
+ self.behavior = behaviorTypes[Math.floor(Math.random() * behaviorTypes.length)];
+ self.behaviorSeed = Math.random() * 10000;
+ self.behaviorTick = 0;
+ self.originX = self.x;
+ self.originY = self.y;
+ self.maxDistance = 1200 + Math.random() * 400;
self.update = function () {
- // --- Smarter AI for creature projectiles ---
- // 1. Find nearest enemy, but also consider if projectile is stuck or circling
- var nearest = null;
- var minDist = 999999;
- for (var i = 0; i < enemies.length; i++) {
- var e = enemies[i];
- var dx = e.x - self.x;
- var dy = e.y - self.y;
- var d = Math.sqrt(dx * dx + dy * dy);
- if (d < minDist) {
- minDist = d;
- nearest = e;
+ self.behaviorTick++;
+ // --- Unique behaviors ---
+ if (self.behavior === 'homing') {
+ // Homing: as before, but with a little more erratic movement
+ var nearest = null;
+ var minDist = 999999;
+ for (var i = 0; i < enemies.length; i++) {
+ var e = enemies[i];
+ var dx = e.x - self.x;
+ var dy = e.y - self.y;
+ var d = Math.sqrt(dx * dx + dy * dy);
+ if (d < minDist) {
+ minDist = d;
+ nearest = e;
+ }
}
- }
- // 2. If no enemy, keep moving in current direction
- if (nearest && minDist > 1) {
- // Predictive homing: aim slightly ahead of moving enemy
- var lead = 0.18 + Math.random() * 0.08; // lead factor, makes AI less dumb
- var ex = nearest.x,
- ey = nearest.y;
- if (typeof nearest.lastX === "number" && typeof nearest.lastY === "number") {
- var vx = nearest.x - nearest.lastX;
- var vy = nearest.y - nearest.lastY;
- ex += vx * lead * (minDist / 64);
- ey += vy * lead * (minDist / 64);
+ if (nearest && minDist > 1) {
+ var lead = 0.18 + Math.random() * 0.08;
+ var ex = nearest.x,
+ ey = nearest.y;
+ if (typeof nearest.lastX === "number" && typeof nearest.lastY === "number") {
+ var vx = nearest.x - nearest.lastX;
+ var vy = nearest.y - nearest.lastY;
+ ex += vx * lead * (minDist / 64);
+ ey += vy * lead * (minDist / 64);
+ }
+ var targetAngle = Math.atan2(ey - self.y, ex - self.x);
+ // Add a little random wiggle
+ targetAngle += Math.sin(self.behaviorTick * 0.13 + self.behaviorSeed) * 0.12;
+ // Smoothly turn toward target
+ var da = targetAngle - self.angle;
+ while (da > Math.PI) da -= Math.PI * 2;
+ while (da < -Math.PI) da += Math.PI * 2;
+ var turnRate = 0.18 + Math.min(0.32, minDist / 400 * 0.25);
+ self.angle += da * turnRate;
}
- var targetAngle = Math.atan2(ey - self.y, ex - self.x);
- // Obstacle avoidance: if about to hit edge, bias angle away from wall
- var margin = 80;
- if (self.x < margin) targetAngle = 0;
- if (self.x > GAME_WIDTH - margin) targetAngle = Math.PI;
- if (self.y < margin) targetAngle = Math.PI / 2;
- if (self.y > GAME_HEIGHT - margin) targetAngle = -Math.PI / 2;
- // Smoothly turn toward target
- var da = targetAngle - self.angle;
- while (da > Math.PI) da -= Math.PI * 2;
- while (da < -Math.PI) da += Math.PI * 2;
- // Adaptive turn rate: turn faster if far from target, slower if close
- var turnRate = 0.18 + Math.min(0.32, minDist / 400 * 0.25);
- self.angle += da * turnRate;
- } else {
- // No enemy, keep moving
+ // Move forward
+ self.x += Math.cos(self.angle) * self.speed;
+ self.y += Math.sin(self.angle) * self.speed;
+ } else if (self.behavior === 'zigzag') {
+ // Zigzag: moves forward, but oscillates side to side
+ var freq = 0.18 + self.behaviorSeed % 0.2;
+ var amp = 48 + self.behaviorSeed % 32;
+ var zig = Math.sin(self.behaviorTick * freq + self.behaviorSeed) * amp;
+ self.x += Math.cos(self.angle) * self.speed + Math.cos(self.angle + Math.PI / 2) * zig * 0.08;
+ self.y += Math.sin(self.angle) * self.speed + Math.sin(self.angle + Math.PI / 2) * zig * 0.08;
+ } else if (self.behavior === 'boomerang') {
+ // Boomerang: flies out, then returns to player
+ var t = self.behaviorTick;
+ var outTime = 32 + self.behaviorSeed % 32;
+ if (t < outTime) {
+ self.x += Math.cos(self.angle) * self.speed;
+ self.y += Math.sin(self.angle) * self.speed;
+ } else {
+ // Return to player
+ var dx = player.x - self.x;
+ var dy = player.y - self.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ if (dist > 0) {
+ self.x += dx / dist * self.speed * 1.2;
+ self.y += dy / dist * self.speed * 1.2;
+ }
+ }
+ } else if (self.behavior === 'spiral') {
+ // Spiral: moves in a spiral pattern
+ var spiralSpeed = self.speed * 0.7;
+ var spiralRadius = 80 + self.behaviorSeed % 40;
+ var spiralAngle = self.angle + self.behaviorTick * 0.13;
+ self.x += Math.cos(self.angle) * spiralSpeed + Math.cos(spiralAngle) * spiralRadius * 0.04;
+ self.y += Math.sin(self.angle) * spiralSpeed + Math.sin(spiralAngle) * spiralRadius * 0.04;
+ } else if (self.behavior === 'dash') {
+ // Dash: moves very fast in a straight line, then slows and homes
+ if (self.behaviorTick < 18) {
+ self.x += Math.cos(self.angle) * self.speed * 2.2;
+ self.y += Math.sin(self.angle) * self.speed * 2.2;
+ } else {
+ // Home in after dash
+ var nearest = null;
+ var minDist = 999999;
+ for (var i = 0; i < enemies.length; i++) {
+ var e = enemies[i];
+ var dx = e.x - self.x;
+ var dy = e.y - self.y;
+ var d = Math.sqrt(dx * dx + dy * dy);
+ if (d < minDist) {
+ minDist = d;
+ nearest = e;
+ }
+ }
+ if (nearest && minDist > 1) {
+ var targetAngle = Math.atan2(nearest.y - self.y, nearest.x - self.x);
+ var da = targetAngle - self.angle;
+ while (da > Math.PI) da -= Math.PI * 2;
+ while (da < -Math.PI) da += Math.PI * 2;
+ self.angle += da * 0.22;
+ }
+ self.x += Math.cos(self.angle) * self.speed * 0.8;
+ self.y += Math.sin(self.angle) * self.speed * 0.8;
+ }
}
- self.x += Math.cos(self.angle) * self.speed;
- self.y += Math.sin(self.angle) * self.speed;
// Save last position for AI
self.lastX = self.x;
self.lastY = self.y;
+ // Remove projectile if it has traveled too far from its origin
+ var dx0 = self.x - self.originX;
+ var dy0 = self.y - self.originY;
+ if (dx0 * dx0 + dy0 * dy0 > self.maxDistance * self.maxDistance) {
+ self.destroy();
+ }
};
return self;
});
// Creature weapon class (auto-attacks for player)
@@ -294,11 +363,9 @@
var creatureWeapon;
var projectiles = [];
var enemies = [];
var expGems = [];
-var wave = 1;
-var waveTimer = 0;
-var waveInterval = 600; // frames between waves
+var wave = 1; // still used for scaling difficulty
var dragNode = null;
// --- GUI Elements ---
var scoreTxt = new Text2('0', {
size: 100,
@@ -434,17 +501,14 @@
gem.value = value;
expGems.push(gem);
game.addChild(gem);
}
-// --- Spawn enemies continuously over time ---
-var enemiesToSpawn = 0;
-var spawnDelay = 20; // frames between spawns, will decrease with wave
+// --- Continuous enemy spawn system ---
+var spawnDelay = 32; // frames between spawns, will decrease as wave increases
var spawnDelayTimer = 0;
-function prepareWave() {
- // Calculate how many enemies to spawn this wave
- enemiesToSpawn = 8 + Math.floor(wave * 2.5) + Math.floor(wave * wave * 0.15);
- spawnDelay = Math.max(6, 32 - Math.floor(wave * 1.2)); // faster spawns as wave increases
- spawnDelayTimer = 0;
+function updateSpawnRate() {
+ // Increase spawn rate over time, but clamp to a minimum
+ spawnDelay = Math.max(4, 32 - Math.floor(wave * 1.2));
}
function spawnEnemy() {
var enemy = new Enemy();
// Spawn at random edge
@@ -487,8 +551,13 @@
// No dragNode needed, movement is always allowed
};
// --- Main game update loop ---
game.update = function () {
+ // Pause all game logic except GUI and popup when level up popup is open
+ if (levelUpPopup) {
+ // Only update GUI, skip all gameplay logic
+ return;
+ }
// Update player
player.update();
// Update weapon
creatureWeapon.update();
@@ -548,27 +617,20 @@
gem.destroy();
expGems.splice(i, 1);
}
}
- // Wave spawning and continuous enemy spawn
+ // Continuous enemy spawn (no waves)
if (!levelUpPopup) {
- // If there are still enemies to spawn, spawn them over time
- if (enemiesToSpawn > 0) {
- spawnDelayTimer++;
- if (spawnDelayTimer >= spawnDelay) {
- spawnEnemy();
- enemiesToSpawn--;
- spawnDelayTimer = 0;
- }
- }
- // If all enemies for this wave are dead and none left to spawn, start next wave after interval
- if (enemies.length === 0 && enemiesToSpawn === 0) {
- waveTimer++;
- if (waveTimer > waveInterval) {
+ spawnDelayTimer++;
+ if (spawnDelayTimer >= spawnDelay) {
+ spawnEnemy();
+ spawnDelayTimer = 0;
+ // Increase difficulty over time
+ if (LK.ticks % (60 * 10) === 0) {
+ // every 10 seconds
wave++;
updateScore();
- prepareWave();
- waveTimer = 0;
+ updateSpawnRate();
}
}
}
};
police pixel art. In-Game asset. 2d. High contrast. No shadows
black person pixel art. In-Game asset. High contrast. No shadows
glock pixel art. In-Game asset. High contrast. No shadows
pixel art bullet. In-Game asset. High contrast. No shadows
money pixel art. In-Game asset. High contrast. No shadows
a police themed level up screen pixel art. In-Game asset. High contrast. No shadows