UDN
Search public documentation:

DevelopmentKitGemsCreatingAMouseInterfaceJP
English Translation
中国翻译
한국어

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 ホーム > Unreal Development Kit Gems > マウス インターフェースの作成
UE3 ホーム > ユーザーインターフェースと HUD > マウス インターフェースの作成


マウス インターフェースの作成


2011年4月に UDK に即して最終テスト実施済み
PC 対応

概要


「Unreal Engine」は、デフォルトで一人称シューティングゲームに設定されています。しかし、適切なコードによって、他のゲームジャンル (たとえば、リアルタイムストラテジーゲームなど) を作成することも十分に可能です。

カーソルをスクリーン上に表示する


スクリーン上にカーソルを加えるには、いくつか新たなスクリプトを追加する必要があります。UIScene が削除されたため、マウスの位置を抽出する、かつての方法は使用できません。これには 2 つの方法があります。Unrealscript と ScaleForm です。

Unrealscript

独自のマウス位置付けコードを作成することができます。そのためには、変化の有無を調べるために aMouseXaMouseY に対してポーリングする (問い合わせる) カスタムのプレイヤー入力を追加します。変化が発生すると、 MousePosition 変数に追加され、スクリーン範囲内にマウスカーソルをとどめておくためにクランプされます。

以下が新たな GameInfo です。この単純な GameInfo は、使用する HUD クラスと PlayerController クラスを定義しています。

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'
}

次は、スクリーン上にマウスカーソルをレンダリングするために使用する HUD です。必要に応じて、マテリアルを使用することも可能です。

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'
}

このコードのロジックは、次のようになります。

  • PostRender が実行されると、PlayerOwner (これは、PlayerController です) から新たな PlayerInput を受け取ります。
  • 新たな PlayerInput を受け取ると、Canvas の位置を、新たな PlayerInput 内に保存されている PlayerInput にセットします。
  • さらに、カラーを、デフォルトプロパティの中で定義されているカーソルのカラーにセットします。
  • 最後に、マウスカーソルを表すテクスチャを描画します。

次は、使用する新たな PlayerInput を定義する、新たな PlayerController です。UpdateRotation は、元の関数が必要ないためスタブ化されています。

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'
}

次は、新たな PlayerInput です。

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
{
}

このコードのロジックは、次のようになります。

  • PlayerInput が実行されると、マウス位置を調整します。
  • HUD によって、マウス位置がビューポート内にクランプ (固定) されるようにします。PlayerInput は、PlayerController の中にあるオブジェクトであるため、PlayerController 内にある変数に直接アクセスすることができます。
  • 入力 config 内にバインドされている aMouseX と aMouseY を、MousePosition 変数に加えます。垂直方向の計算を反転させることによって、正しい計算結果を得るようにします。
  • 計算結果を クランプすることによって、マウス位置が常にビューポート内にとどまるようにします。

ここで、コンパイルします。新たなマップを作成し、PIE GameInfo を MouseInterfaceGameInfo にセットして、テストします。カーソルが表示され、動かすことができるはずです。次の画像では、赤い円の中にカーソルがあります。

MouseInterfacePhaseOne.jpg

ScaleForm

ScaleForm を使用することによっても、マウスの位置をポーリングすることができます。本記事では、スクリプトの大部分がほぼ同じままになっています。例外は、(ScaleForm を使用する場合) 上で作成されたカスタムのプレイヤー入力クラスが、マウスの変化をポーリングしなくなったということです。マウスの位置変化を ScaleForm が検知した場合は、その新たなマウスの位置を UnrealScript に渡します。

以下は、Adobe Flash で書かれた ActionScript です。マウスが動くと、ActionScript によって、カーソルというレイヤーの位置が、ScaleForm のマウス位置に設定され、Unrealscript にも ScaleForm のマウス位置が送られます。

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);

次は、ScaleForm ムービーの Unrealscript サイドです。ScaleForm がそのマウス位置の値を送るために使用するイベントが含まれています。

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
}

