/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ // Launchable object class var LaunchObject = Container.expand(function () { var self = Container.call(this); // Properties set on creation: type, mass self.type = 'box'; self.mass = 1.0; self.vx = 0; self.vy = 0; self.launched = false; self.inTornado = false; self.everInTornado = false; self.maxHeight = 0; self.arrow = null; // Attach correct asset var assetId = 'object_' + self.type; var objGfx = self.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); // Draw launch arrow (only before launch) self.showArrow = function (angle, power) { if (!self.arrow) { self.arrow = self.addChild(LK.getAsset('arrow', { anchorX: 0.0, anchorY: 0.5, alpha: 0.7 })); } self.arrow.rotation = angle; self.arrow.scaleX = Math.max(0.5, Math.min(power / 30, 2.5)); self.arrow.x = 0; self.arrow.y = 0; self.arrow.visible = true; }; self.hideArrow = function () { if (self.arrow) self.arrow.visible = false; }; // Called every tick self.update = function () { if (!self.launched) return; // Gravity self.vy += 0.18 * self.mass; // Tornado force if (tornado) { var force = tornado.getForce(self); self.vx += force.fx; self.vy += force.fy; // If inside tornado, mark var dx = self.x - tornado.centerX; var dy = self.y - tornado.centerY; var dist = Math.sqrt(dx * dx + dy * dy); self.inTornado = dist < tornado.radius; if (self.inTornado) { self.everInTornado = true; } } // Move self.x += self.vx; self.y += self.vy; // Track max height if (self.y < self.maxHeight) self.maxHeight = self.y; // Out of bounds: below screen if (self.y > 2732 + 200) { // Show experiment result before teleporting back var heightGain = Math.max(0, self.y - self.maxHeight); var msg = "Max height above launch: " + Math.round(heightGain) + " px"; // Only show "missed the tornado" if the object was never in the tornado during its flight if (self.inTornado || self.everInTornado) { msg += "\nThe " + self.type + " was caught by the tornado!"; } else { msg += "\nThe " + self.type + " missed the tornado."; } showResult(msg); // Teleport back to starting position and reset velocity self.x = 2048 / 2; self.y = 2732 - 200; self.vx = 0; self.vy = 0; self.launched = false; self.inTornado = false; self.everInTornado = false; self.maxHeight = self.y; self.hideArrow(); // Optionally, you could add a visual effect or sound here } }; return self; }); // Tornado class: swirling, applies force to objects inside var Tornado = Container.expand(function () { var self = Container.call(this); var tornadoGfx = self.attachAsset('tornado', { anchorX: 0.5, anchorY: 0.5, alpha: 0.5 }); // Tornado center self.centerX = 2048 / 2; self.centerY = 1700; self.radius = 300; self.strength = 1.0; // 1.0 = normal, can be changed per round self.rotationSpeed = 0.04; // radians per tick // Animate tornado swirl self.update = function () { // No spinning: do not change tornadoGfx.rotation }; // Returns force vector {fx, fy} for an object at (x, y) with mass self.getForce = function (obj) { var dx = obj.x - self.centerX; var dy = obj.y - self.centerY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > self.radius) return { fx: 0, fy: 0 }; // Outside tornado // Swirl: perpendicular to radius, magnitude decreases with mass, increases with strength var angle = Math.atan2(dy, dx) + Math.PI / 2; var forceMag = self.strength * 2.5 * (1 - dist / self.radius) / (obj.mass || 1); return { fx: Math.cos(angle) * forceMag, fy: Math.sin(angle) * forceMag - 0.12 // Updraft: negative y }; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x222233 }); /**** * Game Code ****/ // Sound for launch // Launch arrow // Object types: box, ball, cow, car (different colors/shapes) // Tornado: swirling ellipse, semi-transparent blue // --- Global variables --- var tornado = null; var currentObject = null; var launchedObjects = []; var round = 1; var roundActive = false; var dragStart = null; var dragPower = 0; var dragAngle = 0; var resultText = null; var infoText = null; var roundText = null; var nextButton = null; var objectTypes = [{ type: 'box', mass: 1.0, label: 'Box' }, { type: 'ball', mass: 0.7, label: 'Ball' }, { type: 'cow', mass: 1.8, label: 'Cow' }, { type: 'car', mass: 2.5, label: 'Car' }]; var tornadoStrengths = [1.0, 1.5, 0.7, 2.0, 0.5]; var objectIndex = 0; // --- UI Elements --- function showInfo(msg) { if (!infoText) { infoText = new Text2(msg, { size: 70, fill: "#fff" }); infoText.anchor.set(0.5, 0); LK.gui.top.addChild(infoText); } infoText.setText(msg); } function showResult(msg) { if (!resultText) { resultText = new Text2(msg, { size: 90, fill: "#fff" }); resultText.anchor.set(0.5, 0.5); LK.gui.center.addChild(resultText); } resultText.setText(msg); resultText.visible = true; } function hideResult() { if (resultText) resultText.visible = false; } function showRound() { if (!roundText) { roundText = new Text2('', { size: 60, fill: "#fff" }); roundText.anchor.set(0.5, 0); LK.gui.top.addChild(roundText); } roundText.setText("Round " + round + " / 5"); } function showNextButton() { if (!nextButton) { nextButton = new Text2('Next', { size: 80, fill: 0xFFEB3B }); nextButton.anchor.set(0.5, 0.5); nextButton.interactive = true; nextButton.buttonMode = true; nextButton.down = function (x, y, obj) { startRound(); }; LK.gui.bottom.addChild(nextButton); } nextButton.visible = true; } function hideNextButton() { if (nextButton) nextButton.visible = false; } // --- Game Logic --- // Place tornado in center lower half tornado = new Tornado(); tornado.centerX = 2048 / 2; tornado.centerY = 1700; tornado.x = tornado.centerX; tornado.y = tornado.centerY; game.addChild(tornado); // Start first round function startRound() { hideResult(); hideNextButton(); showRound(); roundActive = true; // Set tornado strength for this round tornado.strength = tornadoStrengths[(round - 1) % tornadoStrengths.length]; // Pick object type for this round objectIndex = (round - 1) % objectTypes.length; var objType = objectTypes[objectIndex]; // Place object at bottom center currentObject = new LaunchObject(); currentObject.type = objType.type; currentObject.mass = objType.mass; // Attach correct asset var assetId = 'object_' + currentObject.type; var objGfx = currentObject.attachAsset(assetId, { anchorX: 0.5, anchorY: 0.5 }); currentObject.x = 2048 / 2; currentObject.y = 2732 - 200; currentObject.maxHeight = currentObject.y; currentObject.launched = false; currentObject.inTornado = false; currentObject.destroyed = false; game.addChild(currentObject); launchedObjects.push(currentObject); showInfo("Drag and release to launch the " + objType.label + "!\nTornado strength: " + tornado.strength.toFixed(1)); } // End round, show result, allow next function endRound() { roundActive = false; var obj = currentObject; var heightGain = Math.max(0, obj.y - obj.maxHeight); var msg = "Max height above launch: " + Math.round(heightGain) + " px"; if (obj.inTornado) { msg += "\nThe " + obj.type + " was caught by the tornado!"; } else { msg += "\nThe " + obj.type + " missed the tornado."; } showResult(msg); if (round < 5) { showNextButton(); round++; } else { showResult("Experiment complete!\nTry again to test more objects."); LK.setTimeout(function () { LK.showGameOver(); }, 2200); } } // --- Drag/Launch Controls --- // Only allow drag on currentObject before launch game.down = function (x, y, obj) { if (!roundActive || !currentObject || currentObject.launched) return; // Only allow drag if touch is near object var dx = x - currentObject.x; var dy = y - currentObject.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 120) { dragStart = { x: x, y: y }; dragPower = 0; dragAngle = 0; currentObject.showArrow(0, 0); } }; game.move = function (x, y, obj) { if (!dragStart || !currentObject || currentObject.launched) return; var dx = x - dragStart.x; var dy = y - dragStart.y; dragPower = Math.sqrt(dx * dx + dy * dy); dragAngle = Math.atan2(dy, dx); // Limit angle to upward launches only if (dragAngle > Math.PI / 2) dragAngle = Math.PI / 2; if (dragAngle < -Math.PI / 2) dragAngle = -Math.PI / 2; currentObject.showArrow(dragAngle, dragPower); }; game.up = function (x, y, obj) { if (!dragStart || !currentObject || currentObject.launched) return; // Calculate launch velocity (reverse direction: drag back to launch up) var dx = x - dragStart.x; var dy = y - dragStart.y; var power = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); // Only allow upward launches if (angle > Math.PI / 2) angle = Math.PI / 2; if (angle < -Math.PI / 2) angle = -Math.PI / 2; // Convert to velocity (reverse direction) // Increase launch velocity for higher arc var v = Math.min(power / 8, 48); // Increased velocity and cap currentObject.vx = -Math.cos(angle) * v; currentObject.vy = -Math.sin(angle) * v; currentObject.launched = true; currentObject.hideArrow(); dragStart = null; LK.getSound('launch').play(); }; // --- Main update loop --- game.update = function () { // Animate tornado if (tornado) tornado.update(); // Update all objects for (var i = launchedObjects.length - 1; i >= 0; i--) { var obj = launchedObjects[i]; if (obj && obj.update) obj.update(); // End round if launched object is out of bounds if (obj === currentObject && obj.launched && !obj.destroyed) { // If object is below screen or has stopped moving if (obj.y > 2732 + 200 || Math.abs(obj.vx) < 0.2 && Math.abs(obj.vy) < 0.2 && obj.y > tornado.centerY + 400) { obj.destroyed = true; LK.setTimeout(endRound, 700); } } // Remove destroyed objects if (obj.destroyed) { obj.destroy(); launchedObjects.splice(i, 1); } } }; // --- Start game --- startRound(); showRound(); showInfo("Drag and release to launch the object into the tornado!");
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Launchable object class
var LaunchObject = Container.expand(function () {
var self = Container.call(this);
// Properties set on creation: type, mass
self.type = 'box';
self.mass = 1.0;
self.vx = 0;
self.vy = 0;
self.launched = false;
self.inTornado = false;
self.everInTornado = false;
self.maxHeight = 0;
self.arrow = null;
// Attach correct asset
var assetId = 'object_' + self.type;
var objGfx = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Draw launch arrow (only before launch)
self.showArrow = function (angle, power) {
if (!self.arrow) {
self.arrow = self.addChild(LK.getAsset('arrow', {
anchorX: 0.0,
anchorY: 0.5,
alpha: 0.7
}));
}
self.arrow.rotation = angle;
self.arrow.scaleX = Math.max(0.5, Math.min(power / 30, 2.5));
self.arrow.x = 0;
self.arrow.y = 0;
self.arrow.visible = true;
};
self.hideArrow = function () {
if (self.arrow) self.arrow.visible = false;
};
// Called every tick
self.update = function () {
if (!self.launched) return;
// Gravity
self.vy += 0.18 * self.mass;
// Tornado force
if (tornado) {
var force = tornado.getForce(self);
self.vx += force.fx;
self.vy += force.fy;
// If inside tornado, mark
var dx = self.x - tornado.centerX;
var dy = self.y - tornado.centerY;
var dist = Math.sqrt(dx * dx + dy * dy);
self.inTornado = dist < tornado.radius;
if (self.inTornado) {
self.everInTornado = true;
}
}
// Move
self.x += self.vx;
self.y += self.vy;
// Track max height
if (self.y < self.maxHeight) self.maxHeight = self.y;
// Out of bounds: below screen
if (self.y > 2732 + 200) {
// Show experiment result before teleporting back
var heightGain = Math.max(0, self.y - self.maxHeight);
var msg = "Max height above launch: " + Math.round(heightGain) + " px";
// Only show "missed the tornado" if the object was never in the tornado during its flight
if (self.inTornado || self.everInTornado) {
msg += "\nThe " + self.type + " was caught by the tornado!";
} else {
msg += "\nThe " + self.type + " missed the tornado.";
}
showResult(msg);
// Teleport back to starting position and reset velocity
self.x = 2048 / 2;
self.y = 2732 - 200;
self.vx = 0;
self.vy = 0;
self.launched = false;
self.inTornado = false;
self.everInTornado = false;
self.maxHeight = self.y;
self.hideArrow();
// Optionally, you could add a visual effect or sound here
}
};
return self;
});
// Tornado class: swirling, applies force to objects inside
var Tornado = Container.expand(function () {
var self = Container.call(this);
var tornadoGfx = self.attachAsset('tornado', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.5
});
// Tornado center
self.centerX = 2048 / 2;
self.centerY = 1700;
self.radius = 300;
self.strength = 1.0; // 1.0 = normal, can be changed per round
self.rotationSpeed = 0.04; // radians per tick
// Animate tornado swirl
self.update = function () {
// No spinning: do not change tornadoGfx.rotation
};
// Returns force vector {fx, fy} for an object at (x, y) with mass
self.getForce = function (obj) {
var dx = obj.x - self.centerX;
var dy = obj.y - self.centerY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > self.radius) return {
fx: 0,
fy: 0
}; // Outside tornado
// Swirl: perpendicular to radius, magnitude decreases with mass, increases with strength
var angle = Math.atan2(dy, dx) + Math.PI / 2;
var forceMag = self.strength * 2.5 * (1 - dist / self.radius) / (obj.mass || 1);
return {
fx: Math.cos(angle) * forceMag,
fy: Math.sin(angle) * forceMag - 0.12 // Updraft: negative y
};
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222233
});
/****
* Game Code
****/
// Sound for launch
// Launch arrow
// Object types: box, ball, cow, car (different colors/shapes)
// Tornado: swirling ellipse, semi-transparent blue
// --- Global variables ---
var tornado = null;
var currentObject = null;
var launchedObjects = [];
var round = 1;
var roundActive = false;
var dragStart = null;
var dragPower = 0;
var dragAngle = 0;
var resultText = null;
var infoText = null;
var roundText = null;
var nextButton = null;
var objectTypes = [{
type: 'box',
mass: 1.0,
label: 'Box'
}, {
type: 'ball',
mass: 0.7,
label: 'Ball'
}, {
type: 'cow',
mass: 1.8,
label: 'Cow'
}, {
type: 'car',
mass: 2.5,
label: 'Car'
}];
var tornadoStrengths = [1.0, 1.5, 0.7, 2.0, 0.5];
var objectIndex = 0;
// --- UI Elements ---
function showInfo(msg) {
if (!infoText) {
infoText = new Text2(msg, {
size: 70,
fill: "#fff"
});
infoText.anchor.set(0.5, 0);
LK.gui.top.addChild(infoText);
}
infoText.setText(msg);
}
function showResult(msg) {
if (!resultText) {
resultText = new Text2(msg, {
size: 90,
fill: "#fff"
});
resultText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(resultText);
}
resultText.setText(msg);
resultText.visible = true;
}
function hideResult() {
if (resultText) resultText.visible = false;
}
function showRound() {
if (!roundText) {
roundText = new Text2('', {
size: 60,
fill: "#fff"
});
roundText.anchor.set(0.5, 0);
LK.gui.top.addChild(roundText);
}
roundText.setText("Round " + round + " / 5");
}
function showNextButton() {
if (!nextButton) {
nextButton = new Text2('Next', {
size: 80,
fill: 0xFFEB3B
});
nextButton.anchor.set(0.5, 0.5);
nextButton.interactive = true;
nextButton.buttonMode = true;
nextButton.down = function (x, y, obj) {
startRound();
};
LK.gui.bottom.addChild(nextButton);
}
nextButton.visible = true;
}
function hideNextButton() {
if (nextButton) nextButton.visible = false;
}
// --- Game Logic ---
// Place tornado in center lower half
tornado = new Tornado();
tornado.centerX = 2048 / 2;
tornado.centerY = 1700;
tornado.x = tornado.centerX;
tornado.y = tornado.centerY;
game.addChild(tornado);
// Start first round
function startRound() {
hideResult();
hideNextButton();
showRound();
roundActive = true;
// Set tornado strength for this round
tornado.strength = tornadoStrengths[(round - 1) % tornadoStrengths.length];
// Pick object type for this round
objectIndex = (round - 1) % objectTypes.length;
var objType = objectTypes[objectIndex];
// Place object at bottom center
currentObject = new LaunchObject();
currentObject.type = objType.type;
currentObject.mass = objType.mass;
// Attach correct asset
var assetId = 'object_' + currentObject.type;
var objGfx = currentObject.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
currentObject.x = 2048 / 2;
currentObject.y = 2732 - 200;
currentObject.maxHeight = currentObject.y;
currentObject.launched = false;
currentObject.inTornado = false;
currentObject.destroyed = false;
game.addChild(currentObject);
launchedObjects.push(currentObject);
showInfo("Drag and release to launch the " + objType.label + "!\nTornado strength: " + tornado.strength.toFixed(1));
}
// End round, show result, allow next
function endRound() {
roundActive = false;
var obj = currentObject;
var heightGain = Math.max(0, obj.y - obj.maxHeight);
var msg = "Max height above launch: " + Math.round(heightGain) + " px";
if (obj.inTornado) {
msg += "\nThe " + obj.type + " was caught by the tornado!";
} else {
msg += "\nThe " + obj.type + " missed the tornado.";
}
showResult(msg);
if (round < 5) {
showNextButton();
round++;
} else {
showResult("Experiment complete!\nTry again to test more objects.");
LK.setTimeout(function () {
LK.showGameOver();
}, 2200);
}
}
// --- Drag/Launch Controls ---
// Only allow drag on currentObject before launch
game.down = function (x, y, obj) {
if (!roundActive || !currentObject || currentObject.launched) return;
// Only allow drag if touch is near object
var dx = x - currentObject.x;
var dy = y - currentObject.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
dragStart = {
x: x,
y: y
};
dragPower = 0;
dragAngle = 0;
currentObject.showArrow(0, 0);
}
};
game.move = function (x, y, obj) {
if (!dragStart || !currentObject || currentObject.launched) return;
var dx = x - dragStart.x;
var dy = y - dragStart.y;
dragPower = Math.sqrt(dx * dx + dy * dy);
dragAngle = Math.atan2(dy, dx);
// Limit angle to upward launches only
if (dragAngle > Math.PI / 2) dragAngle = Math.PI / 2;
if (dragAngle < -Math.PI / 2) dragAngle = -Math.PI / 2;
currentObject.showArrow(dragAngle, dragPower);
};
game.up = function (x, y, obj) {
if (!dragStart || !currentObject || currentObject.launched) return;
// Calculate launch velocity (reverse direction: drag back to launch up)
var dx = x - dragStart.x;
var dy = y - dragStart.y;
var power = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
// Only allow upward launches
if (angle > Math.PI / 2) angle = Math.PI / 2;
if (angle < -Math.PI / 2) angle = -Math.PI / 2;
// Convert to velocity (reverse direction)
// Increase launch velocity for higher arc
var v = Math.min(power / 8, 48); // Increased velocity and cap
currentObject.vx = -Math.cos(angle) * v;
currentObject.vy = -Math.sin(angle) * v;
currentObject.launched = true;
currentObject.hideArrow();
dragStart = null;
LK.getSound('launch').play();
};
// --- Main update loop ---
game.update = function () {
// Animate tornado
if (tornado) tornado.update();
// Update all objects
for (var i = launchedObjects.length - 1; i >= 0; i--) {
var obj = launchedObjects[i];
if (obj && obj.update) obj.update();
// End round if launched object is out of bounds
if (obj === currentObject && obj.launched && !obj.destroyed) {
// If object is below screen or has stopped moving
if (obj.y > 2732 + 200 || Math.abs(obj.vx) < 0.2 && Math.abs(obj.vy) < 0.2 && obj.y > tornado.centerY + 400) {
obj.destroyed = true;
LK.setTimeout(endRound, 700);
}
}
// Remove destroyed objects
if (obj.destroyed) {
obj.destroy();
launchedObjects.splice(i, 1);
}
}
};
// --- Start game ---
startRound();
showRound();
showInfo("Drag and release to launch the object into the tornado!");
Make a car. In-Game asset. 2d. High contrast. No shadows
Make this a tornado. In-Game asset. 2d. High contrast. No shadows
Make a ball. In-Game asset. 2d. High contrast. No shadows
Make a box. In-Game asset. 2d. High contrast. No shadows
Make a cow. In-Game asset. 2d. High contrast. No shadows
Make a arrow. In-Game asset. 2d. High contrast. No shadows