Richard Ranft EventManager
by Jesse Allen · in Torque 2D Beginner · 11/18/2013 (10:41 am) · 9 replies
Greetings! I've been working with loading up my new level by the push of a button on the main menu screen. Fortunately, it is loading and the main menu screen is going away as planned. I have created a function called loadLevel() that loads up the level module. When the button on the main menu is clicked, it calls this function.
Where I need some clarification is how exactly an EventManager object works. I created an EventManager called LoadEventManager from Richard Ranft's template he linked from another thread (InventoryEventManager). I have this new object initializing within my MainMenu.cs just prior to the actual load level button being loaded up. I can perform the level load with no parse errors so I feel like I'm on the right track; I'd just like to be sure that my EventManager is actually performing the scheduling. At the bottom of the LoadEventManager.cs I have this:
Is this the correct way to use this, so that if I call the function loadLevel() it will actually register the event? Hopefully Richard will come along and answer, as I'd rather not post out the entire block of code from his repository (not sure if that's offensive or okay).
I'd also like to point out that the reason why I'm doing this is to be sure that the level load is scheduled. The level load function contains the code to remove the Main Menu scene so I didn't want there to be any sort of problem switching to the new scene while clicking a button within the old one. If there is a better way, or this is unnecessary (a schedule) I'm all ears!
Where I need some clarification is how exactly an EventManager object works. I created an EventManager called LoadEventManager from Richard Ranft's template he linked from another thread (InventoryEventManager). I have this new object initializing within my MainMenu.cs just prior to the actual load level button being loaded up. I can perform the level load with no parse errors so I feel like I'm on the right track; I'd just like to be sure that my EventManager is actually performing the scheduling. At the bottom of the LoadEventManager.cs I have this:
function LoadEventListener::onLoadLevelRequest(%this)
{
mainScene.schedule(100, "loadLevel()");
}Is this the correct way to use this, so that if I call the function loadLevel() it will actually register the event? Hopefully Richard will come along and answer, as I'd rather not post out the entire block of code from his repository (not sure if that's offensive or okay).
I'd also like to point out that the reason why I'm doing this is to be sure that the level load is scheduled. The level load function contains the code to remove the Main Menu scene so I didn't want there to be any sort of problem switching to the new scene while clicking a button within the old one. If there is a better way, or this is unnecessary (a schedule) I'm all ears!
About the author
Skilled Artist and Musician. Intermediate Torque Developer.
#2
Thanks Richard. Sorry to call you out by name on the board haha, but since you conjured up this beast I thought you best to lay it down.
This does help me to understand somewhat how the LoadEventManager is actually working. I'm a bit confused still about the postEvent function, since there was no such function in the InventoryEventManager you originally created. Where's this coming from, or if it's something I need to create how to do so? Here's exactly what I've got so far:
The button:
The LoadEventManager:
Also, in the MenuDialog.gui.taml I have in the <GuiButtonCtrl:
The loadLevel() function does this:
Setting up the button and LoadEventManager as above obviously gives me the following error in my console.log since I haven't created a postEvent function:
modules/Main/1/scripts/menu.cs (35): Unknown command postEvent.
Object LoadListener(1128) LoadListener -> LoadEventListener -> ScriptMsgListener -> SimObject
This still loads up the level, and I wonder if it is just because the button is doing it from the command field in the .gui.taml directly. Bear in mind all I'm doing here is clicking a button from the Main Menu to load up my test 'world'. I'm not choosing the level from a list. Although I would like this to work both ways (i.e. after loading the level, being able to hit a button in options to return to Main Menu scene). Since my Main Menu is actually a scene, I didn't want the game to crash if I was closing the 'Menu' scene and opening the 'Level' scene. Anyways, thanks a lot Richard, many of your posts have helped me a great deal and I value and respect your approach to solving problems. Cheers!
11/18/2013 (9:21 pm)
Here is the repository in question for anyone following along: github.com/RichardRanft/Torque2D/tree/inventory/modules/Inventory/1/scriptsThanks Richard. Sorry to call you out by name on the board haha, but since you conjured up this beast I thought you best to lay it down.
This does help me to understand somewhat how the LoadEventManager is actually working. I'm a bit confused still about the postEvent function, since there was no such function in the InventoryEventManager you originally created. Where's this coming from, or if it's something I need to create how to do so? Here's exactly what I've got so far:
The button:
function StartGameButton::onMouseUp(%this)
{
LoadListener.postEvent("_LoadLevelRequest", %this.data);
}The LoadEventManager:
function initializeLoadEventManager()
{
if (!isObject(LoadEventManager))
{
$LoadEventManager = new EventManager(LoadEventManager)
{
queue = "LoadEventManager";
};
// Module related signals
LoadEventManager.registerEvent("_LoadLevelRequest");
}
if (!isObject(LoadListener))
{
$LoadListener = new ScriptMsgListener(LoadListener)
{
class = "LoadEventListener";
};
// Module related subscriptions
LoadEventManager.subscribe(LoadListener, "_LoadLevelRequest", "onLoadLevelRequest");
}
}
// Cleanup the LoadEventManager
function destroyLoadEventManager()
{
if (isObject(LoadEventManager) && isObject(LoadListener))
{
// Remove all the subscriptions5 LoadEventManager.remove(LoadListener, "_AnimUpdateRequest");
LoadEventManager.remove(LoadListener, "_LoadLevelRequest");
// Delete the actual objects
LoadEventManager.delete();
LoadListener.delete();
// Clear the global variables, just in case
$LoadEventManager = "";
$LoadListener = "";
}
}
function LoadEventListener::onLoadLevelRequest(%this, %data)
{
mainScene.schedule(100, "loadLevel", %data);
}Also, in the MenuDialog.gui.taml I have in the <GuiButtonCtrl:
command="loadLevel();"
The loadLevel() function does this:
function loadLevel( %moduleDefinition )
{
// Sanity!
if ( !isObject( %moduleDefinition ) )
{
//error( "Cannot load level as the specified level is not available." );
unloadLevel( %moduleDefinition );
}
// Unload the active level.
unloadLevel();
mainSceneWindow.remove(MenuDialog);
// Now is a good time to purge any idle assets.
AssetDatabase.purgeAssets();
// Set active level.
// This must be done here in-case a level depends on it during its initialization.
Main.ActiveLevel = %moduleDefinition;
// Load the level.
ModuleDatabase.loadGroup("worldGroup");
}Setting up the button and LoadEventManager as above obviously gives me the following error in my console.log since I haven't created a postEvent function:
modules/Main/1/scripts/menu.cs (35): Unknown command postEvent.
Object LoadListener(1128) LoadListener -> LoadEventListener -> ScriptMsgListener -> SimObject
This still loads up the level, and I wonder if it is just because the button is doing it from the command field in the .gui.taml directly. Bear in mind all I'm doing here is clicking a button from the Main Menu to load up my test 'world'. I'm not choosing the level from a list. Although I would like this to work both ways (i.e. after loading the level, being able to hit a button in options to return to Main Menu scene). Since my Main Menu is actually a scene, I didn't want the game to crash if I was closing the 'Menu' scene and opening the 'Level' scene. Anyways, thanks a lot Richard, many of your posts have helped me a great deal and I value and respect your approach to solving problems. Cheers!
#3
Look way down at line 595 in InventoryGridContainer.cs - if the item is dropped outside of a container that can accept it a deletion request is fired. This is to avoid trying to delete an object potentially during its own callback.
Just a side note - any SimObject can subscribe to events, so I used them in that AI Tutorial by having the AIPlayers subscribe to events that would let them "call" for help and tell other bots where enemies were.
11/18/2013 (10:19 pm)
Yup - the button is directly handling the call.Look way down at line 595 in InventoryGridContainer.cs - if the item is dropped outside of a container that can accept it a deletion request is fired. This is to avoid trying to delete an object potentially during its own callback.
Just a side note - any SimObject can subscribe to events, so I used them in that AI Tutorial by having the AIPlayers subscribe to events that would let them "call" for help and tell other bots where enemies were.
#4
Do I need to use a scheduler to do this if the button was handling the call directly just fine? The button was able to launch the level, although I haven't been able to get a button to work to return to the Main Menu scene.
11/19/2013 (5:25 am)
Hmm, so far the button is still handling the call. I see your example in InventoryGridContainer.cs using postEvent, although I still have no idea where the actual function 'postEvent' is coming from. If I remove my command from the button so that it doesn't load directly the button just ceases to work. Do I need to use a scheduler to do this if the button was handling the call directly just fine? The button was able to launch the level, although I haven't been able to get a button to work to return to the Main Menu scene.
#5
11/19/2013 (6:07 am)
I finally noticed that I had to change this:function StartGameButton::onMouseUp(%this)
{
LoadListener.postEvent("_LoadLevelRequest", %this.data);
}to this:function StartGameButton::onMouseUp(%this)
{
LoadEventManager.postEvent("_LoadLevelRequest", %this.data);
}So now the actual manager is being called. This removes any console.log errors so now postEvent is being found. This makes me assume that postEvent is an internal function that is already functional with any EventManager. I just went back to the button giving the command though, since that is working and this scheduling business isn't. I still feel like it'll come back to bite me some day, since I can't change scenes once the actual level is loaded (i.e. change back to Main Menu scene).
#6
How is the level selected? Is it from a list or something similar? For instance, do you have a container with buttons for each level? If so, then in that control's selected event handler you'll need to tell the StartGameButton which level you've selected. Theoretically you could use the same event manager/listener set to post an event when a level was selected and have your StartLevelButton subscribe to the "_LoadLevelSelected" event so it could capture the name of the selected level....
11/19/2013 (6:40 am)
Yes, postEvent() is a method of the EventManager class.How is the level selected? Is it from a list or something similar? For instance, do you have a container with buttons for each level? If so, then in that control's selected event handler you'll need to tell the StartGameButton which level you've selected. Theoretically you could use the same event manager/listener set to post an event when a level was selected and have your StartLevelButton subscribe to the "_LoadLevelSelected" event so it could capture the name of the selected level....
#7
Right now all I have are 4 modules: AppCore, Main, World, and Assets. When AppCore loads up the Main module, it just pops up a background image with one single button in the center of the screen. That's my StartGameButton. When I click that, it loads up the World module using a levels.cs script that is basically a modified toys.cs. Within the levels.cs is loadLevel();(posted above, reply#2) which loads the gameGroup "worldGroup". That loads up the World module.
The Main module creates mainSceneWindow and mainScene. When the World module is loaded up, it removes mainScene and creates worldScene but is still using the mainSceneWindow. As far as I know, this is okay as you don't want to remove the window entirely. I am a little bit confused as to the proper way to approach changing scenes, and I just pieced this all together by reverse engineering the Sandbox module. It's worth noting that I'm not running this in the Sandbox at all, it's a standalone folder in which I'm building all this up from scratch. Ultimately, I don't plan on having to be able to change 'levels' as the game world is planned to be one big scene.
I'm trying for a 2D game like Terraria, although figuring out the tile placement has really got me confused. I've looked at compositeSprites as an option, although I'm not sure they can handle all the collision on a per tile basis within the compositeSprite.
edit: Just wanted to add Mike finally got me straight on how to use collision with the compositeSprite!
11/19/2013 (9:14 am)
Thanks for your reply Richard. Before going farther let me point out that I am an absolute beginner with T2D. Much of this topic is probably way over my head about scheduling, and the only reason I was trying to use it to begin with is because I was convinced if I didn't I would crash my game changing scenes. I came to this conclusion by scouring past posts for information about changing scenes, and other people were saying the game could crash if you didn't use a .schedule when changing scenes. Right now all I have are 4 modules: AppCore, Main, World, and Assets. When AppCore loads up the Main module, it just pops up a background image with one single button in the center of the screen. That's my StartGameButton. When I click that, it loads up the World module using a levels.cs script that is basically a modified toys.cs. Within the levels.cs is loadLevel();(posted above, reply#2) which loads the gameGroup "worldGroup". That loads up the World module.
The Main module creates mainSceneWindow and mainScene. When the World module is loaded up, it removes mainScene and creates worldScene but is still using the mainSceneWindow. As far as I know, this is okay as you don't want to remove the window entirely. I am a little bit confused as to the proper way to approach changing scenes, and I just pieced this all together by reverse engineering the Sandbox module. It's worth noting that I'm not running this in the Sandbox at all, it's a standalone folder in which I'm building all this up from scratch. Ultimately, I don't plan on having to be able to change 'levels' as the game world is planned to be one big scene.
I'm trying for a 2D game like Terraria, although figuring out the tile placement has really got me confused. I've looked at compositeSprites as an option, although I'm not sure they can handle all the collision on a per tile basis within the compositeSprite.
edit: Just wanted to add Mike finally got me straight on how to use collision with the compositeSprite!
#8
11/19/2013 (3:39 pm)
Yeah, the problem you could run into is that you can't delete an object during its own callback. So the general method is pause the scene to suspend object updates, then schedule the deletion of the scene contents to give some time for everything to wind down, and then load the contents of the next scene into your sceneWindow. If your startup scene is very simple then it is unlikely you're going to run into an issue here.
#9
11/19/2013 (10:21 pm)
Good little tip about general practice shutting down a scene. I appreciate that Richard. Who knows? When/if I do actually get my 'CompositeSprite dig/build game' terrain started, there could be use for some level loading feature. I know for sure I'll need to be able to get the scene to shut down safely to return to the main menu. I've already got an options menu that will popup over the world scene if I push Esc, but I can't get the return to main menu to fire. Baby steps for sure :)
Torque Owner Richard Ranft
Roostertail Games
function myButton::onMouseUp(%this) { // First, somehow selecting the level from a list sets the data field on // the button to hold the level name. (myButton.data = "levelName") // Then this tells the event manager you want something.... LoadEventListener.postEvent("_LoadLevelRequest", %this.data); }Then we do some stuff like this:
function LoadEventListener::onLoadLevelRequest(%this, %data) { // two ways I might do this: %data.schedule(100, "loadLevel"); // calls the schedule on a Scene passed as %data mainScene.schedule(100, "loadLevel", %data); // schedules mainScene.loadLevel(%data) - assuming %data contains a level name }So you can pass any Scene in the postEvent() call as the data and if it has the loadLevel() callback it will be called.
Keep in mind that the posted event will propagate to all subscribed listeners - the overhead doesn't seem too crazy, I used it in my T3D AI Tutorial with dozens of units messaging each other, but it's something to be aware of.
Also, make sure to register and subscribe to the events correctly.
And remember in calls to schedule() that you pass the function name without decorators, then a comma separated list of parameters to that function.
Hope it helps.
PS - post a link to the repo, it's there for people to learn from!