/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// ChoiceButton: Large button for a story choice
var ChoiceButton = Container.expand(function () {
var self = Container.call(this);
// Arguments: label, index
var label = arguments[0] || '';
var idx = arguments[1] || 0;
// Button background
var btnBg = self.attachAsset('choiceBtn', {
anchorX: 0.5,
anchorY: 0.5
});
btnBg.x = 0;
btnBg.y = 0;
// Button text
var btnText = new Text2(label, {
size: 70,
fill: 0x4A90E2,
wordWrap: true,
wordWrapWidth: 1200,
align: 'center'
});
btnText.anchor.set(0.5, 0.5);
btnText.x = 0;
btnText.y = 0;
self.addChild(btnText);
// Visual feedback on press
self.down = function (x, y, obj) {
tween(btnBg, {
tint: 0xBEE3F8
}, {
duration: 80,
onFinish: function onFinish() {
tween(btnBg, {
tint: 0xffffff
}, {
duration: 120
});
}
});
};
return self;
});
/****
* Story Data
****/
/*
A simple branching story structure.
Each scene has:
- id: unique string
- text: narrative text
- choices: array of { text: string, next: id }
If choices is empty or undefined, it's an ending.
*/
// StoryScene: Represents a single scene in the story
var StoryScene = Container.expand(function () {
var self = Container.call(this);
// Scene background
var bg = self.attachAsset('sceneBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.x = 0;
bg.y = 0;
// Scene text
var text = new Text2('', {
size: 90,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1400,
align: 'center'
});
text.anchor.set(0.5, 0);
text.x = 0;
text.y = -350;
self.addChild(text);
// Holds choice button objects
self.choiceButtons = [];
// Set scene content
self.setScene = function (sceneData) {
text.setText(sceneData.text);
// Remove old choice buttons
for (var i = 0; i < self.choiceButtons.length; i++) {
self.choiceButtons[i].destroy();
}
self.choiceButtons = [];
// Add new choice buttons
var choices = sceneData.choices || [];
var btnYStart = 200;
var btnSpacing = 250;
for (var i = 0; i < choices.length; i++) {
var btn = new ChoiceButton(choices[i].text, i);
btn.x = 0;
btn.y = btnYStart + i * btnSpacing;
self.addChild(btn);
self.choiceButtons.push(btn);
// Attach event
(function (idx) {
btn.down = function (x, y, obj) {
if (typeof self.onChoice === 'function') {
self.onChoice(idx);
}
};
})(i);
}
};
// Animate in
self.animateIn = function () {
self.alpha = 0;
tween(self, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
};
// Animate out
self.animateOut = function (onFinish) {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: onFinish
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222244
});
/****
* Game Code
****/
// Center of the game area
/*
We use simple shapes for story scenes and buttons.
Each scene uses a colored rectangle as a background, and choices are large rounded rectangles.
*/
/****
* Story Data
****/
/*
A simple branching story structure.
Each scene has:
- id: unique string
- text: narrative text
- choices: array of { text: string, next: id }
If choices is empty or undefined, it's an ending.
*/
var storyData = {
start: {
id: 'start',
text: "You wake up in a mysterious forest. The sun is rising, and you hear birds singing. There are two paths ahead.\n\nWhat do you do?",
choices: [{
text: "Take the left path",
next: "leftPath"
}, {
text: "Take the right path",
next: "rightPath"
}]
},
leftPath: {
id: 'leftPath',
text: "You walk down the left path and find a sparkling river. A wooden bridge crosses it, but it looks old.\n\nWhat do you do?",
choices: [{
text: "Cross the bridge",
next: "bridgeCross"
}, {
text: "Follow the river",
next: "followRiver"
}]
},
rightPath: {
id: 'rightPath',
text: "The right path leads you deeper into the woods. You spot a friendly-looking fox watching you.\n\nWhat do you do?",
choices: [{
text: "Approach the fox",
next: "foxFriend"
}, {
text: "Ignore the fox and keep walking",
next: "deepWoods"
}]
},
bridgeCross: {
id: 'bridgeCross',
text: "You step onto the bridge. It creaks, but holds. On the other side, you find a treasure chest!\n\nYou open it and discover gold and a map. Congratulations, you found a happy ending!",
choices: []
},
followRiver: {
id: 'followRiver',
text: "You follow the river and soon get lost. Night falls, and you hear strange noises. Maybe next time you'll choose differently.\n\nThe End.",
choices: []
},
foxFriend: {
id: 'foxFriend',
text: "You approach the fox. It smiles and leads you to a hidden clearing where a picnic is set up just for you. What a magical day!\n\nThe End.",
choices: []
},
deepWoods: {
id: 'deepWoods',
text: "You walk deeper into the woods and soon realize you're going in circles. Eventually, you find your way back to where you started.\n\nDo you want to try again?",
choices: [{
text: "Yes, restart the adventure",
next: "start"
}]
}
};
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Current scene container
var currentScene = null;
// Title text (shown at the top)
var titleText = new Text2("Story Crafter", {
size: 120,
fill: 0xFFFFFF,
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.x = centerX;
titleText.y = 120;
LK.gui.top.addChild(titleText);
// Settings button (top-right, avoids top-left menu)
var settingsBtn = LK.getAsset('choiceBtn', {
anchorX: 1,
anchorY: 0,
width: 300,
height: 120,
x: 2048 - 40,
y: 40
});
var settingsTxt = new Text2("Settings", {
size: 48,
fill: 0x4A90E2,
align: 'center'
});
settingsTxt.anchor.set(0.5, 0.5);
settingsTxt.x = settingsBtn.width / 2 - 150;
settingsTxt.y = settingsBtn.height / 2 - 60;
settingsBtn.addChild(settingsTxt);
LK.gui.topRight.addChild(settingsBtn);
// Visual feedback and placeholder for settings action
settingsBtn.down = function (x, y, obj) {
tween(settingsBtn, {
tint: 0xBEE3F8
}, {
duration: 80,
onFinish: function onFinish() {
tween(settingsBtn, {
tint: 0xffffff
}, {
duration: 120
});
}
});
// Placeholder: Show a simple settings message (could be replaced with real settings)
if (!settingsBtn._settingsMsg) {
var msg = new Text2("Settings coming soon!", {
size: 70,
fill: 0xffffff,
align: 'center'
});
msg.anchor.set(0.5, 0.5);
msg.x = centerX;
msg.y = centerY;
game.addChild(msg);
settingsBtn._settingsMsg = msg;
LK.setTimeout(function () {
if (msg && msg.parent) msg.destroy();
settingsBtn._settingsMsg = null;
}, 1200);
}
};
// Helper: Show a scene by id
function showScene(sceneId) {
var sceneData = storyData[sceneId];
if (!sceneData) return;
// Remove previous scene with fade out
if (currentScene) {
currentScene.animateOut(function () {
currentScene.destroy();
createAndShowScene(sceneData);
});
} else {
createAndShowScene(sceneData);
}
}
// Helper: Create and show a scene
function createAndShowScene(sceneData) {
var scene = new StoryScene();
scene.x = centerX;
scene.y = centerY;
scene.setScene(sceneData);
// Handle choice selection
scene.onChoice = function (choiceIdx) {
var choices = sceneData.choices || [];
if (choices[choiceIdx] && choices[choiceIdx].next) {
showScene(choices[choiceIdx].next);
}
};
game.addChild(scene);
scene.animateIn();
currentScene = scene;
}
// Intro animation: Show 'firegames', then 'story crafter', then 'start the story', then start the story
var introText = new Text2("firegames", {
size: 180,
fill: 0xff6600,
align: 'center'
});
introText.anchor.set(0.5, 0.5);
introText.x = centerX;
introText.y = centerY;
introText.alpha = 0;
game.addChild(introText);
// Animate in 'firegames'
tween(introText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out
LK.setTimeout(function () {
tween(introText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// After 'firegames', show 'story crafter'
if (introText && introText.parent) introText.destroy();
var storyCrafterText = new Text2("Story Crafter", {
size: 180,
fill: 0xffffff,
align: 'center'
});
storyCrafterText.anchor.set(0.5, 0.5);
storyCrafterText.x = centerX;
storyCrafterText.y = centerY;
storyCrafterText.alpha = 0;
game.addChild(storyCrafterText);
tween(storyCrafterText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out
LK.setTimeout(function () {
tween(storyCrafterText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (storyCrafterText && storyCrafterText.parent) storyCrafterText.destroy();
// Show 'start the story'
var startStoryText = new Text2("Start the story", {
size: 120,
fill: 0x4A90E2,
align: 'center'
});
startStoryText.anchor.set(0.5, 0.5);
startStoryText.x = centerX;
startStoryText.y = centerY;
startStoryText.alpha = 0;
game.addChild(startStoryText);
tween(startStoryText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out and start the story
LK.setTimeout(function () {
tween(startStoryText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (startStoryText && startStoryText.parent) startStoryText.destroy();
showScene('start');
}
});
}, 900);
}
});
}
});
}, 900);
}
});
}
});
}, 900);
}
});
// No update loop needed for this game, but placeholder for future features
game.update = function () {
// No per-frame logic needed for MVP
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// ChoiceButton: Large button for a story choice
var ChoiceButton = Container.expand(function () {
var self = Container.call(this);
// Arguments: label, index
var label = arguments[0] || '';
var idx = arguments[1] || 0;
// Button background
var btnBg = self.attachAsset('choiceBtn', {
anchorX: 0.5,
anchorY: 0.5
});
btnBg.x = 0;
btnBg.y = 0;
// Button text
var btnText = new Text2(label, {
size: 70,
fill: 0x4A90E2,
wordWrap: true,
wordWrapWidth: 1200,
align: 'center'
});
btnText.anchor.set(0.5, 0.5);
btnText.x = 0;
btnText.y = 0;
self.addChild(btnText);
// Visual feedback on press
self.down = function (x, y, obj) {
tween(btnBg, {
tint: 0xBEE3F8
}, {
duration: 80,
onFinish: function onFinish() {
tween(btnBg, {
tint: 0xffffff
}, {
duration: 120
});
}
});
};
return self;
});
/****
* Story Data
****/
/*
A simple branching story structure.
Each scene has:
- id: unique string
- text: narrative text
- choices: array of { text: string, next: id }
If choices is empty or undefined, it's an ending.
*/
// StoryScene: Represents a single scene in the story
var StoryScene = Container.expand(function () {
var self = Container.call(this);
// Scene background
var bg = self.attachAsset('sceneBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.x = 0;
bg.y = 0;
// Scene text
var text = new Text2('', {
size: 90,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1400,
align: 'center'
});
text.anchor.set(0.5, 0);
text.x = 0;
text.y = -350;
self.addChild(text);
// Holds choice button objects
self.choiceButtons = [];
// Set scene content
self.setScene = function (sceneData) {
text.setText(sceneData.text);
// Remove old choice buttons
for (var i = 0; i < self.choiceButtons.length; i++) {
self.choiceButtons[i].destroy();
}
self.choiceButtons = [];
// Add new choice buttons
var choices = sceneData.choices || [];
var btnYStart = 200;
var btnSpacing = 250;
for (var i = 0; i < choices.length; i++) {
var btn = new ChoiceButton(choices[i].text, i);
btn.x = 0;
btn.y = btnYStart + i * btnSpacing;
self.addChild(btn);
self.choiceButtons.push(btn);
// Attach event
(function (idx) {
btn.down = function (x, y, obj) {
if (typeof self.onChoice === 'function') {
self.onChoice(idx);
}
};
})(i);
}
};
// Animate in
self.animateIn = function () {
self.alpha = 0;
tween(self, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
};
// Animate out
self.animateOut = function (onFinish) {
tween(self, {
alpha: 0
}, {
duration: 300,
easing: tween.easeIn,
onFinish: onFinish
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x222244
});
/****
* Game Code
****/
// Center of the game area
/*
We use simple shapes for story scenes and buttons.
Each scene uses a colored rectangle as a background, and choices are large rounded rectangles.
*/
/****
* Story Data
****/
/*
A simple branching story structure.
Each scene has:
- id: unique string
- text: narrative text
- choices: array of { text: string, next: id }
If choices is empty or undefined, it's an ending.
*/
var storyData = {
start: {
id: 'start',
text: "You wake up in a mysterious forest. The sun is rising, and you hear birds singing. There are two paths ahead.\n\nWhat do you do?",
choices: [{
text: "Take the left path",
next: "leftPath"
}, {
text: "Take the right path",
next: "rightPath"
}]
},
leftPath: {
id: 'leftPath',
text: "You walk down the left path and find a sparkling river. A wooden bridge crosses it, but it looks old.\n\nWhat do you do?",
choices: [{
text: "Cross the bridge",
next: "bridgeCross"
}, {
text: "Follow the river",
next: "followRiver"
}]
},
rightPath: {
id: 'rightPath',
text: "The right path leads you deeper into the woods. You spot a friendly-looking fox watching you.\n\nWhat do you do?",
choices: [{
text: "Approach the fox",
next: "foxFriend"
}, {
text: "Ignore the fox and keep walking",
next: "deepWoods"
}]
},
bridgeCross: {
id: 'bridgeCross',
text: "You step onto the bridge. It creaks, but holds. On the other side, you find a treasure chest!\n\nYou open it and discover gold and a map. Congratulations, you found a happy ending!",
choices: []
},
followRiver: {
id: 'followRiver',
text: "You follow the river and soon get lost. Night falls, and you hear strange noises. Maybe next time you'll choose differently.\n\nThe End.",
choices: []
},
foxFriend: {
id: 'foxFriend',
text: "You approach the fox. It smiles and leads you to a hidden clearing where a picnic is set up just for you. What a magical day!\n\nThe End.",
choices: []
},
deepWoods: {
id: 'deepWoods',
text: "You walk deeper into the woods and soon realize you're going in circles. Eventually, you find your way back to where you started.\n\nDo you want to try again?",
choices: [{
text: "Yes, restart the adventure",
next: "start"
}]
}
};
var centerX = 2048 / 2;
var centerY = 2732 / 2;
// Current scene container
var currentScene = null;
// Title text (shown at the top)
var titleText = new Text2("Story Crafter", {
size: 120,
fill: 0xFFFFFF,
align: 'center'
});
titleText.anchor.set(0.5, 0);
titleText.x = centerX;
titleText.y = 120;
LK.gui.top.addChild(titleText);
// Settings button (top-right, avoids top-left menu)
var settingsBtn = LK.getAsset('choiceBtn', {
anchorX: 1,
anchorY: 0,
width: 300,
height: 120,
x: 2048 - 40,
y: 40
});
var settingsTxt = new Text2("Settings", {
size: 48,
fill: 0x4A90E2,
align: 'center'
});
settingsTxt.anchor.set(0.5, 0.5);
settingsTxt.x = settingsBtn.width / 2 - 150;
settingsTxt.y = settingsBtn.height / 2 - 60;
settingsBtn.addChild(settingsTxt);
LK.gui.topRight.addChild(settingsBtn);
// Visual feedback and placeholder for settings action
settingsBtn.down = function (x, y, obj) {
tween(settingsBtn, {
tint: 0xBEE3F8
}, {
duration: 80,
onFinish: function onFinish() {
tween(settingsBtn, {
tint: 0xffffff
}, {
duration: 120
});
}
});
// Placeholder: Show a simple settings message (could be replaced with real settings)
if (!settingsBtn._settingsMsg) {
var msg = new Text2("Settings coming soon!", {
size: 70,
fill: 0xffffff,
align: 'center'
});
msg.anchor.set(0.5, 0.5);
msg.x = centerX;
msg.y = centerY;
game.addChild(msg);
settingsBtn._settingsMsg = msg;
LK.setTimeout(function () {
if (msg && msg.parent) msg.destroy();
settingsBtn._settingsMsg = null;
}, 1200);
}
};
// Helper: Show a scene by id
function showScene(sceneId) {
var sceneData = storyData[sceneId];
if (!sceneData) return;
// Remove previous scene with fade out
if (currentScene) {
currentScene.animateOut(function () {
currentScene.destroy();
createAndShowScene(sceneData);
});
} else {
createAndShowScene(sceneData);
}
}
// Helper: Create and show a scene
function createAndShowScene(sceneData) {
var scene = new StoryScene();
scene.x = centerX;
scene.y = centerY;
scene.setScene(sceneData);
// Handle choice selection
scene.onChoice = function (choiceIdx) {
var choices = sceneData.choices || [];
if (choices[choiceIdx] && choices[choiceIdx].next) {
showScene(choices[choiceIdx].next);
}
};
game.addChild(scene);
scene.animateIn();
currentScene = scene;
}
// Intro animation: Show 'firegames', then 'story crafter', then 'start the story', then start the story
var introText = new Text2("firegames", {
size: 180,
fill: 0xff6600,
align: 'center'
});
introText.anchor.set(0.5, 0.5);
introText.x = centerX;
introText.y = centerY;
introText.alpha = 0;
game.addChild(introText);
// Animate in 'firegames'
tween(introText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out
LK.setTimeout(function () {
tween(introText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
// After 'firegames', show 'story crafter'
if (introText && introText.parent) introText.destroy();
var storyCrafterText = new Text2("Story Crafter", {
size: 180,
fill: 0xffffff,
align: 'center'
});
storyCrafterText.anchor.set(0.5, 0.5);
storyCrafterText.x = centerX;
storyCrafterText.y = centerY;
storyCrafterText.alpha = 0;
game.addChild(storyCrafterText);
tween(storyCrafterText, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out
LK.setTimeout(function () {
tween(storyCrafterText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (storyCrafterText && storyCrafterText.parent) storyCrafterText.destroy();
// Show 'start the story'
var startStoryText = new Text2("Start the story", {
size: 120,
fill: 0x4A90E2,
align: 'center'
});
startStoryText.anchor.set(0.5, 0.5);
startStoryText.x = centerX;
startStoryText.y = centerY;
startStoryText.alpha = 0;
game.addChild(startStoryText);
tween(startStoryText, {
alpha: 1,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
// Hold for 900ms, then fade out and start the story
LK.setTimeout(function () {
tween(startStoryText, {
alpha: 0,
scaleX: 1,
scaleY: 1
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
if (startStoryText && startStoryText.parent) startStoryText.destroy();
showScene('start');
}
});
}, 900);
}
});
}
});
}, 900);
}
});
}
});
}, 900);
}
});
// No update loop needed for this game, but placeholder for future features
game.update = function () {
// No per-frame logic needed for MVP
};