Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transitioning between two scenes #302

Open
Sbelletier opened this issue Nov 19, 2017 · 4 comments
Open

Transitioning between two scenes #302

Sbelletier opened this issue Nov 19, 2017 · 4 comments

Comments

@Sbelletier
Copy link

Sbelletier commented Nov 19, 2017

I am 100% sure I must be doing something wrong, but I'm going to open this one at least for the sake of clarification.

A bit of context

I am currently developing some sort of RPG, nothing big, just a sequence of Visual Novel-like dialog Scenes and Battle Scenes. I am currently working on making dialog work.
Individual Dialog Scenes are working perfectly fine but transitioning between different scenes currently gives an horrible mess.

The code

As I wanted to implement an MVC pattern, the code goes as follow

On the model side, nothing much, merely a list of dialog-lines/fade-in/etc... scheduled and calls to the view to modify it. It works exactly as expected, so i will not develop the code in order to keep this as short as possible

On the view side, I use a personal subclass of layer.Layer that contains other subclasses of layer.Layer corresponding to :
- the background Image
- the foreground and its sprites (cocos.sprite.Sprite)
- the sound system (I directly use pyglet's + AVBin for this)
The view - at least on a single individual scene - display what i want so I'm not going to develop it either for the same reason as above

On the control side, I define a generic subclass of scene

class DialogScene(scene.Scene):
    
    def __init__(self, dialogModel, dialogView):
        """ """
        self.model = dialogModel
        self.view = dialogView
        self.control = DialogSceneControl(self.model, self.view, self)
        super(DialogScene, self).__init__(self.view, self.control)
    
    def on_enter(self):
        super(DialogScene, self).on_enter()
        self.model.load()
        self.view.load()
        self.model.bind_view(self.view)
   
    def on_exit(self):
        self.model.clean_dialog()
        self.view.clean_view()
        super(DialogScene, self).on_exit()

    def next_scene(self):
        pass

the .load() methods are used to : get the sprites, position them, get the background image, and play some sound, and generate the model's list

For each given dialog scene, I override load (for both view and model) which leads to something like this in view's case :

def load(self):
        """ On charge la partie graphique/sonore de la scène"""
        #Rose
        roseSprite = dialogView.DialogSprite("rsc1.png",x=0,y=global_sprite_y)
        self.fg.add_sprite("Rose", roseSprite, "active_left")
        #Snow
        snowSprite = dialogView.DialogSprite("rsc2.png",x=0,y=global_sprite_y)
        self.fg.add_sprite("Snow", snowSprite, "inactive_right")
        snowSprite.set_active(False)
        #Sound
        self.sound.load_BGM("smMusic.mp3")

DialogSceneControl class (a subclass of layer.Layer) is used to deal with user-generated events as such

class DialogSceneControl(layer.Layer):
    """ """
    is_event_handler = True
    
    def __init__(self, model, view, scenePointer):
        super(DialogSceneControl, self).__init__()
        self.view = view
        self.model = model
        self.scene = scenePointer
        
    def on_key_release(self, key, modifiers):
        
        if "SPACE" == winKey.symbol_string( key ):
            if self.model.is_done_reading():
                #director.director.pop()#TODO: Better Exiting Code
                self.scene.next_scene()
            else:    
                self.model.read_next_batch()

What works

As long as I use each scene without transition, I can put them all in a scenes.sequences.SequenceScene
and override next_scene in order to call director.pop(), there will be no neat transition effect, but each scene displays the way it should ('Rose' appears then talks then 'Snow' appears and talks).

So I wanted to add a smooth Fade effect

Where it fails

From what i understood from the documentation and https://github.com/liamrahav/cocos2d-python-tutorials/blob/master/basics/transitions.py , what I should be having in order to make this work is a main like this :

if __name__ == "__main__":
    s1 = S1dialogScene()
    director.init(resizable=False, width=WIDTH, height=HEIGHT)
    director.run(s1)

And S1dialogScene (subclass of the above DialogScene) should override next_scene to go to the transition. Which I tried like so :

    def next_scene(self):
        self.s2 = S2dialogScene() #I am keeping an handle on s2 here just in case
        director.replace( cocos.scenes.FadeTransition( self.s2, duration=3) )
        super(S1dialogScene, self).next_scene()#Goes to a pass for now

(S2dialogScene's next_scene is basically just a director.pop on an empty scene_stack thus normally the end of the application)

When I do this, the director does an awkward enter-exit of both scenes, here is the trace when i put print on the specific overrides of on_enter, on_exit, next_scene

on_enter S1dialogScene
next_scene S1dialogScene + scene_stack (before director.replace)
[]
on_exit S1dialogScene + scene_stack
[]
on_enter S1dialogScene
on_enter S2dialogScene
on_exit S1dialogScene + scene_stack
[]
on_exit S2dialogScene + scene_stack
[]
on_enter S2dialogScene
next_scene S2dialogScene + scene_stack (before director.pop)
[]
on_exit S2dialogScene + scene_stack
[]

And the on-screen results is even more awkward. The first scene goes without problem until it reaches its end. Then it flashes out, goes to the transition scene, isn't cleaned properly, the second scene half-begins, then isn't cleaned properly. And Finally it blinks out of the transition scene, into a silent second scene that runs twice.

The behaviour I am trying to achieve

The First Scene freezes in its current state. The Second Scene loads off-screen. The first scene fades out. The second scene fades in.

There is certainly something wrong the way i coded this but I can't seem to find it using the documentation.

@dangillet
Copy link
Contributor

Hello,

There are probably a couple of things to discuss about the MVC architecture. But let's start to look at the most obvious reason why you're having issues.

The code in on_enter and on_exit will be called everytime the Node leaves the scene for whatever reason. This means that you should only put code there which manages the state of your class when it's on stage of off-stage. Maybe you want to play the music when the node is on stage and pause the music when it's off stage.

You should not put code that is about initialising the scene, like loading assets, placing them, etc. This should belong to the __init__ method, or you could have a reset(self) method called from __init__ which can also be called when the scene needs to be reset to its initial state.

The TransitionScene works by creating a new Scene and adding the old scene as one node and the new scene as another node. For the FadeTransition, it makes the old node visible and create a black ColorLayer on top of it with full transparency. Then it launches an action to increase the transparency which gives the illusion that the scene disappears. Once the transparency is full, it removes the old node and makes the new node visible, but behind the black non-transparent ColorLayer. So it's still invisible. Then a new action decreases the transparency until the ColorLayer is fully transparent. At the end, this Scene ends and the new Scene is the active scene.

If we follow our old_scene, once we call director.replace(cocos.scenes.FadeTransition(self.s2, duration=3)) the old_scene leaves the stage and its on_exit is called. Immediately the FadeTransition scene is the top scene, but it adds the old_scene and the new_scene as children. So our old_scene on_enter method is called. The fading animation plays and at the end the FadeTransition uses a director.replace to make the new_scene the current scene. So it's leaving the stage, and all its children. So once more old_scene on_exit is called.

I must say that this has also surprised me in the beginning. I've also looked for ways to determine if my Scene was the active scene or if it was on stage just because it's in a transition. The way to check that is to check if self.parent is None. It the current scene is the top active scene, it does not have a parent. If the scene is actually being played by a TransitionScene, it will have a parent.

You might find this discussion useful.

Dan

@Sbelletier
Copy link
Author

Thanks for your feedback, I understand how it works better now.
After taking it into account I managed to make the transitions work, though I now have some code cleaning to do ^^.
I will change my approach to loading, as I would still like to have access to instances before loading assets into memory.

Can you keep the issue open ? Once things are completely clear in my mind i would like to update the site with more detailed explanation.
Moreover, i remarked that there was no differenciation between the transition that require the src arg and those that don't accept it on this page http://python.cocos2d.org/doc/api/cocos.scenes.transitions.html . I'd like to work on that as well.

@dangillet
Copy link
Contributor

dangillet commented Nov 22, 2017

Hello,

I said earlier that there were other things to mention about the MVC structure. So here it is.
Normally the model does not know anything about the controller or the view. The model uses an event mechanism to alert the outside world that its internal state has changed. But it does not know if one view or ten views are listening to its events.

The view should subscribe to the model events and update ... well its view according to the model states.
The controller normally reacts to user's inputs and use the model API to manipulate it and inform it about what's going on. For instance when the player chooses one line of text in a multiple choice, the controller gets the click, figures out which line of text it is and tells the model that the player chose that line of text.

Regarding the loading, it's a bit of a code smell to read that you want to access an instance without its assets loaded. Maybe I'm wrong. But it could be that you put too much in one class. Otherwise if this is really what you need, you should google lazy loading which is a technique for loading something expensive in memory only when required.

I'd love you to update the documentation to clarify anything that was not clear for you! Document love is always good. ❤️ I don't see a problem keeping this opened while you look into things. @ccanepa any objections?

@ccanepa
Copy link
Contributor

ccanepa commented Nov 22, 2017

no problem

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants