/****
* 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