UDN
Search public documentation:

DevelopmentKitGemsCreatingAMouseInterface
日本語訳
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 Home > Unreal Development Kit Gems > Creating a mouse interface
UE3 Home > User Interfaces & HUDs > Creating a mouse interface

Creating a mouse interface


Last tested against UDK Apr, 2011
PC compatible

Overview


By default Unreal Engine is designed as a first person shooter engine. However, with the right code it is entirely possible to create many other game genres with it as well, such as real time strategy games.

Getting a cursor on the screen


Adding a cursor onto the screen involves adding a few new scripts. Because UIScene was removed, the old methods for extracting the mouse position are now invalid. There are two methods that you could do this; Unrealscript and ScaleForm.

Unrealscript

It is possible to create your own mouse positioning code. This is done by adding a custom player input which polls aMouseX and aMouseY to see if there has been any changes. If there has been a change, it is appended to the MousePosition variable and then clamped to keep the mouse cursor within the screen bounds.

This is the new game info. It is a simple GameInfo which defines the HUD class and PlayerController class to use.

MouseInterfaceGameInfo.uc
class MouseInterfaceGameInfo extends GameInfo;

defaultproperties
{
  // Set the HUD type to the mouse interface HUD
  HUDType=class'MouseInterfaceHUD'
  // Set the player controller to the mouse interface Player Controller
  PlayerControllerClass=class'MouseInterfacePlayerController'
}

This is the new hud which will be used to render the mouse cursor on the screen. You could use a material if you wanted to as well.

MouseInterfaceHUD.uc
class MouseInterfaceHUD extends HUD;

// The texture which represents the cursor on the screen
var const Texture2D CursorTexture;
// The color of the cursor
var const Color CursorColor;

event PostRender()
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;

  // Ensure that we have a valid PlayerOwner and CursorTexture
  if (PlayerOwner != None && CursorTexture != None)
  {
    // Cast to get the MouseInterfacePlayerInput
    MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

    if (MouseInterfacePlayerInput != None)
    {
      // Set the canvas position to the mouse position
      Canvas.SetPos(MouseInterfacePlayerInput.MousePosition.X, MouseInterfacePlayerInput.MousePosition.Y);
      // Set the cursor color
      Canvas.DrawColor = CursorColor;
      // Draw the texture on the screen
      Canvas.DrawTile(CursorTexture, CursorTexture.SizeX, CursorTexture.SizeY, 0.f, 0.f, CursorTexture.SizeX, CursorTexture.SizeY,, true);
    }
  }

  Super.PostRender();
}

defaultproperties
{
  CursorColor=(R=255,G=255,B=255,A=255)
  CursorTexture=Texture2D'EngineResources.Cursors.Arrow'
}

The logic behind the code is as follows:

  • When PostRender is executed we get the new PlayerInput from the PlayerOwner, which is a PlayerController.
  • If we have our new PlayerInput, we then set the Canvas position to the MousePosition stored inside our new PlayerInput.
  • We then set the color to the cursor color defined in the default properties.
  • Finally, we draw the texture which represents the mouse cursor.

This is the new player controller that defines the new PlayerInput to use. UpdateRotation is stubbed as the original function is not required for this gem.

MouseInterfacePlayerController.uc
class MouseInterfacePlayerController extends PlayerController;

// Null this function
function UpdateRotation(float DeltaTime);

defaultproperties
{
  // Set the input class to the mouse interface player input
  InputClass=class'MouseInterfacePlayerInput'
}

This is the new player input.

MouseInterfacePlayerInput.uc
class MouseInterfacePlayerInput extends PlayerInput;

// Stored mouse position. Set to private write as we don't want other classes to modify it, but still allow other classes to access it.
var PrivateWrite IntPoint MousePosition;

event PlayerInput(float DeltaTime)
{
  // Handle mouse
  // Ensure we have a valid HUD
  if (myHUD != None)
  {
    // Add the aMouseX to the mouse position and clamp it within the viewport width
    MousePosition.X = Clamp(MousePosition.X + aMouseX, 0, myHUD.SizeX);
    // Add the aMouseY to the mouse position and clamp it within the viewport height
    MousePosition.Y = Clamp(MousePosition.Y - aMouseY, 0, myHUD.SizeY);
  }

  Super.PlayerInput(DeltaTime);
}

defaultproperties
{
}

The logic behind the code is as follows:

  • When PlayerInput is executed, we handle adjust the mouse position.
  • The HUD is required to clamp the mouse position within the viewport. As the PlayerInput is an object within the PlayerController, we can access the variable within the PlayerController directly.
  • Using aMouseX and aMouseY which is bound within the input config, we add those values to our MousePosition variable. We have to inverse the vertical calculations to produce the right results.
  • By clamping the results, we make sure that the mouse position always stays within the viewport.

Compile. Create a new map, and set the PIE GameInfo to MouseInterfaceGameInfo and test. You should be able to see and move the cursor around. In the image below, the cursor is within the red circle.

MouseInterfacePhaseOne.jpg

ScaleForm

ScaleForm can also be used to poll for the mouse position. In this particular gem, the majority of the scripts stay largely the same. The exception is, is if you use ScaleForm, the custom player input class created above no longer polls for mouse changes. Instead, when ScaleForm detects a change in the mouse position, it will pass the new mouse position to Unrealscript.