HUD は、ScaleForm が作成、初期化される場所です。HUD が破棄されたとき、実行されていたあらゆる ScaleForm の参照も確実に終了させます。 PreCalcValues は、スクリーンの解像度に変更があったか否かを検知することができる関数です。この関数が重要なのは、ScaleForm がそれら変更について通知を受け、それによってビューポートおよびスケールモード、アラインメントが再設定されて解像度に適合するようになるためです。最後に、ScaleForm がカーソルをレンダリングアウトしているため、HUD はカーソルをレンダリングしないように命じられます。

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();
}

最後に、新たな関数が MouseInterfacePlayerInput に追加されています。これによって、他のクラスが MousePosition 変数に書き出すことができるようになります。これは、マウス位置が常に 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
{
}

このように、ScaleForm はそれほど大きくロジックを変更していません。主な変更点は、マウスのカーソル現在位置を検知するために使用されているものです。

関連するテーマ

マウスの 2D 座標を取得する!

マウスの 2D 座標を取得するには、 MouseInterfacePlayerInput にアクセスするだけです。

HUD 内からマウスの 2D 座標を取得する。

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;
    }
  }

PlayerController 内でマウスの 2D 座標を取得する。

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;
  }

関連テーマ

マウス インタラクション インターフェースを追加する


次は、マウスのインタラクト可能なアクタによって実装されることになるインターフェースです。このインターフェースをパッケージに追加します。さらに多くのマウスの関数 (たとえば特別なボタンやマウス軸の動きなど) を実装する場合は、インターフェースに追加することによって、マウスのインタラクト可能なあらゆるアクタによって継承されるようにします。

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();

関連テーマ

PlayerController と HUD を使用して、マウス インタラクションを実現する


マウス入力の処理には、HUD 内にあるペンディングしている bool 型が使用されます。このようにした理由は、逆投影 (deprojection) にはキャンバスへのアクセスが必要となるからです。キャンバスはフレームごとに再割り当てされます。したがって、数フレームに渡ってキャンバスへの参照を保持することは不可能です。そのため、マウスボタンまたはマウスホイールが使用された場合は、それらの入力アクションを 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'
  Name="Default__MouseInterfaceController"
}

この PlayerController のロジックは以下のとおりです。

  • キーバインドを通じて実行関数が呼び出されます。 実行関数のどれが呼び出されても、その実行関数は HandleMouseInput を呼び出し、その HandleMouseInput が、アクションを HUD に引き渡します。

新たな実行関数を追加したので、それらにバインドすべき DefaultInput.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")

マウス入力が HUD に渡されると、HUD は PostRender 関数の中でこれらをフレームごとに処理します。フレームごとに、HUD は、ワールド内でマウスを表すトレースを実行することによって、現在のマウス インタラクション インターフェースを取得します。

より分かりやすくすると、下の画像において、緑の平面はワールド、赤の錘台はワールドビュー視錘体、青い点は 2D マウス位置、青い線はトレースをそれぞれ表します。錘台の平らな上表部 (top) はスクリーンを表しています。(この場合、ワールドはこの上表部の上から見ることになります)。キャンバスの逆投影関数によって 2D 位置がワールドに変換されます。

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;

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

  // 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);
    }
  }

  // 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);
    }
  }

  Super.PostRender();
}

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;
}

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

HUD のロジックは次のようになります。

  • レンダリングされるフレームごとに、マウスカーソルをレンダリングします。
  • ワールド内でトレースを実行することによって、現在のマウス インタラクション インターフェースを取得します。現在のマウス インタラクション インターフェースが前回のインターフェースと異なる場合は、マウスオーバー関数またはマウスアウト関数を適切に呼び出します。
  • 有効なマウス インタラクション インターフェースが得られた場合は、引き渡されたマウス入力を HUD が処理します。
  • 各マウス入力について、適切なインターフェースの関数を呼び出し、bool 型をリセットします。

マウスの 3D 座標を取得する!

マウスの 2D 座標に基づいてマウスの 3D 座標を取得するには、HUD クラス内でつぎの関数を使用します。Deproject (逆投影) 関数が必要なため、HUD クラスが必要となります。

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;
}

