Tutorial Step 8. Level won't reset

Hi there.

I’ve searched the forum for an answer to this little problem, but haven’t found it before, so I’m guessing it’s just me so far.

I completed Step 7 and the game runs perfectly at that point. it’s after applying the changes in Step 8 that I run into difficulty. Step 7 deals with the death of the hero and respawns him on the screen and Step 8 is supposed to reset the level to make it safe for the hero to respawn.

The tutorial instructs me to make a few changes to the code in the Controller, Platform and Hero scripts. But it isn’t very clear about where exactly to apply those changes. I think I put them in the right places but it’s more likely that I haven’t. The result of my work is that the hero no longer respawns and the level doesn’t reset (the platforms keep coming).

I’ll post my code below for scrutiny

controller.script

go.property("speed", 6)

local grid = 460
local platform_heights = { 100, 200, 350 }

function init(self)
    msg.post("ground/controller#script", "set_speed", { speed = self.speed })
    self.gridw = 0
    self.spawns = {} 
end

function update(self, dt)
    self.gridw = self.gridw + self.speed

    if self.gridw >= grid then
        self.gridw = 0

        -- Maybe spawn a platform at random height
        if math.random() > 0.2 then
            local h = platform_heights[math.random(#platform_heights)]
            local f = "#platform_factory"
            if math.random() > 0.5 then
                f = "#platform_long_factory"
            end

            local p = factory.create(f, vmath.vector3(1600, h, 0), nil, {}, 0.6)
            msg.post(p, "set_speed", { speed = self.speed })
            table.insert(self.spawns, p) 
        end
    end
end

function on_message(self, message_id, message, sender)
    if message_id == hash("reset") then 
        -- Tell the hero to reset.
        msg.post("hero#script", "reset")
        -- Delete all platforms
        for i,p in ipairs(self.spawns) do
            go.delete(p)
        end
        self.spawns = {}
    elseif message_id == hash("delete_spawn") then 
        for i,p in ipairs(self.spawns) do
            if p == message.id then
                table.remove(self.spawns, i)
                go.delete(p)
            end
        end
    end
end

platform.script

function init(self)
    self.speed = 9      -- Default speed
end

function update(self, dt)
    local pos = go.get_position()
    if pos.x < -500 then
           msg.post("/level/controller#script", "delete_spawn", { id = go.get_id() })
    end
    pos.x = pos.x - self.speed
    go.set_position(pos)
end

function on_message(self, message_id, message, sender)
    if message_id == hash("set_speed") then
        self.speed = message.speed
    end
end

hero.script

local function play_animation(self, anim)
    -- only play animations which are not already playing
    if self.anim ~= anim then
        -- tell the spine model to play the animation
        spine.play("#spinemodel", anim, go.PLAYBACK_LOOP_FORWARD, 0.15)
        -- remember which animation is playing
        self.anim = anim
    end
end

local function update_animation(self)
    -- make sure the right animation is playing
    if self.ground_contact then
        play_animation(self, hash("run_right"))
    else
        if self.velocity.y > 0 then
            play_animation(self, hash("jump_right"))
        else
            play_animation(self, hash("fall_right"))
        end
    end
end

-- gravity pulling the player down in pixel units/sˆ2
local gravity = -20

-- take-off speed when jumping in pixel units/s
local jump_takeoff_speed = 900

function init(self)
    -- this tells the engine to send input to on_input() in this script
    msg.post(".", "acquire_input_focus")

    -- save the starting position
    self.position = go.get_position()
	
	msg.post(".", "reset")
end

function final(self)
    -- Return input focus when the object is deleted
    msg.post(".", "release_input_focus")
end

function update(self, dt)
    local gravity = vmath.vector3(0, gravity, 0)

    if not self.ground_contact then
        -- Apply gravity if there's no ground contact
        self.velocity = self.velocity + gravity
    end

    -- apply velocity to the player character
    go.set_position(go.get_position() + self.velocity * dt)
    
    update_animation(self)

    -- reset volatile state
    self.correction = vmath.vector3()
    self.ground_contact = false
    
end

local function handle_geometry_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)
    if message_id == hash("contact_point_response") then
        -- check if we received a contact point message. One message for each contact point
        if message.group == hash("geometry") then
            handle_geometry_contact(self, message.normal, message.distance)
        end
    end
end

local function jump(self)
    -- only allow jump from ground
    if self.ground_contact then
        -- set take-off speed
        self.velocity.y = jump_takeoff_speed
    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 == hash("jump") or action_id == hash("touch") then
        if action.pressed then
            jump(self)
        elseif action.released then
            abort_jump(self)
        end
    end
end



function on_message(self, message_id, message, sender)
    if message_id == hash("reset") then
        self.velocity = vmath.vector3(0, 0, 0)
        self.correction = vmath.vector3()
        self.ground_contact = false
        self.anim = nil
        go.set(".", "euler.z", 0)
        go.set_position(self.position)
        msg.post("#collisionobject", "enable")

    elseif message_id == hash("contact_point_response") then
        -- check if we received a contact point message
        if message.group == hash("danger") then
            -- Die and restart
            play_animation(self, hash("die_right"))
            msg.post("#collisionobject", "disable")
            go.animate(".", "euler.z", go.PLAYBACK_ONCE_FORWARD, 160, go.EASING_LINEAR, 0.7) 
            go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
                function()
                    msg.post("controller#script", "reset")
                end)
        elseif message.group == hash("geometry") then
            handle_geometry_contact(self, message.normal, message.distance)
        end
    end
