Unreal Engine 4.25 でアクター（物体、オブジェクト）をマウスで操作するプログラムを書く

UE5 版の記事は下記に作成しました。

- [Unreal Engine 5.3 でアクター（物体、オブジェクト）をマウスで操作するプログラムを書く](https://qiita.com/mml/items/c7663a37f32e93eee572)

# やること

画面内に表示されているアクターをマウスでクリック（ドラッグ）して、そのまま動かしたい先にアクターを移動できるようにするブループリントの書き方についての忘備録です。

実現するには、２つのステップが必要です。

- マウスの入力を受け付ける PlayerController を作成する。
- マウスカーソルでアクターを「掴んで」動かすブループリントのプログラムを書く。

## 参考

- [マウス操作の設定](https://docs.unrealengine.com/ja/Resources/ContentExamples/MouseInterface/MouseControlSetup/index.html)；公式
- [【UE4】物体を「持つ」！「運ぶ」！「放す」！](http://shuntaendo.hatenablog.com/entry/2016/03/04/004331)
- [UE4 マウスの位置をワールド座標に変換する](https://qiita.com/nkdtr/items/3a6768f6a60996136794)

上記の記事を組み合わせるとできるのですが、4.25 では異なっている部分が結構あったので、以下では 4.25 で動く手順をメモしておきます。4.27 でも途中画面が少し違うところがありましたが、動くことは確認しました。

# 準備

スターターコンテンツなしで空のプロジェクトを作ったところから始めます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/62a116ca-e0ba-20cf-a3cf-94bcccc64a90.png)

こんな感じになってるはずです。

# マウスイベントを取得できるようにする

デフォルトで用意されている PlayerController がマウスイベントを取得しないようになっているので、マウスイベントを取得できる PlayerController を作ってレベルに設定します。

## マウスイベントを受け取る PlayerController クラスの作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/46919a7f-7b1d-d971-079b-0ab97ed96d8a.png)

コンテンツブラウザ内で右クリックして、ブループリントクラスを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/970f6120-87f8-2045-6238-2ad9bd8cd96f.png)

親クラスを選択というウィンドが出ます。その中に Player Controller という項目があるので、それを選びます。すると、コンテンツブラウザの中に NewBluePrint という項目ができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ddb2ecfb-6737-e411-387d-5fe6ae17be61.png)

NewBluePrint という名前では何のために作ったクラスか後でわからなくなるので、ここでは名前のところをダブルクリックして MousePlayerController という名前に変更しておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ef5f1876-a388-034e-b4b8-7176a6498c89.png)

クラス名はこの名前でなくても問題ないですが、ここでは MousePlayerController という名前にしたという前提で、以下説明します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/860ad443-8460-a8b0-d136-2e290de10a72.png)

コンテンツブラウザの MousePlayerController をダブルクリックするか、右クリックでメニューを出して「編集」とすると、上のようなウィンドウが出ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/8db1d5b2-deb6-7432-e7f5-8480df3ac35a.png)

右のほうにある、マウスインターフェイスという項目の中にある Enable Click Events というところにチェックを入れます。これでマウスのクリックを拾ってくれるようになります。その上にある Show Mouse Cursor にチェックを入れると、マウスカーソルが表示されるようになります。

## 作成した PlayerController クラスを使う Game Mode Base クラスを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/46919a7f-7b1d-d971-079b-0ab97ed96d8a.png)

もう一度、メインの画面に戻って、今テンスブラウザで右クリックしてブループリントクラスを再び選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/028e419f-2118-556c-13bb-1cd3684f267b.png)

今度は **Game Mode Base** という親クラスを選びます。4.24 では GameMode はこのメニューの親クラスとしては選択できなくなっていて、代わりに Game Mode Base が選べるようになっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f159a8fa-565c-487c-57eb-e9f64fee86bf.png)

コンテンツブラウザ内に、また NewBluePrint クラスができるので、MouseGameMode という名前に変更します。この名前も何でもいいですが、ここではこの名前に変更したものとして説明します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2c515629-eb90-d0aa-85ee-1836d50a1bab.png)