This is the action script written in Adobe Flash. When the mouse moves, action script sets the position of a layer called cursor to the ScaleForm's mouse position and it also sends Unrealscript ScaleForm's mouse position.

MouseInterfaceCursor.fla > actions: Frame 1
import flash.external.ExternalInterface;

// Hide normal "Windows" pointer.
Mouse.hide();

var mouseListener:Object = new Object();

mouseListener.onMouseMove = function() {
  // Set the cursor instance position to the mouse position.
  cursor._x = _root._xmouse;
  cursor._y = _root._ymouse;

  // Pass Unrealscript the new mouse coordinates
  ExternalInterface.call("UpdateMousePosition", _root._xmouse, _root._ymouse);

  updateAfterEvent();
};

Mouse.addListener(mouseListener);

This is the Unrealscript side of the ScaleForm movie. It contains the event which is what ScaleForm uses to send it's mouse position values.

MouseInterfaceGFx.uc
class MouseInterfaceGFx extends GFxMoviePlayer;

var MouseInterfaceHUD MouseInterfaceHUD;

function Init(optional LocalPlayer LocalPlayer)
{
  // Initialize the ScaleForm movie
  Super.Init(LocalPlayer);

  Start();
  Advance(0);
}

event UpdateMousePosition(float X, float Y)
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;

  if (MouseInterfaceHUD != None && MouseInterfaceHUD.PlayerOwner != None)
  {
    MouseInterfacePlayerInput = MouseInterfacePlayerInput(MouseInterfaceHUD.PlayerOwner.PlayerInput);

    if (MouseInterfacePlayerInput != None)
    {
      MouseInterfacePlayerInput.SetMousePosition(X, Y);
    }
  }
}

defaultproperties
{
  bDisplayWithHudOff=false
  TimingMode=TM_Game
  MovieInfo=SwfMovie'MouseInterfaceContent.MouseInterfaceCursor'
  bPauseGameWhileActive=false
}

The HUD is where ScaleForm is created and initialized. When the HUD is destroyed, it also makes sure to close any ScaleForm references it makes. PreCalcValues is a function which can detect if the resolution of the screen has changed. This is important to ensure that ScaleForm is informed of those changes, and thus it resets it's viewport, scale mode and alignment to match the resolution. Lastly, because ScaleForm is now rendering out cursor, the HUD is told not to render the cursor.

MouseInterfaceHUD.uc
class MouseInterfaceHUD extends HUD;

// The texture which represents the cursor on the screen
var const Texture2D CursorTexture;
// The color of the cursor
var const Color CursorColor;
// Use ScaleForm?
var bool UsingScaleForm;
// Scaleform mouse movie
var MouseInterfaceGFx MouseInterfaceGFx;

simulated event PostBeginPlay()
{
  Super.PostBeginPlay();

  // If we are using ScaleForm, then create the ScaleForm movie
  if (UsingScaleForm)
  {
    MouseInterfaceGFx = new () class'MouseInterfaceGFx';
    if (MouseInterfaceGFx != None)
    {
      MouseInterfaceGFx.MouseInterfaceHUD = Self;
      MouseInterfaceGFx.SetTimingMode(TM_Game);

      MouseInterfaceGFx.Init(class'Engine'.static.GetEngine().GamePlayers[MouseInterfaceGFx.LocalPlayerOwnerIndex]);
    }
  }
}

simulated event Destroyed()
{
  Super.Destroyed();

  // If the ScaleForm movie exists, then destroy it
  if (MouseInterfaceGFx != None)
  {
    MouseInterfaceGFx.Close(true);
    MouseInterfaceGFx = None;
  }
}

function PreCalcValues()
{
  Super.PreCalcValues();

  // If the ScaleForm movie exists, then reset it's viewport, scale mode and alignment to match the
  // screen resolution
  if (MouseInterfaceGFx != None)
  {
    MouseInterfaceGFx.SetViewport(0, 0, SizeX, SizeY);
    MouseInterfaceGFx.SetViewScaleMode(SM_NoScale);
    MouseInterfaceGFx.SetAlignment(Align_TopLeft);
  }
}

event PostRender()
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local MouseInterfaceInteractionInterface MouseInteractionInterface;
  local Vector HitLocation, HitNormal;

  Super.PostRender();

  // Ensure that we aren't using ScaleForm and that we have a valid cursor
  if (!UsingScaleForm && CursorTexture != None)
  {
    // Ensure that we have a valid PlayerOwner
    if (PlayerOwner != None)
    {
      // Cast to get the MouseInterfacePlayerInput
      MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

      // If we're not using scale form and we have a valid cursor texture, render it
      if (MouseInterfacePlayerInput != None)
      {
        // Set the canvas position to the mouse position
        Canvas.SetPos(MouseInterfacePlayerInput.MousePosition.X, MouseInterfacePlayerInput.MousePosition.Y);
        // Set the cursor color
        Canvas.DrawColor = CursorColor;
        // Draw the texture on the screen
        Canvas.DrawTile(CursorTexture, CursorTexture.SizeX, CursorTexture.SizeY, 0.f, 0.f, CursorTexture.SizeX, CursorTexture.SizeY,, true);
      }
    }
  }

  Super.PostRender();
}

Lastly, a new function is added to MouseInterfacePlayerInput which allows other classes to write to the MousePosition variable. This is done intentionally to ensure that the mouse position is always within the rendering bounds of the Canvas.

