Hey all,
I have a general question on how to best organize/structure/handle the code for mouse actions, specifically how to handle what happens when you left or right click. In my game what a left or right mouse click should do can change drastically depending on what options the player selected. As an example, depending on what action is currently active a left mouse click could either build a room, delete a room, place a new AI agent, lock a room, or open new options if a specific object is clicked. Right clicking can either open a menu or cancel the previously mentioned actions.
My current code uses a bunch of switch and if statements, which is turning into a headache… and I haven’t even added the majority of options. The checks to make sure the current action is valid is enough to make me want to pull my hair out haha. I’m sure there has to be a better way to handle what happens when you click that makes it easier to decide what action is currently valid.
Thanks for your time!
The software design patterns Model-View-Presenter and Model-View-Controller were created to help organize such code. We use MVP to split up our logic.
Model: The only thing that interacts with the game state. Anything dealing with game stuff goes here.
View: The layout of visible elements and stuff the player can interact with. Used to focus on positioning, the look of buttons, etc.
Presenter: Deals with how to present data from the Model and read input from the View.
I had to set up quite a few menu systems before I got the hang of it, but once I learned MVP it’s become second nature now.
In your example:
The View announces an event when it detects a left-click. The Presenter hears this event and chooses which command on the Model to run to update the game state.
Thanks for the recommendation! I did some more digging around and think I understand the general idea behind the design pattern, but the details of how exactly it gets applied to game interaction is something I’m not 100% clear on.
We can start with a simple example, clicking on an agent in a scene and displaying stats about this. You have one script that acts as the Presenter/Controller that first needs to detect if there was a left click. If so, it finds out what agent was clicked on and retrieves the information that needs to be displayed from that Model. This information is stored with the Model/agent, and only the Presenter/Controller accesses that information directly. It then sends the necessary information–position, name, health, etc–to the View. View never interacts with the Model directly. View then uses the position to decide where to display the info box (perhaps it needs to follow the agent), how big the health bar should be (based off of max health and current health that the Presenter/Controller retrieved from the Model), and what type of icon to display based off of the name.
I think I got that down, but please correct me if there was anything I didn’t get quite right.
There are a few questions that have come up now. In practical terms, what would the “Models” be for actions that can be triggered by a left mouse click? I guess they could just be functions, but I’d really like to stay away from having a single massive script.
Yeah that sounds about right! The devils are in the details, and it might take a couple of times before you figure out how you like to set things up.
I’ll try to write a simple Presenter. I haven’t tested any of this! Hopefully it gives a good idea of how MVP works. (I personally haven’t done MVC much so I’ll stay away from that.)
One additional thing I want to say is that you can have nested MVPs. For example, I have a master MVP that can load child MVPs for each type of GUI window in our game. If I need to break up logic more, I might give each separate part of a GUI window it’s own MVP to control it.
On the game I’m working on now, I have a master GUI MVP that can load a Character Menu MVP. This Character Menu has children MVP can then control the display of the player’s stats. another MVP showing equipment, another MVP displaying the character with all their gear on, etc…
using UnityEngine;
public class RoomBuilderPresenter : MonoBehaviour {
public RoomBuilderView m_view;
public RoomBuilderModel m_model;
void Start() { Bind(); }
/// Listen to events from the View and Model
private void Bind() {
m_view.RoomClickedEvent += HandleRoomClicked;
}
private void HandleRoomClicked(Room clickedRoom, int mouseButtonPressed, Vector3 clickLocation) {
switch (mouseButtonPressed) {
case 0: // left click
SelectRoom(clickedRoom);
break;
case 1: // right click
ShowMenu(clickedRoom, clickLocation);
break;
case 2: // left click
DeleteRoom(clickedRoom);
break;
}
}
/// Display selectedRoom as selected. Tell Model which room has been selected.
private void SelectRoom(Room selectedRoom) {
selectedRoom.ShowSelectedHighlight(true);
m_model.selectedRoom = selectedRoom; // This assumes something else in your game needs to know about the selectedRoom.
}
/// Remove room from scene. Tell Model room should be marked as deleted.
private void DeleteRoom(Room roomToDelete) {
Destroy(roomToDelete.gameObject);
m_model.Delete(roomToDelete); // Update game state.
}
/// Show a menu about Rooms at location.
private void ShowMenu(Room room, Vector3 location) {
// Depending on how complicated this menu is, it might warrent its own MVP.
m_currentlyShownMenu = Instantiate(m_view.menuPrefab, location, Quaternion.identity) as Menu;
m_currentlyShownMenu.DisplayMenu(room);
m_currentlyShownMenu.MenuOptionSelected += HandleMenuOptionSelected;
}
private void HandleMenuOptionSelected(Room room, string selectedOption) {
// take action based on what menu option was selected.
}
private Menu m_currentlyShownMenu;
}
EDIT: I tried to keep this fairly simple, but I think it takes doing a few to make it second nature. All the big development studios on AAA titles have GUI programmers that do nothing but program GUIs.
Awesome, thank you! While I’ll definitely have to play around with MVP a little more, using events/delegates has gotten me another step closer (just now learned how they’re used hah).