/**** * Classes ****/ /** * config { * x : Number || 0, * y : Number || 0, * rotation : Number || 0, * } **/ var ConfigContainer = Container.expand(function (config) { var self = Container.call(this); config = config || {}; ; self.x = config.x || 0; self.y = config.y || 0; self.rotation = config.rotation || 0; if (config.scale !== undefined || config.scaleX !== undefined || config.scaleY !== undefined) { var scaleX = config.scaleX !== undefined ? config.scaleX : config.scale !== undefined ? config.scale : 1; var scaleY = config.scaleY !== undefined ? config.scaleY : config.scale !== undefined ? config.scale : 1; self.scale.set(scaleX, scaleY); } ; return self; }); var Sun = ConfigContainer.expand(function (config) { var self = ConfigContainer.call(this, config); for (var i = 0; i < 10; i++) { self.attachAsset('circle', { width: 500 + 200 * i, height: 500 + 200 * i, color: 0xFFFF00, alpha: 0.1, anchorX: 0.5, anchorY: 0.5 }); } var sunAsset = self.attachAsset('sun', { anchorX: 0.5075, anchorY: 0.5, alpha: 0.75 }); ; self.update = update; ; function update() { if (LK.ticks % 30 == 0) { sunAsset.scale.x *= -1; } } }); var RoadSegment = ConfigContainer.expand(function (config) { var self = ConfigContainer.call(this, config); var background = self.addChild(new Container()); self.attachAsset('roadSegment', { anchorX: 1, anchorY: 1 }); self.attachAsset('roadSegment', { anchorX: 1, anchorY: 1, scaleX: 0.95, scaleY: 0.95, tint: 0x555555 }); if (config.index % 3 === 0) { attachAssetRadial(self, 'square', { offset: 200, width: 100, height: 10, anchorR: 0.5, anchorX: 0.5, anchorY: 0.5 }); } ; self.update = update; self.regenerate = regenerate; self.fadeout = false; self.distance = config.distance; self.step = config.step; self.stepChance = config.stepChance; self.previous = config.previous; ; function attachAssetRadial(parent, asset, obj) { var offsetTotal = ROAD_SEGMENT_HEIGHT - (obj.offset || 10); var rotation = -(1 - obj.anchorR) * ROAD_SEGMENT_ANGLE; parent.attachAsset(asset, { x: Math.sin(rotation) * offsetTotal, y: Math.cos(rotation) * -offsetTotal, width: obj.width, height: obj.height, anchorX: obj.anchorX, anchorY: obj.anchorY, scaleX: obj.scaleX || 1, scaleY: obj.scaleY || 1, rotation: (obj.rotation || 0) + rotation }); } function generateBackground() { console.log(self.distance); background.removeChildren(); var plantCount = Math.floor(Math.random() * (ROAD_GEN_PLANT_MAX + 1)); if (self.distance === SCORE_TOTAL_DISTANCE) { attachAssetRadial(background, 'flag', { anchorR: 0.5, anchorX: 0.1, anchorY: 1 }); } else { if (Math.random() < ROAD_GEN_TREE_CHANCE) { var treeIndex = Math.floor(Math.random() * ROAD_GEN_TREE_ASSETS); var scale = 0.9 + 0.2 * Math.random(); attachAssetRadial(background, 'tree' + treeIndex, { anchorR: 0.4 + 0.2 * Math.random(), anchorX: 0.5, anchorY: 1, scaleX: scale, scaleY: scale }); } for (var i = 0; i < plantCount; i++) { var plantIndex = Math.floor(Math.random() * ROAD_GEN_PLANT_ASSETS); var scale = 0.9 + 0.2 * Math.random(); attachAssetRadial(background, 'plant' + plantIndex, { anchorR: 0.1 + 0.8 * Math.random(), anchorX: 0.5, anchorY: 1, scaleX: Math.random() < 0.5 ? -scale : scale, scaleY: scale }); } } if (Math.random() < ROAD_GEN_CLOUD_CHANCE) { var cloudIndex = Math.floor(Math.random() * ROAD_GEN_CLOUD_ASSETS); var scale = 1.0 + 1.5 * Math.random(); attachAssetRadial(background, 'cloud' + cloudIndex, { offset: -800, anchorR: -1.0 + 2.0 * Math.random(), anchorX: 0.5, anchorY: 0.5, scaleX: scale, scaleY: scale }); } } function regenerate(incrementDistance) { if (incrementDistance) { self.distance += SCORE_SEGMENT_DISTANCE * ROAD_SEGMENT_COUNT; } var stepping = self.distance <= SCORE_TOTAL_DISTANCE; var stepChance = self.previous.stepChance; var stepUp = stepping && (self.previous.step < ROAD_STEP_COUNT ? Math.random() < self.stepChance : false); var stepDown = stepping && (self.previous.step > 0 ? Math.random() < self.stepChance : false); if (stepUp || stepDown) { if (stepUp && stepDown) { if (Math.random() < 0.5) { stepDown = false; } else { stepUp = false; } } self.step = self.previous.step + (stepUp ? 1 : -1); self.stepChance = ROAD_STEP_CHANCE_BASE; } else { self.step = self.previous.step; self.stepChance = self.previous.stepChance + ROAD_STEP_CHANCE_INCREMENT; } generateBackground(); self.fadeout = false; rescale(); } function update() { if (self.fadeout && self.alpha > 0) { if ((self.alpha -= ROAD_SEGMENT_FADEOUT) < 0) { self.alpha = 0; } } else if (!self.fadeout && self.alpha < 1) { if ((self.alpha += ROAD_SEGMENT_FADEOUT) > 1) { self.alpha = 1; } } } function rescale() { var newScale = (ROAD_STEP_HEIGHT_BASE + self.step * ROAD_STEP_HEIGHT_INCREMENT) / ROAD_SEGMENT_HEIGHT; // TEMP self.scale.set(newScale); } ; rescale(); }); var Road = ConfigContainer.expand(function (config) { var self = ConfigContainer.call(this, config); var segments = []; for (var i = 0; i < ROAD_SEGMENT_COUNT; i++) { var segment = segments[i] = self.addChild(new RoadSegment({ index: i, previous: i > 0 ? segments[i - 1] : undefined, rotation: i * ROAD_SEGMENT_ANGLE, distance: (i - 1) * SCORE_SEGMENT_DISTANCE, stepChance: ROAD_STEP_CHANCE_BASE, step: ROAD_STEP_DEFAULT })); if (i === ROAD_SEGMENT_COUNT - 1) { segments[0].previous = segment; } if (i >= ROAD_FREE_RUN_COUNT) { segment.regenerate(); } self.attachAsset('roadSegment', { anchorX: 1, anchorY: 1, scaleX: 0.27, scaleY: 0.27, rotation: i * ROAD_SEGMENT_ANGLE }); } self.attachAsset('circle', { width: 500, height: 500, anchorX: 0.5, anchorY: 0.5, tint: 0x222222 }); self.attachAsset('circle', { width: 490, height: 490, anchorX: 0.5, anchorY: 0.5, tint: 0x87CEEB }); var destroyIndex = -1; var regenerateIndex = -1; ; self.rotate = rotate; self.getIndex = getIndex; self.getNode = getNode; self.getNodeDist = getNodeDist; ; function rotate(speed) { self.rotation -= speed; var newDestroyIndex = getIndex(ROAD_SEGMENT_DESTROY); var newRegenerateIndex = getIndex(20); // TEMP if (newDestroyIndex >= 0 && newDestroyIndex !== destroyIndex && !segments[newDestroyIndex].fadeout) { destroyIndex = newDestroyIndex; segments[destroyIndex].fadeout = true; } if (newRegenerateIndex >= 0 && newRegenerateIndex !== regenerateIndex && segments[newRegenerateIndex].fadeout) { regenerateIndex = newRegenerateIndex; segments[regenerateIndex].regenerate(true); } } function getIndex(shift) { return Math.floor(-self.rotation / MATH_2_PI * ROAD_SEGMENT_COUNT + (shift || 0) + 1) % ROAD_SEGMENT_COUNT; } function getNode(shift) { return segments[getIndex(shift)]; } function getNodeDist() { return ROAD_SEGMENT_ANGLE - -self.rotation % ROAD_SEGMENT_ANGLE; } }); var PlayerBody = ConfigContainer.expand(function (config) { var self = ConfigContainer.call(this, config); // Animation settings var animation = animationStand; var alpha = 0; var blendAnimation = undefined; var blendDecrement = 0; var blendAlpha = 0; var afterTint = 0xAAAAAA; // Body settings var bodyOffsetY = config.y || 0; var pelvisOffsetX = -10; var armUpperAngle = -3 * MATH_EIGHTH_PI; var armUpperOffsetX = 15; var armUpperOffsetY = 30; var armLowerAngle = -MATH_QUARTER_PI; var armLowerOffsetY = 90; var legUpperAngle = 3 * MATH_EIGHTH_PI; var legUpperOffsetX = 10; var legLowerOffsetY = 135; var footOffsetY = 100; // Create and attach body parts var armUpperRight = self.addChild(new ConfigContainer({ y: armUpperOffsetY })); var legUpperRight = self.addChild(new ConfigContainer({ x: pelvisOffsetX + legUpperOffsetX })); var torso = self.attachAsset('torso', { anchorX: 0.5 }); var head = self.attachAsset('head', { anchorX: 0.15, anchorY: 0.95 }); var pelvis = self.attachAsset('pelvis', { x: pelvisOffsetX, y: torso.height, anchorX: 0.5, anchorY: 0.2, tint: afterTint }); var legUpperLeft = self.addChild(new ConfigContainer({ x: pelvisOffsetX - legUpperOffsetX })); var armUpperLeft = self.addChild(new ConfigContainer({ y: armUpperOffsetY })); // Arm extensions armUpperRight.attachAsset('upperArm', { rotation: armUpperAngle, anchorX: 0.85, anchorY: 0.25, tint: afterTint }); armUpperLeft.attachAsset('upperArm', { rotation: armUpperAngle, anchorX: 0.85, anchorY: 0.25 }); var armLowerRight = armUpperRight.attachAsset('lowerArm', { y: armLowerOffsetY, rotation: armLowerAngle, anchorX: 0.95, anchorY: 0.05, tint: afterTint }); var armLowerLeft = armUpperLeft.attachAsset('lowerArm', { y: armLowerOffsetY, rotation: armLowerAngle, anchorX: 0.95, anchorY: 0.05 }); // Leg extensions legUpperRight.attachAsset('upperLeg', { rotation: legUpperAngle, anchorY: 0.1, tint: afterTint }); legUpperLeft.attachAsset('upperLeg', { rotation: legUpperAngle, anchorY: 0.1 }); var legLowerRight = legUpperRight.attachAsset('lowerLeg', { y: legLowerOffsetY, anchorX: 0.65, tint: afterTint }); var legLowerLeft = legUpperLeft.attachAsset('lowerLeg', { y: legLowerOffsetY, anchorX: 0.65 }); var footRight = legLowerRight.attachAsset('foot', { y: footOffsetY, anchorX: 0.2, anchorY: 0.05, tint: afterTint }); var footLeft = legLowerLeft.attachAsset('foot', { y: footOffsetY, anchorX: 0.2, anchorY: 0.05 }); // Additional positioning armUpperLeft.x = -torso.width / 2 + armUpperOffsetX; armUpperRight.x = torso.width / 2 - armUpperOffsetX; legUpperLeft.y = torso.height + pelvis.height / 3; legUpperRight.y = torso.height + pelvis.height / 3; ; self.animate = animate; self.pushAnimation = pushAnimation; ; function animate(increment) { alpha = (alpha + increment) % 1; if (blendAlpha > 0) { if ((blendAlpha -= blendDecrement) <= 0) { blendAlpha = 0; blendAnimation = undefined; } } var pose = animation(alpha); var blendPose = blendAnimation && blendAnimation(alpha); self.y = animateProperty(pose, blendPose, blendAlpha, 'BODY_OFFSET', bodyOffsetY); head.rotation = animateProperty(pose, blendPose, blendAlpha, 'HEAD_ROTATION'); armUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_RIGHT_ROTATION'); armUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_LEFT_ROTATION'); armLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_RIGHT_ROTATION', armLowerAngle); armLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_LEFT_ROTATION', armLowerAngle); legUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_RIGHT_ROTATION'); legUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_LEFT_ROTATION'); legLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_RIGHT_ROTATION'); legLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_LEFT_ROTATION'); footRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_RIGHT_ROTATION'); footLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_LEFT_ROTATION'); } function pushAnimation(newAnimation, newBlendDecrement) { blendDecrement = newBlendDecrement; if (newAnimation !== animation) { blendAnimation = animation; animation = newAnimation; blendAlpha = 1; } } ; animate(0); }); var Player = ConfigContainer.expand(function (config) { var self = ConfigContainer.call(this, config); var speedFactor = PLAYER_SPEED_FACTOR_BASE; var stopped = false; var falling = false; var jumping = false; // If the player is currently jumping (and immune to gravity) var jumpAction = false; // Tracks tap releases var jumpStep = 0; // Tracks min/max jump heights var aerialSpeed = 0; // Actual vertical/air speed var baseY = config.y; var step = ROAD_STEP_DEFAULT; var nodeDistance = 0; var nodeTicks = 0; var nodeMulti = 0; var body = self.addChild(new PlayerBody({ y: -370 })); ; self.update = update; self.jumpStart = jumpStart; self.jumpEnd = jumpEnd; self.jumping = false; ; function update() { var nextNode = road.getNode(1); var currentNode = nextNode.previous; // Update horizontal movement var outputSpeed = PLAYER_SPEED_BASE * speedFactor; var distance = road.getNodeDist(); // Check for collision if (stopped && nextNode.step <= step) { stopped = false; } else if (nextNode.step > step && outputSpeed > distance) { road.rotate(distance - PLAYER_SPACING); speedFactor = PLAYER_SPEED_FACTOR_BASE; stopped = true; if (currentNode.step <= step) { body.pushAnimation(animationStand, PLAYER_POSE_TRANSITION); } } if (!stopped) { road.rotate(outputSpeed); if (!gameOver) { if (speedFactor < PLAYER_SPEED_FACTOR_MAX) { if ((speedFactor += PLAYER_SPEED_FACTOR_INCREMENT) >= PLAYER_SPEED_FACTOR_MAX) { speedFactor = PLAYER_SPEED_FACTOR_MAX; } } } else { if (speedFactor > 0) { if ((speedFactor -= PLAYER_SPEED_DECREMENT) <= 0) { speedFactor = 0; } } } } // TOOD: Get new node if applicable // Update vertical movement if (jumping) { jumpStep += aerialSpeed; if (!jumpAction && jumpStep >= PLAYER_JUMP_STEP_MIN || jumpStep >= PLAYER_JUMP_STEP_MAX) { falling = true; jumping = false; jumpAction = false; jumpStep = 0; } } else if (falling) { aerialSpeed -= PLAYER_GRAVITY; } else if (step > currentNode.step) { falling = true; body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION); } step += aerialSpeed; if (falling && step <= currentNode.step) { step = currentNode.step; falling = false; aerialSpeed = 0; body.pushAnimation(stopped ? animationStand : animationRun, PLAYER_POSE_TRANSITION); } // Score calculations if (!gameOver && currentNode.distance > nodeDistance) { adjustScore(nodeMulti / nodeTicks); currentNode.distance = nodeDistance; nodeMulti = 0; nodeTicks = 0; } nodeMulti += stopped ? SCORE_STOPPED_FACTOR : speedFactor; nodeTicks++; // Adjustments var stepHeight = ROAD_STEP_HEIGHT_BASE + step * ROAD_STEP_HEIGHT_INCREMENT; // TODO: Better scaling self.y = baseY - stepHeight; // TODO: Better scaling self.scale.set(PLAYER_SCALE_BASE * stepHeight / ROAD_SEGMENT_HEIGHT); // TODO: Better scaling body.animate(PLAYER_ANIMATION_SPEED_BASE * speedFactor); multiplierText.setText((stopped ? SCORE_STOPPED_FACTOR : speedFactor).toFixed(1) + 'x'); } function jumpStart() { if (!gameOver && !jumping && !falling) { jumping = true; jumpAction = true; aerialSpeed = PLAYER_JUMP_STEP_INCREMENT; body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION); } } function jumpEnd() { jumpAction = false; } function adjustScore(averageMulti) { var points = Math.floor(averageMulti * SCORE_SEGMENT_POINTS); score = Math.max(0, score + points); scoreText.setText(String(score).padStart(6, '0')); distanceText.setText((distanceRemaining -= SCORE_SEGMENT_DISTANCE) + 'm'); incrementText.setText((points === SCORE_MAX_POINTS ? '[MAX!] +' : '+') + points); if (distanceRemaining <= 0) { body.pushAnimation(animationStand, PLAYER_SPEED_DECREMENT / speedFactor); callGameOver(); } } ; body.pushAnimation(animationRun, PLAYER_POSE_TRANSITION); }); /** * config { * x : Number || 0, // See: ConfigContainer * y : Number || 0, // See: ConfigContainer * rotation : Number || 0, // See: ConfigContainer * anchorX : Number || 0, * anchorY : Number || 1, * size : Number || TEXT_DEFAULT_SIZE, * weight : Number || TEXT_DEFAULT_WEIGHT, * font : String || TEXT_DEFAULT_FONT, * fill : String || TEXT_DEFAULT_FILL, * border : String || TEXT_DEFAULT_BORDER, * } **/ var BorderedText = ConfigContainer.expand(function (text, config) { var self = ConfigContainer.call(this, config); config = config || {}; ; var anchorX = config.anchorX !== undefined ? config.anchorX : 0; var anchorY = config.anchorY !== undefined ? config.anchorY : 1; var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE; var weight = config.weight !== undefined ? config.weight : TEXT_DEFAULT_WEIGHT; var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT; var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL; var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER; var textAssets = []; var mainAsset; ; self.setText = setText; self.setFill = setFill; ; function setText(newText) { for (var i = 0; i < textAssets.length; i++) { textAssets[i].setText(newText); } } function setFill(newFill) { textFill = newFill; mainAsset.fill = newFill; } function buildTextAssets(newText) { for (var i = 0; i < TEXT_OFFSETS.length; i++) { var main = i === TEXT_OFFSETS.length - 1; var fill = main ? textFill : borderFill; var textAsset = textAssets[i]; if (textAsset) { textAsset.destroy(); } textAsset = self.addChild(new Text2(newText, { fill: fill, font: font, size: size })); textAsset.anchor = { x: anchorX, y: anchorY }; // NOTE: Cannot be set in config textAsset.x = TEXT_OFFSETS[i][0] * weight; // NOTE: Cannot be set in config textAsset.y = TEXT_OFFSETS[i][1] * weight; // NOTE: Cannot be set in config textAssets[i] = textAsset; } mainAsset = textAssets[TEXT_OFFSETS.length - 1]; } ; buildTextAssets(text); return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB // Sky blue color }); /**** * Game Code ****/ ; //============================================================================== // Global Constants & Settings //============================================================================== ; // Math Constants / Pre-calculations var MATH_4_PI = Math.PI * 4; var MATH_2_PI = Math.PI * 2; var MATH_HALF_PI = Math.PI / 2; var MATH_THIRD_PI = Math.PI / 3; var MATH_QUARTER_PI = Math.PI / 4; var MATH_FIFTH_PI = Math.PI / 5; var MATH_SIXTH_PI = Math.PI / 5; var MATH_EIGHTH_PI = Math.PI / 8; var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, BorderedShape, SymbolText ; // Text Settings var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText var TEXT_DEFAULT_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText var TEXT_DEFAULT_FONT = 'Courier'; // Required by: BorderedText, SymbolText var TEXT_DEFAULT_SIZE = 80; // Required by: BorderedText, SymbolText ; // Game Constants var GAME_TICKS = 60; var GAME_WIDTH = 2048; var GAME_HEIGHT = 2732; ; // Road Constants var ROAD_SEGMENT_DISTANCE = 1.0; var ROAD_SEGMENT_POINTS = 100; var ROAD_SEGMENT_COUNT = 30; var ROAD_SEGMENT_ANGLE = MATH_2_PI / ROAD_SEGMENT_COUNT; var ROAD_SEGMENT_HEIGHT = 1000; var ROAD_SEGMENT_DESTROY = -2; var ROAD_SEGMENT_FADEOUT = 0.015; var ROAD_FREE_RUN_COUNT = 5; var ROAD_STEP_COUNT = 5; var ROAD_STEP_DEFAULT = 2; var ROAD_STEP_HEIGHT_BASE = 400; var ROAD_STEP_HEIGHT_INCREMENT = 80; var ROAD_STEP_CHANCE_BASE = 0.1; var ROAD_STEP_CHANCE_INCREMENT = 0.05; var ROAD_GEN_PLANT_ASSETS = 3; var ROAD_GEN_PLANT_MAX = 3; var ROAD_GEN_TREE_ASSETS = 4; var ROAD_GEN_TREE_CHANCE = 0.1; var ROAD_GEN_CLOUD_ASSETS = 4; var ROAD_GEN_CLOUD_CHANCE = 0.2; ; // Player Constants var PLAYER_SPACING = ROAD_SEGMENT_ANGLE / 8; // 1/8 of a segment var PLAYER_POSE_TRANSITION = 0.1; var PLAYER_GRAVITY = 0.02; var PLAYER_JUMP_STEP_MIN = 1.0; var PLAYER_JUMP_STEP_MAX = 2.0; var PLAYER_JUMP_STEP_INCREMENT = 0.15; var PLAYER_ANIMATION_SPEED_BASE = 0.02; var PLAYER_SCALE_BASE = 0.5; var PLAYER_SPEED_BASE = MATH_2_PI / (GAME_TICKS * 10); // 10s to complete a revolution var PLAYER_SPEED_FACTOR_BASE = 1.0; var PLAYER_SPEED_FACTOR_MAX = 1.5; var PLAYER_SPEED_FACTOR_INCREMENT = 0.001; var PLAYER_SPEED_DECREMENT = 0.01; ; // Scoring Constants var SCORE_TOTAL_DISTANCE = 500; var SCORE_SEGMENT_DISTANCE = 1; var SCORE_SEGMENT_POINTS = 100; var SCORE_STOPPED_FACTOR = -0.2; var SCORE_MAX_POINTS = SCORE_SEGMENT_POINTS * PLAYER_SPEED_FACTOR_MAX; //============================================================================== // Game Instances & Variables //============================================================================== ; // Variables var score = 0; var winningTime = 0; var gameOver = false; var distanceRemaining = SCORE_TOTAL_DISTANCE; ; // Instances var sun = game.addChild(new Sun({ x: GAME_WIDTH / 2, y: 50 })); var road = game.addChild(new Road({ x: GAME_WIDTH / 2, y: GAME_HEIGHT - GAME_WIDTH / 2 })); var player = game.addChild(new Player({ x: road.x, y: road.y, scale: 0.5 })); var distanceText = game.addChild(new BorderedText(distanceRemaining + 'm', { x: road.x, y: road.y - TEXT_DEFAULT_SIZE, anchorX: 0.5, anchorY: 1 })); var scoreText = game.addChild(new BorderedText('000000', { x: road.x, y: road.y, size: TEXT_DEFAULT_SIZE * 1.5, anchorX: 0.5, anchorY: 0.5 })); var multiplierText = game.addChild(new BorderedText('1.0x', { x: road.x, y: road.y + TEXT_DEFAULT_SIZE, anchorX: 0.5, anchorY: 0 })); var incrementText = game.addChild(new BorderedText('', { x: road.x - 300, y: road.y, anchorX: 1, anchorY: 0.5 })); var winningText = game.addChild(new BorderedText('', { x: GAME_WIDTH / 2, y: 500, anchorX: 0.5, anchorY: 0.5, size: 2 * TEXT_DEFAULT_SIZE })); ; //============================================================================== // Global events //============================================================================== ; game.down = player.jumpStart; game.up = player.jumpEnd; ; //============================================================================== // Global functions //============================================================================== ; // Helpers function callGameOver() { gameOver = true; winningTime = LK.ticks; LK.setScore(score); winningText.setText('Congratulations!'); // Check if winningTime is set and show game over screen after 60 ticks game.update = function () { if (winningTime && LK.ticks >= winningTime + 120) { LK.showGameOver(); } }; } ; // Animations & handlers function animateProperty(animationPose, blendPose, blendAlpha, key, baseValue) { var animationPoseValue = (animationPose || {})[key] || 0; var blendPoseValue = (blendPose || {})[key] || 0; return (baseValue || 0) + animationPoseValue * (1 - blendAlpha) + blendPoseValue * blendAlpha; } function animationRun(alpha) { // Animation constants var armUpperAngle = MATH_FIFTH_PI; var armLowerAngle = -5 * MATH_EIGHTH_PI; var legLowerAngle = MATH_EIGHTH_PI; // Sinusoidals var rightSinusoidal = Math.cos(MATH_2_PI * (alpha + 0.5)); var leftSinusoidal = Math.cos(MATH_2_PI * alpha); var doubleSinusoidal = Math.cos(MATH_4_PI * alpha); // Animation calculations var armUpperRightSwing = -rightSinusoidal * MATH_QUARTER_PI; var armUpperLeftSwing = -leftSinusoidal * MATH_QUARTER_PI; var legUpperRightSwing = rightSinusoidal * MATH_THIRD_PI; var legUpperLeftSwing = leftSinusoidal * MATH_THIRD_PI; return { BODY_OFFSET: doubleSinusoidal * 10, HEAD_ROTATION: (1 + doubleSinusoidal) * Math.PI / 32, ARM_UPPER_RIGHT_ROTATION: armUpperAngle + armUpperRightSwing, ARM_UPPER_LEFT_ROTATION: armUpperAngle + armUpperLeftSwing, ARM_LOWER_RIGHT_ROTATION: armLowerAngle + armUpperRightSwing / 2, ARM_LOWER_LEFT_ROTATION: armLowerAngle + armUpperLeftSwing / 2, LEG_UPPER_RIGHT_ROTATION: legUpperRightSwing, LEG_UPPER_LEFT_ROTATION: legUpperLeftSwing, LEG_LOWER_RIGHT_ROTATION: legLowerAngle + (alpha > 0.5 ? MATH_THIRD_PI + (1 - Math.abs(rightSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperRightSwing)), LEG_LOWER_LEFT_ROTATION: legLowerAngle + (alpha <= 0.5 ? MATH_THIRD_PI + (1 - Math.abs(leftSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperLeftSwing)), FOOT_RIGHT_ROTATION: Math.max(0, legUpperRightSwing * 2 / 3) - legLowerAngle, FOOT_LEFT_ROTATION: Math.max(0, legUpperLeftSwing * 2 / 3) - legLowerAngle }; } function animationJump(alpha) { var sinusoidal = Math.cos(MATH_2_PI * alpha); return animationRun(0.35 + sinusoidal * 0.02); } function animationStand(alpha) { var sinusoidal = Math.cos(MATH_2_PI * alpha); var offsetAngle = Math.PI / 16; var sharedAngle = (2 + sinusoidal) * Math.PI / 64; return { BODY_OFFSET: sinusoidal * 5, ARM_UPPER_RIGHT_ROTATION: 0.5 * offsetAngle, ARM_UPPER_LEFT_ROTATION: 3 * offsetAngle, ARM_LOWER_RIGHT_ROTATION: -4 * offsetAngle, ARM_LOWER_LEFT_ROTATION: -4 * offsetAngle, LEG_UPPER_RIGHT_ROTATION: 1.5 * offsetAngle - sharedAngle, LEG_UPPER_LEFT_ROTATION: -1.5 * offsetAngle - sharedAngle, LEG_LOWER_RIGHT_ROTATION: 2 * sharedAngle, LEG_LOWER_LEFT_ROTATION: 2 * sharedAngle, FOOT_RIGHT_ROTATION: -sharedAngle - 1.5 * offsetAngle, FOOT_LEFT_ROTATION: -sharedAngle + 1.5 * offsetAngle }; }
/****
* Classes
****/
/**
* config {
* x : Number || 0,
* y : Number || 0,
* rotation : Number || 0,
* }
**/
var ConfigContainer = Container.expand(function (config) {
var self = Container.call(this);
config = config || {};
;
self.x = config.x || 0;
self.y = config.y || 0;
self.rotation = config.rotation || 0;
if (config.scale !== undefined || config.scaleX !== undefined || config.scaleY !== undefined) {
var scaleX = config.scaleX !== undefined ? config.scaleX : config.scale !== undefined ? config.scale : 1;
var scaleY = config.scaleY !== undefined ? config.scaleY : config.scale !== undefined ? config.scale : 1;
self.scale.set(scaleX, scaleY);
}
;
return self;
});
var Sun = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
for (var i = 0; i < 10; i++) {
self.attachAsset('circle', {
width: 500 + 200 * i,
height: 500 + 200 * i,
color: 0xFFFF00,
alpha: 0.1,
anchorX: 0.5,
anchorY: 0.5
});
}
var sunAsset = self.attachAsset('sun', {
anchorX: 0.5075,
anchorY: 0.5,
alpha: 0.75
});
;
self.update = update;
;
function update() {
if (LK.ticks % 30 == 0) {
sunAsset.scale.x *= -1;
}
}
});
var RoadSegment = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var background = self.addChild(new Container());
self.attachAsset('roadSegment', {
anchorX: 1,
anchorY: 1
});
self.attachAsset('roadSegment', {
anchorX: 1,
anchorY: 1,
scaleX: 0.95,
scaleY: 0.95,
tint: 0x555555
});
if (config.index % 3 === 0) {
attachAssetRadial(self, 'square', {
offset: 200,
width: 100,
height: 10,
anchorR: 0.5,
anchorX: 0.5,
anchorY: 0.5
});
}
;
self.update = update;
self.regenerate = regenerate;
self.fadeout = false;
self.distance = config.distance;
self.step = config.step;
self.stepChance = config.stepChance;
self.previous = config.previous;
;
function attachAssetRadial(parent, asset, obj) {
var offsetTotal = ROAD_SEGMENT_HEIGHT - (obj.offset || 10);
var rotation = -(1 - obj.anchorR) * ROAD_SEGMENT_ANGLE;
parent.attachAsset(asset, {
x: Math.sin(rotation) * offsetTotal,
y: Math.cos(rotation) * -offsetTotal,
width: obj.width,
height: obj.height,
anchorX: obj.anchorX,
anchorY: obj.anchorY,
scaleX: obj.scaleX || 1,
scaleY: obj.scaleY || 1,
rotation: (obj.rotation || 0) + rotation
});
}
function generateBackground() {
console.log(self.distance);
background.removeChildren();
var plantCount = Math.floor(Math.random() * (ROAD_GEN_PLANT_MAX + 1));
if (self.distance === SCORE_TOTAL_DISTANCE) {
attachAssetRadial(background, 'flag', {
anchorR: 0.5,
anchorX: 0.1,
anchorY: 1
});
} else {
if (Math.random() < ROAD_GEN_TREE_CHANCE) {
var treeIndex = Math.floor(Math.random() * ROAD_GEN_TREE_ASSETS);
var scale = 0.9 + 0.2 * Math.random();
attachAssetRadial(background, 'tree' + treeIndex, {
anchorR: 0.4 + 0.2 * Math.random(),
anchorX: 0.5,
anchorY: 1,
scaleX: scale,
scaleY: scale
});
}
for (var i = 0; i < plantCount; i++) {
var plantIndex = Math.floor(Math.random() * ROAD_GEN_PLANT_ASSETS);
var scale = 0.9 + 0.2 * Math.random();
attachAssetRadial(background, 'plant' + plantIndex, {
anchorR: 0.1 + 0.8 * Math.random(),
anchorX: 0.5,
anchorY: 1,
scaleX: Math.random() < 0.5 ? -scale : scale,
scaleY: scale
});
}
}
if (Math.random() < ROAD_GEN_CLOUD_CHANCE) {
var cloudIndex = Math.floor(Math.random() * ROAD_GEN_CLOUD_ASSETS);
var scale = 1.0 + 1.5 * Math.random();
attachAssetRadial(background, 'cloud' + cloudIndex, {
offset: -800,
anchorR: -1.0 + 2.0 * Math.random(),
anchorX: 0.5,
anchorY: 0.5,
scaleX: scale,
scaleY: scale
});
}
}
function regenerate(incrementDistance) {
if (incrementDistance) {
self.distance += SCORE_SEGMENT_DISTANCE * ROAD_SEGMENT_COUNT;
}
var stepping = self.distance <= SCORE_TOTAL_DISTANCE;
var stepChance = self.previous.stepChance;
var stepUp = stepping && (self.previous.step < ROAD_STEP_COUNT ? Math.random() < self.stepChance : false);
var stepDown = stepping && (self.previous.step > 0 ? Math.random() < self.stepChance : false);
if (stepUp || stepDown) {
if (stepUp && stepDown) {
if (Math.random() < 0.5) {
stepDown = false;
} else {
stepUp = false;
}
}
self.step = self.previous.step + (stepUp ? 1 : -1);
self.stepChance = ROAD_STEP_CHANCE_BASE;
} else {
self.step = self.previous.step;
self.stepChance = self.previous.stepChance + ROAD_STEP_CHANCE_INCREMENT;
}
generateBackground();
self.fadeout = false;
rescale();
}
function update() {
if (self.fadeout && self.alpha > 0) {
if ((self.alpha -= ROAD_SEGMENT_FADEOUT) < 0) {
self.alpha = 0;
}
} else if (!self.fadeout && self.alpha < 1) {
if ((self.alpha += ROAD_SEGMENT_FADEOUT) > 1) {
self.alpha = 1;
}
}
}
function rescale() {
var newScale = (ROAD_STEP_HEIGHT_BASE + self.step * ROAD_STEP_HEIGHT_INCREMENT) / ROAD_SEGMENT_HEIGHT; // TEMP
self.scale.set(newScale);
}
;
rescale();
});
var Road = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var segments = [];
for (var i = 0; i < ROAD_SEGMENT_COUNT; i++) {
var segment = segments[i] = self.addChild(new RoadSegment({
index: i,
previous: i > 0 ? segments[i - 1] : undefined,
rotation: i * ROAD_SEGMENT_ANGLE,
distance: (i - 1) * SCORE_SEGMENT_DISTANCE,
stepChance: ROAD_STEP_CHANCE_BASE,
step: ROAD_STEP_DEFAULT
}));
if (i === ROAD_SEGMENT_COUNT - 1) {
segments[0].previous = segment;
}
if (i >= ROAD_FREE_RUN_COUNT) {
segment.regenerate();
}
self.attachAsset('roadSegment', {
anchorX: 1,
anchorY: 1,
scaleX: 0.27,
scaleY: 0.27,
rotation: i * ROAD_SEGMENT_ANGLE
});
}
self.attachAsset('circle', {
width: 500,
height: 500,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x222222
});
self.attachAsset('circle', {
width: 490,
height: 490,
anchorX: 0.5,
anchorY: 0.5,
tint: 0x87CEEB
});
var destroyIndex = -1;
var regenerateIndex = -1;
;
self.rotate = rotate;
self.getIndex = getIndex;
self.getNode = getNode;
self.getNodeDist = getNodeDist;
;
function rotate(speed) {
self.rotation -= speed;
var newDestroyIndex = getIndex(ROAD_SEGMENT_DESTROY);
var newRegenerateIndex = getIndex(20); // TEMP
if (newDestroyIndex >= 0 && newDestroyIndex !== destroyIndex && !segments[newDestroyIndex].fadeout) {
destroyIndex = newDestroyIndex;
segments[destroyIndex].fadeout = true;
}
if (newRegenerateIndex >= 0 && newRegenerateIndex !== regenerateIndex && segments[newRegenerateIndex].fadeout) {
regenerateIndex = newRegenerateIndex;
segments[regenerateIndex].regenerate(true);
}
}
function getIndex(shift) {
return Math.floor(-self.rotation / MATH_2_PI * ROAD_SEGMENT_COUNT + (shift || 0) + 1) % ROAD_SEGMENT_COUNT;
}
function getNode(shift) {
return segments[getIndex(shift)];
}
function getNodeDist() {
return ROAD_SEGMENT_ANGLE - -self.rotation % ROAD_SEGMENT_ANGLE;
}
});
var PlayerBody = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
// Animation settings
var animation = animationStand;
var alpha = 0;
var blendAnimation = undefined;
var blendDecrement = 0;
var blendAlpha = 0;
var afterTint = 0xAAAAAA;
// Body settings
var bodyOffsetY = config.y || 0;
var pelvisOffsetX = -10;
var armUpperAngle = -3 * MATH_EIGHTH_PI;
var armUpperOffsetX = 15;
var armUpperOffsetY = 30;
var armLowerAngle = -MATH_QUARTER_PI;
var armLowerOffsetY = 90;
var legUpperAngle = 3 * MATH_EIGHTH_PI;
var legUpperOffsetX = 10;
var legLowerOffsetY = 135;
var footOffsetY = 100;
// Create and attach body parts
var armUpperRight = self.addChild(new ConfigContainer({
y: armUpperOffsetY
}));
var legUpperRight = self.addChild(new ConfigContainer({
x: pelvisOffsetX + legUpperOffsetX
}));
var torso = self.attachAsset('torso', {
anchorX: 0.5
});
var head = self.attachAsset('head', {
anchorX: 0.15,
anchorY: 0.95
});
var pelvis = self.attachAsset('pelvis', {
x: pelvisOffsetX,
y: torso.height,
anchorX: 0.5,
anchorY: 0.2,
tint: afterTint
});
var legUpperLeft = self.addChild(new ConfigContainer({
x: pelvisOffsetX - legUpperOffsetX
}));
var armUpperLeft = self.addChild(new ConfigContainer({
y: armUpperOffsetY
}));
// Arm extensions
armUpperRight.attachAsset('upperArm', {
rotation: armUpperAngle,
anchorX: 0.85,
anchorY: 0.25,
tint: afterTint
});
armUpperLeft.attachAsset('upperArm', {
rotation: armUpperAngle,
anchorX: 0.85,
anchorY: 0.25
});
var armLowerRight = armUpperRight.attachAsset('lowerArm', {
y: armLowerOffsetY,
rotation: armLowerAngle,
anchorX: 0.95,
anchorY: 0.05,
tint: afterTint
});
var armLowerLeft = armUpperLeft.attachAsset('lowerArm', {
y: armLowerOffsetY,
rotation: armLowerAngle,
anchorX: 0.95,
anchorY: 0.05
});
// Leg extensions
legUpperRight.attachAsset('upperLeg', {
rotation: legUpperAngle,
anchorY: 0.1,
tint: afterTint
});
legUpperLeft.attachAsset('upperLeg', {
rotation: legUpperAngle,
anchorY: 0.1
});
var legLowerRight = legUpperRight.attachAsset('lowerLeg', {
y: legLowerOffsetY,
anchorX: 0.65,
tint: afterTint
});
var legLowerLeft = legUpperLeft.attachAsset('lowerLeg', {
y: legLowerOffsetY,
anchorX: 0.65
});
var footRight = legLowerRight.attachAsset('foot', {
y: footOffsetY,
anchorX: 0.2,
anchorY: 0.05,
tint: afterTint
});
var footLeft = legLowerLeft.attachAsset('foot', {
y: footOffsetY,
anchorX: 0.2,
anchorY: 0.05
});
// Additional positioning
armUpperLeft.x = -torso.width / 2 + armUpperOffsetX;
armUpperRight.x = torso.width / 2 - armUpperOffsetX;
legUpperLeft.y = torso.height + pelvis.height / 3;
legUpperRight.y = torso.height + pelvis.height / 3;
;
self.animate = animate;
self.pushAnimation = pushAnimation;
;
function animate(increment) {
alpha = (alpha + increment) % 1;
if (blendAlpha > 0) {
if ((blendAlpha -= blendDecrement) <= 0) {
blendAlpha = 0;
blendAnimation = undefined;
}
}
var pose = animation(alpha);
var blendPose = blendAnimation && blendAnimation(alpha);
self.y = animateProperty(pose, blendPose, blendAlpha, 'BODY_OFFSET', bodyOffsetY);
head.rotation = animateProperty(pose, blendPose, blendAlpha, 'HEAD_ROTATION');
armUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_RIGHT_ROTATION');
armUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_UPPER_LEFT_ROTATION');
armLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_RIGHT_ROTATION', armLowerAngle);
armLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'ARM_LOWER_LEFT_ROTATION', armLowerAngle);
legUpperRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_RIGHT_ROTATION');
legUpperLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_UPPER_LEFT_ROTATION');
legLowerRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_RIGHT_ROTATION');
legLowerLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'LEG_LOWER_LEFT_ROTATION');
footRight.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_RIGHT_ROTATION');
footLeft.rotation = animateProperty(pose, blendPose, blendAlpha, 'FOOT_LEFT_ROTATION');
}
function pushAnimation(newAnimation, newBlendDecrement) {
blendDecrement = newBlendDecrement;
if (newAnimation !== animation) {
blendAnimation = animation;
animation = newAnimation;
blendAlpha = 1;
}
}
;
animate(0);
});
var Player = ConfigContainer.expand(function (config) {
var self = ConfigContainer.call(this, config);
var speedFactor = PLAYER_SPEED_FACTOR_BASE;
var stopped = false;
var falling = false;
var jumping = false; // If the player is currently jumping (and immune to gravity)
var jumpAction = false; // Tracks tap releases
var jumpStep = 0; // Tracks min/max jump heights
var aerialSpeed = 0; // Actual vertical/air speed
var baseY = config.y;
var step = ROAD_STEP_DEFAULT;
var nodeDistance = 0;
var nodeTicks = 0;
var nodeMulti = 0;
var body = self.addChild(new PlayerBody({
y: -370
}));
;
self.update = update;
self.jumpStart = jumpStart;
self.jumpEnd = jumpEnd;
self.jumping = false;
;
function update() {
var nextNode = road.getNode(1);
var currentNode = nextNode.previous;
// Update horizontal movement
var outputSpeed = PLAYER_SPEED_BASE * speedFactor;
var distance = road.getNodeDist();
// Check for collision
if (stopped && nextNode.step <= step) {
stopped = false;
} else if (nextNode.step > step && outputSpeed > distance) {
road.rotate(distance - PLAYER_SPACING);
speedFactor = PLAYER_SPEED_FACTOR_BASE;
stopped = true;
if (currentNode.step <= step) {
body.pushAnimation(animationStand, PLAYER_POSE_TRANSITION);
}
}
if (!stopped) {
road.rotate(outputSpeed);
if (!gameOver) {
if (speedFactor < PLAYER_SPEED_FACTOR_MAX) {
if ((speedFactor += PLAYER_SPEED_FACTOR_INCREMENT) >= PLAYER_SPEED_FACTOR_MAX) {
speedFactor = PLAYER_SPEED_FACTOR_MAX;
}
}
} else {
if (speedFactor > 0) {
if ((speedFactor -= PLAYER_SPEED_DECREMENT) <= 0) {
speedFactor = 0;
}
}
}
}
// TOOD: Get new node if applicable
// Update vertical movement
if (jumping) {
jumpStep += aerialSpeed;
if (!jumpAction && jumpStep >= PLAYER_JUMP_STEP_MIN || jumpStep >= PLAYER_JUMP_STEP_MAX) {
falling = true;
jumping = false;
jumpAction = false;
jumpStep = 0;
}
} else if (falling) {
aerialSpeed -= PLAYER_GRAVITY;
} else if (step > currentNode.step) {
falling = true;
body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
}
step += aerialSpeed;
if (falling && step <= currentNode.step) {
step = currentNode.step;
falling = false;
aerialSpeed = 0;
body.pushAnimation(stopped ? animationStand : animationRun, PLAYER_POSE_TRANSITION);
}
// Score calculations
if (!gameOver && currentNode.distance > nodeDistance) {
adjustScore(nodeMulti / nodeTicks);
currentNode.distance = nodeDistance;
nodeMulti = 0;
nodeTicks = 0;
}
nodeMulti += stopped ? SCORE_STOPPED_FACTOR : speedFactor;
nodeTicks++;
// Adjustments
var stepHeight = ROAD_STEP_HEIGHT_BASE + step * ROAD_STEP_HEIGHT_INCREMENT; // TODO: Better scaling
self.y = baseY - stepHeight; // TODO: Better scaling
self.scale.set(PLAYER_SCALE_BASE * stepHeight / ROAD_SEGMENT_HEIGHT); // TODO: Better scaling
body.animate(PLAYER_ANIMATION_SPEED_BASE * speedFactor);
multiplierText.setText((stopped ? SCORE_STOPPED_FACTOR : speedFactor).toFixed(1) + 'x');
}
function jumpStart() {
if (!gameOver && !jumping && !falling) {
jumping = true;
jumpAction = true;
aerialSpeed = PLAYER_JUMP_STEP_INCREMENT;
body.pushAnimation(animationJump, PLAYER_POSE_TRANSITION);
}
}
function jumpEnd() {
jumpAction = false;
}
function adjustScore(averageMulti) {
var points = Math.floor(averageMulti * SCORE_SEGMENT_POINTS);
score = Math.max(0, score + points);
scoreText.setText(String(score).padStart(6, '0'));
distanceText.setText((distanceRemaining -= SCORE_SEGMENT_DISTANCE) + 'm');
incrementText.setText((points === SCORE_MAX_POINTS ? '[MAX!] +' : '+') + points);
if (distanceRemaining <= 0) {
body.pushAnimation(animationStand, PLAYER_SPEED_DECREMENT / speedFactor);
callGameOver();
}
}
;
body.pushAnimation(animationRun, PLAYER_POSE_TRANSITION);
});
/**
* config {
* x : Number || 0, // See: ConfigContainer
* y : Number || 0, // See: ConfigContainer
* rotation : Number || 0, // See: ConfigContainer
* anchorX : Number || 0,
* anchorY : Number || 1,
* size : Number || TEXT_DEFAULT_SIZE,
* weight : Number || TEXT_DEFAULT_WEIGHT,
* font : String || TEXT_DEFAULT_FONT,
* fill : String || TEXT_DEFAULT_FILL,
* border : String || TEXT_DEFAULT_BORDER,
* }
**/
var BorderedText = ConfigContainer.expand(function (text, config) {
var self = ConfigContainer.call(this, config);
config = config || {};
;
var anchorX = config.anchorX !== undefined ? config.anchorX : 0;
var anchorY = config.anchorY !== undefined ? config.anchorY : 1;
var size = config.size !== undefined ? config.size : TEXT_DEFAULT_SIZE;
var weight = config.weight !== undefined ? config.weight : TEXT_DEFAULT_WEIGHT;
var font = config.font !== undefined ? config.font : TEXT_DEFAULT_FONT;
var textFill = config.fill !== undefined ? config.fill : TEXT_DEFAULT_FILL;
var borderFill = config.border !== undefined ? config.border : TEXT_DEFAULT_BORDER;
var textAssets = [];
var mainAsset;
;
self.setText = setText;
self.setFill = setFill;
;
function setText(newText) {
for (var i = 0; i < textAssets.length; i++) {
textAssets[i].setText(newText);
}
}
function setFill(newFill) {
textFill = newFill;
mainAsset.fill = newFill;
}
function buildTextAssets(newText) {
for (var i = 0; i < TEXT_OFFSETS.length; i++) {
var main = i === TEXT_OFFSETS.length - 1;
var fill = main ? textFill : borderFill;
var textAsset = textAssets[i];
if (textAsset) {
textAsset.destroy();
}
textAsset = self.addChild(new Text2(newText, {
fill: fill,
font: font,
size: size
}));
textAsset.anchor = {
x: anchorX,
y: anchorY
}; // NOTE: Cannot be set in config
textAsset.x = TEXT_OFFSETS[i][0] * weight; // NOTE: Cannot be set in config
textAsset.y = TEXT_OFFSETS[i][1] * weight; // NOTE: Cannot be set in config
textAssets[i] = textAsset;
}
mainAsset = textAssets[TEXT_OFFSETS.length - 1];
}
;
buildTextAssets(text);
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x87CEEB // Sky blue color
});
/****
* Game Code
****/
;
//==============================================================================
// Global Constants & Settings
//==============================================================================
;
// Math Constants / Pre-calculations
var MATH_4_PI = Math.PI * 4;
var MATH_2_PI = Math.PI * 2;
var MATH_HALF_PI = Math.PI / 2;
var MATH_THIRD_PI = Math.PI / 3;
var MATH_QUARTER_PI = Math.PI / 4;
var MATH_FIFTH_PI = Math.PI / 5;
var MATH_SIXTH_PI = Math.PI / 5;
var MATH_EIGHTH_PI = Math.PI / 8;
var MATH_HALF_ROOT_3 = Math.sqrt(3) / 2; // Required by: TEXT_OFFSETS, BorderedText, BorderedSymbol, BorderedShape, SymbolText
;
// Text Settings
var TEXT_OFFSETS = [[0, 1], [MATH_HALF_ROOT_3, 0.5], [MATH_HALF_ROOT_3, -0.5], [0, -1], [-MATH_HALF_ROOT_3, -0.5], [-MATH_HALF_ROOT_3, 0.5], [0, 0]]; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_WEIGHT = 4; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_BORDER = '#000000'; // Required by: BorderedText, BorderedSymbol, BorderedShape, SymbolText
var TEXT_DEFAULT_FILL = '#FFFFFF'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_FONT = 'Courier'; // Required by: BorderedText, SymbolText
var TEXT_DEFAULT_SIZE = 80; // Required by: BorderedText, SymbolText
;
// Game Constants
var GAME_TICKS = 60;
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
;
// Road Constants
var ROAD_SEGMENT_DISTANCE = 1.0;
var ROAD_SEGMENT_POINTS = 100;
var ROAD_SEGMENT_COUNT = 30;
var ROAD_SEGMENT_ANGLE = MATH_2_PI / ROAD_SEGMENT_COUNT;
var ROAD_SEGMENT_HEIGHT = 1000;
var ROAD_SEGMENT_DESTROY = -2;
var ROAD_SEGMENT_FADEOUT = 0.015;
var ROAD_FREE_RUN_COUNT = 5;
var ROAD_STEP_COUNT = 5;
var ROAD_STEP_DEFAULT = 2;
var ROAD_STEP_HEIGHT_BASE = 400;
var ROAD_STEP_HEIGHT_INCREMENT = 80;
var ROAD_STEP_CHANCE_BASE = 0.1;
var ROAD_STEP_CHANCE_INCREMENT = 0.05;
var ROAD_GEN_PLANT_ASSETS = 3;
var ROAD_GEN_PLANT_MAX = 3;
var ROAD_GEN_TREE_ASSETS = 4;
var ROAD_GEN_TREE_CHANCE = 0.1;
var ROAD_GEN_CLOUD_ASSETS = 4;
var ROAD_GEN_CLOUD_CHANCE = 0.2;
;
// Player Constants
var PLAYER_SPACING = ROAD_SEGMENT_ANGLE / 8; // 1/8 of a segment
var PLAYER_POSE_TRANSITION = 0.1;
var PLAYER_GRAVITY = 0.02;
var PLAYER_JUMP_STEP_MIN = 1.0;
var PLAYER_JUMP_STEP_MAX = 2.0;
var PLAYER_JUMP_STEP_INCREMENT = 0.15;
var PLAYER_ANIMATION_SPEED_BASE = 0.02;
var PLAYER_SCALE_BASE = 0.5;
var PLAYER_SPEED_BASE = MATH_2_PI / (GAME_TICKS * 10); // 10s to complete a revolution
var PLAYER_SPEED_FACTOR_BASE = 1.0;
var PLAYER_SPEED_FACTOR_MAX = 1.5;
var PLAYER_SPEED_FACTOR_INCREMENT = 0.001;
var PLAYER_SPEED_DECREMENT = 0.01;
;
// Scoring Constants
var SCORE_TOTAL_DISTANCE = 500;
var SCORE_SEGMENT_DISTANCE = 1;
var SCORE_SEGMENT_POINTS = 100;
var SCORE_STOPPED_FACTOR = -0.2;
var SCORE_MAX_POINTS = SCORE_SEGMENT_POINTS * PLAYER_SPEED_FACTOR_MAX;
//==============================================================================
// Game Instances & Variables
//==============================================================================
;
// Variables
var score = 0;
var winningTime = 0;
var gameOver = false;
var distanceRemaining = SCORE_TOTAL_DISTANCE;
;
// Instances
var sun = game.addChild(new Sun({
x: GAME_WIDTH / 2,
y: 50
}));
var road = game.addChild(new Road({
x: GAME_WIDTH / 2,
y: GAME_HEIGHT - GAME_WIDTH / 2
}));
var player = game.addChild(new Player({
x: road.x,
y: road.y,
scale: 0.5
}));
var distanceText = game.addChild(new BorderedText(distanceRemaining + 'm', {
x: road.x,
y: road.y - TEXT_DEFAULT_SIZE,
anchorX: 0.5,
anchorY: 1
}));
var scoreText = game.addChild(new BorderedText('000000', {
x: road.x,
y: road.y,
size: TEXT_DEFAULT_SIZE * 1.5,
anchorX: 0.5,
anchorY: 0.5
}));
var multiplierText = game.addChild(new BorderedText('1.0x', {
x: road.x,
y: road.y + TEXT_DEFAULT_SIZE,
anchorX: 0.5,
anchorY: 0
}));
var incrementText = game.addChild(new BorderedText('', {
x: road.x - 300,
y: road.y,
anchorX: 1,
anchorY: 0.5
}));
var winningText = game.addChild(new BorderedText('', {
x: GAME_WIDTH / 2,
y: 500,
anchorX: 0.5,
anchorY: 0.5,
size: 2 * TEXT_DEFAULT_SIZE
}));
;
//==============================================================================
// Global events
//==============================================================================
;
game.down = player.jumpStart;
game.up = player.jumpEnd;
;
//==============================================================================
// Global functions
//==============================================================================
;
// Helpers
function callGameOver() {
gameOver = true;
winningTime = LK.ticks;
LK.setScore(score);
winningText.setText('Congratulations!');
// Check if winningTime is set and show game over screen after 60 ticks
game.update = function () {
if (winningTime && LK.ticks >= winningTime + 120) {
LK.showGameOver();
}
};
}
;
// Animations & handlers
function animateProperty(animationPose, blendPose, blendAlpha, key, baseValue) {
var animationPoseValue = (animationPose || {})[key] || 0;
var blendPoseValue = (blendPose || {})[key] || 0;
return (baseValue || 0) + animationPoseValue * (1 - blendAlpha) + blendPoseValue * blendAlpha;
}
function animationRun(alpha) {
// Animation constants
var armUpperAngle = MATH_FIFTH_PI;
var armLowerAngle = -5 * MATH_EIGHTH_PI;
var legLowerAngle = MATH_EIGHTH_PI;
// Sinusoidals
var rightSinusoidal = Math.cos(MATH_2_PI * (alpha + 0.5));
var leftSinusoidal = Math.cos(MATH_2_PI * alpha);
var doubleSinusoidal = Math.cos(MATH_4_PI * alpha);
// Animation calculations
var armUpperRightSwing = -rightSinusoidal * MATH_QUARTER_PI;
var armUpperLeftSwing = -leftSinusoidal * MATH_QUARTER_PI;
var legUpperRightSwing = rightSinusoidal * MATH_THIRD_PI;
var legUpperLeftSwing = leftSinusoidal * MATH_THIRD_PI;
return {
BODY_OFFSET: doubleSinusoidal * 10,
HEAD_ROTATION: (1 + doubleSinusoidal) * Math.PI / 32,
ARM_UPPER_RIGHT_ROTATION: armUpperAngle + armUpperRightSwing,
ARM_UPPER_LEFT_ROTATION: armUpperAngle + armUpperLeftSwing,
ARM_LOWER_RIGHT_ROTATION: armLowerAngle + armUpperRightSwing / 2,
ARM_LOWER_LEFT_ROTATION: armLowerAngle + armUpperLeftSwing / 2,
LEG_UPPER_RIGHT_ROTATION: legUpperRightSwing,
LEG_UPPER_LEFT_ROTATION: legUpperLeftSwing,
LEG_LOWER_RIGHT_ROTATION: legLowerAngle + (alpha > 0.5 ? MATH_THIRD_PI + (1 - Math.abs(rightSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperRightSwing)),
LEG_LOWER_LEFT_ROTATION: legLowerAngle + (alpha <= 0.5 ? MATH_THIRD_PI + (1 - Math.abs(leftSinusoidal)) * MATH_SIXTH_PI : Math.abs(-legUpperLeftSwing)),
FOOT_RIGHT_ROTATION: Math.max(0, legUpperRightSwing * 2 / 3) - legLowerAngle,
FOOT_LEFT_ROTATION: Math.max(0, legUpperLeftSwing * 2 / 3) - legLowerAngle
};
}
function animationJump(alpha) {
var sinusoidal = Math.cos(MATH_2_PI * alpha);
return animationRun(0.35 + sinusoidal * 0.02);
}
function animationStand(alpha) {
var sinusoidal = Math.cos(MATH_2_PI * alpha);
var offsetAngle = Math.PI / 16;
var sharedAngle = (2 + sinusoidal) * Math.PI / 64;
return {
BODY_OFFSET: sinusoidal * 5,
ARM_UPPER_RIGHT_ROTATION: 0.5 * offsetAngle,
ARM_UPPER_LEFT_ROTATION: 3 * offsetAngle,
ARM_LOWER_RIGHT_ROTATION: -4 * offsetAngle,
ARM_LOWER_LEFT_ROTATION: -4 * offsetAngle,
LEG_UPPER_RIGHT_ROTATION: 1.5 * offsetAngle - sharedAngle,
LEG_UPPER_LEFT_ROTATION: -1.5 * offsetAngle - sharedAngle,
LEG_LOWER_RIGHT_ROTATION: 2 * sharedAngle,
LEG_LOWER_LEFT_ROTATION: 2 * sharedAngle,
FOOT_RIGHT_ROTATION: -sharedAngle - 1.5 * offsetAngle,
FOOT_LEFT_ROTATION: -sharedAngle + 1.5 * offsetAngle
};
}
white
white
circle sliced into many pieces, flat image. 2d, white background, shadowless.
pixel art of a tall, tree. game asset, 2d, white background, shadowless.
Pixel art street lamp. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art cloud. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.
pixel art sun. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows.