/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { defaults: { highScore: 0, lastDistrict: "downtown" } }); /**** * Classes ****/ var Building = Container.expand(function (width, height) { var self = Container.call(this); width = width || 200; height = height || 200; var buildingGraphics = self.attachAsset('building', { anchorX: 0.5, anchorY: 0.5, width: width, height: height }); return self; }); var District = Container.expand(function (name, color, x, y, width, height) { var self = Container.call(this); self.name = name; self.color = color; self.bounds = { x: x, y: y, width: width, height: height }; // Create district text label self.label = new Text2(name, { size: 40, fill: 0xFFFFFF }); self.label.anchor.set(0.5, 0.5); self.label.x = x + width / 2; self.label.y = y + height / 2; self.addChild(self.label); // Check if a point is inside this district self.contains = function (px, py) { return px >= self.bounds.x && px <= self.bounds.x + self.bounds.width && py >= self.bounds.y && py <= self.bounds.y + self.bounds.height; }; return self; }); var FuelGauge = Container.expand(function () { var self = Container.call(this); // Create fuel gauge background var gaugeBackground = self.attachAsset('fuel_bar_background', { anchorX: 0, anchorY: 0 }); // Create fuel bar self.fuelBar = LK.getAsset('fuel_bar', { anchorX: 0, anchorY: 0 }); self.addChild(self.fuelBar); // Create fuel text self.fuelText = new Text2('FUEL', { size: 20, fill: 0xFFFFFF }); self.fuelText.anchor.set(0.5, 0.5); self.fuelText.x = 100; self.fuelText.y = 15; self.addChild(self.fuelText); self.update = function (fuelLevel) { // Update fuel bar width based on fuel level (0-100) var fuelPercentage = Math.max(0, Math.min(100, fuelLevel)) / 100; self.fuelBar.width = 200 * fuelPercentage; // Update color based on fuel level if (fuelPercentage < 0.3) { self.fuelBar.tint = 0xff0000; // Red when low } else if (fuelPercentage < 0.6) { self.fuelBar.tint = 0xffff00; // Yellow when medium } else { self.fuelBar.tint = 0x00ff00; // Green when high } }; return self; }); var GameObject = Container.expand(function () { var self = Container.call(this); self.velocity = { x: 0, y: 0 }; self.speed = 0; self.maxSpeed = 5; self.direction = 0; self.active = true; self.move = function () { if (!self.active) { return; } self.x += self.velocity.x; self.y += self.velocity.y; }; self.setVelocity = function (speed, direction) { self.speed = Math.min(speed, self.maxSpeed); self.direction = direction; self.velocity.x = Math.cos(direction) * self.speed; self.velocity.y = Math.sin(direction) * self.speed; }; self.checkBoundaries = function () { // Basic boundary checking to keep objects in the game world var buffer = 50; if (self.x < buffer) { self.x = buffer; self.velocity.x = 0; } else if (self.x > worldWidth - buffer) { self.x = worldWidth - buffer; self.velocity.x = 0; } if (self.y < buffer) { self.y = buffer; self.velocity.y = 0; } else if (self.y > worldHeight - buffer) { self.y = worldHeight - buffer; self.velocity.y = 0; } }; return self; }); var Vehicle = GameObject.expand(function (type) { var self = GameObject.call(this); self.type = type || 'car'; self.isOccupied = false; self.fuel = 100; self.maxFuel = 100; // Configure vehicle based on type switch (self.type) { case 'car': self.maxSpeed = 8; self.fuelConsumption = 0.1; var vehicleGraphics = self.attachAsset('car', { anchorX: 0.5, anchorY: 0.5 }); break; case 'truck': self.maxSpeed = 6; self.fuelConsumption = 0.15; var vehicleGraphics = self.attachAsset('truck', { anchorX: 0.5, anchorY: 0.5 }); break; case 'motorcycle': self.maxSpeed = 10; self.fuelConsumption = 0.08; var vehicleGraphics = self.attachAsset('motorcycle', { anchorX: 0.5, anchorY: 0.5 }); break; } self.update = function () { if (self.isOccupied) { // When occupied, update fuel if (self.fuel > 0 && (self.velocity.x !== 0 || self.velocity.y !== 0)) { self.fuel -= self.fuelConsumption; if (self.fuel <= 0) { self.fuel = 0; self.velocity.x = 0; self.velocity.y = 0; } } } else { // AI behavior when not occupied if (Math.random() < 0.01) { var angle = Math.random() * Math.PI * 2; self.setVelocity(self.maxSpeed * 0.5, angle); } } self.move(); self.checkBoundaries(); // Update rotation based on movement direction if (self.velocity.x !== 0 || self.velocity.y !== 0) { self.rotation = Math.atan2(self.velocity.y, self.velocity.x); } }; return self; }); var Player = GameObject.expand(function () { var self = GameObject.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); self.health = 100; self.maxSpeed = 6; self.inVehicle = null; self.hasWeapon = true; self.score = 0; self.update = function () { if (self.inVehicle) { return; } self.move(); self.checkBoundaries(); }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { self.health = 0; LK.showGameOver(); } }; self.exitVehicle = function () { if (!self.inVehicle) { return; } // Position player next to vehicle self.x = self.inVehicle.x + 80; self.y = self.inVehicle.y; self.inVehicle.isOccupied = false; self.inVehicle = null; self.visible = true; }; self.shootBullet = function () { if (!self.hasWeapon || self.inVehicle) { return; } var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.lastWasIntersecting = false; bullet.setVelocity(10, self.direction); bullets.push(bullet); gameWorld.addChild(bullet); LK.getSound('gun_shot').play(); }; return self; }); var Pedestrian = GameObject.expand(function () { var self = GameObject.call(this); var pedestrianGraphics = self.attachAsset('pedestrian', { anchorX: 0.5, anchorY: 0.5 }); self.health = 50; self.maxSpeed = 2; self.isAggressive = false; self.targetX = self.x; self.targetY = self.y; self.decisionCooldown = 0; self.update = function () { self.decisionCooldown--; if (self.decisionCooldown <= 0) { // Make new decision about movement if (Math.random() < 0.7) { // Random movement self.targetX = self.x + (Math.random() * 400 - 200); self.targetY = self.y + (Math.random() * 400 - 200); // Ensure target is within bounds self.targetX = Math.max(100, Math.min(worldWidth - 100, self.targetX)); self.targetY = Math.max(100, Math.min(worldHeight - 100, self.targetY)); // Calculate direction to target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var angle = Math.atan2(dy, dx); self.setVelocity(self.maxSpeed * 0.5 + Math.random() * self.maxSpeed * 0.5, angle); } else { // Stop and idle self.velocity.x = 0; self.velocity.y = 0; } // Set cooldown before next decision self.decisionCooldown = 60 + Math.floor(Math.random() * 120); } // If aggressive, chase player if (self.isAggressive && player && !player.inVehicle) { var dx = player.x - self.x; var dy = player.y - self.y; var distanceToPlayer = Math.sqrt(dx * dx + dy * dy); if (distanceToPlayer < 500) { var angle = Math.atan2(dy, dx); self.setVelocity(self.maxSpeed, angle); // Attack player if close enough if (distanceToPlayer < 60 && Math.random() < 0.02) { player.takeDamage(5); } } } self.move(); self.checkBoundaries(); // Update rotation based on movement direction if (self.velocity.x !== 0 || self.velocity.y !== 0) { self.rotation = Math.atan2(self.velocity.y, self.velocity.x); } }; self.takeDamage = function (amount) { self.health -= amount; LK.effects.flashObject(self, 0xff0000, 300); LK.getSound('pedestrian_hurt').play(); // Turn aggressive when damaged if (!self.isAggressive && self.health > 0) { self.isAggressive = true; } if (self.health <= 0) { self.health = 0; self.active = false; self.visible = false; // Add score for defeating pedestrian if (player) { player.score += 10; updateScore(); } // Remove from game var index = pedestrians.indexOf(self); if (index > -1) { pedestrians.splice(index, 1); } self.destroy(); } }; return self; }); var Bullet = GameObject.expand(function () { var self = GameObject.call(this); var bulletGraphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.damage = 20; self.lifespan = 60; // frames alive before auto-destruction self.update = function () { self.move(); self.lifespan--; // Check if bullet has expired if (self.lifespan <= 0) { self.active = false; return; } // Ensure lastIntersecting is initialized if (self.lastIntersecting === undefined) { self.lastIntersecting = false; } }; return self; }); var MiniMap = Container.expand(function () { var self = Container.call(this); // Create minimap background var mapBackground = self.attachAsset('minimap_background', { anchorX: 0, anchorY: 0, alpha: 0.7 }); // Player indicator on minimap self.playerIndicator = LK.getAsset('minimap_player', { anchorX: 0.5, anchorY: 0.5 }); self.addChild(self.playerIndicator); // Elements collection for buildings and roads self.elements = []; self.addElement = function (type, x, y, width, height, rotation) { var element; if (type === 'building') { element = LK.getAsset('minimap_building', { anchorX: 0.5, anchorY: 0.5, width: width / 10, // Scale down for minimap height: height / 10 }); } else if (type === 'road') { element = LK.getAsset('minimap_road', { anchorX: 0.5, anchorY: 0.5, width: width / 10, height: height / 10 }); } if (element) { element.x = x / 10; // Scale to minimap coordinates element.y = y / 10; if (rotation !== undefined) { element.rotation = rotation; } self.addChild(element); self.elements.push(element); } }; self.update = function () { if (player) { // Update player indicator position on minimap self.playerIndicator.x = player.x / 10; self.playerIndicator.y = player.y / 10; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222222 }); /**** * Game Code ****/ // World dimensions var worldWidth = 4000; var worldHeight = 4000; // Game objects var player = null; var gameWorld = null; var camera = null; var minimap = null; var fuelGauge = null; var scoreText = null; var districtText = null; // Collections var vehicles = []; var pedestrians = []; var buildings = []; var roads = []; var bullets = []; var districts = []; // Game state var currentDistrict = null; var dragMode = false; // Initialize the game world function initGameWorld() { // Create world container gameWorld = new Container(); game.addChild(gameWorld); // Create districts createDistricts(); // Create city layout createCityLayout(); // Create player player = new Player(); player.x = worldWidth / 2; player.y = worldHeight / 2; gameWorld.addChild(player); // Create vehicles createVehicles(); // Create pedestrians createPedestrians(); // Setup camera camera = { x: 0, y: 0, update: function update() { // Center camera on player if (player) { var targetX = -player.x + 2048 / 2; var targetY = -player.y + 2732 / 2; // Smooth camera movement camera.x += (targetX - camera.x) * 0.1; camera.y += (targetY - camera.y) * 0.1; // Apply camera position to game world gameWorld.x = camera.x; gameWorld.y = camera.y; } } }; // Create minimap minimap = new MiniMap(); minimap.x = 2048 - 420; minimap.y = 20; LK.gui.addChild(minimap); // Add buildings and roads to minimap for (var i = 0; i < buildings.length; i++) { minimap.addElement('building', buildings[i].x, buildings[i].y, buildings[i].width, buildings[i].height); } for (var i = 0; i < roads.length; i++) { minimap.addElement('road', roads[i].x, roads[i].y, roads[i].width, roads[i].height, roads[i].rotation); } // Create fuel gauge fuelGauge = new FuelGauge(); fuelGauge.x = 20; fuelGauge.y = 20; fuelGauge.visible = false; // Only visible when in vehicle LK.gui.addChild(fuelGauge); // Create score display scoreText = new Text2('SCORE: 0', { size: 40, fill: 0xFFFFFF }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Create district display districtText = new Text2('', { size: 30, fill: 0xFFFFFF }); districtText.anchor.set(0, 0); districtText.x = 20; districtText.y = 70; LK.gui.addChild(districtText); // Play background music LK.playMusic('city_ambience'); } function createDistricts() { // Create different city districts districts.push(new District('Downtown', 0xff3333, 0, 0, worldWidth / 3, worldHeight / 3)); districts.push(new District('Suburbs', 0x33cc33, worldWidth / 3, 0, worldWidth / 3, worldHeight / 3)); districts.push(new District('Industrial', 0x3366ff, 2 * worldWidth / 3, 0, worldWidth / 3, worldHeight / 3)); districts.push(new District('Harbor', 0xffcc00, 0, worldHeight / 3, worldWidth / 3, worldHeight / 3)); districts.push(new District('Business', 0x9933ff, worldWidth / 3, worldHeight / 3, worldWidth / 3, worldHeight / 3)); districts.push(new District('Park', 0x33cccc, 2 * worldWidth / 3, worldHeight / 3, worldWidth / 3, worldHeight / 3)); districts.push(new District('Shopping', 0xff9933, 0, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3)); districts.push(new District('Residential', 0xcc33cc, worldWidth / 3, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3)); districts.push(new District('Entertainment', 0xffff33, 2 * worldWidth / 3, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3)); // Add district boundaries for (var i = 0; i < districts.length; i++) { gameWorld.addChild(districts[i]); // Create visual boundaries between districts if (i % 3 !== 2) { // Vertical boundaries (except last column) var boundary = LK.getAsset('district_boundary', { anchorX: 0.5, anchorY: 0.5 }); boundary.x = districts[i].bounds.x + districts[i].bounds.width; boundary.y = districts[i].bounds.y + districts[i].bounds.height / 2; boundary.height = districts[i].bounds.height; gameWorld.addChild(boundary); } if (i < 6) { // Horizontal boundaries (except last row) var boundary = LK.getAsset('district_boundary', { anchorX: 0.5, anchorY: 0.5 }); boundary.x = districts[i].bounds.x + districts[i].bounds.width / 2; boundary.y = districts[i].bounds.y + districts[i].bounds.height; boundary.rotation = Math.PI / 2; boundary.height = districts[i].bounds.width; gameWorld.addChild(boundary); } } } function createCityLayout() { // Create buildings for (var i = 0; i < 200; i++) { var building = new Building(100 + Math.random() * 300, // Random width 100 + Math.random() * 300 // Random height ); // Position building randomly within the world building.x = Math.random() * worldWidth; building.y = Math.random() * worldHeight; // Prevent overlapping with existing buildings (simplified) var overlapping = false; for (var j = 0; j < buildings.length; j++) { var other = buildings[j]; var distance = Math.sqrt(Math.pow(building.x - other.x, 2) + Math.pow(building.y - other.y, 2)); if (distance < 350) { overlapping = true; break; } } if (!overlapping) { buildings.push(building); gameWorld.addChild(building); } } // Create roads for (var i = 0; i < 40; i++) { // Horizontal roads var roadH = LK.getAsset('road', { anchorX: 0.5, anchorY: 0.5, width: worldWidth - 200, height: 120 }); roadH.x = worldWidth / 2; roadH.y = (i + 1) * (worldHeight / 41); roads.push(roadH); gameWorld.addChild(roadH); // Add sidewalks alongside roads var sidewalkH1 = LK.getAsset('sidewalk', { anchorX: 0.5, anchorY: 0.5, width: worldWidth - 200, height: 50 }); sidewalkH1.x = worldWidth / 2; sidewalkH1.y = roadH.y - 85; gameWorld.addChild(sidewalkH1); var sidewalkH2 = LK.getAsset('sidewalk', { anchorX: 0.5, anchorY: 0.5, width: worldWidth - 200, height: 50 }); sidewalkH2.x = worldWidth / 2; sidewalkH2.y = roadH.y + 85; gameWorld.addChild(sidewalkH2); } for (var i = 0; i < 30; i++) { // Vertical roads var roadV = LK.getAsset('road', { anchorX: 0.5, anchorY: 0.5, width: 120, height: worldHeight - 200 }); roadV.rotation = Math.PI / 2; roadV.x = (i + 1) * (worldWidth / 31); roadV.y = worldHeight / 2; roads.push(roadV); gameWorld.addChild(roadV); // Add sidewalks alongside roads var sidewalkV1 = LK.getAsset('sidewalk', { anchorX: 0.5, anchorY: 0.5, width: 50, height: worldHeight - 200 }); sidewalkV1.x = roadV.x - 85; sidewalkV1.y = worldHeight / 2; sidewalkV1.rotation = Math.PI / 2; gameWorld.addChild(sidewalkV1); var sidewalkV2 = LK.getAsset('sidewalk', { anchorX: 0.5, anchorY: 0.5, width: 50, height: worldHeight - 200 }); sidewalkV2.x = roadV.x + 85; sidewalkV2.y = worldHeight / 2; sidewalkV2.rotation = Math.PI / 2; gameWorld.addChild(sidewalkV2); } } function createVehicles() { // Vehicle types var types = ['car', 'truck', 'motorcycle']; // Create a variety of vehicles for (var i = 0; i < 30; i++) { var type = types[Math.floor(Math.random() * types.length)]; var vehicle = new Vehicle(type); // Position vehicle on roads var onVerticalRoad = Math.random() < 0.5; if (onVerticalRoad) { vehicle.x = (Math.floor(Math.random() * 30) + 1) * (worldWidth / 31); vehicle.y = Math.random() * worldHeight; } else { vehicle.x = Math.random() * worldWidth; vehicle.y = (Math.floor(Math.random() * 40) + 1) * (worldHeight / 41); } vehicles.push(vehicle); gameWorld.addChild(vehicle); } } function createPedestrians() { // Create pedestrians for (var i = 0; i < 50; i++) { var pedestrian = new Pedestrian(); // Position pedestrian randomly on sidewalks pedestrian.x = Math.random() * worldWidth; pedestrian.y = Math.random() * worldHeight; pedestrians.push(pedestrian); gameWorld.addChild(pedestrian); } } function updateScore() { if (player) { scoreText.setText('SCORE: ' + player.score); // Update high score if needed if (player.score > storage.highScore) { storage.highScore = player.score; } } } function checkCollisions() { // Player with vehicles (for entering) if (player && !player.inVehicle) { for (var i = 0; i < vehicles.length; i++) { var vehicle = vehicles[i]; if (!vehicle.isOccupied && player.intersects(vehicle)) { // Player can enter vehicle if (dragMode) { // Enter vehicle player.inVehicle = vehicle; vehicle.isOccupied = true; player.visible = false; // Play sound LK.getSound('car_start').play(); // Show fuel gauge fuelGauge.visible = true; fuelGauge.update(vehicle.fuel); break; } } } } // Bullets with pedestrians and vehicles for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; // Initialize lastWasIntersecting if not present if (bullet.lastWasIntersecting === undefined) { bullet.lastWasIntersecting = false; } // Check collision with pedestrians for (var j = 0; j < pedestrians.length; j++) { if (bullet.active && !bullet.lastWasIntersecting && bullet.intersects(pedestrians[j])) { pedestrians[j].takeDamage(bullet.damage); bullet.active = false; break; } } // Check collision with vehicles for (var j = 0; j < vehicles.length; j++) { if (bullet.active && bullet.intersects(vehicles[j])) { // Damage vehicle or make it explode LK.effects.flashObject(vehicles[j], 0xff0000, 300); LK.getSound('collision').play(); // Bullets stop when hitting vehicles bullet.active = false; break; } } // Check collision with buildings for (var j = 0; j < buildings.length; j++) { if (bullet.active && bullet.intersects(buildings[j])) { bullet.active = false; break; } } // Remove inactive bullets if (!bullet.active) { bullet.destroy(); bullets.splice(i, 1); } } // Vehicle with vehicle collisions for (var i = 0; i < vehicles.length; i++) { for (var j = i + 1; j < vehicles.length; j++) { if (vehicles[i].intersects(vehicles[j])) { // Simplified collision response - just reverse direction vehicles[i].velocity.x *= -0.5; vehicles[i].velocity.y *= -0.5; vehicles[j].velocity.x *= -0.5; vehicles[j].velocity.y *= -0.5; LK.getSound('collision').play(); } } } // Vehicle with building collisions for (var i = 0; i < vehicles.length; i++) { for (var j = 0; j < buildings.length; j++) { if (vehicles[i].intersects(buildings[j])) { // Simplified collision response - just stop vehicles[i].velocity.x = 0; vehicles[i].velocity.y = 0; // Move away from building var dx = vehicles[i].x - buildings[j].x; var dy = vehicles[i].y - buildings[j].y; var angle = Math.atan2(dy, dx); vehicles[i].x += Math.cos(angle) * 5; vehicles[i].y += Math.sin(angle) * 5; if (vehicles[i].isOccupied) { LK.getSound('collision').play(); } } } } } function checkCurrentDistrict() { // Find what district the player is in if (player) { var foundDistrict = null; for (var i = 0; i < districts.length; i++) { if (districts[i].contains(player.x, player.y)) { foundDistrict = districts[i]; break; } } if (foundDistrict !== currentDistrict) { currentDistrict = foundDistrict; if (currentDistrict) { // Update district display districtText.setText('DISTRICT: ' + currentDistrict.name); // Save last district storage.lastDistrict = currentDistrict.name; // Show district notification var notification = new Text2('Entering ' + currentDistrict.name, { size: 60, fill: 0xFFFFFF }); notification.anchor.set(0.5, 0.5); notification.x = 2048 / 2; notification.y = 2732 / 2; LK.gui.addChild(notification); // Fade out notification tween(notification, { alpha: 0 }, { duration: 2000, onFinish: function onFinish() { notification.destroy(); } }); } } } } function handleMove(x, y, obj) { // Convert screen coordinates to world coordinates var worldX = x - gameWorld.x; var worldY = y - gameWorld.y; if (dragMode && player) { if (player.inVehicle) { // If in vehicle, set direction based on touch position var dx = worldX - player.inVehicle.x; var dy = worldY - player.inVehicle.y; var angle = Math.atan2(dy, dx); // Only move if there's fuel if (player.inVehicle.fuel > 0) { player.inVehicle.setVelocity(player.inVehicle.maxSpeed, angle); } } else { // If on foot, move player towards touch position var dx = worldX - player.x; var dy = worldY - player.y; var angle = Math.atan2(dy, dx); player.setVelocity(player.maxSpeed, angle); } } } game.down = function (x, y, obj) { dragMode = true; handleMove(x, y, obj); }; game.up = function (x, y, obj) { dragMode = false; // Stop player movement when touch is released if (player) { if (player.inVehicle) { player.inVehicle.velocity.x = 0; player.inVehicle.velocity.y = 0; } else { player.velocity.x = 0; player.velocity.y = 0; } } // Check if tap was to exit vehicle if (player && player.inVehicle) { // Double check if drag distance was small enough to be considered a tap var dx = x - (player.inVehicle.x + gameWorld.x); var dy = y - (player.inVehicle.y + gameWorld.y); var dragDistance = Math.sqrt(dx * dx + dy * dy); if (dragDistance < 50) { // Exit vehicle player.exitVehicle(); fuelGauge.visible = false; } } // Check if tap was to shoot if (player && !player.inVehicle) { // Convert screen coordinates to world coordinates var worldX = x - gameWorld.x; var worldY = y - gameWorld.y; // Calculate direction to shoot var dx = worldX - player.x; var dy = worldY - player.y; player.direction = Math.atan2(dy, dx); // Shoot bullet player.shootBullet(); } }; game.move = handleMove; game.update = function () { // Skip if game not initialized yet if (!player || !gameWorld) { return; } // Update all game objects player.update(); for (var i = 0; i < vehicles.length; i++) { vehicles[i].update(); } for (var i = 0; i < pedestrians.length; i++) { pedestrians[i].update(); } for (var i = 0; i < bullets.length; i++) { // Save intersection state before updating for (var j = 0; j < pedestrians.length; j++) { if (bullets[i].intersects(pedestrians[j])) { bullets[i].lastWasIntersecting = true; break; } else { bullets[i].lastWasIntersecting = false; } } bullets[i].update(); } // Update camera camera.update(); // Update minimap minimap.update(); // Update fuel gauge if in vehicle if (player.inVehicle) { fuelGauge.update(player.inVehicle.fuel); } // Check collisions checkCollisions(); // Check current district checkCurrentDistrict(); }; // Initialize game world initGameWorld();
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
defaults: {
highScore: 0,
lastDistrict: "downtown"
}
});
/****
* Classes
****/
var Building = Container.expand(function (width, height) {
var self = Container.call(this);
width = width || 200;
height = height || 200;
var buildingGraphics = self.attachAsset('building', {
anchorX: 0.5,
anchorY: 0.5,
width: width,
height: height
});
return self;
});
var District = Container.expand(function (name, color, x, y, width, height) {
var self = Container.call(this);
self.name = name;
self.color = color;
self.bounds = {
x: x,
y: y,
width: width,
height: height
};
// Create district text label
self.label = new Text2(name, {
size: 40,
fill: 0xFFFFFF
});
self.label.anchor.set(0.5, 0.5);
self.label.x = x + width / 2;
self.label.y = y + height / 2;
self.addChild(self.label);
// Check if a point is inside this district
self.contains = function (px, py) {
return px >= self.bounds.x && px <= self.bounds.x + self.bounds.width && py >= self.bounds.y && py <= self.bounds.y + self.bounds.height;
};
return self;
});
var FuelGauge = Container.expand(function () {
var self = Container.call(this);
// Create fuel gauge background
var gaugeBackground = self.attachAsset('fuel_bar_background', {
anchorX: 0,
anchorY: 0
});
// Create fuel bar
self.fuelBar = LK.getAsset('fuel_bar', {
anchorX: 0,
anchorY: 0
});
self.addChild(self.fuelBar);
// Create fuel text
self.fuelText = new Text2('FUEL', {
size: 20,
fill: 0xFFFFFF
});
self.fuelText.anchor.set(0.5, 0.5);
self.fuelText.x = 100;
self.fuelText.y = 15;
self.addChild(self.fuelText);
self.update = function (fuelLevel) {
// Update fuel bar width based on fuel level (0-100)
var fuelPercentage = Math.max(0, Math.min(100, fuelLevel)) / 100;
self.fuelBar.width = 200 * fuelPercentage;
// Update color based on fuel level
if (fuelPercentage < 0.3) {
self.fuelBar.tint = 0xff0000; // Red when low
} else if (fuelPercentage < 0.6) {
self.fuelBar.tint = 0xffff00; // Yellow when medium
} else {
self.fuelBar.tint = 0x00ff00; // Green when high
}
};
return self;
});
var GameObject = Container.expand(function () {
var self = Container.call(this);
self.velocity = {
x: 0,
y: 0
};
self.speed = 0;
self.maxSpeed = 5;
self.direction = 0;
self.active = true;
self.move = function () {
if (!self.active) {
return;
}
self.x += self.velocity.x;
self.y += self.velocity.y;
};
self.setVelocity = function (speed, direction) {
self.speed = Math.min(speed, self.maxSpeed);
self.direction = direction;
self.velocity.x = Math.cos(direction) * self.speed;
self.velocity.y = Math.sin(direction) * self.speed;
};
self.checkBoundaries = function () {
// Basic boundary checking to keep objects in the game world
var buffer = 50;
if (self.x < buffer) {
self.x = buffer;
self.velocity.x = 0;
} else if (self.x > worldWidth - buffer) {
self.x = worldWidth - buffer;
self.velocity.x = 0;
}
if (self.y < buffer) {
self.y = buffer;
self.velocity.y = 0;
} else if (self.y > worldHeight - buffer) {
self.y = worldHeight - buffer;
self.velocity.y = 0;
}
};
return self;
});
var Vehicle = GameObject.expand(function (type) {
var self = GameObject.call(this);
self.type = type || 'car';
self.isOccupied = false;
self.fuel = 100;
self.maxFuel = 100;
// Configure vehicle based on type
switch (self.type) {
case 'car':
self.maxSpeed = 8;
self.fuelConsumption = 0.1;
var vehicleGraphics = self.attachAsset('car', {
anchorX: 0.5,
anchorY: 0.5
});
break;
case 'truck':
self.maxSpeed = 6;
self.fuelConsumption = 0.15;
var vehicleGraphics = self.attachAsset('truck', {
anchorX: 0.5,
anchorY: 0.5
});
break;
case 'motorcycle':
self.maxSpeed = 10;
self.fuelConsumption = 0.08;
var vehicleGraphics = self.attachAsset('motorcycle', {
anchorX: 0.5,
anchorY: 0.5
});
break;
}
self.update = function () {
if (self.isOccupied) {
// When occupied, update fuel
if (self.fuel > 0 && (self.velocity.x !== 0 || self.velocity.y !== 0)) {
self.fuel -= self.fuelConsumption;
if (self.fuel <= 0) {
self.fuel = 0;
self.velocity.x = 0;
self.velocity.y = 0;
}
}
} else {
// AI behavior when not occupied
if (Math.random() < 0.01) {
var angle = Math.random() * Math.PI * 2;
self.setVelocity(self.maxSpeed * 0.5, angle);
}
}
self.move();
self.checkBoundaries();
// Update rotation based on movement direction
if (self.velocity.x !== 0 || self.velocity.y !== 0) {
self.rotation = Math.atan2(self.velocity.y, self.velocity.x);
}
};
return self;
});
var Player = GameObject.expand(function () {
var self = GameObject.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxSpeed = 6;
self.inVehicle = null;
self.hasWeapon = true;
self.score = 0;
self.update = function () {
if (self.inVehicle) {
return;
}
self.move();
self.checkBoundaries();
};
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xff0000, 300);
if (self.health <= 0) {
self.health = 0;
LK.showGameOver();
}
};
self.exitVehicle = function () {
if (!self.inVehicle) {
return;
}
// Position player next to vehicle
self.x = self.inVehicle.x + 80;
self.y = self.inVehicle.y;
self.inVehicle.isOccupied = false;
self.inVehicle = null;
self.visible = true;
};
self.shootBullet = function () {
if (!self.hasWeapon || self.inVehicle) {
return;
}
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.lastWasIntersecting = false;
bullet.setVelocity(10, self.direction);
bullets.push(bullet);
gameWorld.addChild(bullet);
LK.getSound('gun_shot').play();
};
return self;
});
var Pedestrian = GameObject.expand(function () {
var self = GameObject.call(this);
var pedestrianGraphics = self.attachAsset('pedestrian', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 50;
self.maxSpeed = 2;
self.isAggressive = false;
self.targetX = self.x;
self.targetY = self.y;
self.decisionCooldown = 0;
self.update = function () {
self.decisionCooldown--;
if (self.decisionCooldown <= 0) {
// Make new decision about movement
if (Math.random() < 0.7) {
// Random movement
self.targetX = self.x + (Math.random() * 400 - 200);
self.targetY = self.y + (Math.random() * 400 - 200);
// Ensure target is within bounds
self.targetX = Math.max(100, Math.min(worldWidth - 100, self.targetX));
self.targetY = Math.max(100, Math.min(worldHeight - 100, self.targetY));
// Calculate direction to target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var angle = Math.atan2(dy, dx);
self.setVelocity(self.maxSpeed * 0.5 + Math.random() * self.maxSpeed * 0.5, angle);
} else {
// Stop and idle
self.velocity.x = 0;
self.velocity.y = 0;
}
// Set cooldown before next decision
self.decisionCooldown = 60 + Math.floor(Math.random() * 120);
}
// If aggressive, chase player
if (self.isAggressive && player && !player.inVehicle) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var distanceToPlayer = Math.sqrt(dx * dx + dy * dy);
if (distanceToPlayer < 500) {
var angle = Math.atan2(dy, dx);
self.setVelocity(self.maxSpeed, angle);
// Attack player if close enough
if (distanceToPlayer < 60 && Math.random() < 0.02) {
player.takeDamage(5);
}
}
}
self.move();
self.checkBoundaries();
// Update rotation based on movement direction
if (self.velocity.x !== 0 || self.velocity.y !== 0) {
self.rotation = Math.atan2(self.velocity.y, self.velocity.x);
}
};
self.takeDamage = function (amount) {
self.health -= amount;
LK.effects.flashObject(self, 0xff0000, 300);
LK.getSound('pedestrian_hurt').play();
// Turn aggressive when damaged
if (!self.isAggressive && self.health > 0) {
self.isAggressive = true;
}
if (self.health <= 0) {
self.health = 0;
self.active = false;
self.visible = false;
// Add score for defeating pedestrian
if (player) {
player.score += 10;
updateScore();
}
// Remove from game
var index = pedestrians.indexOf(self);
if (index > -1) {
pedestrians.splice(index, 1);
}
self.destroy();
}
};
return self;
});
var Bullet = GameObject.expand(function () {
var self = GameObject.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 20;
self.lifespan = 60; // frames alive before auto-destruction
self.update = function () {
self.move();
self.lifespan--;
// Check if bullet has expired
if (self.lifespan <= 0) {
self.active = false;
return;
}
// Ensure lastIntersecting is initialized
if (self.lastIntersecting === undefined) {
self.lastIntersecting = false;
}
};
return self;
});
var MiniMap = Container.expand(function () {
var self = Container.call(this);
// Create minimap background
var mapBackground = self.attachAsset('minimap_background', {
anchorX: 0,
anchorY: 0,
alpha: 0.7
});
// Player indicator on minimap
self.playerIndicator = LK.getAsset('minimap_player', {
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.playerIndicator);
// Elements collection for buildings and roads
self.elements = [];
self.addElement = function (type, x, y, width, height, rotation) {
var element;
if (type === 'building') {
element = LK.getAsset('minimap_building', {
anchorX: 0.5,
anchorY: 0.5,
width: width / 10,
// Scale down for minimap
height: height / 10
});
} else if (type === 'road') {
element = LK.getAsset('minimap_road', {
anchorX: 0.5,
anchorY: 0.5,
width: width / 10,
height: height / 10
});
}
if (element) {
element.x = x / 10; // Scale to minimap coordinates
element.y = y / 10;
if (rotation !== undefined) {
element.rotation = rotation;
}
self.addChild(element);
self.elements.push(element);
}
};
self.update = function () {
if (player) {
// Update player indicator position on minimap
self.playerIndicator.x = player.x / 10;
self.playerIndicator.y = player.y / 10;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222222
});
/****
* Game Code
****/
// World dimensions
var worldWidth = 4000;
var worldHeight = 4000;
// Game objects
var player = null;
var gameWorld = null;
var camera = null;
var minimap = null;
var fuelGauge = null;
var scoreText = null;
var districtText = null;
// Collections
var vehicles = [];
var pedestrians = [];
var buildings = [];
var roads = [];
var bullets = [];
var districts = [];
// Game state
var currentDistrict = null;
var dragMode = false;
// Initialize the game world
function initGameWorld() {
// Create world container
gameWorld = new Container();
game.addChild(gameWorld);
// Create districts
createDistricts();
// Create city layout
createCityLayout();
// Create player
player = new Player();
player.x = worldWidth / 2;
player.y = worldHeight / 2;
gameWorld.addChild(player);
// Create vehicles
createVehicles();
// Create pedestrians
createPedestrians();
// Setup camera
camera = {
x: 0,
y: 0,
update: function update() {
// Center camera on player
if (player) {
var targetX = -player.x + 2048 / 2;
var targetY = -player.y + 2732 / 2;
// Smooth camera movement
camera.x += (targetX - camera.x) * 0.1;
camera.y += (targetY - camera.y) * 0.1;
// Apply camera position to game world
gameWorld.x = camera.x;
gameWorld.y = camera.y;
}
}
};
// Create minimap
minimap = new MiniMap();
minimap.x = 2048 - 420;
minimap.y = 20;
LK.gui.addChild(minimap);
// Add buildings and roads to minimap
for (var i = 0; i < buildings.length; i++) {
minimap.addElement('building', buildings[i].x, buildings[i].y, buildings[i].width, buildings[i].height);
}
for (var i = 0; i < roads.length; i++) {
minimap.addElement('road', roads[i].x, roads[i].y, roads[i].width, roads[i].height, roads[i].rotation);
}
// Create fuel gauge
fuelGauge = new FuelGauge();
fuelGauge.x = 20;
fuelGauge.y = 20;
fuelGauge.visible = false; // Only visible when in vehicle
LK.gui.addChild(fuelGauge);
// Create score display
scoreText = new Text2('SCORE: 0', {
size: 40,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Create district display
districtText = new Text2('', {
size: 30,
fill: 0xFFFFFF
});
districtText.anchor.set(0, 0);
districtText.x = 20;
districtText.y = 70;
LK.gui.addChild(districtText);
// Play background music
LK.playMusic('city_ambience');
}
function createDistricts() {
// Create different city districts
districts.push(new District('Downtown', 0xff3333, 0, 0, worldWidth / 3, worldHeight / 3));
districts.push(new District('Suburbs', 0x33cc33, worldWidth / 3, 0, worldWidth / 3, worldHeight / 3));
districts.push(new District('Industrial', 0x3366ff, 2 * worldWidth / 3, 0, worldWidth / 3, worldHeight / 3));
districts.push(new District('Harbor', 0xffcc00, 0, worldHeight / 3, worldWidth / 3, worldHeight / 3));
districts.push(new District('Business', 0x9933ff, worldWidth / 3, worldHeight / 3, worldWidth / 3, worldHeight / 3));
districts.push(new District('Park', 0x33cccc, 2 * worldWidth / 3, worldHeight / 3, worldWidth / 3, worldHeight / 3));
districts.push(new District('Shopping', 0xff9933, 0, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3));
districts.push(new District('Residential', 0xcc33cc, worldWidth / 3, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3));
districts.push(new District('Entertainment', 0xffff33, 2 * worldWidth / 3, 2 * worldHeight / 3, worldWidth / 3, worldHeight / 3));
// Add district boundaries
for (var i = 0; i < districts.length; i++) {
gameWorld.addChild(districts[i]);
// Create visual boundaries between districts
if (i % 3 !== 2) {
// Vertical boundaries (except last column)
var boundary = LK.getAsset('district_boundary', {
anchorX: 0.5,
anchorY: 0.5
});
boundary.x = districts[i].bounds.x + districts[i].bounds.width;
boundary.y = districts[i].bounds.y + districts[i].bounds.height / 2;
boundary.height = districts[i].bounds.height;
gameWorld.addChild(boundary);
}
if (i < 6) {
// Horizontal boundaries (except last row)
var boundary = LK.getAsset('district_boundary', {
anchorX: 0.5,
anchorY: 0.5
});
boundary.x = districts[i].bounds.x + districts[i].bounds.width / 2;
boundary.y = districts[i].bounds.y + districts[i].bounds.height;
boundary.rotation = Math.PI / 2;
boundary.height = districts[i].bounds.width;
gameWorld.addChild(boundary);
}
}
}
function createCityLayout() {
// Create buildings
for (var i = 0; i < 200; i++) {
var building = new Building(100 + Math.random() * 300,
// Random width
100 + Math.random() * 300 // Random height
);
// Position building randomly within the world
building.x = Math.random() * worldWidth;
building.y = Math.random() * worldHeight;
// Prevent overlapping with existing buildings (simplified)
var overlapping = false;
for (var j = 0; j < buildings.length; j++) {
var other = buildings[j];
var distance = Math.sqrt(Math.pow(building.x - other.x, 2) + Math.pow(building.y - other.y, 2));
if (distance < 350) {
overlapping = true;
break;
}
}
if (!overlapping) {
buildings.push(building);
gameWorld.addChild(building);
}
}
// Create roads
for (var i = 0; i < 40; i++) {
// Horizontal roads
var roadH = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: worldWidth - 200,
height: 120
});
roadH.x = worldWidth / 2;
roadH.y = (i + 1) * (worldHeight / 41);
roads.push(roadH);
gameWorld.addChild(roadH);
// Add sidewalks alongside roads
var sidewalkH1 = LK.getAsset('sidewalk', {
anchorX: 0.5,
anchorY: 0.5,
width: worldWidth - 200,
height: 50
});
sidewalkH1.x = worldWidth / 2;
sidewalkH1.y = roadH.y - 85;
gameWorld.addChild(sidewalkH1);
var sidewalkH2 = LK.getAsset('sidewalk', {
anchorX: 0.5,
anchorY: 0.5,
width: worldWidth - 200,
height: 50
});
sidewalkH2.x = worldWidth / 2;
sidewalkH2.y = roadH.y + 85;
gameWorld.addChild(sidewalkH2);
}
for (var i = 0; i < 30; i++) {
// Vertical roads
var roadV = LK.getAsset('road', {
anchorX: 0.5,
anchorY: 0.5,
width: 120,
height: worldHeight - 200
});
roadV.rotation = Math.PI / 2;
roadV.x = (i + 1) * (worldWidth / 31);
roadV.y = worldHeight / 2;
roads.push(roadV);
gameWorld.addChild(roadV);
// Add sidewalks alongside roads
var sidewalkV1 = LK.getAsset('sidewalk', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: worldHeight - 200
});
sidewalkV1.x = roadV.x - 85;
sidewalkV1.y = worldHeight / 2;
sidewalkV1.rotation = Math.PI / 2;
gameWorld.addChild(sidewalkV1);
var sidewalkV2 = LK.getAsset('sidewalk', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: worldHeight - 200
});
sidewalkV2.x = roadV.x + 85;
sidewalkV2.y = worldHeight / 2;
sidewalkV2.rotation = Math.PI / 2;
gameWorld.addChild(sidewalkV2);
}
}
function createVehicles() {
// Vehicle types
var types = ['car', 'truck', 'motorcycle'];
// Create a variety of vehicles
for (var i = 0; i < 30; i++) {
var type = types[Math.floor(Math.random() * types.length)];
var vehicle = new Vehicle(type);
// Position vehicle on roads
var onVerticalRoad = Math.random() < 0.5;
if (onVerticalRoad) {
vehicle.x = (Math.floor(Math.random() * 30) + 1) * (worldWidth / 31);
vehicle.y = Math.random() * worldHeight;
} else {
vehicle.x = Math.random() * worldWidth;
vehicle.y = (Math.floor(Math.random() * 40) + 1) * (worldHeight / 41);
}
vehicles.push(vehicle);
gameWorld.addChild(vehicle);
}
}
function createPedestrians() {
// Create pedestrians
for (var i = 0; i < 50; i++) {
var pedestrian = new Pedestrian();
// Position pedestrian randomly on sidewalks
pedestrian.x = Math.random() * worldWidth;
pedestrian.y = Math.random() * worldHeight;
pedestrians.push(pedestrian);
gameWorld.addChild(pedestrian);
}
}
function updateScore() {
if (player) {
scoreText.setText('SCORE: ' + player.score);
// Update high score if needed
if (player.score > storage.highScore) {
storage.highScore = player.score;
}
}
}
function checkCollisions() {
// Player with vehicles (for entering)
if (player && !player.inVehicle) {
for (var i = 0; i < vehicles.length; i++) {
var vehicle = vehicles[i];
if (!vehicle.isOccupied && player.intersects(vehicle)) {
// Player can enter vehicle
if (dragMode) {
// Enter vehicle
player.inVehicle = vehicle;
vehicle.isOccupied = true;
player.visible = false;
// Play sound
LK.getSound('car_start').play();
// Show fuel gauge
fuelGauge.visible = true;
fuelGauge.update(vehicle.fuel);
break;
}
}
}
}
// Bullets with pedestrians and vehicles
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Initialize lastWasIntersecting if not present
if (bullet.lastWasIntersecting === undefined) {
bullet.lastWasIntersecting = false;
}
// Check collision with pedestrians
for (var j = 0; j < pedestrians.length; j++) {
if (bullet.active && !bullet.lastWasIntersecting && bullet.intersects(pedestrians[j])) {
pedestrians[j].takeDamage(bullet.damage);
bullet.active = false;
break;
}
}
// Check collision with vehicles
for (var j = 0; j < vehicles.length; j++) {
if (bullet.active && bullet.intersects(vehicles[j])) {
// Damage vehicle or make it explode
LK.effects.flashObject(vehicles[j], 0xff0000, 300);
LK.getSound('collision').play();
// Bullets stop when hitting vehicles
bullet.active = false;
break;
}
}
// Check collision with buildings
for (var j = 0; j < buildings.length; j++) {
if (bullet.active && bullet.intersects(buildings[j])) {
bullet.active = false;
break;
}
}
// Remove inactive bullets
if (!bullet.active) {
bullet.destroy();
bullets.splice(i, 1);
}
}
// Vehicle with vehicle collisions
for (var i = 0; i < vehicles.length; i++) {
for (var j = i + 1; j < vehicles.length; j++) {
if (vehicles[i].intersects(vehicles[j])) {
// Simplified collision response - just reverse direction
vehicles[i].velocity.x *= -0.5;
vehicles[i].velocity.y *= -0.5;
vehicles[j].velocity.x *= -0.5;
vehicles[j].velocity.y *= -0.5;
LK.getSound('collision').play();
}
}
}
// Vehicle with building collisions
for (var i = 0; i < vehicles.length; i++) {
for (var j = 0; j < buildings.length; j++) {
if (vehicles[i].intersects(buildings[j])) {
// Simplified collision response - just stop
vehicles[i].velocity.x = 0;
vehicles[i].velocity.y = 0;
// Move away from building
var dx = vehicles[i].x - buildings[j].x;
var dy = vehicles[i].y - buildings[j].y;
var angle = Math.atan2(dy, dx);
vehicles[i].x += Math.cos(angle) * 5;
vehicles[i].y += Math.sin(angle) * 5;
if (vehicles[i].isOccupied) {
LK.getSound('collision').play();
}
}
}
}
}
function checkCurrentDistrict() {
// Find what district the player is in
if (player) {
var foundDistrict = null;
for (var i = 0; i < districts.length; i++) {
if (districts[i].contains(player.x, player.y)) {
foundDistrict = districts[i];
break;
}
}
if (foundDistrict !== currentDistrict) {
currentDistrict = foundDistrict;
if (currentDistrict) {
// Update district display
districtText.setText('DISTRICT: ' + currentDistrict.name);
// Save last district
storage.lastDistrict = currentDistrict.name;
// Show district notification
var notification = new Text2('Entering ' + currentDistrict.name, {
size: 60,
fill: 0xFFFFFF
});
notification.anchor.set(0.5, 0.5);
notification.x = 2048 / 2;
notification.y = 2732 / 2;
LK.gui.addChild(notification);
// Fade out notification
tween(notification, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
notification.destroy();
}
});
}
}
}
}
function handleMove(x, y, obj) {
// Convert screen coordinates to world coordinates
var worldX = x - gameWorld.x;
var worldY = y - gameWorld.y;
if (dragMode && player) {
if (player.inVehicle) {
// If in vehicle, set direction based on touch position
var dx = worldX - player.inVehicle.x;
var dy = worldY - player.inVehicle.y;
var angle = Math.atan2(dy, dx);
// Only move if there's fuel
if (player.inVehicle.fuel > 0) {
player.inVehicle.setVelocity(player.inVehicle.maxSpeed, angle);
}
} else {
// If on foot, move player towards touch position
var dx = worldX - player.x;
var dy = worldY - player.y;
var angle = Math.atan2(dy, dx);
player.setVelocity(player.maxSpeed, angle);
}
}
}
game.down = function (x, y, obj) {
dragMode = true;
handleMove(x, y, obj);
};
game.up = function (x, y, obj) {
dragMode = false;
// Stop player movement when touch is released
if (player) {
if (player.inVehicle) {
player.inVehicle.velocity.x = 0;
player.inVehicle.velocity.y = 0;
} else {
player.velocity.x = 0;
player.velocity.y = 0;
}
}
// Check if tap was to exit vehicle
if (player && player.inVehicle) {
// Double check if drag distance was small enough to be considered a tap
var dx = x - (player.inVehicle.x + gameWorld.x);
var dy = y - (player.inVehicle.y + gameWorld.y);
var dragDistance = Math.sqrt(dx * dx + dy * dy);
if (dragDistance < 50) {
// Exit vehicle
player.exitVehicle();
fuelGauge.visible = false;
}
}
// Check if tap was to shoot
if (player && !player.inVehicle) {
// Convert screen coordinates to world coordinates
var worldX = x - gameWorld.x;
var worldY = y - gameWorld.y;
// Calculate direction to shoot
var dx = worldX - player.x;
var dy = worldY - player.y;
player.direction = Math.atan2(dy, dx);
// Shoot bullet
player.shootBullet();
}
};
game.move = handleMove;
game.update = function () {
// Skip if game not initialized yet
if (!player || !gameWorld) {
return;
}
// Update all game objects
player.update();
for (var i = 0; i < vehicles.length; i++) {
vehicles[i].update();
}
for (var i = 0; i < pedestrians.length; i++) {
pedestrians[i].update();
}
for (var i = 0; i < bullets.length; i++) {
// Save intersection state before updating
for (var j = 0; j < pedestrians.length; j++) {
if (bullets[i].intersects(pedestrians[j])) {
bullets[i].lastWasIntersecting = true;
break;
} else {
bullets[i].lastWasIntersecting = false;
}
}
bullets[i].update();
}
// Update camera
camera.update();
// Update minimap
minimap.update();
// Update fuel gauge if in vehicle
if (player.inVehicle) {
fuelGauge.update(player.inVehicle.fuel);
}
// Check collisions
checkCollisions();
// Check current district
checkCurrentDistrict();
};
// Initialize game world
initGameWorld();