MouseInterfacePlayerInput.uc
class MouseInterfacePlayerInput extends PlayerInput;

var PrivateWrite IntPoint MousePosition;

event PlayerInput(float DeltaTime)
{
  local MouseInterfaceHUD MouseInterfaceHUD;

  // Handle mouse movement
  // Check that we have the appropriate HUD class
  MouseInterfaceHUD = MouseInterfaceHUD(MyHUD);
  if (MouseInterfaceHUD != None)
  {
    if (!MouseInterfaceHUD.UsingScaleForm)
    {
      // If we are not using ScaleForm, then read the mouse input directly
      // Add the aMouseX to the mouse position and clamp it within the viewport width
      MousePosition.X = Clamp(MousePosition.X + aMouseX, 0, MouseInterfaceHUD.SizeX);
      // Add the aMouseY to the mouse position and clamp it within the viewport height
      MousePosition.Y = Clamp(MousePosition.Y - aMouseY, 0, MouseInterfaceHUD.SizeY);
    }
  }

  Super.PlayerInput(DeltaTime);
}

function SetMousePosition(int X, int Y)
{
  if (MyHUD != None)
  {
    MousePosition.X = Clamp(X, 0, MyHUD.SizeX);
    MousePosition.Y = Clamp(Y, 0, MyHUD.SizeY);
  }
}

defaultproperties
{
}

So, ScaleForm doesn't change the logic too much here. The main change here is what is used to detect where the current position of the mouse is.

Related Topics

I just want the mouse 2D coordinates!

To retrieve the mouse 2D coordinates, you just need access to the MouseInterfacePlayerInput.

To retrieve it from within the HUD.

MouseInterfaceHUD.uc
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local IntPoint MousePosition;

  // Ensure that we have a valid PlayerOwner
  if (PlayerOwner != None)
  {
    // Cast to get the MouseInterfacePlayerInput
    MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

    if (MouseInterfacePlayerInput != None)
    {
      // To retrieve/use the mouse X position
      MousePosition.X = MouseInterfacePlayerInput.MousePosition.X;
      // To retrieve/use the mouse Y position
      MousePosition.Y = MouseInterfacePlayerInput.MousePosition.Y;
    }
  }

To retrieve it within the PlayerController.

MouseInterfacePlayerController.uc
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local IntPoint MousePosition;

  // Cast to get the MouseInterfacePlayerInput
  MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerInput);

  if (MouseInterfacePlayerInput != None)
  {
    // To retrieve/use the mouse X position
    MousePosition.X = MouseInterfacePlayerInput.MousePosition.X;
    // To retrieve/use the mouse Y position
    MousePosition.Y = MouseInterfacePlayerInput.MousePosition.Y;
  }

Related topics

Adding the mouse interaction interface


This is the interface that mouse interactable actors will implement. Add this interface to your package. If you wish to implement more mouse functions such as extra buttons or mouse axial movement, append it to this interface so that every mouse interactable actor will inherit it.

MouseInterfaceInteractionInterface.uc
interface MouseInterfaceInteractionInterface;

// Called when the left mouse button is pressed
function MouseLeftPressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal);

// Called when the left mouse button is released
function MouseLeftReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the right mouse button is pressed
function MouseRightPressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal);

// Called when the right mouse button is released
function MouseRightReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the middle mouse button is pressed
function MouseMiddlePressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal);