MouseGameMode をダブルクリックして開いたウィンドウの右に、Classes という項目があるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/3f7e303d-3475-53b5-cbc1-4b2d348a68f5.png)

その中の、Player Controller Class という項目のポップアップメニューを開いて、Mouse Player Controller を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/9df54791-d682-2d71-4f94-fa8c6593262a.png)

メインの画面に戻って、画面上部にある「ブループリント」のメニューから、ゲームモード、Game Mode Base を選択、と進んで MouseGameMode を選びます。

これで「プレイ」すればマウスのクリックを拾ってくれるはずですが、クリックのイベントを受け取るアクターが何もないので、確認する方法がありません。

## アクターでクリックイベントを受け取るようにする

立方体のアクターを画面内において、その立方体をクリックしたら画面に「クリックしたで！」と表示するようにしてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/78b49bd3-3f09-3d7d-1ebb-7e105c01e1f6.png)

左側の「モード」から「基本」を選んで、その中にある「キューブ」をドラッグドロップして画面内にキューブを配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f06f2c4f-f460-d234-871b-8fb1b18d289a.png)

配置する場所は、最初からおいてある Floor の中央より奥側（X軸の正の方向）に置きます。カメラがFloor の中央付近から X 軸の正方向を向いた状態で置かれているので、そのあたりに置かないとプレイしたときにキューブが表示されません。もちろん、カメラを移動させて好きな位置にキューブを配置してもOKです。ここでは、x,y,z = 280, 10, 70 くらいのところにキューブを置いてみました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/043abba8-a493-d47b-4338-a7c2265ab561.png)

キューブが配置出来たら、右にある「アウトライナ」の中で Cube という項目を選んで、その下のほうにある「ブループリント／スクリプトを追加」というボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/0b918d36-f436-ad01-a33e-e6c4766a2f07.png)

こんな感じのウィンドウが出たら、そのまま「ブループリントを作成」を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/1de9466e-193d-8578-63a7-6f5464b50316.png)

こんな感じのウィンドウが出るので、上にある「イベントグラフ」というタブを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a4e5bdb3-46da-0f9e-3f0e-ac35169a0fe1.png)

イベントグラフの中で右クリックして、ActorOn と入力します。すると、下のリストの中に ActorOnClicked という項目が出てくるので、それを選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/574cb993-8b8d-f643-e860-80183c8e10a9.png)

これで、キューブがクリックされたときのイベントが受け取れるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/31cd549c-1fe5-2f8a-fb6c-8242eeec55a1.png)

同様にして、右クリックしてから PrintText と入力して PrintText ノードを配置します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/0fdd07b4-cf80-702d-5d9e-3b9cde246cd1.png)

配置したら線をつなげて、InText のところは Clicked に変更しておきます。これで、Cube がクリックされたら画面に Clicked と表示されるようになります。

## 実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ee9b7d0f-d34f-09a7-e1e2-45db4a00a89d.png)

メインの画面に戻って、プレイボタンを押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/f42056fd-5434-32a5-8bd3-0da6dfa5cf7c.png)

キューブをクリックすると、画面の左側に Clicked と表示されるはずです。されなければ、ここまでの手順のどこかが間違っています…

これで、クリックイベントが受け取れるようになりました。

# クリックしたアクター（オブジェクト、物体）をつかんで移動できるようにする

ここからは、クリックしたアクターをマウスで移動できるようにする方法についてです。マウスでの移動については簡単にできる関数のようなものは用意されていなくて、それなりにブループリントでイベントグラフを描く必要があります。やることは、だいたい以下の通りです。

1. マウスのボタンがクリックされたら、マウスカーソルの二次元座標をワールド座標に変換して、マウスカーソルのある位置に存在するワールド上のアクターを、ライントレース機能を使って取得する。
1. マウスカーソルが1フレーム間に移動した量を計算し、アクターの位置をその移動量に合わせて変更する。
1. マウスのボタンが離されたら、アクターの位置の更新を止める。

こんな感じです。1. が少し大変ですが、基本的には内容を理解してなくてもコピペで実現できます(多分)。

