User prompt
ördek vurunca points image ı kullan ve yukarı doğru çıksın çıkarken yavaşça kaybolsun ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Every ducks give 60 point
User prompt
Crossair animation is z-index more thane ducks image
User prompt
When I shoot anywhere first crosshair image apeear the tip of the gun then show on the point of cursor ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Some of ducks move fast then others
User prompt
use the Bonus image as bonus
User prompt
Use clause iamge as cloud 3-4 times top of the screen
User prompt
Some of ducks drop someting like bonus
User prompt
Reverse the hunters turning while shooting
User prompt
Remove Rotation effect of hunters
User prompt
Remove hunters rotaion animation
User prompt
Hunters sadece 90 derece sağa ve sola yatabilsin
User prompt
Replace the Hunters image from hunter and gun image
User prompt
Silah için avcının üzerinde mantıklı bir pozisyon ayarla
User prompt
Silahın ucu ateş her zaman mousenin olduğu yöne doğru baksın ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Revers the duck face the direciton they are moving right to left and left to right
User prompt
Soldan gelen ördekler sağa bakmalı
User prompt
Front of the gun never look bottom side ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Gun rotaion is not perfect make it happen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Gun always sticky from hunters right arm ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Gun always sticky from hunters right arm ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Gun animation always to top ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The gun appear behing of hunter image
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 0; self.targetY = 0; self.speed = 25; self.travelled = 0; self.maxDistance = 200; 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.travelled < self.maxDistance) { var moveX = dx / distance * self.speed; var moveY = dy / distance * self.speed; self.x += moveX; self.y += moveY; self.travelled += Math.sqrt(moveX * moveX + moveY * moveY); } else { self.alpha -= 0.1; } }; return self; }); var Crosshair = Container.expand(function () { var self = Container.call(this); var crosshairGraphics = self.attachAsset('crosshair', { anchorX: 0.5, anchorY: 0.5 }); crosshairGraphics.alpha = 0.7; self.visible = false; return self; }); var Duck = Container.expand(function () { var self = Container.call(this); var duckGraphics = self.attachAsset('duck', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3 + Math.random() * 4; // Random speed between 3-7 self.direction = Math.random() < 0.5 ? 1 : -1; // Left or right self.verticalSpeed = (Math.random() - 0.5) * 2; // Random vertical movement self.isHit = false; self.points = 100; // Set initial position based on direction if (self.direction > 0) { self.x = -100; } else { self.x = 2148; } self.y = 300 + Math.random() * 1000; // Random height self.update = function () { if (self.isHit) return; self.x += self.speed * self.direction; self.y += self.verticalSpeed; // Add some wobble to flight pattern self.y += Math.sin(LK.ticks * 0.1 + self.x * 0.01) * 2; // Keep ducks within screen bounds vertically if (self.y < 200) { self.y = 200; self.verticalSpeed = Math.abs(self.verticalSpeed); } if (self.y > 2500) { self.y = 2500; self.verticalSpeed = -Math.abs(self.verticalSpeed); } }; self.hit = function () { if (self.isHit) return false; self.isHit = true; LK.getSound('hit').play(); // Flash effect LK.effects.flashObject(self, 0xFFFFFF, 300); // Fall down animation tween(self, { y: self.y + 200, alpha: 0, rotation: Math.PI }, { duration: 800, easing: tween.easeIn }); return true; }; return self; }); var Grass = Container.expand(function () { var self = Container.call(this); var grassGraphics = self.attachAsset('grass', { anchorX: 0.5, anchorY: 1.0 }); grassGraphics.alpha = 0.8; return self; }); var Hunter = Container.expand(function () { var self = Container.call(this); var gunGraphics = self.attachAsset('Gun', { anchorX: 0.2, anchorY: 0.8, x: 40, y: -120 }); var hunterGraphics = self.attachAsset('Hunterr', { anchorX: 0.5, anchorY: 1.0 }); self.defaultY = 0; self.shootOffset = -30; self.isLookingRight = true; self.shoot = function (targetX, targetY) { // Determine which direction to look based on target var shouldLookRight = targetX > self.x; if (shouldLookRight !== self.isLookingRight) { self.isLookingRight = shouldLookRight; // Flip hunter sprite hunterGraphics.scaleX = shouldLookRight ? 1 : -1; // Position gun on correct arm with smooth transition var newGunX = shouldLookRight ? 40 : -40; var newGunScaleX = shouldLookRight ? 1 : -1; tween(gunGraphics, { x: newGunX, scaleX: newGunScaleX }, { duration: 200, easing: tween.easeOut }); } // Calculate precise angle to target for gun rotation var gunWorldX = self.x + gunGraphics.x * hunterGraphics.scaleX; var gunWorldY = self.y + gunGraphics.y - 120; var dx = targetX - gunWorldX; var dy = targetY - gunWorldY; var angle = Math.atan2(dy, dx); // Adjust angle based on hunter direction if (!shouldLookRight) { angle = Math.PI - angle; } // Limit gun angle to reasonable shooting range var maxAngle = Math.PI / 3; // 60 degrees var minAngle = -Math.PI / 6; // -30 degrees if (shouldLookRight) { angle = Math.max(minAngle, Math.min(maxAngle, angle)); } else { // When facing left, adjust angle constraints var leftMaxAngle = Math.PI + Math.PI / 6; // 210 degrees var leftMinAngle = Math.PI - Math.PI / 3; // 120 degrees angle = Math.max(leftMinAngle, Math.min(leftMaxAngle, angle)); } // Pre-aim animation - quick aim to target tween(gunGraphics, { rotation: angle, y: gunGraphics.y - 10 // Slight lift for aiming }, { duration: 120, easing: tween.easeOut, onFinish: function onFinish() { // Shooting recoil - gun kicks back slightly tween(gunGraphics, { y: gunGraphics.y + 15, rotation: angle + (shouldLookRight ? 0.2 : -0.2) }, { duration: 80, easing: tween.easeOut, onFinish: function onFinish() { // Return gun to aimed position tween(gunGraphics, { y: -120, rotation: angle }, { duration: 150, easing: tween.easeIn }); } }); } }); // Enhanced hunter body recoil animation tween(self, { y: self.defaultY + 25, x: self.x + (shouldLookRight ? -8 : 8) // Slight backward lean }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { tween(self, { y: self.defaultY, x: 2048 / 2 // Return to center }, { duration: 250, easing: tween.easeInOut }); } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ var ducks = []; var bullets = []; var crosshair = null; var missedDucks = 0; var maxMissedDucks = 5; var consecutiveHits = 0; var waveNumber = 1; var ducksInWave = 3; var ducksSpawned = 0; var waveTimer = 0; // UI Elements var scoreTxt = new Text2('Score: 0', { size: 80, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); LK.gui.top.addChild(scoreTxt); var missedTxt = new Text2('Missed: 0/5', { size: 60, fill: 0xFF0000 }); missedTxt.anchor.set(1, 0); LK.gui.topRight.addChild(missedTxt); var waveTxt = new Text2('Wave: 1', { size: 60, fill: 0x00FF00 }); waveTxt.anchor.set(0, 0); waveTxt.x = 150; // Offset from top-left to avoid menu waveTxt.y = 50; LK.gui.topLeft.addChild(waveTxt); // Create crosshair crosshair = game.addChild(new Crosshair()); // Create hunter positioned at bottom center var hunter = game.addChild(new Hunter()); hunter.x = 2048 / 2; hunter.y = 2732 - 50; hunter.defaultY = hunter.y; // Create grass across the entire bottom of the screen var grassObjects = []; var grassWidth = 400; // Width of each grass asset var numGrassObjects = Math.ceil(2048 / grassWidth) + 1; // Cover full width plus some overlap for (var g = 0; g < numGrassObjects; g++) { var grass = game.addChild(new Grass()); grass.x = g * (grassWidth * 0.8); // Slight overlap between grass objects grass.y = 2732 - 30; grassObjects.push(grass); } function spawnDuck() { var duck = new Duck(); // Adjust speed based on wave duck.speed += waveNumber * 0.5; duck.points += waveNumber * 10; ducks.push(duck); game.addChild(duck); ducksSpawned++; } function updateScore() { scoreTxt.setText('Score: ' + LK.getScore()); } function updateMissed() { missedTxt.setText('Missed: ' + missedDucks + '/' + maxMissedDucks); } function updateWave() { waveTxt.setText('Wave: ' + waveNumber); } function checkHit(x, y) { var hitDuck = null; var minDistance = 100; // Hit radius for (var i = 0; i < ducks.length; i++) { var duck = ducks[i]; if (duck.isHit) continue; var dx = duck.x - x; var dy = duck.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < minDistance) { minDistance = distance; hitDuck = duck; } } return hitDuck; } function startNextWave() { waveNumber++; ducksSpawned = 0; ducksInWave = Math.min(3 + Math.floor(waveNumber / 2), 8); // Max 8 ducks per wave waveTimer = 0; updateWave(); } // Game controls game.down = function (x, y, obj) { // Show crosshair crosshair.x = x; crosshair.y = y; crosshair.visible = true; // Create bullet var bullet = new Bullet(); bullet.x = x; bullet.y = y; bullet.targetX = x; bullet.targetY = y; bullets.push(bullet); game.addChild(bullet); // Play shoot sound LK.getSound('shoot').play(); // Trigger hunter shooting animation hunter.shoot(x, y); // Check for hit var hitDuck = checkHit(x, y); if (hitDuck) { if (hitDuck.hit()) { consecutiveHits++; var points = hitDuck.points; // Bonus for consecutive hits if (consecutiveHits > 1) { points += consecutiveHits * 10; } LK.setScore(LK.getScore() + points); updateScore(); } } else { consecutiveHits = 0; LK.getSound('miss').play(); } // Hide crosshair after short delay LK.setTimeout(function () { crosshair.visible = false; }, 100); }; game.update = function () { waveTimer++; // Spawn ducks for current wave if (ducksSpawned < ducksInWave && waveTimer % 120 === 0) { // Spawn every 2 seconds spawnDuck(); } // Update and remove off-screen ducks for (var i = ducks.length - 1; i >= 0; i--) { var duck = ducks[i]; if (duck.lastX === undefined) duck.lastX = duck.x; if (duck.lastOffScreen === undefined) duck.lastOffScreen = false; var currentOffScreen = duck.x < -150 || duck.x > 2198; // Check if duck just went off screen if (!duck.lastOffScreen && currentOffScreen && !duck.isHit) { missedDucks++; updateMissed(); consecutiveHits = 0; if (missedDucks >= maxMissedDucks) { LK.showGameOver(); return; } } // Remove duck if it's been off screen for a while or hit and faded if (currentOffScreen && (duck.lastOffScreen || duck.isHit) || duck.alpha <= 0) { duck.destroy(); ducks.splice(i, 1); } else { duck.lastX = duck.x; duck.lastOffScreen = currentOffScreen; } } // Update and remove bullets for (var j = bullets.length - 1; j >= 0; j--) { var bullet = bullets[j]; if (bullet.alpha <= 0) { bullet.destroy(); bullets.splice(j, 1); } } // Check if wave is complete if (ducksSpawned >= ducksInWave && ducks.length === 0) { startNextWave(); } // Win condition - survive 10 waves if (waveNumber > 10) { LK.showYouWin(); } }; // Initialize first wave spawnDuck();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.speed = 25;
self.travelled = 0;
self.maxDistance = 200;
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.travelled < self.maxDistance) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
self.travelled += Math.sqrt(moveX * moveX + moveY * moveY);
} else {
self.alpha -= 0.1;
}
};
return self;
});
var Crosshair = Container.expand(function () {
var self = Container.call(this);
var crosshairGraphics = self.attachAsset('crosshair', {
anchorX: 0.5,
anchorY: 0.5
});
crosshairGraphics.alpha = 0.7;
self.visible = false;
return self;
});
var Duck = Container.expand(function () {
var self = Container.call(this);
var duckGraphics = self.attachAsset('duck', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3 + Math.random() * 4; // Random speed between 3-7
self.direction = Math.random() < 0.5 ? 1 : -1; // Left or right
self.verticalSpeed = (Math.random() - 0.5) * 2; // Random vertical movement
self.isHit = false;
self.points = 100;
// Set initial position based on direction
if (self.direction > 0) {
self.x = -100;
} else {
self.x = 2148;
}
self.y = 300 + Math.random() * 1000; // Random height
self.update = function () {
if (self.isHit) return;
self.x += self.speed * self.direction;
self.y += self.verticalSpeed;
// Add some wobble to flight pattern
self.y += Math.sin(LK.ticks * 0.1 + self.x * 0.01) * 2;
// Keep ducks within screen bounds vertically
if (self.y < 200) {
self.y = 200;
self.verticalSpeed = Math.abs(self.verticalSpeed);
}
if (self.y > 2500) {
self.y = 2500;
self.verticalSpeed = -Math.abs(self.verticalSpeed);
}
};
self.hit = function () {
if (self.isHit) return false;
self.isHit = true;
LK.getSound('hit').play();
// Flash effect
LK.effects.flashObject(self, 0xFFFFFF, 300);
// Fall down animation
tween(self, {
y: self.y + 200,
alpha: 0,
rotation: Math.PI
}, {
duration: 800,
easing: tween.easeIn
});
return true;
};
return self;
});
var Grass = Container.expand(function () {
var self = Container.call(this);
var grassGraphics = self.attachAsset('grass', {
anchorX: 0.5,
anchorY: 1.0
});
grassGraphics.alpha = 0.8;
return self;
});
var Hunter = Container.expand(function () {
var self = Container.call(this);
var gunGraphics = self.attachAsset('Gun', {
anchorX: 0.2,
anchorY: 0.8,
x: 40,
y: -120
});
var hunterGraphics = self.attachAsset('Hunterr', {
anchorX: 0.5,
anchorY: 1.0
});
self.defaultY = 0;
self.shootOffset = -30;
self.isLookingRight = true;
self.shoot = function (targetX, targetY) {
// Determine which direction to look based on target
var shouldLookRight = targetX > self.x;
if (shouldLookRight !== self.isLookingRight) {
self.isLookingRight = shouldLookRight;
// Flip hunter sprite
hunterGraphics.scaleX = shouldLookRight ? 1 : -1;
// Position gun on correct arm with smooth transition
var newGunX = shouldLookRight ? 40 : -40;
var newGunScaleX = shouldLookRight ? 1 : -1;
tween(gunGraphics, {
x: newGunX,
scaleX: newGunScaleX
}, {
duration: 200,
easing: tween.easeOut
});
}
// Calculate precise angle to target for gun rotation
var gunWorldX = self.x + gunGraphics.x * hunterGraphics.scaleX;
var gunWorldY = self.y + gunGraphics.y - 120;
var dx = targetX - gunWorldX;
var dy = targetY - gunWorldY;
var angle = Math.atan2(dy, dx);
// Adjust angle based on hunter direction
if (!shouldLookRight) {
angle = Math.PI - angle;
}
// Limit gun angle to reasonable shooting range
var maxAngle = Math.PI / 3; // 60 degrees
var minAngle = -Math.PI / 6; // -30 degrees
if (shouldLookRight) {
angle = Math.max(minAngle, Math.min(maxAngle, angle));
} else {
// When facing left, adjust angle constraints
var leftMaxAngle = Math.PI + Math.PI / 6; // 210 degrees
var leftMinAngle = Math.PI - Math.PI / 3; // 120 degrees
angle = Math.max(leftMinAngle, Math.min(leftMaxAngle, angle));
}
// Pre-aim animation - quick aim to target
tween(gunGraphics, {
rotation: angle,
y: gunGraphics.y - 10 // Slight lift for aiming
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
// Shooting recoil - gun kicks back slightly
tween(gunGraphics, {
y: gunGraphics.y + 15,
rotation: angle + (shouldLookRight ? 0.2 : -0.2)
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return gun to aimed position
tween(gunGraphics, {
y: -120,
rotation: angle
}, {
duration: 150,
easing: tween.easeIn
});
}
});
}
});
// Enhanced hunter body recoil animation
tween(self, {
y: self.defaultY + 25,
x: self.x + (shouldLookRight ? -8 : 8) // Slight backward lean
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
y: self.defaultY,
x: 2048 / 2 // Return to center
}, {
duration: 250,
easing: tween.easeInOut
});
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB
});
/****
* Game Code
****/
var ducks = [];
var bullets = [];
var crosshair = null;
var missedDucks = 0;
var maxMissedDucks = 5;
var consecutiveHits = 0;
var waveNumber = 1;
var ducksInWave = 3;
var ducksSpawned = 0;
var waveTimer = 0;
// UI Elements
var scoreTxt = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreTxt);
var missedTxt = new Text2('Missed: 0/5', {
size: 60,
fill: 0xFF0000
});
missedTxt.anchor.set(1, 0);
LK.gui.topRight.addChild(missedTxt);
var waveTxt = new Text2('Wave: 1', {
size: 60,
fill: 0x00FF00
});
waveTxt.anchor.set(0, 0);
waveTxt.x = 150; // Offset from top-left to avoid menu
waveTxt.y = 50;
LK.gui.topLeft.addChild(waveTxt);
// Create crosshair
crosshair = game.addChild(new Crosshair());
// Create hunter positioned at bottom center
var hunter = game.addChild(new Hunter());
hunter.x = 2048 / 2;
hunter.y = 2732 - 50;
hunter.defaultY = hunter.y;
// Create grass across the entire bottom of the screen
var grassObjects = [];
var grassWidth = 400; // Width of each grass asset
var numGrassObjects = Math.ceil(2048 / grassWidth) + 1; // Cover full width plus some overlap
for (var g = 0; g < numGrassObjects; g++) {
var grass = game.addChild(new Grass());
grass.x = g * (grassWidth * 0.8); // Slight overlap between grass objects
grass.y = 2732 - 30;
grassObjects.push(grass);
}
function spawnDuck() {
var duck = new Duck();
// Adjust speed based on wave
duck.speed += waveNumber * 0.5;
duck.points += waveNumber * 10;
ducks.push(duck);
game.addChild(duck);
ducksSpawned++;
}
function updateScore() {
scoreTxt.setText('Score: ' + LK.getScore());
}
function updateMissed() {
missedTxt.setText('Missed: ' + missedDucks + '/' + maxMissedDucks);
}
function updateWave() {
waveTxt.setText('Wave: ' + waveNumber);
}
function checkHit(x, y) {
var hitDuck = null;
var minDistance = 100; // Hit radius
for (var i = 0; i < ducks.length; i++) {
var duck = ducks[i];
if (duck.isHit) continue;
var dx = duck.x - x;
var dy = duck.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < minDistance) {
minDistance = distance;
hitDuck = duck;
}
}
return hitDuck;
}
function startNextWave() {
waveNumber++;
ducksSpawned = 0;
ducksInWave = Math.min(3 + Math.floor(waveNumber / 2), 8); // Max 8 ducks per wave
waveTimer = 0;
updateWave();
}
// Game controls
game.down = function (x, y, obj) {
// Show crosshair
crosshair.x = x;
crosshair.y = y;
crosshair.visible = true;
// Create bullet
var bullet = new Bullet();
bullet.x = x;
bullet.y = y;
bullet.targetX = x;
bullet.targetY = y;
bullets.push(bullet);
game.addChild(bullet);
// Play shoot sound
LK.getSound('shoot').play();
// Trigger hunter shooting animation
hunter.shoot(x, y);
// Check for hit
var hitDuck = checkHit(x, y);
if (hitDuck) {
if (hitDuck.hit()) {
consecutiveHits++;
var points = hitDuck.points;
// Bonus for consecutive hits
if (consecutiveHits > 1) {
points += consecutiveHits * 10;
}
LK.setScore(LK.getScore() + points);
updateScore();
}
} else {
consecutiveHits = 0;
LK.getSound('miss').play();
}
// Hide crosshair after short delay
LK.setTimeout(function () {
crosshair.visible = false;
}, 100);
};
game.update = function () {
waveTimer++;
// Spawn ducks for current wave
if (ducksSpawned < ducksInWave && waveTimer % 120 === 0) {
// Spawn every 2 seconds
spawnDuck();
}
// Update and remove off-screen ducks
for (var i = ducks.length - 1; i >= 0; i--) {
var duck = ducks[i];
if (duck.lastX === undefined) duck.lastX = duck.x;
if (duck.lastOffScreen === undefined) duck.lastOffScreen = false;
var currentOffScreen = duck.x < -150 || duck.x > 2198;
// Check if duck just went off screen
if (!duck.lastOffScreen && currentOffScreen && !duck.isHit) {
missedDucks++;
updateMissed();
consecutiveHits = 0;
if (missedDucks >= maxMissedDucks) {
LK.showGameOver();
return;
}
}
// Remove duck if it's been off screen for a while or hit and faded
if (currentOffScreen && (duck.lastOffScreen || duck.isHit) || duck.alpha <= 0) {
duck.destroy();
ducks.splice(i, 1);
} else {
duck.lastX = duck.x;
duck.lastOffScreen = currentOffScreen;
}
}
// Update and remove bullets
for (var j = bullets.length - 1; j >= 0; j--) {
var bullet = bullets[j];
if (bullet.alpha <= 0) {
bullet.destroy();
bullets.splice(j, 1);
}
}
// Check if wave is complete
if (ducksSpawned >= ducksInWave && ducks.length === 0) {
startNextWave();
}
// Win condition - survive 10 waves
if (waveNumber > 10) {
LK.showYouWin();
}
};
// Initialize first wave
spawnDuck();
Flying Duck. In-Game asset. 2d. High contrast. No shadows
Bomb effect. In-Game asset. 2d. High contrast. No shadows
Bullet. In-Game asset. 2d. High contrast. No shadows
grass. In-Game asset. 2d. High contrast. No shadows
Duck hunter gun. In-Game asset. 2d. High contrast. No shadows
Duck hunter without gun from behind of him. In-Game asset. 2d. High contrast. No shadows
Silahını yukarı doğrultup nişan almış bir ördek avcısının arkadan görünüşünü çiz. In-Game asset. 2d. High contrast. No shadows
cloud. In-Game asset. 2d. High contrast. No shadows
Bonus coin. In-Game asset. 2d. High contrast. No shadows