// Called when the middle mouse button is released
function MouseMiddleReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the middle mouse button is scrolled up
function MouseScrollUp(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the middle mouse button is scrolled down
function MouseScrollDown(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the mouse is moved over the actor
function MouseOver(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Called when the mouse is moved out from the actor (when it was previously over it)
function MouseOut(Vector MouseWorldOrigin, Vector MouseWorldDirection);

// Returns the hit location of the mouse trace
function Vector GetHitLocation();

// Returns the hit normal of the mouse trace
function Vector GetHitNormal();

// Returns the mouse world origin calculated by the deprojection within the canvas
function Vector GetMouseWorldOrigin();

// Returns the mouse world direction calculated by the deprojection within the canvas
function Vector GetMouseWorldDirection();

Related topics

Using the PlayerController and HUD to provide the mouse interaction


Mouse input is handled by using pending booleans within the HUD. The reason why this was done was because the deprojection requires access to the canvas. The canvas is reassigned per frame, so it is not possible to retain a reference to it over several frames. Therefore, when the mouse buttons or the mouse wheel is used, we defer those input actions to the HUD.

MouseInterfacePlayerController.uc
class MouseInterfacePlayerController extends PlayerController;

// Mouse event enum
enum EMouseEvent
{
  LeftMouseButton,
  RightMouseButton,
  MiddleMouseButton,
  ScrollWheelUp,
  ScrollWheelDown,
};

// Handle mouse inputs
function HandleMouseInput(EMouseEvent MouseEvent, EInputEvent InputEvent)
{
  local MouseInterfaceHUD MouseInterfaceHUD;

  // Type cast to get our HUD
  MouseInterfaceHUD = MouseInterfaceHUD(myHUD);

  if (MouseInterfaceHUD != None)
  {
    // Detect what kind of input this is
    if (InputEvent == IE_Pressed)
    {
      // Handle pressed event
      switch (MouseEvent)
      {
        case LeftMouseButton:
     MouseInterfaceHUD.PendingLeftPressed = true;
     break;

   case RightMouseButton:
     MouseInterfaceHUD.PendingRightPressed = true;
     break;

   case MiddleMouseButton:
     MouseInterfaceHUD.PendingMiddlePressed = true;
     break;

   case ScrollWheelUp:
     MouseInterfaceHUD.PendingScrollUp = true;
     break;

   case ScrollWheelDown:
     MouseInterfaceHUD.PendingScrollDown = true;
     break;

   default:
     break;
      }
    }
    else if (InputEvent == IE_Released)
    {
      // Handle released event
      switch (MouseEvent)
      {
        case LeftMouseButton:
     MouseInterfaceHUD.PendingLeftReleased = true;
     break;

   case RightMouseButton:
     MouseInterfaceHUD.PendingRightReleased = true;
     break;

   case MiddleMouseButton:
     MouseInterfaceHUD.PendingMiddleReleased = true;
     break;

   default:
     break;
      }
    }
  }
}

// Hook used for the left and right mouse button when pressed
exec function StartFire(optional byte FireModeNum)
{
  HandleMouseInput((FireModeNum == 0) ? LeftMouseButton : RightMouseButton, IE_Pressed);
  Super.StartFire(FireModeNum);
}

// Hook used for the left and right mouse button when released
exec function StopFire(optional byte FireModeNum)
{
  HandleMouseInput((FireModeNum == 0) ? LeftMouseButton : RightMouseButton, IE_Released);
  Super.StopFire(FireModeNum);
}

// Called when the middle mouse button is pressed
exec function MiddleMousePressed()
{
  HandleMouseInput(MiddleMouseButton, IE_Pressed);
}

// Called when the middle mouse button is released
exec function MiddleMouseReleased()
{
  HandleMouseInput(MiddleMouseButton, IE_Released);
}

// Called when the middle mouse wheel is scrolled up
exec function MiddleMouseScrollUp()
{
  HandleMouseInput(ScrollWheelUp, IE_Pressed);
}

// Called when the middle mouse wheel is scrolled down
exec function MiddleMouseScrollDown()
{
  HandleMouseInput(ScrollWheelDown, IE_Pressed);
}

// Null this function
function UpdateRotation(float DeltaTime);

// Override this state because StartFire isn't called globally when in this function
auto state PlayerWaiting
{
  exec function StartFire(optional byte FireModeNum)
  {
    Global.StartFire(FireModeNum);
  }
}

defaultproperties
{
  // Set the input class to the mouse interface player input
  InputClass=class'MouseInterfacePlayerInput'
}

The logic of the player controller is as follows:

  • Exec functions are called via key binds.
  • If any of the exec functions are called, they then call HandleMouseInput which then defers the action to the HUD.

As we've added new exec functions, we need to modify the DefaultInput.ini to bind to them. Modifying DefaultInput.ini ensures that subsequent configuration files are updated with the change; but you can also modify UDKInput.ini.

DefaultInput.ini
; Remove the previous mouse scroll up and scroll down key binds
-Bindings=(Name="MouseScrollUp",Command="PrevWeapon")
-Bindings=(Name="MouseScrollDown",Command="NextWeapon")
; Add the middle mouse button and new scroll wheel key binds
.Bindings=(Name="MiddleMouseButton",Command="MiddleMousePressed | OnRelease MiddleMouseReleased")
.Bindings=(Name="MouseScrollUp",Command="GBA_PrevWeapon | MiddleMouseScrollUp")
.Bindings=(Name="MouseScrollDown",Command="GBA_NextWeapon | MiddleMouseScrollDown")

When the mouse input is deferred to the HUD, the HUD handles these per frame within the PostRender function. Per frame, the HUD gets the current mouse interaction interface by performing a trace which represents the mouse within the world.

To further clarify, in the image below the green plane represents the world, the red frustum represents the world view frustum, the blue spot represents the 2D mouse position and the blue line represents the trace. The flat top of the frustum represents the screen (in this case, viewing the world over the top). The canvas deprojection function converts a 2D position into the world.

FrustrumExplanation.jpg

MouseInterfaceHUD.uc
class MouseInterfaceHUD extends HUD;

// The texture which represents the cursor on the screen
var const Texture2D CursorTexture;
// The color of the cursor
var const Color CursorColor;
// Pending left mouse button pressed event
var bool PendingLeftPressed;
// Pending left mouse button released event
var bool PendingLeftReleased;
// Pending right mouse button pressed event
var bool PendingRightPressed;
// Pending right mouse button released event
var bool PendingRightReleased;
// Pending middle mouse button pressed event
var bool PendingMiddlePressed;
// Pending middle mouse button released event
var bool PendingMiddleReleased;
// Pending mouse wheel scroll up event
var bool PendingScrollUp;
// Pending mouse wheel scroll down event
var bool PendingScrollDown;
// Cached mouse world origin
var Vector CachedMouseWorldOrigin;
// Cached mouse world direction
var Vector CachedMouseWorldDirection;
// Last mouse interaction interface
var MouseInterfaceInteractionInterface LastMouseInteractionInterface;
// Use ScaleForm?
var bool UsingScaleForm;
// Scaleform mouse movie
var MouseInterfaceGFx MouseInterfaceGFx;

simulated event PostBeginPlay()
{
  Super.PostBeginPlay();

  // If we are using ScaleForm, then create the ScaleForm movie
  if (UsingScaleForm)
  {
    MouseInterfaceGFx = new () class'MouseInterfaceGFx';
    if (MouseInterfaceGFx != None)
    {
      MouseInterfaceGFx.MouseInterfaceHUD = Self;
      MouseInterfaceGFx.SetTimingMode(TM_Game);

      MouseInterfaceGFx.Init(class'Engine'.static.GetEngine().GamePlayers[MouseInterfaceGFx.LocalPlayerOwnerIndex]);
    }
  }
}

simulated event Destroyed()
{
  Super.Destroyed();

  // If the ScaleForm movie exists, then destroy it
  if (MouseInterfaceGFx != None)
  {
    MouseInterfaceGFx.Close(true);
    MouseInterfaceGFx = None;
  }
}

function PreCalcValues()
{
  Super.PreCalcValues();

  // If the ScaleForm movie exists, then reset it's viewport, scale mode and alignment to match the
  // screen resolution
  if (MouseInterfaceGFx != None)
  {
    MouseInterfaceGFx.SetViewport(0, 0, SizeX, SizeY);
    MouseInterfaceGFx.SetViewScaleMode(SM_NoScale);
    MouseInterfaceGFx.SetAlignment(Align_TopLeft);
  }
}

event PostRender()
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local MouseInterfaceInteractionInterface MouseInteractionInterface;
  local Vector HitLocation, HitNormal;

  Super.PostRender();

  // Ensure that we aren't using ScaleForm and that we have a valid cursor
  if (!UsingScaleForm && CursorTexture != None)
  {
    // Ensure that we have a valid PlayerOwner
    if (PlayerOwner != None)
    {
      // Cast to get the MouseInterfacePlayerInput
      MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

      // If we're not using scale form and we have a valid cursor texture, render it
      if (MouseInterfacePlayerInput != None)
      {
        // Set the canvas position to the mouse position
        Canvas.SetPos(MouseInterfacePlayerInput.MousePosition.X, MouseInterfacePlayerInput.MousePosition.Y);
        // Set the cursor color
        Canvas.DrawColor = CursorColor;
        // Draw the texture on the screen
        Canvas.DrawTile(CursorTexture, CursorTexture.SizeX, CursorTexture.SizeY, 0.f, 0.f, CursorTexture.SizeX, CursorTexture.SizeY,, true);
      }
    }
  }

  // Get the current mouse interaction interface
  MouseInteractionInterface = GetMouseActor(HitLocation, HitNormal);

  // Handle mouse over and mouse out
  // Did we previously had a mouse interaction interface?
  if (LastMouseInteractionInterface != None)
  {
    // If the last mouse interaction interface differs to the current mouse interaction
    if (LastMouseInteractionInterface != MouseInteractionInterface)
    {
      // Call the mouse out function
      LastMouseInteractionInterface.MouseOut(CachedMouseWorldOrigin, CachedMouseWorldDirection);
      // Assign the new mouse interaction interface
      LastMouseInteractionInterface = MouseInteractionInterface;

      // If the last mouse interaction interface is not none
      if (LastMouseInteractionInterface != None)
      {
        // Call the mouse over function
        LastMouseInteractionInterface.MouseOver(CachedMouseWorldOrigin, CachedMouseWorldDirection); // Call mouse over
      }
    }
  }
  else if (MouseInteractionInterface != None)
  {
    // Assign the new mouse interaction interface
    LastMouseInteractionInterface = MouseInteractionInterface;
    // Call the mouse over function
    LastMouseInteractionInterface.MouseOver(CachedMouseWorldOrigin, CachedMouseWorldDirection);
  }

  if (LastMouseInteractionInterface != None)
  {
    // Handle left mouse button
    if (PendingLeftPressed)
    {
      if (PendingLeftReleased)
      {
        // This is a left click, so discard
        PendingLeftPressed = false;
        PendingLeftReleased = false;
      }
      else
      {
        // Left is pressed
        PendingLeftPressed = false;
        LastMouseInteractionInterface.MouseLeftPressed(CachedMouseWorldOrigin, CachedMouseWorldDirection, HitLocation, HitNormal);
      }
    }
    else if (PendingLeftReleased)
    {
      // Left is released
      PendingLeftReleased = false;
      LastMouseInteractionInterface.MouseLeftReleased(CachedMouseWorldOrigin, CachedMouseWorldDirection);
    }

    // Handle right mouse button
    if (PendingRightPressed)
    {
      if (PendingRightReleased)
      {
        // This is a right click, so discard
        PendingRightPressed = false;
        PendingRightReleased = false;
      }
      else
      {
        // Right is pressed
        PendingRightPressed = false;
        LastMouseInteractionInterface.MouseRightPressed(CachedMouseWorldOrigin, CachedMouseWorldDirection, HitLocation, HitNormal);
      }
    }
    else if (PendingRightReleased)
    {
      // Right is released
      PendingRightReleased = false;
      LastMouseInteractionInterface.MouseRightReleased(CachedMouseWorldOrigin, CachedMouseWorldDirection);
    }

    // Handle middle mouse button
    if (PendingMiddlePressed)
    {
      if (PendingMiddleReleased)
      {
        // This is a middle click, so discard
        PendingMiddlePressed = false;
        PendingMiddleReleased = false;
      }
      else
      {
        // Middle is pressed
        PendingMiddlePressed = false;
        LastMouseInteractionInterface.MouseMiddlePressed(CachedMouseWorldOrigin, CachedMouseWorldDirection, HitLocation, HitNormal);
      }
    }
    else if (PendingMiddleReleased)
    {
      PendingMiddleReleased = false;
      LastMouseInteractionInterface.MouseMiddleReleased(CachedMouseWorldOrigin, CachedMouseWorldDirection);
    }

    // Handle middle mouse button scroll up
    if (PendingScrollUp)
    {
      PendingScrollUp = false;
      LastMouseInteractionInterface.MouseScrollUp(CachedMouseWorldOrigin, CachedMouseWorldDirection);
    }

    // Handle middle mouse button scroll down
    if (PendingScrollDown)
    {
      PendingScrollDown = false;
      LastMouseInteractionInterface.MouseScrollDown(CachedMouseWorldOrigin, CachedMouseWorldDirection);
    }
  }
}

function MouseInterfaceInteractionInterface GetMouseActor(optional out Vector HitLocation, optional out Vector HitNormal)
{
  local MouseInterfaceInteractionInterface MouseInteractionInterface;
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local Vector2D MousePosition;
  local Actor HitActor;

  // Ensure that we have a valid canvas and player owner
  if (Canvas == None || PlayerOwner == None)
  {
    return None;
  }

  // Type cast to get the new player input
  MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

  // Ensure that the player input is valid
  if (MouseInterfacePlayerInput == None)
  {
    return None;
  }

  // We stored the mouse position as an IntPoint, but it's needed as a Vector2D
  MousePosition.X = MouseInterfacePlayerInput.MousePosition.X;
  MousePosition.Y = MouseInterfacePlayerInput.MousePosition.Y;
  // Deproject the mouse position and store it in the cached vectors
  Canvas.DeProject(MousePosition, CachedMouseWorldOrigin, CachedMouseWorldDirection);

  // Perform a trace actor interator. An interator is used so that we get the top most mouse interaction
  // interface. This covers cases when other traceable objects (such as static meshes) are above mouse
  // interaction interfaces.
  ForEach TraceActors(class'Actor', HitActor, HitLocation, HitNormal, CachedMouseWorldOrigin + CachedMouseWorldDirection * 65536.f, CachedMouseWorldOrigin,,, TRACEFLAG_Bullet)
  {
    // Type cast to see if the HitActor implements that mouse interaction interface
    MouseInteractionInterface = MouseInterfaceInteractionInterface(HitActor);
    if (MouseInteractionInterface != None)
    {
      return MouseInteractionInterface;
    }
  }

  return None;
}

function Vector GetMouseWorldLocation()
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local Vector2D MousePosition;
  local Vector MouseWorldOrigin, MouseWorldDirection, HitLocation, HitNormal;

  // Ensure that we have a valid canvas and player owner
  if (Canvas == None || PlayerOwner == None)
  {
    return Vect(0, 0, 0);
  }

  // Type cast to get the new player input
  MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

  // Ensure that the player input is valid
  if (MouseInterfacePlayerInput == None)
  {
    return Vect(0, 0, 0);
  }

  // We stored the mouse position as an IntPoint, but it's needed as a Vector2D
  MousePosition.X = MouseInterfacePlayerInput.MousePosition.X;
  MousePosition.Y = MouseInterfacePlayerInput.MousePosition.Y;
  // Deproject the mouse position and store it in the cached vectors
  Canvas.DeProject(MousePosition, MouseWorldOrigin, MouseWorldDirection);

  // Perform a trace to get the actual mouse world location.
  Trace(HitLocation, HitNormal, MouseWorldOrigin + MouseWorldDirection * 65536.f, MouseWorldOrigin , true,,, TRACEFLAG_Bullet);
  return HitLocation;
}

defaultproperties
{
  // Set to false if you wish to use Unreal's player input to retrieve the mouse coordinates
  UsingScaleForm=true
  CursorColor=(R=255,G=255,B=255,A=255)
  CursorTexture=Texture2D'EngineResources.Cursors.Arrow'
}

The logic of the HUD is as follows:

  • For every frame that is rendered, we render the mouse cursor
  • Get the current mouse interaction interface by performing a trace within the world. If the current mouse interaction interface differs from the last one, call the mouse over and or mouse out functions appropriately.
  • If we have a valid mouse interaction interface, the HUD then handles the deferred mouse input
  • For each mouse input call the appropriate interface function and reset the boolean

I just want to get the mouse 3D coordinates!

To get the mouse 3D coordinates based on the mouse 2D coordinates, you can use this function within your HUD class. The HUD class is required because the Deproject function is required.

MouseInterfaceHUD.uc
function Vector GetMouseWorldLocation()
{
  local MouseInterfacePlayerInput MouseInterfacePlayerInput;
  local Vector2D MousePosition;
  local Vector MouseWorldOrigin, MouseWorldDirection, HitLocation, HitNormal;

  // Ensure that we have a valid canvas and player owner
  if (Canvas == None || PlayerOwner == None)
  {
    return Vect(0, 0, 0);
  }

  // Type cast to get the new player input
  MouseInterfacePlayerInput = MouseInterfacePlayerInput(PlayerOwner.PlayerInput);

  // Ensure that the player input is valid
  if (MouseInterfacePlayerInput == None)
  {
    return Vect(0, 0, 0);
  }

  // We stored the mouse position as an IntPoint, but it's needed as a Vector2D
  MousePosition.X = MouseInterfacePlayerInput.MousePosition.X;
  MousePosition.Y = MouseInterfacePlayerInput.MousePosition.Y;
  // Deproject the mouse position and store it in the cached vectors
  Canvas.DeProject(MousePosition, MouseWorldOrigin, MouseWorldDirection);

  // Perform a trace to get the actual mouse world location.
  Trace(HitLocation, HitNormal, MouseWorldOrigin + MouseWorldDirection * 65536.f, MouseWorldOrigin , true,,, TRACEFLAG_Bullet);
  return HitLocation;
}

Related topics

Adding the Kismet Mouse Input Event


This is the custom Kismet event which allows Kismet to detect when an actor is interacted by the player.

SeqEvent_MouseInput.uc
class SeqEvent_MouseInput extends SequenceEvent;

var Vector HitLocation;
var Vector HitNormal;
var Vector MouseWorldOrigin;
var Vector MouseWorldDirection;

event Activated()
{
  local MouseInterfaceInteractionInterface MouseInteractionInterface;

  // Type cast the originator to ensure that it is a mouse interaction interface
  MouseInteractionInterface = MouseInterfaceInteractionInterface(Originator);

  if (MouseInteractionInterface != None)
  {
    // Get the appropriate values so we can push them out when the event is activated
    MouseWorldOrigin = MouseInteractionInterface.GetMouseWorldOrigin();
    MouseWorldDirection = MouseInteractionInterface.GetMouseWorldDirection();
    HitLocation = MouseInteractionInterface.GetHitLocation();
    HitNormal = MouseInteractionInterface.GetHitNormal();
  }
}

defaultproperties
{
  ObjName="Mouse Input"
  ObjCategory="Input"

  bPlayerOnly=false
  MaxTriggerCount=0

  OutputLinks(0)=(LinkDesc="Left Pressed")
  OutputLinks(1)=(LinkDesc="Left Released")
  OutputLinks(2)=(LinkDesc="Right Pressed")
  OutputLinks(3)=(LinkDesc="Right Released")
  OutputLinks(4)=(LinkDesc="Middle Pressed")
  OutputLinks(5)=(LinkDesc="Middle Released")
  OutputLinks(6)=(LinkDesc="Scroll Up")
  OutputLinks(7)=(LinkDesc="Scroll Down")
  OutputLinks(8)=(LinkDesc="Mouse Over")
  OutputLinks(9)=(LinkDesc="Mouse Out")

  VariableLinks(1)=(ExpectedType=class'SeqVar_Vector',LinkDesc="HitLocation",bWriteable=true,PropertyName=HitLocation)
  VariableLinks(2)=(ExpectedType=class'SeqVar_Vector',LinkDesc="HitNormal",bWriteable=true,PropertyName=HitNormal)
  VariableLinks(3)=(ExpectedType=class'SeqVar_Vector',LinkDesc="MouseWorldOrigin",bWriteable=true,PropertyName=MouseWorldOrigin)
  VariableLinks(4)=(ExpectedType=class'SeqVar_Vector',LinkDesc="MouseWorldDirection",bWriteable=true,PropertyName=MouseWorldDirection)
}

There isn't much logic within the Kismet mouse input sequence. This Kismet event really acts as a gateway between Unrealscript and Kismet. It is also able to output some of the calculated values from the HUD. Actors should implement the interaction interface, and put this Kismet node within the SupportedEvents array.

Related topics

Adding a subclassed KActor as an example


This is an example usage of the mouse interaction interface and the Kismet mouse input event node.

By implementing the mouse interaction interface, the mouse interface HUD is able to use it. This can be extended to a multitude of classes such as a Pawn, Projectile or Vehicles.

By supporting the mouse input Kismet sequence event, the level designer is able to take advantage of the code and create mouse interactions without having to write a line of code him/herself.

MouseInterfaceKActor.uc
class MouseInterfaceKActor extends KActor
  Implements(MouseInterfaceInteractionInterface);

var Vector CachedMouseHitLocation;
var Vector CachedMouseHitNormal;
var Vector CachedMouseWorldOrigin;
var Vector CachedMouseWorldDirection;

// ===
// MouseInterfaceInteractionInterface implementation
// ===
function MouseLeftPressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = HitLocation;
  CachedMouseHitNormal = HitNormal;
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 0);
}

function MouseLeftReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 1);
}

function MouseRightPressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = HitLocation;
  CachedMouseHitNormal = HitNormal;
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 2);
}

function MouseRightReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 3);
}

function MouseMiddlePressed(Vector MouseWorldOrigin, Vector MouseWorldDirection, Vector HitLocation, Vector HitNormal)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = HitLocation;
  CachedMouseHitNormal = HitNormal;
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 4);
}

function MouseMiddleReleased(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 5);
}

function MouseScrollUp(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 6);
}

function MouseScrollDown(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 7);
}

function MouseOver(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 8);
}

function MouseOut(Vector MouseWorldOrigin, Vector MouseWorldDirection)
{
  CachedMouseWorldOrigin = MouseWorldOrigin;
  CachedMouseWorldDirection = MouseWorldDirection;
  CachedMouseHitLocation = Vect(0.f, 0.f, 0.f);
  CachedMouseHitNormal = Vect(0.f, 0.f, 0.f);
  TriggerEventClass(class'SeqEvent_MouseInput', Self, 9);
}

function Vector GetHitLocation()
{
  return CachedMouseHitLocation;
}

function Vector GetHitNormal()
{
  return CachedMouseHitNormal;
}

function Vector GetMouseWorldOrigin()
{
  return CachedMouseWorldOrigin;
}