なお、ブループリントはレベルブループリントに書く前提です。レベルブループリントでないところに書いても実現できますが、変数やイベント、関数などの書き方を少し変える必要があるかもしれません。

## 動かされる側のアクターの Simulate Physics をオンにする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/04de1d98-7c3b-eda5-06f4-531afd53779b.png)

さきほど追加したキューブの Simulate Physics にチェックを入れます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/9e40319b-f8ab-b78b-8dc1-2062914aad97.png)

「可動性」がムーバブルになっていることも確認します（なってなければムーバブルにする）。

これらを設定しておかないと、動かすことができません。

## クリックしたときの処理

レベルブループリントにプログラムを書いていきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/d0fd6331-0d7a-4353-d66b-6b2176448ae6.png)

全体図は以下のような感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/da80f9aa-42e7-5963-a5e4-37c0535a29e8.png)

変数リストです。内容については後述します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/2dc7ee3d-8b46-c071-c60d-cd428e954000.png)

このへんは、マウスカーソルの位置をワールド座標に変換して、マウスカーソルから衝突判定のためのビーム（？）を発射するところです。LineTraceForObjects でビームを発射して、ぶつかったオブジェクトの情報を取得しています。Draw Debug Type を for Duration にしておくと、発射されたビームが赤い線で表示され、物体にぶつかった位置が赤い矩形で表示されるようになります（本番時に指定を解除するのを忘れずに）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/69ce62ef-f17e-ab18-2464-9fdaa8f4b6f8.png)

上のグラフは、発射されたビームがぶつかった Physics Object を見つけて、その Object を「掴んだ」状態にするまでのところです。掴むためには、Physics Handle Component というものを使います。このコンポーネントを使うことで、ビームがヒットしたオブジェクトをつかんだ状態にすることができます。掴んだ後は、掴んだオブジェクトを移動させるのではなくて、掴んだ側の Physics Handle Component のほうを動かします。Physics Handle Component は物をつかむためのロボットアームのようなものだと思えばだいたいあってます（多分）。

BreakHitResult は、LineTraceForObjects で得られた発射したビームがアクターにぶつかった場所や角度などの情報が入ったオブジェクトを、位置情報やぶつかったオブジェクトなどの個々の情報に分解しています。そして GrabComponentAtLocationWithRotation というノードで、ビームのぶつかったオブジェクトをロボットアーム（Physics Handle Component）でつかみ、Physics Handle Component の初期位置をつかんだオブジェクトの位置に設定している、という感じです。「Physics Handle Component を追加」というノードは、AddPhysicsHandleComponent という名前で検索できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b323afb7-e469-a9bc-059e-9bf14891c659.png)

上のグラフは、アクターを「掴んでいる」という状態を Handled という変数で覚えておきつつ、掴んだ側のロボットアームを Physics Object 変数に、掴まれた側を HandledObject 変数に入れています。これらは、オブジェクトを動かしたり、離したりする処理のために覚えておく必要があります。

### 変数について

PreLocation は、オブジェクトをつかんだ時の最初のマウスカーソルの位置を記録しする変数です。マウスカーソルを動かしたときに、PreLocation との差分をとることで、どの方向に動かせばよいかをわかるようにしています。一度動かした後は、直前のマウスカーソルの位置を記録するようにしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/e1d938de-9235-f194-105c-3c3e8e23bf3c.png)

TraceDistance は、マウスカーソルから発射するビームの長さを決める変数です。1でカメラから 1cm だけビームを飛ばすので、最低でも100 程度の数字を入れないと、少し離れたアクターすらつかめないと思います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/fb2f6895-3e79-b165-953b-c4f5881627be.png)

ObjectTypes は、ビームを発射して衝突の判定をするときに、衝突したかどうかを調べるオブジェクトのタイプを指定するための変数です。先に LineTraceForObjects のノードを置いてから、線を引っ張り出してきて「変数に昇格」して作るほうが楽です。デフォルト値は、＋を押してから Physics Body を選びます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/a0a2703f-ff78-deaa-d35b-ae352e4755f0.png)

