Awaiting Animations Triggered by Signals

Question

I have a MVC system set up like so:

  • BoardState: The state of the board at any given time. Handles all modification made to the board
  • Board: The Controller and Viewer as one script. Triggers modifications by calling functions on BoardState and observes signals like BoardState.card_added or BoardState.card_attacks in order to modify the view to accurately reflect the new board state.

The issue I have is that certain events like BoardState.card_attacked will trigger animations I’m handling in coroutines, but emit() doesn’t return the list of observer callables so await card_attacked.emit() isn’t an option. I’m unsure if I can obtain a reference to the instance of the coroutine either as the following also doesn’t work:

cards_awarded.emit(player_cards,enemy_cards)
for conn in cards_awarded.get_connections():
  await conn

I can work around this by having the Model retain a reference to the View and every time I fire a change I know will trigger an animation I await a signal from View:

cards_awarded.emit(player_cards,enemy_cards)
await board_view.finished_animation

This is fine, but I’m wondering if there’s an alternative that doesn’t cause the Model to have to care about the View’s animation state.

In an MVC architecture, your model should know nothing about the view or the controller. The controller should know about the model and view and tell each what to do. If you make your model know anything about either, you no longer have MVC and things become very tightly coupled.

Your view in Godot is your scene, and your controller is your script attached to the scene root. Typically your model in Godot is going to be a resource, but there are other options like a database. All the other scripts in a scene, like a script attached to a card to animate it, would be more technically your view code.

That aside, you just need an animation_done() signal from your view that’s monitored by your controller, which then executes based on whatever state it’s in. You want asnychronous execution, and the best way to do that is make sure your controller and view are separate scripts - because it’ll be very easy to tell where information is flowing and isn’t.

1 Like

Thanks.

Just to make sure I understand, for the Controller/View relationship, the View should be observing the Model for data changes and reacting appropriately and the Controller should be listening to Inputs from the View and making requests to the model?
So, if a card is damaged for example, the View see’s the Model change: card_damaged and marks the damage on the card, not regarding the Controller at all.

Right so something akin to this?

I think that’s better, since the Model is successfully disconnected from the View again, but I’ve been thinking more about the flexibility of animations and having the View’s animation be in lock-step with model changes might be too limiting.
For example, If a card_attack event results in a player_death I might want to animate the player’s portrait to explode at the moment the card does damage. So I’m gonna give making an animation queue a shot for battles at least where many changes happen.


This way the battle changes are calculated all at once and emitted as a set of changes which the View picks up on and plays.

However, I’ll keep the first solution in my pocket, since I might have simpler sequences of model changes that involve animations outside the battle steps :+1:

This is MVC architecture:

   View
    |
Controller
    |
  Model

Anything else isn’t MVC.

You can do whatever you want, but you’re using an architecture invented for Smalltalk in the 1970s. It was popularized for web development by Rails in the early 2000’s. (Ruby on Rails was the original language used to make Twitter.) It is now a popular architecture for web, because it fits well with a client/server architecture. The view portion runs on the client (the web browser), and the controller (business logic) and model (data) stay secure on the web server.

You can use MVC in Godot, but as soon as the View or Model have any idea that the other even exists, you are starting to tightly couple your code. Godot itself is intended to be used with OOP principles and relies heavily on composition and inheritance. It encourages encapsulation with signals.

Again, you can do whatever you want, but my question would be what led you to decide on an MVC architecture in the first place? It’s not really a good architecture for a game unless you’re doing a multiplayer game, and even then it has limitations.

In looking more closely at your diagrams, I believe you may be confusing MVC’s definition of controller, with the gamer definition of controller. All the inputs in MVC are handled by the View. The controller is the logic that takes what the user is doing, retrieves data, and tells the View and Model what to change. The controller in the game is the Game Loop,

In your first diagram, all the arrows are what the Controller does. In your second diagram, what you have marked as Controller is what the View does - and all the stuff in purple, and most of the arrows are what the Controller does.

