Game Development Community

Problem Handling HUD mouse events

by N.K. · in Torque Game Builder · 01/22/2010 (4:42 pm) · 16 replies


I'm creating a Hud for my game in a separate sceneWindow. I have created a .t2d file in the editor with all the Hud elements placed out as static sprites and I load this level into my new hud scenewindow. The problem I'm running into is that once I load the hud window on top of the main game window (sceneWindow2D), the game window stops receiving mouse events. When I set the hud-window to non-modal I stop getting mouse events on the hud-window.

Here's what I'm doing:

%guiContent = new GuiControl(mainScreenGui) {
   canSaveDynamicFields = "0";
   Profile = "GuiBlackContentProfile";
   HorizSizing = "width";
   VertSizing = "height";
   Position = "0 0";
   Extent = "1024 768";
   MinExtent = "8 8";
   canSave = "1";
   Visible = "1";
   useVariable = "0";
	
   new t2dSceneWindow(sceneWindow2D) {
      canSaveDynamicFields = "0";
      Profile = "GuiContentProfile";
      //Profile = "NonModalContentProfile";
      HorizSizing = "width";
      VertSizing = "height";
      Position = "0 0";
      Extent = "1024 768";
      MinExtent = "8 8";
      canSave = "1";
      Visible = "1";
      lockMouse = "0";
      useWindowMouseEvents = "1";
      useObjectMouseEvents = "1";
   };
   	
   new t2dSceneWindow(hudWindow) {
      canSaveDynamicFields = "0";
      Profile = "NonModalContentProfile";
      HorizSizing = "width";
      VertSizing = "height";
      Position = "0 0";
      Extent = "1024 768";
      MinExtent = "8 8";
      canSave = "1";
      Visible = "1";
      lockMouse = "0";
      useWindowMouseEvents = "1";
      useObjectMouseEvents = "1";
   };
   
};

Here's how NonModalContentProfile is defined:

if(!isObject(NonModalContentProfile)) new GuiControlProfile(NonModalContentProfile)
{
	opaque = false;
  fillColor = "255 255 255";
	Modal = false;
	//noCursor = "1";
};

Is there something special I have to do to be able to get mouse events on the hud window if the player clicks on one of the HUD static sprites and have them pass throught to the main game window if he/she clicks in empty space?

#1
01/22/2010 (5:18 pm)
You could try passing all window events (like hudWindow::onMouseDown) to the lower window (just call sceneWindow2d.onMouseDown). Unfortunately, that won't create object mouse events. You'll have to use the "pickPoint" function and manually create the object events. (At least, that's the only way I've found to do it... somebody else may have figured out a better way.)