ちなみに LineTraceByChannel ノードを使うと、オブジェクトのタイプを指定しなくても、オブジェクトを見つけられます。ただし、その場合は Physics ではないオブジェクトも拾ってしまいます。今回のように、動かせるオブジェクトだけを見つけたい場合は、Physics Body を選んだほうが良いでしょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/098fdf4f-ea18-09ff-b1c5-60e02c3cf129.png)

PhysicsObject は上で説明したように、オブジェクトを「掴む」ことができる、ロボットアームのようなコンポーネントを入れておく変数です。マウスカーソルを動かしたときには、このコンポーネントを動かすことで、間接的に移動させたいオブジェクトを移動させることができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/3388c9e5-b03d-3724-4232-ac0a677280a2.png)

作成するときは、Add Physics Handle Component ノードの Return Value から線を引き出して「変数を昇格」して作成すると楽です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/974ce6b3-4c0c-8a65-e409-bb6709772b96.png)

HandledObject は掴まれた側のコンポーネント（オブジェクト）を入れておく変数です。移動させる処理の間には使いませんが、「離す」処理をするときに必要になるので、変数に入れておきます。これも、作成するときは BreakHitResult ノードの Hit Component から線を引き出して「変数に昇格」すると作るのが楽です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/ea55273b-f5e0-51a5-cc1d-6850e42d5182.png)

Handled はオブジェクトをつかんでいる状態を覚えておくための変数です。デフォルト値は False （チェックなし）にする必要があります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/d655cce5-6ea7-ad06-3aa8-488bf1d81e16.png)


## ボタンを離したときの処理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/317d61db-7c7e-8f40-658d-dde6d43395a9.png)

掴んだオブジェクトを離す処理は、ReleaseComponent ノードで行います。同時に、掴んでいる状態を示す Handled 変数を False にしています。

## オブジェクトを移動する処理

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/266247/b950eb74-b8a9-73fc-dd6d-91292640a8f1.png)

オブジェクトの移動は、毎Tick計算する必要があります。移動の計算方法は、いろいろなやり方があると思うのですが、一番シンプルっぽい方法で書いたものが上の図です（それでも結構ややこしいですが…）。

マウスカーソルの毎Tickの位置を PreLocation 変数で覚えておいて、前のTickと現在のTickでのマウスカーソルの位置の差分を計算してます。そして、掴んでいる「ロボットアーム」に相当する Physical Object の位置を、マウスカーソルの移動量に合わせて移動させています。

移動量を50倍しているのは、マウスカーソルはあくまでカメラのレンズの面上を移動しているだけなので、ワールド座標系に変換されたときの移動量が非常に小さくなっているからです。倍率が１とか２くらいだとカーソルを動かしても微動だにしません。UEって結構大きな数字を倍率にしないと微動だにしなくて、プログラムがバグってるのかと思ったら、単に倍率が小さすぎただけみたいなこと多いよね…

GetTargetLocationAndRotation の TargetRotation 出力を SetTargetLocationAndRotation につながないと、動かしているときに物体が回転することがあります。物体の向きを変えたくない場合は、この線をつないでおく必要があります（とはいえ、落としたりすると勝手に回ってしまったりしますが）。

## 余談

この計算方法ではオブジェクト画面に対して前後方向に動かすことはできません。マウスはあくまで画面に対して上下左右方向にしか動かないからです。前後方向にも動かしたい場合は、マウスの移動量から前後方向への移動量を自分で計算するなどする必要があります。

プロジェクトのデフォルトのまま （DefaultPawn のまま）で上記のプログラムを動かした場合、マウスでオブジェクトをつかんだまま AWSD キーでカメラ位置を移動すると、オブジェクトがあらぬ方向にすっとんでいきます。カメラの移動にオブジェクトもついていくようにしたければ、カメラの移動量も計算式に入れる必要があります。カメラを固定したい場合は、Pawn クラスを自分で作成する必要があります（多分）。

マウスカーソルでのオブジェクトの移動は、UEにしては珍しく（？）面倒な感じがするので、いつか劇的に改善されてこの記事が無駄になるかもしれません。むしろなったらいいな。とはいえ、すでに5年くらい仕様が変わってないようなので、変更があるにしても近い将来（UE5とか）ではないかもしれません…