/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { particles: [], settings: { gravity: 0.5, friction: 0.98, elasticity: 0.8 } }); /**** * Classes ****/ var MaterialButton = Container.expand(function (materialType, color) { var self = Container.call(this); self.materialType = materialType; self.isSelected = false; // Create button background self.background = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.5 }); self.background.tint = 0x333333; // Create material preview self.preview = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.8 }); self.preview.tint = color; // Apply special effects for different materials switch (materialType) { case 'water': self.preview.alpha = 0.8; break; case 'gas': self.preview.alpha = 0.6; break; case 'fire': // Create flickering effect LK.setInterval(function () { if (self.preview) { var flicker = 0.9 + Math.random() * 0.2; self.preview.alpha = flicker; } }, 100); break; } // Event handlers self.down = function (x, y, obj) { selectMaterial(self.materialType); }; self.setSelected = function (selected) { self.isSelected = selected; if (selected) { tween(self.background, { tint: 0x3498db }, { duration: 200 }); tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } else { tween(self.background, { tint: 0x333333 }, { duration: 200 }); tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }; return self; }); var Particle = Container.expand(function (type, size, color) { var self = Container.call(this); // Default properties if not specified self.type = type || 'solid'; self.size = size || 20; self.color = color || 0xFFFFFF; // Physical properties self.vx = 0; self.vy = 0; self.mass = self.size / 10; self.elasticity = 0.8; self.friction = 0.98; self.isFixed = false; self.lifespan = -1; // -1 means infinite self.age = 0; // Visual representation self.visual = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: self.size / 20, scaleY: self.size / 20 }); self.visual.tint = self.color; // Type-specific properties switch (self.type) { case 'water': self.elasticity = 0.3; self.visual.tint = 0x3498db; self.visual.alpha = 0.8; break; case 'fire': self.lifespan = 120; self.elasticity = 0.1; self.visual.tint = 0xe74c3c; break; case 'gas': self.mass = self.size / 20; self.elasticity = 0.1; self.visual.tint = 0xecf0f1; self.visual.alpha = 0.6; break; case 'bouncy': self.elasticity = 1.2; self.visual.tint = 0x2ecc71; break; case 'heavy': self.mass = self.size / 5; self.visual.tint = 0x7f8c8d; break; } // Event handlers self.down = function (x, y, obj) { // Start dragging this particle self.isDragging = true; self.dragOffsetX = x; self.dragOffsetY = y; // If we're in fixed mode, toggle fixed state if (currentTool === 'fix') { self.isFixed = !self.isFixed; self.visual.alpha = self.isFixed ? 0.5 : 1.0; LK.getSound('pop').play(); } // If we're in erase mode, mark for deletion if (currentTool === 'erase') { self.toDelete = true; LK.getSound('pop').play(); } }; self.up = function (x, y, obj) { self.isDragging = false; // Apply velocity from drag if we just released if (currentTool === 'move' && !self.isFixed) { self.vx = (x - self.dragOffsetX) / 10; self.vy = (y - self.dragOffsetY) / 10; } }; self.update = function () { // Skip physics for fixed particles if (self.isFixed) { return; } // Age particles with lifespans if (self.lifespan > 0) { self.age++; if (self.age >= self.lifespan) { self.toDelete = true; return; } // Fade out as we age self.visual.alpha = 1 - self.age / self.lifespan; } // Skip physics for particles being dragged if (self.isDragging && currentTool === 'move') { return; } // Apply gravity based on mass and global gravity setting if (currentTool !== 'antigravity' && !isAntigravityActive) { self.vy += settings.gravity * self.mass; } else { self.vy -= settings.gravity * self.mass / 2; } // Apply type-specific behavior if (self.type === 'gas') { self.vy -= settings.gravity * 1.5; // Gases rise self.vx += (Math.random() - 0.5) * 0.3; // Random horizontal drift } else if (self.type === 'fire') { self.vy -= settings.gravity * 1.2; // Fire rises self.vx += (Math.random() - 0.5) * 0.5; // More horizontal drift } // Apply velocity self.x += self.vx; self.y += self.vy; // Apply friction self.vx *= settings.friction; self.vy *= settings.friction; // Boundary collisions handleBoundaryCollisions(self); }; return self; }); var ToolButton = Container.expand(function (toolName, iconColor, iconShape) { var self = Container.call(this); self.toolName = toolName; self.isSelected = false; // Create button background self.background = self.attachAsset('particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.5 }); self.background.tint = 0x333333; // Create icon self.icon = self.attachAsset(iconShape || 'particle', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.8, scaleY: 1.8 }); self.icon.tint = iconColor || 0xFFFFFF; // Event handlers self.down = function (x, y, obj) { selectTool(self.toolName); }; self.setSelected = function (selected) { self.isSelected = selected; if (selected) { tween(self.background, { tint: 0x3498db }, { duration: 200 }); tween(self, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } else { tween(self.background, { tint: 0x333333 }, { duration: 200 }); tween(self, { scaleX: 1.0, scaleY: 1.0 }, { duration: 200 }); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // Game settings var settings = storage.settings || { gravity: 0.5, friction: 0.98, elasticity: 0.8 }; // Game state var particles = []; var currentTool = 'create'; var currentMaterial = 'solid'; var toolButtons = []; var materialButtons = []; var isMouseDown = false; var lastCreationTime = 0; var creationDelay = 5; // Ticks between particle creation var isAntigravityActive = false; var dragTarget = null; var particleSize = 20; // Create UI elements function createUI() { // Create toolbar var toolbar = new Container(); toolbar.x = 2048 / 2; toolbar.y = 2732 - 100; game.addChild(toolbar); // Create tool buttons var toolDefs = [{ name: 'create', color: 0xFFFFFF, shape: 'particle' }, { name: 'move', color: 0x3498db, shape: 'particle' }, { name: 'erase', color: 0xe74c3c, shape: 'eraser' }, { name: 'fix', color: 0xf1c40f, shape: 'particle' }, { name: 'antigravity', color: 0x00FFFF, shape: 'gravityIndicator' }]; var buttonSpacing = 100; for (var i = 0; i < toolDefs.length; i++) { var def = toolDefs[i]; var button = new ToolButton(def.name, def.color, def.shape); button.x = (i - Math.floor(toolDefs.length / 2)) * buttonSpacing; toolbar.addChild(button); toolButtons.push(button); } // Create material selector var materials = new Container(); materials.x = 100; materials.y = 2732 / 2; game.addChild(materials); // Create material buttons var materialDefs = [{ type: 'solid', color: 0xFFFFFF }, { type: 'water', color: 0x3498db }, { type: 'bouncy', color: 0x2ecc71 }, { type: 'heavy', color: 0x7f8c8d }, { type: 'fire', color: 0xe74c3c }, { type: 'gas', color: 0xecf0f1 }]; for (var j = 0; j < materialDefs.length; j++) { var matDef = materialDefs[j]; var matButton = new MaterialButton(matDef.type, matDef.color); matButton.y = (j - Math.floor(materialDefs.length / 2)) * buttonSpacing; materials.addChild(matButton); materialButtons.push(matButton); } // Create size controls var sizeControls = new Container(); sizeControls.x = 2048 - 100; sizeControls.y = 2732 / 2; game.addChild(sizeControls); var sizeUpButton = new ToolButton('sizeUp', 0xFFFFFF, 'particle'); sizeUpButton.y = -50; sizeUpButton.down = function () { particleSize = Math.min(particleSize + 5, 50); }; sizeControls.addChild(sizeUpButton); var sizeDownButton = new ToolButton('sizeDown', 0xFFFFFF, 'particle'); sizeDownButton.y = 50; sizeDownButton.down = function () { particleSize = Math.max(particleSize - 5, 10); }; sizeControls.addChild(sizeDownButton); // Set initially selected tool and material selectTool('create'); selectMaterial('solid'); // Create info text var infoText = new Text2('Universal Sandbox', { size: 40, fill: 0xFFFFFF }); infoText.anchor.set(0.5, 0); LK.gui.top.addChild(infoText); } // Tool selection logic function selectTool(toolName) { currentTool = toolName; // Update button appearances for (var i = 0; i < toolButtons.length; i++) { var button = toolButtons[i]; button.setSelected(button.toolName === currentTool); } // Special case for antigravity tool isAntigravityActive = currentTool === 'antigravity'; } // Material selection logic function selectMaterial(materialType) { currentMaterial = materialType; // Update button appearances for (var i = 0; i < materialButtons.length; i++) { var button = materialButtons[i]; button.setSelected(button.materialType === currentMaterial); } } // Particle creation logic function createParticle(x, y) { // Get appropriate color based on material var color; switch (currentMaterial) { case 'water': color = 0x3498db; break; case 'fire': color = 0xe74c3c; break; case 'gas': color = 0xecf0f1; break; case 'bouncy': color = 0x2ecc71; break; case 'heavy': color = 0x7f8c8d; break; default: color = 0xFFFFFF; // solid } var particle = new Particle(currentMaterial, particleSize, color); particle.x = x; particle.y = y; // Add some randomness to initial state particle.vx = (Math.random() - 0.5) * 2; particle.vy = (Math.random() - 0.5) * 2; // Add to game game.addChild(particle); particles.push(particle); // Play creation sound if (particles.length % 5 === 0) { LK.getSound('pop').play(); } return particle; } // Boundary collision handling function handleBoundaryCollisions(particle) { var padding = particle.size / 2; // Left and right edges if (particle.x < padding) { particle.x = padding; particle.vx = -particle.vx * particle.elasticity; if (Math.abs(particle.vx) > 1) { LK.getSound('bounce').play(); } } else if (particle.x > 2048 - padding) { particle.x = 2048 - padding; particle.vx = -particle.vx * particle.elasticity; if (Math.abs(particle.vx) > 1) { LK.getSound('bounce').play(); } } // Top and bottom edges if (particle.y < padding) { particle.y = padding; particle.vy = -particle.vy * particle.elasticity; if (Math.abs(particle.vy) > 1) { LK.getSound('bounce').play(); } } else if (particle.y > 2732 - 200 - padding) { // Bottom minus toolbar height particle.y = 2732 - 200 - padding; particle.vy = -particle.vy * particle.elasticity; if (Math.abs(particle.vy) > 1) { LK.getSound('bounce').play(); } } } // Particle collision detection and response function handleParticleCollisions() { for (var i = 0; i < particles.length; i++) { var p1 = particles[i]; if (p1.toDelete || p1.isFixed) { continue; } for (var j = i + 1; j < particles.length; j++) { var p2 = particles[j]; if (p2.toDelete || p2.isFixed) { continue; } // Calculate distance var dx = p2.x - p1.x; var dy = p2.y - p1.y; var distance = Math.sqrt(dx * dx + dy * dy); var minDistance = (p1.size + p2.size) / 2; // Collision detected if (distance < minDistance) { // Normalize collision vector var nx = dx / distance; var ny = dy / distance; // Calculate relative velocity var vx = p1.vx - p2.vx; var vy = p1.vy - p2.vy; // Calculate relative velocity in terms of the normal direction var velocityAlongNormal = vx * nx + vy * ny; // Do not resolve if velocities are separating if (velocityAlongNormal > 0) { continue; } // Calculate combined elasticity var combinedElasticity = (p1.elasticity + p2.elasticity) / 2; // Calculate impulse scalar var impulseScalar = -(1 + combinedElasticity) * velocityAlongNormal; impulseScalar /= 1 / p1.mass + 1 / p2.mass; // Apply impulse var impulseX = impulseScalar * nx; var impulseY = impulseScalar * ny; p1.vx -= impulseX / p1.mass; p1.vy -= impulseY / p1.mass; p2.vx += impulseX / p2.mass; p2.vy += impulseY / p2.mass; // Move particles apart to prevent sticking var overlap = minDistance - distance; var correctionX = overlap * nx * 0.5; var correctionY = overlap * ny * 0.5; p1.x -= correctionX; p1.y -= correctionY; p2.x += correctionX; p2.y += correctionY; // Play sound for significant collisions if (Math.abs(velocityAlongNormal) > 5) { if (p1.type === 'water' || p2.type === 'water') { LK.getSound('splash').play(); } else { LK.getSound('bounce').play(); } } } } } } // Event handlers game.down = function (x, y, obj) { isMouseDown = true; lastCreationTime = 0; // Reset to create immediately // Handle tool-specific behavior if (currentTool === 'create') { createParticle(x, y); } else if (currentTool === 'move') { // Find particle under cursor for (var i = particles.length - 1; i >= 0; i--) { var p = particles[i]; var dx = p.x - x; var dy = p.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < p.size) { dragTarget = p; dragTarget.isDragging = true; dragTarget.dragOffsetX = x; dragTarget.dragOffsetY = y; break; } } } }; game.up = function (x, y, obj) { isMouseDown = false; // Release drag target if (dragTarget) { dragTarget.up(x, y, obj); dragTarget = null; } }; game.move = function (x, y, obj) { // Handle continuous creation if (isMouseDown && currentTool === 'create' && LK.ticks - lastCreationTime > creationDelay) { createParticle(x, y); lastCreationTime = LK.ticks; } // Handle eraser tool if (isMouseDown && currentTool === 'erase') { for (var i = particles.length - 1; i >= 0; i--) { var p = particles[i]; var dx = p.x - x; var dy = p.y - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < p.size + 25) { p.toDelete = true; } } } // Update drag target position if (dragTarget) { dragTarget.x = x; dragTarget.y = y; } }; // Initialize game createUI(); // Start background music LK.playMusic('ambient', { loop: true, fade: { start: 0, end: 0.6, duration: 1000 } }); // Set background color game.setBackgroundColor(0x1a1a2e); // Main game loop game.update = function () { // Clean up particles marked for deletion for (var i = particles.length - 1; i >= 0; i--) { if (particles[i].toDelete) { particles[i].destroy(); particles.splice(i, 1); } } // Handle particle collisions handleParticleCollisions(); // Special effects for different materials for (var j = 0; j < particles.length; j++) { var p = particles[j]; // Fire effects if (p.type === 'fire' && Math.random() < 0.1) { // Create smoke particles occasionally if (Math.random() < 0.05) { var smoke = new Particle('gas', p.size * 0.8, 0x7f8c8d); smoke.x = p.x + (Math.random() - 0.5) * 10; smoke.y = p.y - 10; smoke.lifespan = 60; game.addChild(smoke); particles.push(smoke); } } // Water effects if (p.type === 'water' && Math.random() < 0.02) { // Make water particles occasionally merge or split if (Math.random() < 0.5 && p.size > 15) { // Split var droplet = new Particle('water', p.size * 0.6, 0x3498db); droplet.x = p.x + (Math.random() - 0.5) * 20; droplet.y = p.y + (Math.random() - 0.5) * 20; droplet.vx = p.vx + (Math.random() - 0.5) * 2; droplet.vy = p.vy + (Math.random() - 0.5) * 2; game.addChild(droplet); particles.push(droplet); p.size *= 0.9; p.visual.scaleX = p.size / 20; p.visual.scaleY = p.size / 20; } } } };
===================================================================
--- original.js
+++ change.js
@@ -1,6 +1,657 @@
-/****
+/****
+* Plugins
+****/
+var tween = LK.import("@upit/tween.v1");
+var storage = LK.import("@upit/storage.v1", {
+ particles: [],
+ settings: {
+ gravity: 0.5,
+ friction: 0.98,
+ elasticity: 0.8
+ }
+});
+
+/****
+* Classes
+****/
+var MaterialButton = Container.expand(function (materialType, color) {
+ var self = Container.call(this);
+ self.materialType = materialType;
+ self.isSelected = false;
+ // Create button background
+ self.background = self.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.background.tint = 0x333333;
+ // Create material preview
+ self.preview = self.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.8,
+ scaleY: 1.8
+ });
+ self.preview.tint = color;
+ // Apply special effects for different materials
+ switch (materialType) {
+ case 'water':
+ self.preview.alpha = 0.8;
+ break;
+ case 'gas':
+ self.preview.alpha = 0.6;
+ break;
+ case 'fire':
+ // Create flickering effect
+ LK.setInterval(function () {
+ if (self.preview) {
+ var flicker = 0.9 + Math.random() * 0.2;
+ self.preview.alpha = flicker;
+ }
+ }, 100);
+ break;
+ }
+ // Event handlers
+ self.down = function (x, y, obj) {
+ selectMaterial(self.materialType);
+ };
+ self.setSelected = function (selected) {
+ self.isSelected = selected;
+ if (selected) {
+ tween(self.background, {
+ tint: 0x3498db
+ }, {
+ duration: 200
+ });
+ tween(self, {
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 200
+ });
+ } else {
+ tween(self.background, {
+ tint: 0x333333
+ }, {
+ duration: 200
+ });
+ tween(self, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 200
+ });
+ }
+ };
+ return self;
+});
+var Particle = Container.expand(function (type, size, color) {
+ var self = Container.call(this);
+ // Default properties if not specified
+ self.type = type || 'solid';
+ self.size = size || 20;
+ self.color = color || 0xFFFFFF;
+ // Physical properties
+ self.vx = 0;
+ self.vy = 0;
+ self.mass = self.size / 10;
+ self.elasticity = 0.8;
+ self.friction = 0.98;
+ self.isFixed = false;
+ self.lifespan = -1; // -1 means infinite
+ self.age = 0;
+ // Visual representation
+ self.visual = self.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: self.size / 20,
+ scaleY: self.size / 20
+ });
+ self.visual.tint = self.color;
+ // Type-specific properties
+ switch (self.type) {
+ case 'water':
+ self.elasticity = 0.3;
+ self.visual.tint = 0x3498db;
+ self.visual.alpha = 0.8;
+ break;
+ case 'fire':
+ self.lifespan = 120;
+ self.elasticity = 0.1;
+ self.visual.tint = 0xe74c3c;
+ break;
+ case 'gas':
+ self.mass = self.size / 20;
+ self.elasticity = 0.1;
+ self.visual.tint = 0xecf0f1;
+ self.visual.alpha = 0.6;
+ break;
+ case 'bouncy':
+ self.elasticity = 1.2;
+ self.visual.tint = 0x2ecc71;
+ break;
+ case 'heavy':
+ self.mass = self.size / 5;
+ self.visual.tint = 0x7f8c8d;
+ break;
+ }
+ // Event handlers
+ self.down = function (x, y, obj) {
+ // Start dragging this particle
+ self.isDragging = true;
+ self.dragOffsetX = x;
+ self.dragOffsetY = y;
+ // If we're in fixed mode, toggle fixed state
+ if (currentTool === 'fix') {
+ self.isFixed = !self.isFixed;
+ self.visual.alpha = self.isFixed ? 0.5 : 1.0;
+ LK.getSound('pop').play();
+ }
+ // If we're in erase mode, mark for deletion
+ if (currentTool === 'erase') {
+ self.toDelete = true;
+ LK.getSound('pop').play();
+ }
+ };
+ self.up = function (x, y, obj) {
+ self.isDragging = false;
+ // Apply velocity from drag if we just released
+ if (currentTool === 'move' && !self.isFixed) {
+ self.vx = (x - self.dragOffsetX) / 10;
+ self.vy = (y - self.dragOffsetY) / 10;
+ }
+ };
+ self.update = function () {
+ // Skip physics for fixed particles
+ if (self.isFixed) {
+ return;
+ }
+ // Age particles with lifespans
+ if (self.lifespan > 0) {
+ self.age++;
+ if (self.age >= self.lifespan) {
+ self.toDelete = true;
+ return;
+ }
+ // Fade out as we age
+ self.visual.alpha = 1 - self.age / self.lifespan;
+ }
+ // Skip physics for particles being dragged
+ if (self.isDragging && currentTool === 'move') {
+ return;
+ }
+ // Apply gravity based on mass and global gravity setting
+ if (currentTool !== 'antigravity' && !isAntigravityActive) {
+ self.vy += settings.gravity * self.mass;
+ } else {
+ self.vy -= settings.gravity * self.mass / 2;
+ }
+ // Apply type-specific behavior
+ if (self.type === 'gas') {
+ self.vy -= settings.gravity * 1.5; // Gases rise
+ self.vx += (Math.random() - 0.5) * 0.3; // Random horizontal drift
+ } else if (self.type === 'fire') {
+ self.vy -= settings.gravity * 1.2; // Fire rises
+ self.vx += (Math.random() - 0.5) * 0.5; // More horizontal drift
+ }
+ // Apply velocity
+ self.x += self.vx;
+ self.y += self.vy;
+ // Apply friction
+ self.vx *= settings.friction;
+ self.vy *= settings.friction;
+ // Boundary collisions
+ handleBoundaryCollisions(self);
+ };
+ return self;
+});
+var ToolButton = Container.expand(function (toolName, iconColor, iconShape) {
+ var self = Container.call(this);
+ self.toolName = toolName;
+ self.isSelected = false;
+ // Create button background
+ self.background = self.attachAsset('particle', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 2.5,
+ scaleY: 2.5
+ });
+ self.background.tint = 0x333333;
+ // Create icon
+ self.icon = self.attachAsset(iconShape || 'particle', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 1.8,
+ scaleY: 1.8
+ });
+ self.icon.tint = iconColor || 0xFFFFFF;
+ // Event handlers
+ self.down = function (x, y, obj) {
+ selectTool(self.toolName);
+ };
+ self.setSelected = function (selected) {
+ self.isSelected = selected;
+ if (selected) {
+ tween(self.background, {
+ tint: 0x3498db
+ }, {
+ duration: 200
+ });
+ tween(self, {
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 200
+ });
+ } else {
+ tween(self.background, {
+ tint: 0x333333
+ }, {
+ duration: 200
+ });
+ tween(self, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 200
+ });
+ }
+ };
+ return self;
+});
+
+/****
* Initialize Game
-****/
+****/
var game = new LK.Game({
backgroundColor: 0x000000
-});
\ No newline at end of file
+});
+
+/****
+* Game Code
+****/
+// Game settings
+var settings = storage.settings || {
+ gravity: 0.5,
+ friction: 0.98,
+ elasticity: 0.8
+};
+// Game state
+var particles = [];
+var currentTool = 'create';
+var currentMaterial = 'solid';
+var toolButtons = [];
+var materialButtons = [];
+var isMouseDown = false;
+var lastCreationTime = 0;
+var creationDelay = 5; // Ticks between particle creation
+var isAntigravityActive = false;
+var dragTarget = null;
+var particleSize = 20;
+// Create UI elements
+function createUI() {
+ // Create toolbar
+ var toolbar = new Container();
+ toolbar.x = 2048 / 2;
+ toolbar.y = 2732 - 100;
+ game.addChild(toolbar);
+ // Create tool buttons
+ var toolDefs = [{
+ name: 'create',
+ color: 0xFFFFFF,
+ shape: 'particle'
+ }, {
+ name: 'move',
+ color: 0x3498db,
+ shape: 'particle'
+ }, {
+ name: 'erase',
+ color: 0xe74c3c,
+ shape: 'eraser'
+ }, {
+ name: 'fix',
+ color: 0xf1c40f,
+ shape: 'particle'
+ }, {
+ name: 'antigravity',
+ color: 0x00FFFF,
+ shape: 'gravityIndicator'
+ }];
+ var buttonSpacing = 100;
+ for (var i = 0; i < toolDefs.length; i++) {
+ var def = toolDefs[i];
+ var button = new ToolButton(def.name, def.color, def.shape);
+ button.x = (i - Math.floor(toolDefs.length / 2)) * buttonSpacing;
+ toolbar.addChild(button);
+ toolButtons.push(button);
+ }
+ // Create material selector
+ var materials = new Container();
+ materials.x = 100;
+ materials.y = 2732 / 2;
+ game.addChild(materials);
+ // Create material buttons
+ var materialDefs = [{
+ type: 'solid',
+ color: 0xFFFFFF
+ }, {
+ type: 'water',
+ color: 0x3498db
+ }, {
+ type: 'bouncy',
+ color: 0x2ecc71
+ }, {
+ type: 'heavy',
+ color: 0x7f8c8d
+ }, {
+ type: 'fire',
+ color: 0xe74c3c
+ }, {
+ type: 'gas',
+ color: 0xecf0f1
+ }];
+ for (var j = 0; j < materialDefs.length; j++) {
+ var matDef = materialDefs[j];
+ var matButton = new MaterialButton(matDef.type, matDef.color);
+ matButton.y = (j - Math.floor(materialDefs.length / 2)) * buttonSpacing;
+ materials.addChild(matButton);
+ materialButtons.push(matButton);
+ }
+ // Create size controls
+ var sizeControls = new Container();
+ sizeControls.x = 2048 - 100;
+ sizeControls.y = 2732 / 2;
+ game.addChild(sizeControls);
+ var sizeUpButton = new ToolButton('sizeUp', 0xFFFFFF, 'particle');
+ sizeUpButton.y = -50;
+ sizeUpButton.down = function () {
+ particleSize = Math.min(particleSize + 5, 50);
+ };
+ sizeControls.addChild(sizeUpButton);
+ var sizeDownButton = new ToolButton('sizeDown', 0xFFFFFF, 'particle');
+ sizeDownButton.y = 50;
+ sizeDownButton.down = function () {
+ particleSize = Math.max(particleSize - 5, 10);
+ };
+ sizeControls.addChild(sizeDownButton);
+ // Set initially selected tool and material
+ selectTool('create');
+ selectMaterial('solid');
+ // Create info text
+ var infoText = new Text2('Universal Sandbox', {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ infoText.anchor.set(0.5, 0);
+ LK.gui.top.addChild(infoText);
+}
+// Tool selection logic
+function selectTool(toolName) {
+ currentTool = toolName;
+ // Update button appearances
+ for (var i = 0; i < toolButtons.length; i++) {
+ var button = toolButtons[i];
+ button.setSelected(button.toolName === currentTool);
+ }
+ // Special case for antigravity tool
+ isAntigravityActive = currentTool === 'antigravity';
+}
+// Material selection logic
+function selectMaterial(materialType) {
+ currentMaterial = materialType;
+ // Update button appearances
+ for (var i = 0; i < materialButtons.length; i++) {
+ var button = materialButtons[i];
+ button.setSelected(button.materialType === currentMaterial);
+ }
+}
+// Particle creation logic
+function createParticle(x, y) {
+ // Get appropriate color based on material
+ var color;
+ switch (currentMaterial) {
+ case 'water':
+ color = 0x3498db;
+ break;
+ case 'fire':
+ color = 0xe74c3c;
+ break;
+ case 'gas':
+ color = 0xecf0f1;
+ break;
+ case 'bouncy':
+ color = 0x2ecc71;
+ break;
+ case 'heavy':
+ color = 0x7f8c8d;
+ break;
+ default:
+ color = 0xFFFFFF;
+ // solid
+ }
+ var particle = new Particle(currentMaterial, particleSize, color);
+ particle.x = x;
+ particle.y = y;
+ // Add some randomness to initial state
+ particle.vx = (Math.random() - 0.5) * 2;
+ particle.vy = (Math.random() - 0.5) * 2;
+ // Add to game
+ game.addChild(particle);
+ particles.push(particle);
+ // Play creation sound
+ if (particles.length % 5 === 0) {
+ LK.getSound('pop').play();
+ }
+ return particle;
+}
+// Boundary collision handling
+function handleBoundaryCollisions(particle) {
+ var padding = particle.size / 2;
+ // Left and right edges
+ if (particle.x < padding) {
+ particle.x = padding;
+ particle.vx = -particle.vx * particle.elasticity;
+ if (Math.abs(particle.vx) > 1) {
+ LK.getSound('bounce').play();
+ }
+ } else if (particle.x > 2048 - padding) {
+ particle.x = 2048 - padding;
+ particle.vx = -particle.vx * particle.elasticity;
+ if (Math.abs(particle.vx) > 1) {
+ LK.getSound('bounce').play();
+ }
+ }
+ // Top and bottom edges
+ if (particle.y < padding) {
+ particle.y = padding;
+ particle.vy = -particle.vy * particle.elasticity;
+ if (Math.abs(particle.vy) > 1) {
+ LK.getSound('bounce').play();
+ }
+ } else if (particle.y > 2732 - 200 - padding) {
+ // Bottom minus toolbar height
+ particle.y = 2732 - 200 - padding;
+ particle.vy = -particle.vy * particle.elasticity;
+ if (Math.abs(particle.vy) > 1) {
+ LK.getSound('bounce').play();
+ }
+ }
+}
+// Particle collision detection and response
+function handleParticleCollisions() {
+ for (var i = 0; i < particles.length; i++) {
+ var p1 = particles[i];
+ if (p1.toDelete || p1.isFixed) {
+ continue;
+ }
+ for (var j = i + 1; j < particles.length; j++) {
+ var p2 = particles[j];
+ if (p2.toDelete || p2.isFixed) {
+ continue;
+ }
+ // Calculate distance
+ var dx = p2.x - p1.x;
+ var dy = p2.y - p1.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ var minDistance = (p1.size + p2.size) / 2;
+ // Collision detected
+ if (distance < minDistance) {
+ // Normalize collision vector
+ var nx = dx / distance;
+ var ny = dy / distance;
+ // Calculate relative velocity
+ var vx = p1.vx - p2.vx;
+ var vy = p1.vy - p2.vy;
+ // Calculate relative velocity in terms of the normal direction
+ var velocityAlongNormal = vx * nx + vy * ny;
+ // Do not resolve if velocities are separating
+ if (velocityAlongNormal > 0) {
+ continue;
+ }
+ // Calculate combined elasticity
+ var combinedElasticity = (p1.elasticity + p2.elasticity) / 2;
+ // Calculate impulse scalar
+ var impulseScalar = -(1 + combinedElasticity) * velocityAlongNormal;
+ impulseScalar /= 1 / p1.mass + 1 / p2.mass;
+ // Apply impulse
+ var impulseX = impulseScalar * nx;
+ var impulseY = impulseScalar * ny;
+ p1.vx -= impulseX / p1.mass;
+ p1.vy -= impulseY / p1.mass;
+ p2.vx += impulseX / p2.mass;
+ p2.vy += impulseY / p2.mass;
+ // Move particles apart to prevent sticking
+ var overlap = minDistance - distance;
+ var correctionX = overlap * nx * 0.5;
+ var correctionY = overlap * ny * 0.5;
+ p1.x -= correctionX;
+ p1.y -= correctionY;
+ p2.x += correctionX;
+ p2.y += correctionY;
+ // Play sound for significant collisions
+ if (Math.abs(velocityAlongNormal) > 5) {
+ if (p1.type === 'water' || p2.type === 'water') {
+ LK.getSound('splash').play();
+ } else {
+ LK.getSound('bounce').play();
+ }
+ }
+ }
+ }
+ }
+}
+// Event handlers
+game.down = function (x, y, obj) {
+ isMouseDown = true;
+ lastCreationTime = 0; // Reset to create immediately
+ // Handle tool-specific behavior
+ if (currentTool === 'create') {
+ createParticle(x, y);
+ } else if (currentTool === 'move') {
+ // Find particle under cursor
+ for (var i = particles.length - 1; i >= 0; i--) {
+ var p = particles[i];
+ var dx = p.x - x;
+ var dy = p.y - y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < p.size) {
+ dragTarget = p;
+ dragTarget.isDragging = true;
+ dragTarget.dragOffsetX = x;
+ dragTarget.dragOffsetY = y;
+ break;
+ }
+ }
+ }
+};
+game.up = function (x, y, obj) {
+ isMouseDown = false;
+ // Release drag target
+ if (dragTarget) {
+ dragTarget.up(x, y, obj);
+ dragTarget = null;
+ }
+};
+game.move = function (x, y, obj) {
+ // Handle continuous creation
+ if (isMouseDown && currentTool === 'create' && LK.ticks - lastCreationTime > creationDelay) {
+ createParticle(x, y);
+ lastCreationTime = LK.ticks;
+ }
+ // Handle eraser tool
+ if (isMouseDown && currentTool === 'erase') {
+ for (var i = particles.length - 1; i >= 0; i--) {
+ var p = particles[i];
+ var dx = p.x - x;
+ var dy = p.y - y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < p.size + 25) {
+ p.toDelete = true;
+ }
+ }
+ }
+ // Update drag target position
+ if (dragTarget) {
+ dragTarget.x = x;
+ dragTarget.y = y;
+ }
+};
+// Initialize game
+createUI();
+// Start background music
+LK.playMusic('ambient', {
+ loop: true,
+ fade: {
+ start: 0,
+ end: 0.6,
+ duration: 1000
+ }
+});
+// Set background color
+game.setBackgroundColor(0x1a1a2e);
+// Main game loop
+game.update = function () {
+ // Clean up particles marked for deletion
+ for (var i = particles.length - 1; i >= 0; i--) {
+ if (particles[i].toDelete) {
+ particles[i].destroy();
+ particles.splice(i, 1);
+ }
+ }
+ // Handle particle collisions
+ handleParticleCollisions();
+ // Special effects for different materials
+ for (var j = 0; j < particles.length; j++) {
+ var p = particles[j];
+ // Fire effects
+ if (p.type === 'fire' && Math.random() < 0.1) {
+ // Create smoke particles occasionally
+ if (Math.random() < 0.05) {
+ var smoke = new Particle('gas', p.size * 0.8, 0x7f8c8d);
+ smoke.x = p.x + (Math.random() - 0.5) * 10;
+ smoke.y = p.y - 10;
+ smoke.lifespan = 60;
+ game.addChild(smoke);
+ particles.push(smoke);
+ }
+ }
+ // Water effects
+ if (p.type === 'water' && Math.random() < 0.02) {
+ // Make water particles occasionally merge or split
+ if (Math.random() < 0.5 && p.size > 15) {
+ // Split
+ var droplet = new Particle('water', p.size * 0.6, 0x3498db);
+ droplet.x = p.x + (Math.random() - 0.5) * 20;
+ droplet.y = p.y + (Math.random() - 0.5) * 20;
+ droplet.vx = p.vx + (Math.random() - 0.5) * 2;
+ droplet.vy = p.vy + (Math.random() - 0.5) * 2;
+ game.addChild(droplet);
+ particles.push(droplet);
+ p.size *= 0.9;
+ p.visual.scaleX = p.size / 20;
+ p.visual.scaleY = p.size / 20;
+ }
+ }
+ }
+};
\ No newline at end of file