#このチュートリアルはUE5.0まで支持できます。
5.1以降のはいろんなバグが出てくる。(主にInputの方です)
時間があったら5.1より新しいバージョンのチュートリアルを書いてみる
先ずは最終結果を見てみましょう。
上手くタイムリバースな感じができましたね。
では、始めましょう!。
0.予備知識(上達者はスキップしてもいい)Blueprintsの種類
Blueprintsは山ほどあるが、今回使える四種類だけを紹介する
1. Actor
名の通りアクターの意味、ゲーム世界を舞台に想像すればこの舞台の俳優達だね。Blueprintsの基本単位である。
2. Component
パーツの意味だね。Actorは一眼カメラだったらComponentはカメラのレンズみたいな感じ。でも一つのActorに複数のComponentを付けるのも可能。直接Actorにプログラムを導入するのも可能だが、プロジェクトが大きくなったらめんどくさくなるからあんまり勧めしないです。
3. Structure
「データを保存する時はこのストラクチャーで保存する」っで意味。まぁ、説明難しいけど、使えば分かれるので、後で直接使ってみよう。
4. Interface
ライングループのようなもの。同じInterface類に入るとデータのやりとりはより簡単にできる。
1.構想とプロジェクトの設定
1.タイムリバースを実現する方法
ある物は時間とともに動いてる。そして変化している物は Location(位置),Rotation(回す),LinerVelocity(動く速度),AngularVelocity(回す速度)四つある。だから毎フレームでこの四つのデータを読んで記録すれば、この物の毎フレームの状態が復元できる。
このようなデータ配列を作って、各フレームのLocation(位置),Rotation(回す),LinerVelocity(動く速度),AngularVelocity(回す速度)を記入して、タイムリバースする時は黄色の矢印のように最後から読み込めばタイムリバース機能ができる。
2.プロジェクトを新規作成
今度使うのはUE5ですが、UE4でもできるはずですよ。今回はFPSゲームをベースとして作りたいと思いますので、ゲームで新規作成しましょう。
関数の名前やBlueprintsのターミナルも英語で作るので、エンジンの言語設定は英語がお勧め。
3.入力の設定
タイムリバースを発動するための設定
Edit→ProjectSetting
Input→ActionMappings
ここで好きな入力方法(私の場合はマウスの右クリック)を設定する。そしてTimeReverseという名前をつける。
これで事前準備を完成しました!さっさと本番の作成に入りましょう!
2.配列の作成と導入
1.配列の作成
Contents→FirstPerson→Blueprintsフォルダーをオープンして。空いてるところで右クリック→Blueprints→Structueを作成して、TimeInfoという名前を付けて保存。
ダブルクリックして、ストラクチャーをLocation(Vector),Rotation(Rotator),LinerVelocity(Vector),AngularVelocity(Vector)に設定する。この四つ以外に時間も記入しなければならないので、DeltaTime(Float)も設定する。
2.配列の導入
空いてるところで右クリック→Blueprints Class→ActorComponentを作成して、TimeComponentという名前を付けて保存。これはタイムリバースを実現するための一番重要なコンポーネント、色々なプログラムはここに書く。
TimeComponentのVariablesのところで新しい変数を作って、TimeFramesという名前を付ける。ここは物の運動データを保存するところので配列Variable TypeをArrayに設定してください
3.配列をActorへの導入
配列は既にTimeComponentに導入したので、TimeComponentをActorに付けばActorへの導入も完成できる。
先ずはActorを作りましょう。空いてるところで右クリック→Blueprints Class→Actorを作成して、MyActorという名前を付けて保存。
MyActorをダブルクリックして、Add→TimeComponentを付ける。Variables→ComponentのところでTimeComponentを見えるなら導入成功。
このActorの実体→StaticMeshを作成しよう、作成したらStaticMeshをDefaultSceneRootのところにDragAndDropする。こうしたらStaticMeshはこのActorの一番偉い物になる、StaticMeshの動きはこのActorの動きになる。
StaticMeshのところでこの青い箱を選択してください。
そしてSimulatePhysicsのところのチェックボックスをYesにしてください。
3.Interfaceの作成と導入
1.Interfaceの作成
空いてるところで右クリック→Blueprints→Blueprint Interfaceを作成して、BI_Timeという名前を付けて保存。
ダブルクリックして、新しい関数を作ってUpdateTimeReverseという名前を付けて、InputのところでIsTimeReversingという変数を作成する。 ここの変数はメッセージを送るや受ける時に読み込めるデータである。
2.Interfaceの導入
BP_FirstPersonCharacterを開いてClassSettingでInherited InterfaceのところでAdd→BI_Timeで、先ほど作ったInterfaceを導入する。BP_FirstPersonCharacterはプレイヤー操作するキャラクターなので他のActorへのメッセージをここから発信する。
同じ操作でTimeComponentにもBI_Timeを導入する。
今回のプロジェクトには発信者と受信者一人つづで、発信するBlueprintsと受信するBlueprintsも分けてるが、実際に複数のBlueprintsをお互いに発信、受信はできる。背景知識のところに書いていたようにライングループのようだね。
3.Interfaceをテストする。
テストとは言え、ここで描いたBlueprintsは本番にも使うから スキップしないでください。
先ずはBP_FirstPersonCharacterのところでTimeCubeという変数を作り、TimeCubeという名前を付け、データタイプはMyActor(青いやつ)。VariableTypeをArryにしてください。
このようなBlueprintsを書いてください。Blueprintsの書き方はマウスを右クリックして、そしてターミナルの名前を入力すれば正しいターミナルを出る。そして線を繋がるでオッケ。
ここのBlueprintsの意味はこのBlueprintsを動き始める時、シーンの中の全部のMyActorというActorの連絡先をTimeCubeに保存する。
*実際に保存したのは連絡先ではなくて、Actorのコピーである(ここはあくまでも理解しやすいために不適切な例えをした)。今回のプログラムには連絡先として働いているが、実際にこの変数でできることがもっとある。
次はこのようなBlueprintsを書いてください。
意味はTimeCubeの中に記録したActor(始める時このシーンの中の全部のActor)を一つつづ読んでMyActorに付けたTimeComponentを受信者としてUpdateTimeReverse関数を利用して、IsTimeRersingという変数を送る。
次はTimeComponentでこのようなBlueprintsを書いてください。
意味はUpdateTimeReverse関数が動く時、この関数のIsTimeRersing変数を読んでPrintする
完成したら、両方Compileして。Alt+Pでゲームを実行してみよう。
画面の左上にマウスの右キーを押した場合Tureを見えて、外した場合はFalseを見えたらInterfaceの導入は成功した。
4.TimeReverseプログラムの作成
いよいよプログラムの作成に入った。
では復習として、やるべきことをもう一度確認しよう。
1.物の動きを読み込んで、配列に記録する。
2.配列を最後から読み込んで、物の状態を変える。
では、やりましょう!
1.事前準備
TimeComponentを開いて、下のスクリーンショットのように変数と関数を作成する。
ここで変数と関数の意味を説明する:
変数
TimeFrames:これはもう作ったもの。
IsTimeRersing:Tureの場合は配列を最後から読み込んで、物の状態を変えるする、Falseの場合は物の動きを読み込んで、配列に記録する。
IsOutData:記録したデータは全部読んだかどうか。(こう読むと全然意味わからんかも知らないが、後で使う時に詳しく説明する)。
RecordTimeLength:記録したデータ最初から最後までの長さ(例えば「ここまで5秒のデータが記録した」の5を保存する場所)。
Owner:TimeComponentの所有者(TimeComponentを付けられたActor)。
IsInited:プログラムの安全性のため付けた。このプログラムがイニシャライズされたかを記録する変数。
関数
RecordTimeInfo:物の動きを読み込んで、配列に記録する関数。
ExcuTimeInfo:配列を最後から読み込んで、物の状態を変える関数。
__________________________________________
そしてTimeComponentのEventGraphにこのように書いてください。
先ずこのプログラムはイニシャライズされたかを判断する、もしイニシャライズされたらなんもしない、されなかった場合はIsInitedをTrueにして(イニシャライズ完了で意味)、そして所有者よ読み込んで、Ownerに設定する。
そしてこのように書いてください。EventTickの意味はフレームつづする事件。最初はイニシャライズされたかどうかを判断して。された場合はIsTimeRersingを判断して、Falseの場合はRecordTimeInfoを実行する(ここでRecordTimeのInputのところ新しい変数を作成する)。Tureの場合は先ずIsOutDataで配列のデータは全部読んだかを判断する、まだ全部読んでない場合はExcuTimeInfoを実行する。
RecordTimeInfoに送ったDeltaSecondsの意味はこのフレームの時間(10fpsの場合は一フレーム0.1秒)。
2.RecordTimeInfoを書く
先ずはこのように書いてください。
意味はRecordTime関数が実行された場合、先ず所有者からStaticMeshを読んで、安全性のために有効かどうかを確認する。有効の場合だけで次にいく。そしてRecordTimeLengthを設定した値(ここの値はタイムリバース可能の一番長い時間、私のように10に設定した場合は10秒前までリバース可能っで意味)より小さいかどうかを判断する。Trueの場合はOwnerのStaticMeshのLinerVelocity,AngularVelocityと**OwnerのLocation(位置),Rotation(回す)**読み込んで、TimeFrames配列の中に新しい要素を以上のデータで作成して、配列に書き込み。そして、RecordTimeInfoからInputされたNewParam(これはこのフレームに掛かった時間)も要素に書き込んでから、RecordTimeLengthと加算する(これで今まで記録した時間がわかる。)最後はIsOutDataをFalseに設定する(配列の中にまだ読んだことがないデータがあるを示す)。
「画像が小さかった場合は画像をクリックして見たら?」
次はRecordTimeLengthは10秒より長い場合(Falseの場合)を書く
先ずはTimeFrames配列の長さを最後索引に引いて(結果はこの配列の中に一番古い要素の索引)、一番古い要素のDeltaTimeを読んでRecordTimeLengthに引く(これでRecordTimeLengthは消した要素分短くなった)。そこでTimeFrames配列で一番古い要素を消す。
この処理をしたら判断のところに戻して新しいデータを書き込む
RecordTimeInfo関数の全体的のBlueprintsはこういう感じのはず。
3.ExcuTimeInfoを書く
先ずはTimeFrames配列の長さを読んで、0<=の場合はTimeFrames配列の中にデータがもうないの意味だから、IsOutDataをTureにするだけで終わる。
0より大きいの意味はTimeFrames配列の中にデータがある、タイムリバースが可能だから、TimeFrames配列の中のLastIndex(最新のデータ)を読んで、一対一対応してOwnerのStaticMeshのLinerVelocity,AngularVelocityとOwnerのLocation,Rotationに設定すればいい。全部設定したら先読んだフレームの時間をRecordTimeLengthに引いて、データ(TimeFrames配列の中のLastIndex)を消せば終わり
5.遊んでみよう!
先ほど作ったMyActor以外にも、他のActorにTimeComponentを付けばタイムリバースができる。そしてMyActorを複数シーンの中に放置してもいいだから、色々やってみよう!
色々Bugが出る可能性があるが、それは私のプログラムの問題かも知れないので、課題として自分で解決してみよう!
又は何かわからないところがあるもしくBugが発見された場合は私と連絡してみよう Twitter:DrunkSouzan
6.リファレンス
このゲームのプログラムは@游戏人YRのビデオ(中国語)を参考しながら作ったので。中国語ができる人はこのビデオがおすすめだね。
https://www.bilibili.com/video/BV1de4y187nZ/?spm_id_from=333.337.top_right_bar_window_custom_collection.content.click