function Vector GetMouseWorldDirection()
{
  return CachedMouseWorldDirection;
}

defaultproperties
{
  SupportedEvents(4)=class'SeqEvent_MouseInput'
}

The logic behind the MouseInterfaceKActor is as follows:

  • When any of the interface implementation functions are executed via the HUD, we trigger the attached Mouse Input Kismet node. The number at end of the trigger call is the output index.

Using the Kismet Mouse Input Event


Create a new map and open up the WorldInfo properties. To do this, click on the "View" menu and click on the "World Info Properties". Expand the "World Info" tab. Set the GameType for PIE to the MouseInterfaceGameInfo. By default it will use UTDeathmatch.

Kismet_00_AdjustWorldInfo.jpg

Expand the "Lightmass" tab and uncheck the "Use Global Illumination" check box. There is no need to use light mass for the test map.

Kismet_01_AdjustWorldInfoLightmass.jpg

Open up the "Content Browser" and select the "Actor Classes" tab. From there expand the class tree select the "MouseInterfaceKActor". This is so that when you open up the context menu within the world viewport, it will allow you to add it within the world.

Kismet_02_SelectMouseInterfaceKActor.jpg

Right click within the world viewport to open the viewport context menu. Select and click "Add MouseInterfaceKActor Here" to add a MouseInterfaceKActor.