end

Well, I managed to solve it myself. It’s not especially tidy but it works at least

The original problem was a line in the hero.script file:

go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
                function()
                    msg.post("controller#script", "reset")
                end)

The debug console was telling me that it couldn’t find “hero/controller#script” so I solved that by changing it to this :

go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
                function()
                    msg.post("level/controller#script", "reset")
                end)

So after I made this change the function was successfully calling the other function in controller.script. But I was then faced with another, similar problem.

Now the platforms were successfully clearing allowing a safe space for the hero to respawn… but he wasn’t respawning. The debug console was now telling me that the code in controller.script couldn’t find “level/hero.script”. here’s the offending code:

function on_message(self, message_id, message, sender)
    if message_id == hash("reset") then 
        -- Tell the hero to reset.
       msg.post("hero#script", "reset")
       -- Delete all platforms
     ...

so after failing to respawn the hero the function continues with the rest of its instructions and deletes all the platforms. I tried the same solution, I replaced “hero#script” with “hero/hero#script” but to no avail. the debug console told me it still couldn’t find it even though it was definitely there. Eventually after a lot of head scratching I decided to comment that particular line out of controller.script and placed it into hero.script like this :

go.animate(".", "position.y", go.PLAYBACK_ONCE_FORWARD, go.get_position().y - 200, go.EASING_INSINE, 0.5, 0.2,
                function()
                     msg.post("level/controller#script", "reset")
                     msg.post("#", "reset")
                end)

That did the trick.

Like I said, it’s a bit messy but it works.

This also raises the question if I was missing something altogether and doing something wrong, or is this a bug in defold? The debug console told me that it couldn’t find hero/hero#script when it was definitely there.

1 Like

Hi there.

Is it possible that you created you’re Hero Game Object in the main.collection? Had the same Problem because of this. What solved the problem for me with the original sourcecode was to add the Hero Game Object in the level.collection instead of the main.collection.

But thanks a lot for your solution cause this helped me in the first place after finding out the real issue.

There is a lot of confusion around the addressing in our messaging system, partly because it’s not intuitive to everyone and also because it’s indeed different from many other engines/systems. We are of course looking into ways to simplify learning how it works. There are a lot of good reasons to why it works the way it does, and we hope that it won’t be a struggle once you have understood how it works and why. A very quick way to understand right now is to print out addresses from scripts that you have problems with, using one of the two shortcuts:

  • “#” means “this component”, print(msg.url("#"))
  • “.” means “this game object instance”, print(msg.url("."))

When you do this from a script, “#” will give you the global address (url) of the script instance(s), and “.” will give you the similar for the game object instance(s) that script is attached to.

We have no known bugs in the addressing system, so please report everything that seems inconsistent.