Because I have 4 layers of HUDs/GUIs in my game, I modified the engine. If an object mouse event would happen, then the current layer gets the event, otherwise I keep passing the event down to the next window. (Actually, there's code for onLeave and special code if windowMouseEvents are also used, but you get the gist.)
#2
06/08/2010 (12:52 pm)
Wow, this sounds like a nightmare. If a add another scenewindow, all of my mouse events are broken in the current scenewindow?
#3
06/10/2010 (2:26 pm)
Not broken, just intercepted. A modal GuiControl, such as a scene window, will "eat" mouse events that hit it. Usually desirable behavior for buttons and popup windows, but limiting in the case of overlaid scene windows.

William, that sounds like a very handy engine mod. Was it just a change to the t2dSceneWindow class, or did you have to alter the way GuiCanvas sends the events?
#4
06/10/2010 (3:01 pm)
There was also a change to GuiCanvas. Since findHitControl in t2dSceneWindow may return NULL to say "No, I don't want this event", I have to process that in rootMouseDown so that it continues onto the next (lower) control.

I'll put the changes I made here, but on one condition: you shouldn't implement this unless you are skilled at C++. I'm not filling in some (easy) details to filter out lower skill. USE AT YOUR OWN RISK!

Add findHitControl to t2dSceneWindow:
GuiControl* t2dSceneWindow::findHitControl( const Point2I &pt, S32 initialLayer )
{
  GuiControl *initialPick = Parent::findHitControl( pt, initialLayer );
  if( initialPick != this )
    return initialPick;

  if( getUseWindowMouseEvents() )
    return this;

  if( getUseObjectMouseEvents() )
  {
    if( !getSceneGraph() )
      return NULL;

    if( getSceneGraph()->getMouseLockedObjectSet().size() > 0 )
      return this;

    if( mLastPickVector.size() > 0 )
      return this;

    t2dVector worldMousePoint;
    windowToSceneCoord( t2dVector( pt ), worldMousePoint );

    if( getSceneGraph()->pickPoint( worldMousePoint, mMouseEventGroupMaskFilter, mMouseEventLayerMaskFilter, mMouseEventInvisibleFilter ) > 0 )
    {
      typeSceneObjectVector pickVector = getSceneGraph()->getPickList();
      for( U32 i = 0; i < pickVector.size(); i++ )
      {
        t2dSceneObject* object = pickVector[i];
        if( object && object->getUseMouseEvents() ) return this;
      }
    }
  }

  return NULL;
}

Modify GuiCanvas::rootMouseDown with:
void GuiCanvas::rootMouseDown(const GuiEvent &event)
{
   mPrevMouseTime = Platform::getVirtualMilliseconds();
   mMouseButtonDown = true;

   //pass the event to the mouse locked control
   if (bool(mMouseCapturedControl))
      mMouseCapturedControl->onMouseDown(event);

   //else pass it to whoever is underneath the cursor
   else
   {
      iterator i;
      i = end();
      while (i != begin())
      {
         i--;
         GuiControl *ctrl = static_cast<GuiControl *>(*i);
         GuiControl *controlHit = ctrl->findHitControl(event.mousePoint);

         // WLS - Allow for NULLs
         if( controlHit == NULL )
           continue;

         //see if the controlHit is a modeless dialog...
         if ((! controlHit->mActive) && (! controlHit->mProfile->mModal))
            continue;
         else
         {
            controlHit->onMouseDown(event);
            break;
         }
      }
   }
   if (bool(mMouseControl))
      mMouseControlClicked = true;
}

Modify GuiControl::findHitControl to also handle NULLs being returned.
GuiControl* GuiControl::findHitControl(const Point2I &pt, S32 initialLayer)
{
   iterator i = end(); // find in z order (last to first)
   while (i != begin())
   {
      i--;
      GuiControl *ctrl = static_cast<GuiControl *>(*i);
      if (initialLayer >= 0 && ctrl->mLayer > initialLayer)
      {
         continue;
      }
      else if (ctrl->mVisible && ctrl->pointInControl(pt))
      {
         Point2I ptemp = pt - ctrl->mBounds.point;
         GuiControl *hitCtrl = ctrl->findHitControl(ptemp);

         // WLS - Hit Control can be null
         if(hitCtrl && hitCtrl->mProfile->mModal)
            return hitCtrl;
      }
   }
   return this;
}

One final warning. I have a mix of 1.7.4 and 1.7.5 and a heavily modified engine at this point. There's a chance I missed a function. I will help anybody who implements this, but will ignore anybody with no C++ skill.
#5
06/10/2010 (6:58 pm)
Is it possible to create a custom shape for a scenewindow? For example, can I define a scenewindow as a small square in the upper left corner of the screen with its own mouse events, while leaving the rest of the exposed sceneWindow2D to function with its existing (and unintercepted) mouse events?
#6
06/10/2010 (9:03 pm)
Most definitely! Just define your sceneWindow to not fill the screen as much:

new t2dSceneWindow(sceneWindow2D) {
      canSaveDynamicFields = "0";
      isContainer = "0";
      Profile = "GuiContentProfile";
      HorizSizing = "bottom";
      VertSizing = "right";
      Position = "0 0";
      Extent = "1024 768";
      MinExtent = "8 8";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      lockMouse = "0";
      useWindowMouseEvents = "1";
      useObjectMouseEvents = "1";
   };
   new t2dSceneWindow(HUDWindow2D) {
      canSaveDynamicFields = "0";
      isContainer = "0";
      Profile = "GuiContentProfile";
      HorizSizing = "bottom";
      VertSizing = "right";
      Position = "0 0";      // <-- This is the position
      Extent = "400 400";   // <-- This is the size
      MinExtent = "8 8";
      canSave = "1";
      Visible = "1";
      hovertime = "1000";
      lockMouse = "0";
      useWindowMouseEvents = "0";
      useObjectMouseEvents = "1";
   };

If you want the region to scale with the resolution, use "relative" for both HorizSizing and VertSizing. The way I have it now, it'll stay 400x400 no matter what the resolution.
#7
06/11/2010 (4:12 am)
This is great news! Does this mean the graphical piece in the HUD window needs to fill the entire HUD window so that when its seen on top of the main level its the correct size?

sceneWindow2D.extent = 800 x 600
minimap.size = 19 x 17

minimapHUD.extent = 152 x 102 ??
minimap.size (in the HUD) = 100 x 100 ??
#8
06/11/2010 (8:28 am)
With the minimap, this is what I have to do to make it the size and position I want:
new t2dSceneWindow(mmWindow) {
      canSaveDynamicFields = "0";
      Profile = "ModalContentProfile";  
      HorizSizing = "bottom";
      VertSizing = "right";
      Position = "640 25";//"39.7 -25.7";
      Extent = "152 102";//19 x 17
      MinExtent = "8 8";
      canSave = "1";
      Visible = "1";
      lockMouse = "0";
      useWindowMouseEvents = "1";
      useObjectMouseEvents = "1";
   };

In the minimap level, I stretched the graphic to take up the entire viewing area. Its working, but maybe there's an easier way to do this?
#9
06/11/2010 (2:16 pm)
You can set up a camera that's, say, 152x102 and put your graphics in that area. Whatever your camera is set to (area-wise), it'll "stretch" to fill the t2dSceneWindow.
#10
06/11/2010 (4:56 pm)
Do I set the camera with setViewLimitOn?
#11
06/11/2010 (5:08 pm)
In TGB, under the "Project" tab, select t2dSceneGraph. Then under the "Edit" tab, set-up your camera. Then, when you load in the scene to your little scene window, it should automatically be set for you.
#12
06/11/2010 (7:33 pm)
Yes! That is so much easier, thanks!
#13
09/10/2010 (1:50 am)
I just wanted to add that the code in Comment #4 above works in 1.7.5.

One thing that I should have mentioned was that the t2dSceneWindows that sit on top need to have "useWindowMouseEvents" set to false and "useObjectMouseEvents" set to true. The bottom-most t2dSceneWindow can have "useWindowMouseEvents" set to true.
#14
06/23/2011 (8:56 am)
Hi Guys,
So I am trying what as written here.
I was expecting by resizing and placing my hudWindow2D, out of the way of the btns in the sceneWindow2D that I would be able to access those btns. Unfortunalty it is not cooperating right now.

//--- OBJECT WRITE BEGIN ---
%guiContent = new GuiControl(hudScreenGui) {
   canSaveDynamicFields = "0";
   isContainer = "1";
   Profile = "GuiHUDProfile";
   HorizSizing = "width";
   VertSizing = "height";
   Position = "1.135 -212.610";   ///I set the positioning here and in the window below
   Extent = "330.643 58.260";     /// sizing here of course. 
   MinExtent = "330.643 58.260";
   canSave = "1";
   Visible = "1";
   hovertime = "1000";
   modal = "0";

   new t2dSceneWindow(hudWindow2D) {
      canSaveDynamicFields = "0";
      isContainer = "0";
      Profile = "GuiHUDProfile";
      HorizSizing = "width";
      VertSizing = "height";
      Position = "1.135 -212.610";  ///
      Extent = "330.643 58.260";    ///
      MinExtent = "100 50";
      canSave = "1";
      Visible = "1";
      tooltipprofile = "GuiHUDProfile";
      hovertime = "1000";
      lockMouse = "0";
      useWindowMouseEvents = "1";
      useObjectMouseEvents = "1";
      modal = "1";
   };      
};
//--- OBJECT WRITE END ---



I am not sure what to so. I have played with the useWindowMouseEvents = "1"; useObjectMouseEvents = "1"; modal = "1"; but can not get it. I am not very skilled at C++ so am hesitant to use Williams rootDown scripts.



Am I missing something???!

Thanks.
#15
06/23/2011 (11:00 am)
On a further note. I have set the extent and position to the exact size and position of my HUD object. I then stretched the HUD object to double the length, loaded it, and it looked normal, unwarped. No surprise I guess. I thought it was perhaps because of the the extent settings. So I changed the extent settings and again, no warping at all. Any thoughts?
#16
11/03/2011 (10:18 pm)
void GuiCanvas::maintainSizing()

this function make any control in the canvas become the same size as the screen. so my extend set in a GuiControl is disabled...

even the GUI Editor itself can't make it