Monarch doesn't trigger sequenced transitions correctly

Hello fellow Defolders!

I’m new to Defold, and Lua in general. I came here to port a nice and complete game I’ve made using libGDX and I really like Defold so far, it’s simple, extensive, and has great compatibility and performance.

Now, I’m using many extensions to help me reduce the stress, including: Monarch, Druid, Defold-Saver, and Defold-Lang.

So far, I can say that the only thing that’s missing is really just beginner-friendly documentation. Not just for Monarch, but for most extensions.

So, my current issue is that I’m stuck with Monarch, I’m trying to re-create an effect of a fading-in initial-screen that then automatically switches (fades-out) to the next screen (e.g. the “menu”).

However, it seems like I’m handling it wrongly. The main framework I have is really simple, we have the main “controller” that includes the collection-proxies for the screen and the built-in monarch script for each parent of the proxies (e.g. /monarch/screen_proxy.script), and of course a simple script that handles the switching of the screens.

This is is the simple snippet that controls everything:

function on_message(self, message_id, message, sender)
    if message_id == hash("switch") then
        monarch.show(message.scene)
    end
end

And my main “init” function looks like this:

function init(self)
    lang.init()
    druid.set_text_function(lang.txp)
    monarch.debug()
    msg.post(".", "acquire_input_focus")
    msg.post("#", "switch", { scene = "initial" })
end

And so far everything’s great… the “initial” screen initializes and shows correctly:

function init(self)
    self.transition = transitions.create()
    self.transition.show_in(gui.get_node("root"), transitions.fade_in, gui.EASING_LINEAR, 0.7, 0)
    self.transition.show_in(gui.get_node("logo"), transitions.slide_in_left, gui.EASING_LINEAR, 0.3, 0)
    self.transition.show_out(gui.get_node("logo"), transitions.slide_out_right, gui.EASING_LINEAR, 0.3, 0)
    self.transition.show_out(gui.get_node("root"), transitions.fade_out, gui.EASING_LINEAR, 0.7, 0)
    monarch.on_transition("initial", self.transition)
    monarch.add_listener()
end

function final()
    monarch.remove_listener()
end

function on_message(self, message_id, message, sender)
    monarch.on_message(message_id, message, sender)
    if message_id == monarch.SCREEN_TRANSITION_IN_FINISHED then
        msg.post("main:/scene", "switch", { scene = "menu" })
    end
end

So, where’s the issue you might ask? Now, the issue comes whenever the “initial” screen wants to fade-out and the “menu” screen wants to fade-in… Then the transition just bugs-out.

And in case you’re wondering, here’s how I initialize my menu screen:

function init(self)
    self.transition = transitions.create(gui.get_node("root"))
    self.transition.show_in(transitions.fade_in, gui.EASING_LINEAR, 0.5, 0)
    self.transition.back_in(transitions.fade_in, gui.EASING_LINEAR, 0.5, 0)
    self.transition.show_out(transitions.fade_out, gui.EASING_LINEAR, 0.5, 0)
    self.transition.back_out(transitions.fade_out, gui.EASING_LINEAR, 0.5, 0)
    monarch.on_transition("menu", self.transition)
    monarch.add_listener()
end

... SOME OTHER CODE HERE ...

function final(self)
    monarch.remove_listener()
end

function on_message(self, message_id, message, sender)
    monarch.on_message(message_id, message, sender)
end

So, hopefully the professionals among the community can already see the issue (Though it might not be a code-issue but rather a structural one)… If not, maybe a minimal, reproductible example would help?

You can download it here:
Defold-Monarch-Issue.zip (1.9 MB)

I really hope that we may find a solution together, thanks!

This piece of code gets triggered twice. Once after scene_a transition has finished and then again after scene_b transition has finished.

	if message_id == monarch.SCREEN_TRANSITION_IN_FINISHED then
		msg.post("main:/controller", "switch", { scene = "scene_b" })
	end

This happens because the scene_a is still in the screen stack after switching to scene_b. There are multiple ways to fix this, depending on your needs. One way is to replace the scene_a with scene_b and another way is to add a condition to the on_message() in scene_a like so:

	if message_id == monarch.SCREEN_TRANSITION_IN_FINISHED and sender.path == hash("/scene_a") then
		msg.post("main:/controller", "switch", { scene = "scene_b" })
	end

Btw. you can see your monarch screen stack at any time with pprint(monarch.get_stack()).

Now I’ll tell you how I quickly found out the issue; I call it the print farming as it often gives good yields. :grin:

I put print("SCENE_A") and print("SCENE_B") in each of the scenes init() functions and when running build this is what happens:

It becomes clear that the scene_b init() function is triggering twice.

I wonder if maybe there is something I could do to improve this within Monarch itself? If you are able to share a minimal repro case in an issue on GitHub then I’ll promise to take a look!

Ok, so I managed to somewhat fix the issue you mentioned using two solutions, but the effect is still wrong…

As you mentioned, there has to be a condition to ensure that we’re tracking the right message.

So, similarly to your solution, I did this:

function on_message(self, message_id, message, sender)
	monarch.on_message(message_id, message, sender)
	if message_id == monarch.SCREEN_TRANSITION_IN_FINISHED and hash("scene_a") == message.screen then
		msg.post("main:/controller", "switch", { scene = "scene_b" })
	end
end

And fair enough, after some testing, the init() of “scene_b” is no longer triggered twice.

However, the issue still persists…

I’ve also tried various other solutions, such as using timer.delay, following the documentation (which didn’t work, maybe because it’s outdated?), copying code from other repositories, etc.

The open-sourced games that use Monarch seem to transition to the next screen only when an event occurs, so the player has to manually press a button or so. That’s not what I need, I need it to be automatic (so the screen transitions are seamlessly played in a sequence), like so:

scene_a show in → scene_a show out → scene_b show in → …

I pushed it to GitHub in case you want to see the code online:

So, thank god, I found a workaround!

When the effect happens immediately (delay = 0) then it doesn’t actually bug out, it just plays it immediately and doesn’t wait till the first effect is done (intended behavior I guess).

Adding a simple and precise delay made the issue fade away :ok_hand:!

I added the delay to the show_intransition of scene_b and it works flawlessly:

self.transition.show_in(transitions.fade_in, gui.EASING_LINEAR, 0.5, 0.5)

(The last parameter is the delay in seconds).

Nevertheless, great thanks to the support of this community!

1 Like

Maybe you could add an option to “make the animation wait before playing”? That way one can create predictably/sequenced animations without them mixing up…

I can take a look. Please create an issue on GitHub!