Kismet_03_AddMouseInterfaceKActor.jpg

Because the MouseInterfaceKActor doesn't have a visible static mesh, it will first appear as the selected transform widget (selection, movement, rotation or scaling widget). Press F4 to open up the actors property window. Click on the padlock icon at the top to set the selection.

Kismet_04_OpenTheKActorProperties.jpg

Find an interesting static mesh within the "Content Browser". Select it within the "Content Browser".

Kismet_05_SelectAPhysicsStaticMesh.jpg

Expand the "Dynamic SMActor" tab, expand the "Static Mesh Component" object, then lastly the "Static Mesh Component" tab. Press the green arrow next to the "Static Mesh" field. This will set the static mesh you've selected in the "Content Browser". You should see the barrel appear within the world viewport.

Kismet_06_SetTheStaticMesh.jpg

Add the necessary lighting within the level and compile the level.

Kismet_07_AddADominantDirectionalLight.jpg

Open up the Kismet window by pressing Kismet_08_OpenKismet.jpg within the Editor toolbar.

With the MouseInterfaceKActor still selected within the world viewport, right click within the empty space within the Kismet window to bring up a context menu. Add our new Mouse Input Event. This method ensures that our event is attached to the MouseInterfaceKActor that is in the world.

Kismet_09_CreateNewMouseInputEvent.jpg