関連テーマ

Kismet マウス入力イベントを追加する


以下は、アクタがプレイヤーによってインタラクトされたときに Kismet が探知できるようにするためのカスタムの Kismet イベントです。

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)

  Name="Default__SeqEvent_MouseLeftClicked"
}

この Kismet マウス入力シーケンスには、それほど多くのロジックは含まれていません。Kismet イベントは実際に、UnrealScript と Kismet の間にあるゲートウェイとして機能します。また、HUD から計算された値のいくつかを出力することができます。アクタは、インタラクション インターフェースを実装し、SupportedEvents 配列内に、この Kismet ノードを追加する必要があります。

関連テーマ

サブクラス化した KActor を例として追加する


以下は、マウス インタラクション インターフェースと Kismet マウス入力イベントノードの使用例です。

マウス インタラクション インターフェースを実装することによって、マウス インターフェース HUD がそのインターフェースを使用することができます。このクラスは、Pawn や Projectile 、Vehicles といった多数のクラスに拡張することができます。

マウス入力 Kismet シーケンスイベントをサポートすることによって、レベル設計者は自身でコードを書かなくても、コードを利用することによってマウス インタラクションを作成することができるようになります。

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'
  Name="Default__MouseInterfaceKActor"
}

MouseInterfaceKActor のロジックは次のようになります。

  • インターフェース実装関数のどれかが HUD を通じて実行された場合、付属している Mouse Input Kismet ノードがトリガーされます。トリガーコールの最後にある番号が、出力インデックスとなります。

Kismet マウス入力イベントを使用する


新たなマップを作成し、WorldInfo プロパティを開きます。そのためには、[View] (ビュー) メニューをクリックし、[World Info Properties] (ワールド Info プロパティ) をクリックします。 [World Info] タブを展開します。GameType for PIE (PIE のためのゲームタイプ) を MouseInterfaceGameInfo にセットします。デフォルトでは、UTDeathmatch が使用されることになっています。

Kismet_00_AdjustWorldInfo.jpg

[Lightmass] タブを展開し、[Use Global Illumination] (グローバルイルミネーションを使用する) チェックボックスのチェックを外します。このテストマップに Lightmass を使用する必要はありません。

Kismet_01_AdjustWorldInfoLightmass.jpg

[Content Browser] (コンテンツブラウザ) を開き、[Actor Classes] (アクタクラス) タブを選択します。そこでクラスツリーを展開し、MouseInterfaceKActor を選択します。これによって、ワールドビューポート内でコンテクストメニューを開いたときに、このアクタをワールド内に追加できるようになります。

Kismet_02_SelectMouseInterfaceKActor.jpg

ワールドビューポート内で右クリックして、ビューポートコンテクストメニューを開きます。 [Add MouseInterfaceKActor Here] (ここに MouseInterfaceKActor を追加する) をクリックして、MouseInterfaceKActor を追加します。

Kismet_03_AddMouseInterfaceKActor.jpg

MouseInterfaceKActor は、可視的な静的メッシュをもたないため、最初は、選択された変形ウィジェット (選択、移動、回転、スケーリングウィジェット) として現れます。F4 キーを押し、アクタ プロパティ ウィンドウを開きます。最上部にあるロック (南京錠) のアイコンをクリックすることによって、選択をセットします。

Kismet_04_OpenTheKActorProperties.jpg

[Content Browser] 内で適当な静的メッシュを探します。 [Content Browser] 内でそのメッシュを選択します。

Kismet_05_SelectAPhysicsStaticMesh.jpg

[Dynamic SMActor] (動的 SMActor) タブを展開し、[Static Mesh Component] (静的メッシュコンポーネント) オブジェクトを選択し、最後に、[Static Mesh Component] (静的メッシュコンポーネント) タブを展開します。 [Static Mesh] (静的メッシュ) 入力フィールドの隣りにある緑の矢印をクリックします。これによって、[Content Browser] 内で選択した静的メッシュがセットされます。ワールドビューポート内に樽が表示されるはずです。