How come you’re trying to shoehorn MVC?

Thank you for the historical context.

Justification for MVC

what led you to decide on an MVC architecture in the first place? It’s not really a good architecture for a game unless you’re doing a multiplayer game, and even then it has limitations.

I’ve made two card games prior with complex rule sets(triggered abilities, replacement effects) and it became intuitive to me that the data and the representation of the data should be entirely decoupled. I brought this up with the following digram:

To which I got the reply indicating I had effectively proposed MVC:

UI animations should belong to the UI. The underlying data/model changes instantly. The UI monitors changes in this data and then creates a tween to play a transition animation when the data it displays changes. Look at how CSS transitions work.

This lead me to examine MVC and I found that it was in line with what I wanted to achieve:

  • Decoupling game logic from the views so I can worry about animations separate from game logic
  • Having multiple views on the same logic(the board itself, a combat log, possibly multiplayer)

I think it is a good line of criticism that the Model has to make some allowances for how the View is being updated, but I’ve found that the changes I’ve made to the Model’s Observers like providing a change set when a function like calculate_battle is more just the natural result of the Model emitting ALL the changes made in a sensible fashion.

Historical/Pattern Discussion

You can use MVC in Godot, but as soon as the View or Model have any idea that the other even exists, you are starting to tightly couple your code.

I Don’t agree. Design Patterns(GoF) page 4 specifically states that the Observer pattern is fundimental to MVC:

The model communicates with its views when its values change and the views communicate with the model to access these values…This more general pattern is described by the Observer design pattern.

The View is observing the model, there is a line connecting the two. The Client-Server model muddies this relationship since HTTP usually means that calling the model also results in a synchronous update, e.g. I call POST /people and receive back the person object that was written to the db, but if your web page uses socket communication, it could just as easily observe the model by watching a provided event stream without even touching the controller. Further, calling GET /people?name='John' is most assuredly has nothing to do with how the View responds to user input.

Controller’s role

In your first diagram, all the arrows are what the Controller does. In your second diagram, what you have marked as Controller is what the View does - and all the stuff in purple, and most of the arrows are what the Controller does.

For the second diagram, I think the View should be responsible for disabling itself, but I don’t see how the rest of the relationships changing except maybe the View calling the Controller since I should be able to swap the Controller at will.

I hope this explains some of my thinking.

I don’t have my copy of Design Patterns handy but it was written in '94. Keep in mind a few things.

  1. Design Patterns was written with 1990s C++ in mind.
  2. Multithreading wasn’t really a thing at all until 1995 and the Pentium Pro which had dual-core and quad-core processors.
  3. Multithreading was not introduced to C++ as a basic library until 2011.
  4. Asynchronous programming came to popularity in 2005 with the popularity of AJAX.
  5. MVC was popularized and refined with the introduction (and subsequent popularity) of Ruby on Rails in 1994.

That book was written 31 years ago (by some very brilliant people). I love that book. I don’t agree that it is the authority on MVC anymore. For your game, I don’t think an MVC architecture is really what you’re going for. It’s close, but not quite. But again, I’m basing this on my personal experience in Rails and web development. I have seen so many people combine code from the View with the Model and end up with bugs they have trouble tracking down, because it should be in the controller, but that part is over here you see…

Godot is an asynchronous language. It allows easily for things that would require a lot more coding in 1994 C++. For example signals. I think you should use copious amounts of signals, which yes, are the Observer Pattern. All I’m saying is that in my experience, when the view needs info from the model and bypasses the controller, you end up with data mismatch issues when the controller doesn’t know something it should.

TBH I think you’d be better served with a simpler Clent/Server architecture, where the UI is the Client, and the Model and Controller are the same thing, and just the Server. It’s my suspicion (without having seen it) that this architecture would simplify your code.

But if you have a different opinion, and believe that MVC architecture is the way to go, then go for it. There are many flavors of MVC (and MCV, etc.) and so if that’s what you’re using, ok.