ソースを参照

Converted and moved the last set of tutorials/samples to public repo.

Mikael Säker 8 年 前
コミット
a1c411c630

+ 22 - 26
docs/en/tutorials/car.md

@@ -239,33 +239,29 @@ Now, select <kbd>Project ▸ Build And Launch</kbd> from the main menu and take
     <button id="game-button">
         START GAME <span class="icon"></span>
     </button>
-</div>
-
-<!-- -->
-<script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmloader.js">
-</script>
-<script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async>
-</script>
-<!-- -->
-
-<script type='text/javascript'>
-    /* Load app on click in container. */
-    document.getElementById("game-button").onclick = function (e) {
-        var extra_params = {
-            archive_location_filter: function( path ) {
-                return ("//storage.googleapis.com/defold-doc/assets/car" + path + "");
-            },
-            load_done: function() {},
-            game_start: function() {
-                var e = document.getElementById("game-preview");
-                e.parentElement.removeChild(e);
+    <script src="//storage.googleapis.com/defold-doc/assets/dmloader.js">
+    </script>
+    <script src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async>
+    </script>
+    <script>
+        /* Load app on click in container. */
+        document.getElementById("game-button").onclick = function (e) {
+            var extra_params = {
+                archive_location_filter: function( path ) {
+                    return ("//storage.googleapis.com/defold-doc/assets/car" + path + "");
+                },
+                load_done: function() {},
+                game_start: function() {
+                    var e = document.getElementById("game-preview");
+                    e.parentElement.removeChild(e);
+                }
             }
-        }
-        Module.runApp("game-canvas", extra_params);
-        document.getElementById("game-button").style.display = 'none';
-        document.getElementById("game-button").onclick = null;
-    };
-</script>
+            Module.runApp("game-canvas", extra_params);
+            document.getElementById("game-button").style.display = 'none';
+            document.getElementById("game-button").onclick = null;
+        };
+    </script>
+</div>
 
 If you want you can try to add more instances of *car.collection* to *main.collection*. Each instance is a clone of what's inside *car.collection* with the exact same behavior. Each one listens to input and reacts to the same messages.
 

+ 145 - 0
docs/en/tutorials/hud.md

@@ -0,0 +1,145 @@
+---
+title: HUD code sample
+brief: In this sample, we demonstrate effects for score counting.
+---
+# HUD
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/bcU9PCrPAeY" frameborder="0" allowfullscreen></iframe>
+
+In this sample, we demonstrate effects for score counting. The scores appear randomly over the screen, simulating a game where the player obtains scores at different positions.
+
+The scores float for a while after they appear. To achieve this, we set the scores to transparent and then fade in their color. We also animate them upwards. This is done in `on_message()` below.
+
+Then they move up to the total score in the top of the screen where they are summed up.
+They are also slightly fading out while moving up. This is done in `float_done()`.
+
+When they have reached the top score, their amounts are added to a target score that the total score counts up towards. This is done in `swoosh_done()`.
+
+When the script is updated, it checks if the target score has been increased and the total score needs to be counted up. When this is true, the total score is incremented by a smaller step.
+The scale of the total score is then animated to give a bouncing effect. This is done in `update()`.
+
+Each time the total is incremented, we spawn an amount of smaller stars and animate them out from the total score. The stars are spawned, animated and deleted in `spawn_stars()`, `fade_out_star()` and `delete_star()`.
+
+```lua
+-- file: hud.gui_script
+-- how fast the score counts up per second
+local score_inc_speed = 1000
+
+function init(self)
+    -- the target score is the current score in the game
+    self.target_score = 0
+    -- the current score being counted up towards the target score
+    self.current_score = 0
+    -- the score as displayed in the hud
+    self.displayed_score = 0
+    -- keep a reference to the node displaying the score for later use below
+    self.score_node = gui.get_node("score")
+end
+
+local function delete_star(self, star)
+    -- star has finished animation, delete it
+    gui.delete_node(star)
+end
+
+local function fade_out_star(self, star)
+    -- fade out the star before deletion
+    gui.animate(star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0), gui.EASING_INOUT, 0.2, 0.0, delete_star)
+end
+
+local function spawn_stars(self, amount)
+    -- position of the score node, to be used for placing the stars
+    local p = gui.get_position(self.score_node)
+    -- distance from the position where the star is spawned
+    local start_distance = 0
+    -- distance where the star stops
+    local end_distance = 240
+    -- angle distance between each star in the star circle
+    local angle_step = 2 * math.pi / amount
+    -- randomize start angle
+    local angle = angle_step * math.random()
+    for i=1,amount do
+        -- increment the angle by the step to get an even distribution of stars
+        angle = angle + angle_step
+        -- direction of the star movement
+        local dir = vmath.vector3(math.cos(angle), math.sin(angle), 0)
+        -- start/end positions of the star
+        local start_p = p + dir * start_distance
+        local end_p = p + dir * end_distance
+        -- create the star node
+        local star = gui.new_box_node(vmath.vector3(start_p.x, start_p.y, 0), vmath.vector3(30, 30, 0))
+        -- set its texture
+        gui.set_texture(star, "star")
+        -- set to transparent
+        gui.set_color(star, vmath.vector4(1, 1, 1, 0))
+        -- fade in
+        gui.animate(star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_OUT, 0.2, 0.0, fade_out_star)
+        -- animate position
+        gui.animate(star, gui.PROP_POSITION, end_p, gui.EASING_NONE, 0.55)
+    end
+end
+
+function update(self, dt)
+    -- check if the score needs to be updated
+    if self.current_score < self.target_score then
+        -- increment the score for this timestep to grow towards the target score
+        self.current_score = self.current_score + score_inc_speed * dt
+        -- clamp the score so it doesn't grow past the target score
+        self.current_score = math.min(self.current_score, self.target_score)
+        -- floor the score so it can be displayed without decimals
+        local floored_score = math.floor(self.current_score)
+        -- check if the displayed score should be updated
+        if self.displayed_score ~= floored_score then
+            -- update displayed score
+            self.displayed_score = floored_score
+            -- update the text of the score node
+            gui.set_text(self.score_node, string.format("%d p", self.displayed_score))
+            -- set the scale of the score node to be slightly bigger than normal
+            local s = 1.3
+            gui.set_scale(self.score_node, vmath.vector3(s, s, s))
+            -- then animate the scale back to the original value
+            s = 1.0
+            gui.animate(self.score_node, gui.PROP_SCALE, vmath.vector3(s, s, s), gui.EASING_OUT, 0.2)
+            -- spawn stars
+            spawn_stars(self, 4)
+        end
+    end
+end
+
+-- this function stores the added score so that the displayed score can be counted up in the update function
+local function swoosh_done(self, node)
+    -- retrieve score from node
+    local amount = tonumber(gui.get_text(node))
+    -- increase the target score, see the update function for how the score is updated to match the target score
+    self.target_score = self.target_score + amount
+    -- remove the temp score
+    gui.delete_node(node)
+end
+
+-- this function animates the node from having floated first to swoosh away towards the displayed total score
+local function float_done(self, node)
+    local duration = 0.2
+    -- swoosh away towards the displayed score
+    gui.animate(node, gui.PROP_POSITION, gui.get_position(self.score_node), gui.EASING_IN, duration, 0.0, swoosh_done)
+    -- also fade out partially during the swoosh
+    gui.animate(node, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0.6), gui.EASING_IN, duration)
+end
+
+function on_message(self, message_id, message, sender)
+    -- register added score, this message could be sent by anyone wanting to increment the score
+    if message_id == hash("add_score") then
+        -- create a new temporary score node
+        local node = gui.new_text_node(message.position, tostring(message.amount))
+        -- use the small font for it
+        gui.set_font(node, "small_score")
+        -- initially transparent
+        gui.set_color(node, vmath.vector4(1, 1, 1, 0))
+        gui.set_outline(node, vmath.vector4(0, 0, 0, 0))
+        -- fade in
+        gui.animate(node, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_OUT, 0.3)
+        gui.animate(node, gui.PROP_OUTLINE, vmath.vector4(0, 0, 0, 1), gui.EASING_OUT, 0.3)
+        -- float
+        local offset = vmath.vector3(0, 20, 0)
+        gui.animate(node, gui.PROP_POSITION, gui.get_position(node) + offset, gui.EASING_NONE, 0.5, 0.0, float_done)
+    end
+end
+```

BIN
docs/en/tutorials/images/platformer/integration.png


+ 226 - 0
docs/en/tutorials/level-complete.md

@@ -0,0 +1,226 @@
+---
+title: Level complete code sample
+brief: In this sample, we demonstrate effects to show the score counting that could occur when a level has been completed.
+---
+# Level complete
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/t9I9gqbmyj8" frameborder="0" allowfullscreen></iframe>
+
+In this sample, we demonstrate effects to show the score counting that could occur when a level has been completed. A total score is counted up and three stars appears when different levels have been reached. The sample also uses the reload functionality for fast turn-around when tweaking values.
+
+The scene is triggered by a message from the game.
+The message contains the total score obtained and at which levels the three stars should appear.
+When this happens, the heading text ("Level completed!") is fading in, while being scaled down to regular size (100%). This is done in `on_message()` below.
+
+After the animation of the heading text has completed, the total score is starting to count up. Each time this happens, the current score is incremented by a small step. Then we check if one of the star-levels has been crossed, in which case the animation of a star starts (see below). As long as we haven't reached the target score, the total score is animated with a bouncing effect.
+It will also grow towards a maximum in scale, the closer to the total score it gets. In the same manner, its color shifts gradually from white to green. This is done in `inc_score()`.
+
+Each time a star appears, it fades in and shrinks into regular size. This is done in `animate_star()`.
+
+When the star has finished animating, the smaller stars are spawned in a circle around the bigger star. This is done in `spawn_small_stars()`.
+
+Then they are animated to shoot out randomly from the star. They are both randomized in speed and scale while expanding out. Then they fade out and are eventually deleted. This is done in `animate_small_star()` and `delete_small_star()`.
+
+When the score has reached the total score, the high-score imprint fades in and shrinks back into place. This is started at the end of `inc_score()` and performed in `animate_imprint()`.
+
+The `setup()` function makes sure the nodes have the correct initial values. By calling `setup()` from `on_reload()`, we make sure that everything is setup correctly each time the script is reloaded from the Defold Editor.
+
+```lua
+-- file: level_complete.gui_script
+
+-- how fast the score is incremented per second
+local score_inc_speed = 51100
+-- how long time between each update of the score
+local dt = 0.03
+-- scale of the score at the start of counting
+local score_start_scale = 0.7
+-- scale of the score when the target score has been reached
+local score_end_scale = 1.0
+-- how much the score "bounces" at each increment
+local score_bounce_factor = 1.1
+-- how many small stars to spawn for each big star
+local small_star_count = 16
+
+local function setup(self)
+    -- make heading color transparent
+    local c = gui.get_color(self.heading)
+    c.w = 0
+    gui.set_color(self.heading, c)
+    -- make heading shadow transparent
+    c = gui.get_shadow(self.heading)
+    c.w = 0
+    gui.set_shadow(self.heading, c)
+    -- set heading to twice the scale initially
+    local s = 2
+    gui.set_scale(self.heading, vmath.vector3(s, s, s))
+    -- set initial score (0)
+    gui.set_text(self.score, "0")
+    -- set score color to opaque white
+    gui.set_color(self.score, vmath.vector4(1, 1, 1, 1))
+    -- set scale so the score can grow while counting
+    gui.set_scale(self.score, vmath.vector4(score_start_scale, score_start_scale, 1, 0))
+
+    -- make all big stars transparent
+    for i=1,#self.stars do
+        gui.set_color(self.stars[i], vmath.vector4(1, 1, 1, 0))
+    end
+    -- make the imprint transparent
+    gui.set_color(self.imprint, vmath.vector4(1, 1, 1, 0))
+    -- the score currently being displayed
+    self.current_score = 0
+    -- the target score when counting
+    self.target_score = 0
+end
+
+function init(self)
+    -- retrieve nodes for easier access
+    self.heading = gui.get_node("heading")
+    self.stars = {gui.get_node("star_left"), gui.get_node("star_mid"), gui.get_node("star_right")}
+    self.score = gui.get_node("score")
+    self.imprint = gui.get_node("imprint")
+    -- start color of the score
+    self.score_start_color = vmath.vector4(1, 1, 1, 1)
+    -- save score color and animate towards it during counting later
+    self.score_end_color = gui.get_color(self.score)
+    setup(self)
+end
+
+-- delete a small star, called when the star has finished animating
+local function delete_small_star(self, small_star)
+    gui.delete_node(small_star)
+end
+
+-- animate a small star according to the given initial position and angle
+local function animate_small_star(self, pos, angle)
+    -- direction of travel for the small star
+    local dir = vmath.vector3(math.cos(angle), math.sin(angle), 0, 0)
+    -- create a small star
+    local small_star = gui.new_box_node(pos + dir * 20, vmath.vector3(64, 64, 0))
+    -- set its texture
+    gui.set_texture(small_star, "small_star")
+    -- set its color to full white
+    gui.set_color(small_star, vmath.vector4(1, 1, 1, 1))
+    -- set start scale low
+    local start_s = 0.3
+    gui.set_scale(small_star, vmath.vector3(start_s, start_s, 1))
+    -- variation in scale of each small star
+    local end_s_var = 1
+    -- actual end scale of this star
+    local end_s = 0.5 + math.random() * end_s_var
+    gui.animate(small_star, gui.PROP_SCALE, vmath.vector4(end_s, end_s, 1, 0), gui.EASING_NONE, 0.5)
+    -- variation in distance traveled (essentially speed of the star)
+    local dist_var = 300
+    -- actual distance the star will travel
+    local dist = 400 + math.random() * dist_var
+    gui.animate(small_star, gui.PROP_POSITION, pos + dir * dist, gui.EASING_NONE, 0.5)
+    gui.animate(small_star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 0), gui.EASING_OUT, 0.3, 0.2, delete_small_star)
+end
+
+-- spawn a number of small stars
+local function spawn_small_stars(self, star)
+    -- position of the big star the small star will spawn around
+    local p = gui.get_position(star)
+    for i = 1,small_star_count do
+        -- calculate the angle of the particular small star
+        local angle = 2 * math.pi * i/small_star_count
+        -- as well as position
+        local pos = vmath.vector3(p.x, p.y, 0)
+        -- spawn and animate the small star
+        animate_small_star(self, pos, angle)
+    end
+end
+
+-- start the animation of a big star fading in
+local function animate_star(self, star)
+    -- fade in duration
+    local fade_in = 0.2
+    -- make it transparent
+    gui.set_color(star, vmath.vector4(1, 1, 1, 0))
+    -- fade in
+    gui.animate(star, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in)
+    -- initial scale
+    local scale = 5
+    gui.set_scale(star, vmath.vector3(scale, scale, 1))
+    -- shrink back into place
+    gui.animate(star, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, 0, spawn_small_stars)
+end
+
+-- start the animation of the imprint fading in
+local function animate_imprint(self)
+    -- wait a bit before the imprint appears
+    local delay = 0.8
+    -- fade in duration
+    local fade_in = 0.2
+    -- initial scale
+    local scale = 4
+    gui.set_scale(self.imprint, vmath.vector4(scale, scale, 1, 0))
+    -- shrink back into place
+    gui.animate(self.imprint, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, fade_in, delay)
+    -- also fade in
+    gui.animate(self.imprint, gui.PROP_COLOR, vmath.vector4(1, 1, 1, 1), gui.EASING_IN, fade_in, delay)
+end
+
+-- increment the score one step towards the target
+local function inc_score(self, node)
+    -- how much the score is incremented this step
+    local score_inc = score_inc_speed * dt
+    -- new score after increment
+    local new_score = self.current_score + score_inc
+    for i = 1,#self.stars do
+        -- start animating a big star if we cross the level in score for it to appear
+        if self.current_score < self.star_levels[i] and new_score >= self.star_levels[i] then
+            animate_star(self, self.stars[i])
+        end
+    end
+    -- update score, but clamp at target
+    self.current_score = math.min(new_score, self.target_score)
+    -- update the score on screen
+    gui.set_text(self.score, tostring(self.current_score))
+    -- if we are not yet done, keep animating and incrementing
+    if self.current_score < self.target_score then
+        -- how close we are to the target
+        local f = self.current_score / self.target_score
+        -- blend the color to get a slow fade
+        local c = vmath.lerp(f, self.score_start_color, self.score_end_color)
+        gui.animate(self.score, gui.PROP_COLOR, c, gui.EASING_NONE, dt, 0, inc_score)
+        -- new scale for this step
+        local s = vmath.lerp(f, score_start_scale, score_end_scale)
+        -- increase the scale by the bounce factor
+        local sp = s * score_bounce_factor
+        -- animate from bounced scale back to the appropriate scale
+        gui.set_scale(self.score, vmath.vector4(sp, sp, 1, 0))
+        gui.animate(self.score, gui.PROP_SCALE, vmath.vector4(s, s, 1, 0), gui.EASING_NONE, dt)
+    else
+        -- we are done, fade in the imprint
+        -- NOTE! this should in a real case be checked against the actual stored high score
+        animate_imprint(self)
+    end
+end
+
+function on_message(self, message_id, message, sender)
+    -- someone tells us that we should display the level completed scene
+    if message_id == hash("level_completed") then
+        -- retrieve the obtained score and at which score levels the stars should be displayed
+        self.target_score = message.score
+        self.star_levels = message.star_levels
+        -- fade in heading ("level completed")
+        local c = gui.get_color(self.heading)
+        c.w = 1
+        gui.animate(self.heading, gui.PROP_COLOR, c, gui.EASING_IN, dt, 0.0, inc_score)
+        c = gui.get_shadow(self.heading)
+        c.w = 1
+        gui.animate(self.heading, gui.PROP_SHADOW, c, gui.EASING_IN, dt, 0.0)
+        -- shrink it into place
+        gui.animate(self.heading, gui.PROP_SCALE, vmath.vector4(1, 1, 1, 0), gui.EASING_IN, 0.2, 0.0)
+    end
+end
+
+-- this function is called when the script is reloaded
+-- by setting up the scene and simulating level complete, we get a really fast workflow for tweaking
+function on_reload(self)
+    -- make sure any setup changes are taken into account
+    setup(self)
+    -- simulate that the level has been completed
+    msg.post("#gui", "level_completed", {score = 102000, star_levels = {40000, 70000, 100000}})
+end
+```

+ 21 - 22
docs/en/tutorials/magic-link.md

@@ -16,29 +16,27 @@ This tutorial is written as a step-by-step guide where we build the game on a co
     <button id="game-button">
         START GAME <span class="icon"></span>
     </button>
-</div>
-<!--
-<script src="//storage.googleapis.com/defold-doc/assets/dmloader.js"></script>
-<script src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async></script>
-<script>
-    /* Load app on click in container. */
-    document.getElementById("game-button").onclick = function (e) {
-        var extra_params = {
-            archive_location_filter: function( path ) {
-                return ("//storage.googleapis.com/defold-doc/assets/magic-link" + path + "");
-            },
-            load_done: function() {},
-            game_start: function() {
-                var e = document.getElementById("game-preview");
-                e.parentElement.removeChild(e);
+    <script src="//storage.googleapis.com/defold-doc/assets/dmloader.js"></script>
+    <script src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async></script>
+    <script>
+        /* Load app on click in container. */
+        document.getElementById("game-button").onclick = function (e) {
+            var extra_params = {
+                archive_location_filter: function( path ) {
+                    return ("//storage.googleapis.com/defold-doc/assets/magic-link" + path + "");
+                },
+                load_done: function() {},
+                game_start: function() {
+                    var e = document.getElementById("game-preview");
+                    e.parentElement.removeChild(e);
+                }
             }
-        }
-        Module.runApp("game-canvas", extra_params);
-        document.getElementById("game-button").style.display = 'none';
-        document.getElementById("game-button").onclick = null;
-    };
-</script>
--->
+            Module.runApp("game-canvas", extra_params);
+            document.getElementById("game-button").style.display = 'none';
+            document.getElementById("game-button").onclick = null;
+        };
+    </script>
+</div>
 
 ## Getting started
 
@@ -1369,6 +1367,7 @@ This little game has some interesting properties and you are encouraged to exper
 
 * Clarify interaction. A new player may have a hard time understanding how the game works and what she can interact with. Spend some time making the game more clear, without inserting tutorial elements.
 * Add sounds. The game is currently totally silent and would benefit from a nice soundtrack and interaction sounds.
+* Auto detect game over.
 * High score. Add a high score functionality that is persistent.
 * Re-implement the game using only the GUI APIs.
 * Currently, the game continues by adding one magic block for each level increase. That is not sustainable forever. Find a satisfying solution to this problem.

+ 91 - 0
docs/en/tutorials/main-menu.md

@@ -0,0 +1,91 @@
+---
+title: Main menu code sample
+brief: In this sample, we demonstrate effects to present a main menu.
+---
+# Main menu
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/ndkyRuXUr-4" frameborder="0" allowfullscreen></iframe>
+
+In this sample, we demonstrate effects to present a main menu. The menu contains a background and two menu items.
+
+Each of the background and the two menu items, have the same animations applied to them, but with different delays. This is set up in `init()` below.
+
+The first animations is to have each node fade in while being scaled from 70% to 110%.
+This is done in `anim1()`.
+
+During the following animations, the scale is animated back and forth from 110% to 98%, to 106% and then to 100%. This gives the bouncing effect and is done in `anim2()`, `anim3()` and `anim4()`.
+
+The background has a special slight fade out at the end, which is applied in `anim5()`.
+
+```lua
+-- file: menu.gui_script
+
+-- the functions animX represents the animation time-lime
+-- first is anim1 executed, when finished anim2 is executed, etc
+-- anim1 to anim4 creates a bouncing rubber effect.
+-- anim5 fades down alpha and is only used for the background
+
+local function anim5(self, node)
+    if gui.get_node("background") == node then
+        -- special case for background. animate alpha to 60%
+        local to_color = gui.get_color(node)
+        to_color.w = 0.6
+        gui.animate(node, gui.PROP_COLOR, to_color, gui.EASING_OUT, 1.2, 0.1)
+    end
+end
+
+local function anim4(self, node)
+    -- animate scale to 100%
+    local s = 1
+    gui.animate(node, gui.PROP_SCALE, vmath.vector4(s, s, s, 0), gui.EASING_INOUT, 0.12, 0, anim5)
+end
+
+local function anim3(self, node)
+    -- animate scale to 106%
+    local s = 1.06
+    gui.animate(node, gui.PROP_SCALE, vmath.vector4(s, s, s, 0), gui.EASING_INOUT, 0.12, 0, anim4)
+end
+
+local function anim2(self, node)
+    -- animate scale to 98%
+    local s = 0.98
+    gui.animate(node, gui.PROP_SCALE, vmath.vector4(s, s, s, 0), gui.EASING_INOUT, 0.12, 0, anim3)
+end
+
+local function anim1(node, d)
+    -- set scale to 70%
+    local start_scale = 0.7
+    gui.set_scale(node, vmath.vector4(start_scale, start_scale, start_scale, 0))
+
+    -- get current color and set alpha to 0 to fade up
+    local from_color = gui.get_color(node)
+    local to_color = gui.get_color(node)
+    from_color.w = 0
+    gui.set_color(node, from_color)
+
+    -- animate alpha value from 0 to 1
+    gui.animate(node, gui.PROP_COLOR, to_color, gui.EASING_IN, 0.2, d)
+
+    -- animate scale from %70 to 110%
+    local s = 1.1
+    gui.animate(node, gui.PROP_SCALE, vmath.vector4(s, s, s, 0), gui.EASING_IN, 0.2, d, anim2)
+end
+
+function init(self)
+    -- start animations for all nodes
+    -- background, button-boxes and text are animated equally
+    -- d is the animation start delay
+    local d = 0.4
+    anim1(gui.get_node("new_game"), d)
+    anim1(gui.get_node("new_game_shadow"), d)
+    anim1(gui.get_node("new_game_button"), d)
+
+    d = 0.3
+    anim1(gui.get_node("quit"), d)
+    anim1(gui.get_node("quit_shadow"), d)
+    anim1(gui.get_node("quit_button"), d)
+
+    d = 0.1
+    anim1(gui.get_node("background"), d)
+end
+```

+ 81 - 0
docs/en/tutorials/parallax.md

@@ -0,0 +1,81 @@
+---
+title: Parallax code sample
+brief: In this sample, we demonstrate how to use a parallax effect to simulate depth in the game world.
+---
+# Parallax
+
+<iframe width="560" height="315" src="https://www.youtube.com/embed/rv7GrtmUrPU" frameborder="0" allowfullscreen></iframe>
+
+In this sample, we demonstrate how to use a parallax effect to simulate depth in the game world.
+There are two layers of clouds, where one of the layers has the appearance of being further back than the other. There is also an animated saucer for flavor.
+
+The cloud layers are built as two separate game objects, containing a *Tile Map* and *Script* each.
+The layers are moved at different speeds, to give the parallax effect. This is done in `update()` of *background1.script* and *background2.script* below.
+
+```lua
+-- file: background1.script
+
+function init(self)
+    msg.post("@render:", "clear_color", { color = vmath.vector4(0.52, 0.80, 1, 0) } )
+end
+
+-- the background is a tilemap in a gameobject
+-- we move the gameobject for the parallax effect
+
+function update(self, dt)
+    -- decrease x-position by 1 units per frame for parallax effect
+    local p = go.get_position()
+    p.x = p.x + 1
+    go.set_position(p)
+end
+```
+
+```lua
+-- file: background2.script
+
+-- the background is a tilemap in a gameobject
+-- we move the gameobject for the parallax effect
+
+function update(self, dt)
+    -- decrease x-position by 0.5 units per frame for parallax effect
+    local p = go.get_position()
+    p.x = p.x + 0.5
+    go.set_position(p)
+end
+```
+
+The saucer is a separate game object, containing a *Sprite* and a *Script*.
+It is moved to the left at a constant speed. The up-down-motion is obtained by animating its y-component around a fixed value using the Lua sine function (`math.sin()`). This is done in `update()` of *spaceship.script*.
+
+
+```lua
+-- file: spaceship.script
+
+function init(self)
+    -- remeber initial y position such that we
+    -- can move the spaceship without changing the script
+    self.start_y = go.get_position().y
+    -- set counter to zero. use for sin-movement below
+    self.counter = 0
+end
+
+function update(self, dt)
+    -- decrease x-position by 2 units per frame
+    local p = go.get_position()
+    p.x = p.x - 2
+
+    -- move the y position around initial y
+    p.y = self.start_y + 8 * math.sin(self.counter * 0.08)
+
+    -- update position
+    go.set_position(p)
+
+    -- remove shaceship when outside of screen
+    if p.x < - 32 then
+        go.delete()
+    end
+
+    -- increase the counter
+    self.counter = self.counter + 1
+end
+```

+ 450 - 0
docs/en/tutorials/platformer.md

@@ -0,0 +1,450 @@
+---
+title: Platformer Defold tutorial
+brief: In this article, we go through the implementation of a basic tile-based 2D platformer in Defold. The mechanics we will learn are moving left/right, jumping and falling.
+---
+
+# Platformer
+
+In this article, we go through the implementation of a basic tile-based 2D platformer in Defold. The mechanics we will learn are moving left/right, jumping and falling.
+
+<div id="game-container" class="game-container">
+  <img id="game-preview" src="//storage.googleapis.com/defold-doc/assets/platformer/preview.jpg"/>
+  <canvas id="game-canvas" tabindex="1" width="1024" height="768">
+  </canvas>
+  <button id="game-button">
+    START GAME <span class="icon"></span>
+  </button>
+  <script src="//storage.googleapis.com/defold-doc/assets/dmloader.js"></script>
+  <script src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async></script>
+  <script>
+      /* Load app on click in container. */
+      document.getElementById("game-button").onclick = function (e) {
+          var extra_params = {
+              archive_location_filter: function( path ) {
+                  return ("//storage.googleapis.com/defold-doc/assets/platformer" + path + "");
+              },
+              load_done: function() {},
+              game_start: function() {
+                  var e = document.getElementById("game-preview");
+                  e.parentElement.removeChild(e);
+              }
+          }
+          Module.runApp("game-canvas", extra_params);
+          document.getElementById("game-button").style.display = 'none';
+          document.getElementById("game-button").onclick = null;
+      };
+  </script>
+</div>
+
+There are many different ways to go about creating a platformer. Rodrigo Monteiro has written an exhaustive analysis on the subject and more [here](http://higherorderfun.com/blog/2012/05/20/the-guide-to-implementing-2d-platformers/).
+
+We highly recommend you read it if you are new to making platformers, as it contains plenty of valuable information. We will go into a bit more detail on a few of the methods described and how to implement them in Defold. Everything should however be easy to port to other platforms and languages (we use Lua in Defold).
+
+We assume that you're familiar with a bit of vector mathematics (linear algebra). If you're not, it's a good idea to read up on it since it's insanely useful for game development. David Rosen at Wolfire has written a very good series about it [here](http://blog.wolfire.com/2009/07/linear-algebra-for-game-developers-part-1/).
+
+If you are already using Defold, you can create a new project based on the _Platformer_ template-project and play around with that while reading this article.
+
+We would love to hear your feedback, so please comment at the bottom of the page!
+
+::: sidenote
+Some readers has brought up that our suggested method is not possible with the default implementation of Box2D. We made a few modifications to Box2D to make this work:
+
+Collisions between kinematic and static objects are ignored. Change the checks in `b2Body::ShouldCollide` and `b2ContactManager::Collide`.
+
+Also, the contact distance (called separation in Box2D) is not supplied to the callback-function.
+Add a distance-member to `b2ManifoldPoint` and make sure it's updated in the `b2Collide*` functions.
+:::
+
+## Collision Detection
+
+Collision detection is needed to keep the player from moving through the level geometry.
+There are a number of ways to deal with this depending on your game and its specific requirements.
+One of the easiest ways, if possible, is to let a physics engine take care of it.
+In Defold we use the physics engine [Box2D](http://box2d.org/) for 2D games.
+The default implementation of Box2D does not have all the features needed, see the bottom of this article for how we modified it.
+
+A physics engine stores the states of the physics objects along with their shapes in order to simulate physical behaviour. It also reports collisions while simulating, so the game can react as they happen. In most physics engines there are three types of objects: _static_, _dynamic_ and _kinematic_ objects (these names might be different in other physics engines). There are other types of objects too, but let's ignore them for now.
+
+- A *static* object will never move (e.g. level geometry).
+- A *dynamic* object is influenced by forces and torques which are transformed into velocities during the simulation.
+- A *kinematic* object is controlled by the application logic, but still affects other dynamic objects.
+
+In a game like this, we are looking for something that resembles physical real-world behaviour, but having responsive controls and balanced mechanics is far more important. A jump that feels good does not need to be physically accurate or act under real-world gravity. [This](http://hypertextbook.com/facts/2007/mariogravity.shtml) analysis shows however that the gravity in Mario games gets closer to a gravity of 9.8 m/s^2^ for each version. :-)
+
+It's important that we have full control of what's going on so we can design and tweak the mechanics to achieve the intended experience. This is why we choose to model the player character by a kinematic object. Then we can move the player character around as we please, without having to deal with physical forces. This means that we will have to solve separation between the character and level geometry ourselves (more about this later), but that's a drawback we are willing to accept. We will represent the player character by a box shape in the physics world.
+
+## Movement
+
+Now that we have decided that the player character will be represented by a kinematic object, we can move it around freely by setting the position. Let's start with moving left/right.
+
+The movement will be acceleration-based, to give a sense of weight to the character. Like for a regular vehicle, the acceleration defines how fast the player character can reach the max speed and change direction. The acceleration is acting over the frame time-step---usually provided in a parameter `dt` (delta-`t`)---and then added to the velocity. Similarly, the velocity acts over the frame and the resulting translation is added to the position. In maths, this is called [integration over time](http://en.wikipedia.org/wiki/Integral).
+
+![Approximative velocity integration](images/platformer/integration.png)
+
+The two vertical lines marks the beginning and end of the frame. The height of the lines is the velocity the player character has at these two points in time. Let us call these velocities `v`~0~ and `v`~1~ . `v`~1~ is given by applying the acceleration (the slope of the curve) for the time-step `dt`:
+
+$$
+v1 = v0 + acceleration \times dt
+$$
+
+The orange area is the translation we are supposed to apply to the player character during the current frame. Geometrically, we can approximate the area as:
+
+$$
+translation = \frac{(v0 + v1) \times dt}{2}
+$$
+
+This is how we integrate the acceleration and velocity to move the character in the update-loop:
+
+1. Determine the target speed based on input
+2. Calculate the difference between our current speed and the target speed
+3. Set the acceleration to work in the direction of the difference
+4. Calculate the velocity change this frame (dv is short for delta-velocity), as above:
+
+    ```lua
+    local dv = acceleration * dt
+    ```
+
+5. Check if dv exceeds the intended speed difference, clamp it in that case
+6. Save the current velocity for later use (+self.velocity+, which right now is the velocity used the previous frame):
+
+    ```lua
+    local v0 = self.velocity
+    ```
+
+7. Calculate the new velocity by adding the velocity change:
+
+    ```lua
+    self.velocity = self.velocity + dv
+    ```
+
+8. Calculate the x-translation this frame by integrating the velocity, as above:
+
+    ```lua
+    local dx = (v0 + self.velocity) * dt * 0.5
+    ```
+
+9. Apply it to the player character
+
+If you are unsure how to handle input in Defold, there's a guide about that [here](/manuals/input).
+
+At this stage, we can move the character left and right and have a weighted and smooth feel to the controls. Now, let's add gravity!
+
+Gravity is also an acceleration, but it affects the player along the y-axis. This means that it will be applied in the same manner as the movement acceleration described above. If we just change the calculations above to vectors and make sure we include gravity in the y-component of the acceleration at step 3), it will just work. Gotta love vector-math! :-)
+
+## Collision Response
+
+Now our player character can move and fall, so it's time to look at collision responses.
+We obviously need to land and move along the level geometry. We will use the contact points provided by the physics engine to make sure we never overlap anything.
+
+A contact point carries a _normal_ of the contact (pointing out from the object we collide with, but might be different in other engines) as well as a _distance_, which measures how far we have penetrated the other object. This is all we need to separate the player from the level geometry.
+Since we are using a box, we might get multiple contact points during a frame. This happens for example when two corners of the box intersect the horizontal ground, or the player is moving into a corner.
+
+![Contact normals acting on the player character](images/platformer/collision.png)
+
+To avoid making the same correction multiple times, we accumulate the corrections in a vector to make sure we don't over-compensate. This would make us end up too far away from the object we collided with. In the image above, you can see that we currently have two contact points, visualized by the two arrows (normals). The penetration distance is the same for both contacts, if we would use that blindly each time we would end up moving the player twice the intended amount.
+
+::: sidenote
+It's important to reset the accumulated corrections each frame to the 0-vector.
+Put something like this at the end of the `update()` function:
+`self.corrections = vmath.vector3()`
+:::
+
+Assuming there is a callback-function that will be called for each contact point, here's how to do the separation in that function:
+
+```lua
+local proj = vmath.dot(self.correction, normal) -- <1>
+local comp = (distance - proj) * normal -- <2>
+self.correction = self.correction + comp -- <3>
+go.set_position(go.get_position() + comp) -- <4>
+```
+1. Project the correction vector onto the contact normal (the correction vector is the 0-vector for the first contact point)
+2. Calculate the compensation we need to make for this contact point
+3. Add it to the correction vector
+4. Apply the compensation to the player character
+
+We also need to cancel out the part of the player velocity that moves towards the contact point:
+
+```lua
+proj = vmath.dot(self.velocity, message.normal) -- <1>
+if proj < 0 then
+    self.velocity = self.velocity - proj * message.normal -- <2>
+end
+```
+1. Project the velocity onto the normal
+2. If the projection is negative, it means that some of the velocity points towards the contact point; remove that component in that case
+
+## Jumping
+
+Now that we can run on the level geometry and fall down, it's time to jump! Platformer-jumping can be done in many different ways. In this game we are aiming for something similar to Super Mario Bros and Super Meat Boy. When jumping, the player character is thrusted upwards by an impulse, which is basically a fixed speed.
+
+Gravity will continuously pull the character down again, resulting in a nice jump arc. While in the air, the player can still control the character. If the player lets go of the jump button before the peak of the jump arc, the upwards speed is scaled down to halt the jump prematurely.
+
+1. When the input is pressed, do:
+
+    ```lua
+    -- jump_takeoff_speed is a constant defined elsewhere
+    self.velocity.y = jump_takeoff_speed
+    ```
+
+    This should only be done when the input is _pressed_, not each frame it is continuously _held down_.
+
+2. When the input is released, do:
+
+    ```lua
+    -- cut the jump short if we are still going up
+    if self.velocity.y > 0 then
+        -- scale down the upwards speed
+        self.velocity.y = self.velocity.y * 0.5
+    end
+    ```
+
+ExciteMike has made some nice graphs of the jump arcs in [Super Mario Bros 3](http://meyermike.com/wp/?p=175) and [Super Meat Boy](http://meyermike.com/wp/?p=160) that are worth checking out.
+
+## Level Geometry
+
+The level geometry is the collision shapes of the environment that the player character (and possibly other things) collide with. In Defold, there are two ways to create this geometry.
+
+Either you create separate collision shapes on top of the levels you build. This method is very flexible and allows fine positioning of graphics. It is especially useful if you want soft slopes.
+The game [Braid](http://www.davidhellman.net/blog/archives/85) used this method of building levels, and it is the method the example level in this tutorial is built too. Here is how it looks in the Defold editor:
+
+![The Defold Editor with the level geometry and player placed into the world](images/platformer/editor.png)
+
+Another option is to build levels out of tiles and have the editor automatically generate the physics shapes, based on tile graphics. This means that the level geometry will be automatically updated when you change the levels which can be extremely useful.
+
+The placed tiles will get their physics shapes automatically merged into one if they align.
+This eliminates the gaps that can make your player character stop or bump when sliding across several horizontal tiles. This is done by replacing the tile polygons with edge shapes in Box2D at load-time.
+
+![Multiple tile-based polygons stitched into one](images/platformer/stitching.png)
+
+Above is an example where we created five neighboring tiles out of a piece of the platformer graphics. In the image you can see how the placed tiles (top) correspond to one single shape that has been stitched together into one (bottom grey contour).
+
+Check out our guides about [physics](/manuals/physics) and [tiles](/manuals/2dgraphics) for more info.
+
+## Final Words
+
+If you want more information about platformer mechanics, here is an impressively huge amount of info about the physics in [Sonic](http://info.sonicretro.org/Sonic_Physics_Guide).
+
+If you try our template project on an iOS device or with a mouse, the jump can feel really awkward.
+That's just our feeble attempt at platforming with one-touch-input. :-)
+
+We did not talk about how we handled the animations in this game. You can get an idea by checking out the *player.script* below, look for the function `update_animations()`.
+
+We hope you found this information useful!
+Please make a great platformer so we all can play it! <3
+
+## Code
+
+Here is the content of *player.script*:
+
+```lua
+-- player.script
+
+-- these are the tweaks for the mechanics, feel free to change them for a different feeling
+-- the acceleration to move right/left
+local move_acceleration = 3500
+-- acceleration factor to use when air-borne
+local air_acceleration_factor = 0.8
+-- max speed right/left
+local max_speed = 450
+-- gravity pulling the player down in pixel units
+local gravity = -1000
+-- take-off speed when jumping in pixel units
+local jump_takeoff_speed = 550
+-- time within a double tap must occur to be considered a jump (only used for mouse/touch controls)
+local touch_jump_timeout = 0.2
+
+-- pre-hashing ids improves performance
+local msg_contact_point_response = hash("contact_point_response")
+local msg_animation_done = hash("animation_done")
+local group_obstacle = hash("obstacle")
+local input_left = hash("left")
+local input_right = hash("right")
+local input_jump = hash("jump")
+local input_touch = hash("touch")
+local anim_run = hash("run")
+local anim_idle = hash("idle")
+local anim_jump = hash("jump")
+local anim_fall = hash("fall")
+
+function init(self)
+    -- this lets us handle input in this script
+    msg.post(".", "acquire_input_focus")
+
+    -- initial player velocity
+    self.velocity = vmath.vector3(0, 0, 0)
+    -- support variable to keep track of collisions and separation
+    self.correction = vmath.vector3()
+    -- if the player stands on ground or not
+    self.ground_contact = false
+    -- movement input in the range [-1,1]
+    self.move_input = 0
+    -- the currently playing animation
+    self.anim = nil
+    -- timer that controls the jump-window when using mouse/touch
+    self.touch_jump_timer = 0
+end
+
+local function play_animation(self, anim)
+    -- only play animations which are not already playing
+    if self.anim ~= anim then
+        -- tell the sprite to play the animation
+        msg.post("#sprite", "play_animation", {id = anim})
+        -- remember which animation is playing
+        self.anim = anim
+    end
+end
+
+local function update_animations(self)
+    -- make sure the player character faces the right way
+    sprite.set_hflip("#sprite", self.move_input < 0)
+    -- make sure the right animation is playing
+    if self.ground_contact then
+        if self.velocity.x == 0 then
+            play_animation(self, anim_idle)
+        else
+            play_animation(self, anim_run)
+        end
+    else
+        if self.velocity.y > 0 then
+            play_animation(self, anim_jump)
+        else
+            play_animation(self, anim_fall)
+        end
+    end
+end
+
+function update(self, dt)
+    -- determine the target speed based on input
+    local target_speed = self.move_input * max_speed
+    -- calculate the difference between our current speed and the target speed
+    local speed_diff = target_speed - self.velocity.x
+    -- the complete acceleration to integrate over this frame
+    local acceleration = vmath.vector3(0, gravity, 0)
+    if speed_diff ~= 0 then
+        -- set the acceleration to work in the direction of the difference
+        if speed_diff < 0 then
+            acceleration.x = -move_acceleration
+        else
+            acceleration.x = move_acceleration
+        end
+        -- decrease the acceleration when air-borne to give a slower feel
+        if not self.ground_contact then
+            acceleration.x = air_acceleration_factor * acceleration.x
+        end
+    end
+    -- calculate the velocity change this frame (dv is short for delta-velocity)
+    local dv = acceleration * dt
+    -- check if dv exceeds the intended speed difference, clamp it in that case
+    if math.abs(dv.x) > math.abs(speed_diff) then
+        dv.x = speed_diff
+    end
+    -- save the current velocity for later use
+    -- (self.velocity, which right now is the velocity used the previous frame)
+    local v0 = self.velocity
+    -- calculate the new velocity by adding the velocity change
+    self.velocity = self.velocity + dv
+    -- calculate the translation this frame by integrating the velocity
+    local dp = (v0 + self.velocity) * dt * 0.5
+    -- apply it to the player character
+    go.set_position(go.get_position() + dp)
+
+    -- update the jump timer
+    if self.touch_jump_timer > 0 then
+        self.touch_jump_timer = self.touch_jump_timer - dt
+    end
+
+    update_animations(self)
+
+    -- reset volatile state
+    self.correction = vmath.vector3()
+    self.move_input = 0
+    self.ground_contact = false
+
+end
+
+local function handle_obstacle_contact(self, normal, distance)
+    -- project the correction vector onto the contact normal
+    -- (the correction vector is the 0-vector for the first contact point)
+    local proj = vmath.dot(self.correction, normal)
+    -- calculate the compensation we need to make for this contact point
+    local comp = (distance - proj) * normal
+    -- add it to the correction vector
+    self.correction = self.correction + comp
+    -- apply the compensation to the player character
+    go.set_position(go.get_position() + comp)
+    -- check if the normal points enough up to consider the player standing on the ground
+    -- (0.7 is roughly equal to 45 degrees deviation from pure vertical direction)
+    if normal.y > 0.7 then
+        self.ground_contact = true
+    end
+    -- project the velocity onto the normal
+    proj = vmath.dot(self.velocity, normal)
+    -- if the projection is negative, it means that some of the velocity points towards the contact point
+    if proj < 0 then
+        -- remove that component in that case
+        self.velocity = self.velocity - proj * normal
+    end
+end
+
+function on_message(self, message_id, message, sender)
+    -- check if we received a contact point message
+    if message_id == msg_contact_point_response then
+        -- check that the object is something we consider an obstacle
+        if message.group == group_obstacle then
+            handle_obstacle_contact(self, message.normal, message.distance)
+        end
+    end
+end
+
+local function jump(self)
+    -- only allow jump from ground
+    -- (extend this with a counter to do things like double-jumps)
+    if self.ground_contact then
+        -- set take-off speed
+        self.velocity.y = jump_takeoff_speed
+        -- play animation
+        play_animation(self, anim_jump)
+    end
+end
+
+local function abort_jump(self)
+    -- cut the jump short if we are still going up
+    if self.velocity.y > 0 then
+        -- scale down the upwards speed
+        self.velocity.y = self.velocity.y * 0.5
+    end
+end
+
+function on_input(self, action_id, action)
+    if action_id == input_left then
+        self.move_input = -action.value
+    elseif action_id == input_right then
+        self.move_input = action.value
+    elseif action_id == input_jump then
+        if action.pressed then
+            jump(self)
+        elseif action.released then
+            abort_jump(self)
+        end
+    elseif action_id == input_touch then
+        -- move towards the touch-point
+        local diff = action.x - go.get_position().x
+        -- only give input when far away (more than 10 pixels)
+        if math.abs(diff) > 10 then
+            -- slow down when less than 100 pixels away
+            self.move_input = diff / 100
+            -- clamp input to [-1,1]
+            self.move_input = math.min(1, math.max(-1, self.move_input))
+        end
+        if action.released then
+            -- start timing the last release to see if we are about to jump
+            self.touch_jump_timer = touch_jump_timeout
+        elseif action.pressed then
+            -- jump on double tap
+            if self.touch_jump_timer > 0 then
+                jump(self)
+            end
+        end
+    end
+end
+```

+ 28 - 35
docs/en/tutorials/runner.md

@@ -9,41 +9,34 @@ In this tutorial we start with an empty project and build a complete runner game
 
 
 <div id="game-container" class="game-container">
-    <img id="game-preview" src="//storage.googleapis.com/defold-doc/assets/runner/preview.jpg"/>
-    <canvas id="game-canvas" tabindex="1" width="1280" height="720">
-    </canvas>
-    <button id="game-button">
-        START GAME <span class="icon"></span>
-    </button>
+  <img id="game-preview" src="//storage.googleapis.com/defold-doc/assets/runner/preview.jpg"/>
+  <canvas id="game-canvas" tabindex="1" width="1280" height="720">
+  </canvas>
+  <button id="game-button">
+    START GAME <span class="icon"></span>
+  </button>
+  <script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmloader.js"></script>
+  <script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async></script>
+  <script type='text/javascript'>
+      /* Load app on click in container. */
+      document.getElementById("game-button").onclick = function (e) {
+          var extra_params = {
+              archive_location_filter: function( path ) {
+                  return ("//storage.googleapis.com/defold-doc/assets/runner" + path + "");
+              },
+              load_done: function() {},
+              game_start: function() {
+                  var e = document.getElementById("game-preview");
+                  e.parentElement.removeChild(e);
+              }
+          }
+          Module.runApp("game-canvas", extra_params);
+          document.getElementById("game-button").style.display = 'none';
+          document.getElementById("game-button").onclick = null;
+      };
+  </script>
 </div>
 
-<!-- -->
-<script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmloader.js">
-</script>
-<script type='text/javascript' src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async>
-</script>
-<!-- -->
-
-<script type='text/javascript'>
-    /* Load app on click in container. */
-    document.getElementById("game-button").onclick = function (e) {
-        var extra_params = {
-            archive_location_filter: function( path ) {
-                return ("//storage.googleapis.com/defold-doc/assets/runner" + path + "");
-            },
-            load_done: function() {},
-            game_start: function() {
-                var e = document.getElementById("game-preview");
-                e.parentElement.removeChild(e);
-            }
-        }
-        Module.runApp("game-canvas", extra_params);
-        document.getElementById("game-button").style.display = 'none';
-        document.getElementById("game-button").onclick = null;
-    };
-</script>
-
-
 There is a lot to take in when learning a new game engine, so we have created this tutorial to get you started. It is a fairly complete tutorial that walks through how the engine and the editor works. We assume that you have some famililiarity with programming.
 
 If you need an introduction to Lua programming, check out our [Lua in Defold manual](/manuals/lua).
@@ -142,8 +135,8 @@ That's it!
 ::: sidenote
 The Defold editor works on files. By double-clicking a file in the *Project Explorer* you open it in a suitable editor. You can then work with the contents of the file.
 
-When you are done editing a file you have to save it. Select <kbd>File ▸ Save</kbd> in the main menu. The editor gives a hint by adding an asterisk '*' to the filename in the tab for any file that contain unsaved changes.
-
+When you are done editing a file you have to save it. Select <kbd>File ▸ Save</kbd> in the main menu. The editor gives a hint by adding an asterisk '\*' to the filename in the tab for any file that contain unsaved changes.
+  
 ![File with unsaved changes](images/runner/1/file_changed.png)
 :::
 

+ 160 - 0
docs/en/tutorials/side-scroller.md

@@ -0,0 +1,160 @@
+---
+title: Side scroller tutorial
+brief: This tutorial is intended to give a taste of what making games in Defold is about. It goes through creating a new project, based on a simple side-scroller. You will then learn how to tweak the game to make it more fun. Finally you will add a new game object. The tutorial should only take about 10 minutes.
+---
+
+# Side scroller
+
+The game is extremely simple. The player controls a space ship and is supposed to collect stars that appear on the screen. The ship is controlled with the up and down arrow keys on the keyboard.
+
+(You can try the game right here in the browser. Use the arrow keys to control the ship.)
+
+<div id="game-container" class="game-container">
+  <img id="game-preview" src="//storage.googleapis.com/defold-doc/assets/side-scroller/preview.jpg"/>
+  <canvas id="game-canvas" tabindex="1" width="1280" height="720">
+  </canvas>
+  <button id="game-button">
+    START GAME <span class="icon"></span>
+  </button>
+  <script src="//storage.googleapis.com/defold-doc/assets/dmloader.js"></script>
+  <script src="//storage.googleapis.com/defold-doc/assets/dmengine.js" async></script>
+  <script>
+      /* Load app on click in container. */
+      document.getElementById("game-button").onclick = function (e) {
+          var extra_params = {
+              archive_location_filter: function( path ) {
+                  return ("//storage.googleapis.com/defold-doc/assets/side-scroller" + path + "");
+              },
+              load_done: function() {},
+              game_start: function() {
+                  var e = document.getElementById("game-preview");
+                  e.parentElement.removeChild(e);
+              }
+          }
+          Module.runApp("game-canvas", extra_params);
+          document.getElementById("game-button").style.display = 'none';
+          document.getElementById("game-button").onclick = null;
+      };
+  </script>
+</div>
+
+## Setup
+
+1. Start by going to the [dashboard](//dashboard.defold.com), log in and click *New Project*.
+2. Select the "Side-scroller" tutorial as the template project.
+3. Start the editor and open the project you just created with <kbd>File ▸ Open Project</kbd> (the editor can be downloaded from the [dashboard](//dashboard.defold.com)).
+4. Select your project and click *Next*.
+5. Create a new branch. A branch is like a personal view of the project. Other members of your project won't see your changes until you synchronize your branch (which can be done with <kbd>File ▸ Synchronize</kbd>.
+
+  ![New branch](images/side-scroller/side-scroller_new_branch.png)
+
+6. Try the game with <kbd>Project ▸ Build And Launch</kbd>.
+
+  ![Run the game](images/side-scroller/side-scroller_run_game.png)
+
+## Tweaking
+
+We can tweak the game in order to make it more fun. You can exit the game with the <kbd>Esc</kbd> key, but let's keep the game running and update it live. The speed of the space ship could be faster so let's fix that first:
+
+1. Switch back to the editor and open the file *spaceship.script* with <kbd>Edit ▸ Open Resource...</kbd>
+2. In the beginning of the file, change:
+
+    ```lua
+    local max_speed = 60
+    ```
+
+    to:
+
+    ```lua
+    local max_speed = 150
+    ```
+
+    This will increase the movement speed of the space ship.
+
+3. Reload the script file into the running game with <kbd>Edit ▸ Reload Resource</kbd>.
+4. Try moving the space ship with the arrow-keys on your keyboard. Notice how the it moves faster now.
+
+Currently, the player only gets 1 point for each star collected. More score is more fun so let's take care of that.
+
+1. Open the file *star.script*.
+2. In the beginning of the file, change:
+
+    ```lua
+    local score = 1
+    ```
+
+    to:
+
+    ```lua
+    local score = 1000
+    ```
+
+3. Reload the file again with <kbd>Edit ▸ Reload Resource</kbd>.
+4. Collect some stars and notice how the score has dramatically increased.
+
+## Add a bonus star
+
+Finally, the game would be a bit more interesting if bonus stars would appear now and then. In order to have bonus stars appear, we first need to create a game object, which works as a blueprint.
+
+1. Add a new game object file called *bonus_star.go* in the *stars* directory with <kbd>File ▸ New ▸ Game Object File</kbd>.
+2. Add a *Sprite* component to the game object with <kbd>Game Object ▸ Add Component</kbd>. This attaches graphics to the bonus star.
+3. In the *Outline* view (upper right), a new item appeared called "sprite". Its properties are displayed in the Properties-view below.
+
+    - Set *Image* property to `stars.atlas` by using the browse-button *...*. The atlas contains the graphics for the bonus star.
+    - Set *Default Animation* to "bonus_star" and hit *ENTER*. "bonus_star" is an animation defined in the atlas.
+
+    A green star should appear on the screen. Hit the <kbd>F</kbd> key or select <kbd>Scene ▸ Frame Objects</kbd> if the view of the star is not very good.
+
+4. Select the game object again by clicking on the *Game Object* item in the *Outline*-view.
+5. Add a _Collision Object_ component to the game object with <kbd>Game Object ▸ Add Component</kbd>. This lets the bonus stars collide with other game objects, specifically the player space ship in our case.
+
+    - Click on the "collisionobject" item in the Outline-view to show its properties.
+    - In the *Properties* view, set its *Type* to `Kinematic`. This means that the collision object will follow the game object it belongs to.
+    - Right click "collisionobject" in the *Outline* view and select *Add Shape*. Add a `Sphere` shape to the collision object.
+   
+    The shape(s) you add defines the boundary as far as collisions are concerned.
+
+6. Scale the sphere in the scene view until it reasonably covers the star; press <kbd>R</kbd> to use the Scale tool. You can also move the sphere by pressing <kbd>W</kbd>.
+7. Select the *Game Object* item again and add the script *bonus_star.script* with <kbd>Game Object ▸ Add Component From File</kbd>. This script moves the bonus stars and make sure the player gets the right amount of points for catching them.
+
+![Bonus star game object](images/side-scroller/side-scroller_bonus_star.png)
+
+## Create a factory component
+
+The factory component is responsible for making sure the bonus stars appear in the game.
+
+1. Open the file *factory.go* with <kbd>Edit ▸ Open Resource...</kbd>
+2. Add another _Factory_ component to it with <kbd>Game Object ▸ Add Component</kbd>.
+    - Set the new factory component's _Prototype_ to `bonus_star.go` with the browse-button.
+    - Set its _Id_ to "bonus_factory".
+
+![Factory component](images/side-scroller/side-scroller_factory.png)
+
+## Modify the factory script
+
+Now we will make sure the factory game object starts creating the bonus stars by modifying its script.
+
+1. Open *factory.script* with <kbd>Edit ▸ Open Resource...</kbd>
+2. Near the bottom of the file, change:
+
+    ```lua
+    -- component = "#bonus_factory"
+    ```
+
+    to:
+    
+    ```lua
+    component = "#bonus_factory"
+    ```
+    
+    This makes the bonus stars appear roughly 20% of the time.
+
+3. Restart the game by closing the window (or <kbd>Esc</kbd>) to exit, then <kbd>Project ▸ Build and Launch</kbd> in the editor.
+
+    The new bonus stars will start to appear!
+
+![Run final game](images/side-scroller/side-scroller_run_final.png)
+
+**You Win!**
+
+Now go ahead and create more games in Defold!

+ 1 - 0
docs/sass/defold-md.sass

@@ -159,6 +159,7 @@ body
       color: $text-black
       font-weight: 600
       background-color: #bfbfbf
+      border: 1px solid #000
       margin-left: 1.4em
       padding: 0.2em 0.4em