Kismet_06_SetTheStaticMesh.jpg

レベル内に必要な光源を追加し、レベルをコンパイルします。

Kismet_07_AddADominantDirectionalLight.jpg

エディタツールバー内の Kismet_08_OpenKismet.jpg をクリックすることによって、Kismet ウィンドウを開きます。

MouseInterfaceKActor がワールドビューポート内で選択されたままで、Kismet ウィンドウ内の空きスペースで右クリックし、コンテクストメニューを開きます。新たなマウス入力イベントを追加します。この方法によって、この新たなイベントが、ワールド内にある MouseInterfaceKActor に付属します。

Kismet_09_CreateNewMouseInputEvent.jpg

次は、この新たなマウス入力イベントです。出力リンクのすべてが、発生する可能性のあるマウス入力イベントのタイプにつながっていることが分かります。また、このイベントは、利用可能なさまざまな変数を出力します。

Kismet_10_TheMouseInputEvent.jpg

このマウス入力イベントをテストするために、ログアクションをいくつか作成してみます。

Kismet_11_AddLogAction.jpg

テスト対象のイベント出力に適切なテキストを出力するように、ログアクションを設定します。

Kismet_12_SetLogAction.jpg

イベント出力すべてをテストするログアクションをさらに作成します。これらすべてを適切に接続します。

Kismet_13_DuplicateAndSetLogActions.jpg

PIE (Play In Editor) 内でレベルを起動すると、マウス入力 Kismet インターフェースをテストすることができます。なお、マウスボタンとマウススクロールは、MouseInterfaceKActor 上にマウスが位置しているときにのみ反応します。

Kismet_14_Test.jpg

MouseInterfaceKActor を撃つことができるようにするシーケンスを作成して、この例を拡張してみます。まず、新たなベクター変数を作成します。このベクター変数は、マウスの原点とマウスのヒット位置を保存するためのものです。

Kismet_15_AddVectorVariable.jpg

これらベクター変数を、イベントの変数出力である HitLocation および Mouse WorldOrigin にバインドします。

Kismet_16_BindVectorToEvent.jpg

新たな Spawn Projectile (発射物をスポーンする) アクションを追加します。このアクションが実行されると、発射物が作成されます。

Kismet_17_CreateSpawnProjectileAction.jpg

次が、新たに作成された Spawn Projectile (発射物をスポーンする) アクションです。

Kismet_18_SpawnProjectileAction.jpg

左マウスボタンが押されたときにアクティベートされる Log アクションに、この Spawn Projectile アクションを接続します。Mouse WorldOrigin (マウスワールド原点) 変数を、Spawn Projectile イベントの Spawn Location (スポーン位置) 入力に接続します。 HitLocation (ヒット位置) 変数を、Spawn Projectile イベントの Target Location (ターゲット位置) 入力に接続します。これによって、Spawn Projectile がマウスワールド原点で発射物をスポーンできるようになり、発射物がマウスヒット位置に向かって飛んで行くようになります。このようにして、ワールド内の MouseInterfaceKActor を狙って撃ったように見えるのです。

Kismet_19_ConnectSpawnProjectileAction.jpg

スポーンする発射物の種類をセットします。

Kismet_20_SetSpawnProjectileAction.jpg

All Players (全プレイヤー) 変数を追加します。これは、Spawn Projectile アクションに必要となる入力です。

Kismet_21_AddPlayerVariable.jpg

以下のように、All Players 変数が新たに作成されました。

Kismet_22_PlayerVariable.jpg

All Players 変数を Spawn Projectile に接続します。

Kismet_23_ConnectPlayerVariable.jpg

PIE で起動して、この Kismet をテストします。これで MouseInterfaceKActor に向かって発砲することができるはずです。

Kismet_24_Test.jpg

関連テーマ

ダウンロード


  • 以上のサンプルで使用したコンテンツは、 ここ からダウンロードすることができます。 (MouseInterface.zip)