This is our Mouse Input event. As you can see, all of the output links refer to the mouse input types that may occur. The event also outputs various variables that we can also use.

Kismet_10_TheMouseInputEvent.jpg

To test our Mouse Input event, let's create a few log actions.

Kismet_11_AddLogAction.jpg

Set the log action to output some text that is appropriate for the event output we want to test.

Kismet_12_SetLogAction.jpg

Create more log actions to test all of the event outputs. Connect them all up appropriately.

Kismet_13_DuplicateAndSetLogActions.jpg

Run the level within PIE and you should be able to test the mouse input Kismet interface. Remember that the mouse buttons and scrolling are only responsive when the mouse is over the MouseInterfaceKActor.

Kismet_14_Test.jpg

Let's expand this example by creating a sequence which allows us to shoot at the MouseInterfaceKActor. Start by creating a new vector variable. This is so that we can store the mouse origin and the mouse hit location.

Kismet_15_AddVectorVariable.jpg

Bind the vector variables to the event's variable outputs, "HitLocation" and "Mouse WorldOrigin".

Kismet_16_BindVectorToEvent.jpg

Add a new "Spawn Projectile" action. This creates a projectile when the action is executed.

Kismet_17_CreateSpawnProjectileAction.jpg

This is the newly created "Spawn Projectile" action.

Kismet_18_SpawnProjectileAction.jpg

Connect the "Spawn Projectile" action to the "Log" action which is activated when the left mouse button is pressed. Connect the "Mouse WorldOrigin" variable to the "Spawn Location" input on the "Spawn Projectile" event. Connect the "HitLocation" variable to the "Target Location" input on the "Spawn Projectile" event. This will cause the "Spawn Projectile" to spawn a projectile at the mouse world origin location that will travel towards the mouse hit location. Thus making it look like that we are shooting at the MouseInterfaceKActor in the world.

Kismet_19_ConnectSpawnProjectileAction.jpg

Set what kind of projectile to spawn.

Kismet_20_SetSpawnProjectileAction.jpg

Add an "All Players" variable. This is a required input of the "Spawn Projectile" action.

Kismet_21_AddPlayerVariable.jpg

Here is the newly created "All Players" variable.

Kismet_22_PlayerVariable.jpg

Connect the "All Players" variable to the "Spawn Projectile".

Kismet_23_ConnectPlayerVariable.jpg

Run PIE and test the Kismet. You should now be able to fire at the MouseInterfaceKActor.

Kismet_24_Test.jpg

Related topics

Downloads


  • Download the content used in this gem. (MouseInterface.zip)