<前文>
先週に続き、日本人が誤解しているアメリカ、特に日本文化とキリスト教文化の違いから生じる誤解とその解決法について気が付く範囲ですが書いて置きます。
<守破離は日本固有の文化>
皆さんは、守破離って聞いた事ありますか?
武道などの習い事で、よく言われる言葉ですが、簡単に言えば上達のためのノウハウみたいなのものです。
初心者の段階は、師匠の言う事が理不尽だったり、良く理解出来なくても言われた通りにやる事が最も上達が速いです。
中級者になって師匠の言っている意味が完全に理解出来たら、自分の場合に当てはめて、敢えて師匠から教わった内容を破る事が必要になります。中級者になっても師匠の言っている事をずっと守っているだけではそれ以上の上達は見込めません。
最後の段階では、自分の自由にやります。教わった内容からは全く離れます。
これは別に日本の伝統技能に限らず、何にでも応用出来ます。
例えばProgrammingの勉強だったら、
最初はSample codeに何が書いてあるのか理解は出来なくても丸写ししてしまいます。そして何回も読んだり、分からない部分を調べたりします。この段階では、このサンプルコードは絶対正しいと仮定して勉強します。つまり守の段階です。
Sample codeに何が書いてあるのか大体理解したら、次の段階です。少しだけコードを変更してみます。自分の理解が正しいかそれで確認します。そうです。少しだけSample codeを破壊します。ので破の段階です。
最後の段階は、自分の目的に最適化するように全部書き直します。つまり離の段階です。
皆さんはこれって当たり前の事だと思うかもしれませんが、この考え方って日本独自の考え方なんですよ。
アメリカではというかキリスト教文化圏では、守破離と言う考え方自体がありません。ではどうやって師匠が弟子に教育するのかと言うと、質問、考える(調べる)、回答です。
まず師匠が弟子に質問するんです。最初は弟子は質問の意味も分かりません。
弟子はひたすら考えたり調べたりします。
そして弟子が師匠の質問の意味を正しく理解して、これが質問の答えだと自信を持って言える様になると師匠と面会します。
弟子は自分が正しいと思う答えを師匠に回答します。すると師匠は弟子の回答を検討したり、導き方を厳しくチェックしたりします。そして弟子が正しく理解しているのを確認すると次の質問をします。
そして最初に戻ります。
こうやって質問、考える(調べる)、回答を繰り返す事で弟子を教育していきます。
私がアメリカの大学の研究室に入った時もこんな感じでした。最初に教授が課題を出すんです。
それで終。
来週までにやってこいと。
因みに課題に書かれている内容なんか全く理解出来ません。でも担当の教授は一切質問は受け付けません。仕方ないので自分で一週間ひたすら調べます。すると何となく課題の言っている事が分かって来て、こんな事を教授は言っているに違いないとコードを書いて持っていきます。
すると教授は私が書いたコードを逐一チェックして、ここはどうしてこうしたとか、厳しく聞いてきます。
私がそれはこんな理由でそう書きました。とか調べたらこんな資料が見つかって、そこにはこう説明されていた。から参考にしました。と言うと、凄い真剣に聞いていて、フンフンとうなずいたりします。
そして教授が言うんです。「よし、ここは正しく理解出来ている。」って。
そうすると直ぐに次の質問です。
それで最初に戻ります。
これを延々と繰り返します。
これがアメリカというかキリスト教圏の伝統的な教育方法なんです。
此処から深い話に入ります。
日本人がアメリカ人に教える立場になった時、それは漫画の描き方でも、寿司の握り方でも何でいいですが、守破離が世界基準だと思って、最初は何でも黙って師匠の言う事聞いてろ。ってやるとアメリカ人から詐欺師だと思われますよ。
Karate Kidと言う映画で、主人公がひたすら床掃除をさせられていて、頭に来て空手の師匠に文句を言ったら、その掃除のフォームがそのまま空手の防御だったという、シーンがあります。
それを見ていたアメリカ人は椅子から落っこちる位びっくりしますが、それはあくまで映画の話です。
現実では生徒が怒って、詐欺師と認定されて、とんでもないしっぺ返しを食らう事になります。
アメリカでは騙されたら生きていけないんです。そしてアメリカ人は自分を騙したヤツは絶対許しません。とことん追いつめられますよ。
ではどうやってそれを回避するかと言うと、
まずこれは下策ですが、最初から日本における指導方法は守破離に基づいて行うので、最初は理不尽と思っても黙って師匠の言う事聞く事と説明するのが一つです。これは時間限定で3日しても何も学べないと生徒が感じたら終わりです。
質問を作成して、弟子がそれの回答を必死に考える訳です。
これはアメリカ方式に沿っているからいけるだろうと思われますが、実際、敢えて日本文化にある意味逃げ込んでくるアメリカ人達というのは現行の教育システムからの落ちこぼれで、この方法で学ぶのが凄い苦手な人達なんです。
だから、詐欺師には思われないかもしれませんが、一向に生徒が育たない事態になる可能性が大です。
中策と呼びましょう。
最後ですが、これはかなり上手いと思ったやり方を紹介します。
海外の人にラーメンの作り方を教える動画で見たんですが、まず日本の昆布のダシの取り方を教えます。この部分は日本の守破離の守のやり方そのもので、兎に角、こうやれと教えます。生徒に疑問を挟む隙は与えません。
日本の守破離方式の凄い所は、最初の守を強制する事で、あんまり才能の無い生徒でも一応、やり方はマスター出来るところです。
そしてアメリカでは水質が違うから同じやり方でやると苦くなる。と言ってどうすれば良いのかを自分達で考えさせます。ここはアメリカ方式になっています。生徒はそれぞれ日本の水と近い水質の水を遠くまで汲みに行ったり、昆布を煮る時間を長くしたりして工夫します。
そうやって工夫している内に最初に教わった昆布のダシの取り方が、どういう理屈でダシを取っているのかをアメリカ人の生徒が理解していくんです。
もし最初からアメリカ方式で全部自分で考えろとやったら多分ほとんどの生徒は意味が分からなくて挫折しているはずです。しかしそこは日本方式の守破離の守で基本を無理やり押し付けてしまう事で回避しているんです。
しかし、日本方式の守破離だけだったら、アメリカ人は一体何を学んでいるのか理解出来ず、騙されているんじゃないかとなって怒って止めてしまったでしょう。そこにアメリカでは水質が違うから苦くなる。自分でその問題を解決しろ。とやる事で自分達のやるべき事が理解出来き、その先に進めるわけです。
実に上手いやり方です。これこそ上策でしょう。
ちなみに上記の動画は全部英語ですので、苦いはBitterと言っています。日本語の苦いは英語では絶対言ってはいけないあの言葉に聞こえますのでアメリカで料理を教える人は超注意して下さい。
それでは今週の勉強を始めます。
<本文>
1. 今週の予定
先週はちょっとUE4の勉強が多すぎてRPGの製作に掛けた時間が少な過ぎました。勉強するのは好きですし、得意でもあるのでどうしてもそっちに重心が移ってしまいます。
しかしみんなが使いたいアプリは糞コードで書かれていても、完全無欠のプログラミングで書かれた誰も使わないアプリより遥かに価値があります。
ゲームだってそうです。面白いゲームを作るのが最も大切で、技術的に完全無欠にする必要は全くありません。
そして今、私が作成してるRPGは完成させるのが第一の目的でそれ以外はほどほどにやっておくべきです。
そこで今週からUE4の勉強に関しては量を大幅に減らしてRPGの製作にもっと力を注ぐ事にします。
1.1 先週までの勉強の総括
先週の勉強は何をしたのかを確認します。
必要な勉強とあまり必要でない勉強に分けて、あまり必要でない勉強は中止する事にします。
<Niagara Particle System>
RPGの製作と言う面から言えばもっとも必要のない勉強です。現在使用している4.24ではNiagaraは使用出来ませんから。しかしやっとCGHOW氏のTutorialが理解出来るようになったのと将来性の高さから勉強は継続すべきと考えています。
<Cascade Particle System>
今、作成しているRPGに使用するVFXを作成するとなるとこっちの勉強が重要になりますが、先週の砂のEffectのように参考になるTutorialが見つからない場合が多い気がします。参考になるTutorialが見つからないなら削るのも有りかと思います。
今考えると先週の砂のRenderingの勉強は無駄だったと思います。Cascade Particle Systemとは全く関係ないですから。
<別なマップに移動してる間に別なアニメーションを表示する方法についての調査>
これも絶対必要かと言えばそうでもないです。しかし先週勉強したAsync Loading Screens and Transition Levels | Unreal Fest Europe 2019 | Unreal Engine [1] は、大変優れたTutorialであるので勉強しておきたいです。
以下にこのTutorialが何故、重要なのかについてまとめておきます。
- UE4BPからUEC++、そして自作したModuleの呼び出し方を学べる。
- ActionRPGに書かれた実際のコードをどのように参考にすべきかを学べる。
- Multi-threadのようなProgrammerが対応しないととんでもないバグを引き起こす箇所はBlack box化して、BPを触るDesignerにはMulti-threadは触らせないDesign設計も学べる。
つまりこのTutorialはLoading Screenの作成方法以上に大切なものが学べるって訳です。
思い出して来ました。
このTutorialを勉強するのは、RPGの作成に必要な事です。しかしここから派生してMulti-threadについての理解が不十分だから勉強しよう。となるとそれって絶対今しないといけない事なのとなります。
そうです。
Multi-threadの勉強は、勉強のための勉強で今すべき事じゃありません。絶対に始めるべきじゃないです。
<総括>
こうやってまとめたら思ったより勉強してなかったです。
うーん。
思い出しました。先週、Air conditionerから水が噴き出してきて、修理の人が来るまで地獄の暑さの中でやってたんでした。直った途端に大雨が降って来てエアコンが要らなくなると言うおまけ付きでした。
それで滅茶苦茶大変な思いして勉強してたんで非常に沢山やった気になってたんでした。実際滅茶苦茶疲れましたし。
どうしようかな。
Cascadeの勉強は切るつもりでしたが、こうやってまとめてみるとCascadeの勉強が一番、今のRPGの製作に関係してますね。
分かりました。
勉強のための勉強は切るべきです。
例えば先週のCascadeの勉強で、砂のEffectの作成方法を勉強するのが目的でしたが、実際に学んだのは砂のRenderingの方法でCascadeとは何の関係もない分野の勉強でした。これはやるべきではなかったです。
同様にC++のMulti-Threadの勉強もやるべきではないです。
1.2 今週の予定
前節の結論を踏まえて今週の予定を決めました。
- Particle Systemの勉強
結局やる内容はあんまり変わってないですね。
2.Niagara Systemの勉強
先週の続きをやっていきます。CGHOW氏のNebula Effect | UE4 Niagara Nebula | Download Project Files [2] です。
- Moduleの勉強
- Static Mesh Location Moduleについて
- Curl Noise Force Moduleについて
- Drag Moduleについて
- Vortex Force Moduleについて
- Scratch Moduleについて
- Scratch Moduleの中身の実装について
- 最初のサラの状態で存在するノードの機能や役割を調べる。
- 左脇に表示されているToolについて調べる。
- 作成したModuleの機能について解説する。
- 前回、作成しなかった残りのEffectも作成する。
をやっていきます。
2.1 Moduleの勉強
Nebula Effect | UE4 Niagara Nebula | Download Project Files [2]で始めて使用したModuleについて調べます。
先週初めて使用したModuleはStatic Mesh Location Module、Curl Noise Force Module、Drag Module、Vortex Force ModuleそしてScratch Moduleの5つです。
<Static Mesh Location Module>
先週のブログでは以下の様に解説されています。
思い出しました。指定したStatic Meshの形にParticle を発生させるModuleです。
後、Static Meshを指定するためにSample Static Mesh Moduleが必要になります。Sample Static Mesh Moduleの事は忘れていました。このModuleについても調べます。
公式のDocumentであるParticle Spawn Group [3] には、
とだけ説明されていました。
気になるのはその二つ上にSkeletal Mesh Location Moduleと言うのがあります。
これ使用したらPlayerが操作するキャラとかMonsterの形からParticle に移行出来るんでしょうか?
それが出来たら凄いですね。忍者が逃走する時に消えるEffectとか、One Pieceのロギア系が攻撃された時のEffectみたいなの作れそうですね。
それは兎も角、もっとStatic Mesh Location Moduleについての資料はないのかと探したらありました。
gameDev Outpost のUE4 - Niagara Static Mesh and Surface [4] です。
滅茶苦茶、分かり易く説明されていました。
<Sample Static Mesh Module>
これは、gameDev Outpost のUE4 - Niagara Static Mesh and Surface [4]にあった説明で十分理解出来ました。
<Curl Noise Force Moduleについて>
先週のBlogを見てみると以下の様に使用しています。
それぞれのParticle にCurl Noiseに沿って速度を追加しているみたいですね。
Curl Noiseって何?
色んなサイトで少しずつ説明されていていました。一個だけ代表を選ぶとすると、このサイト(Intro to Curl Noise [5] )にあるPDFのスライドですかね。
このスライドや他のサイトそしてYouTubeにある説明を総合してまとめます。
まずCurl Noiseは以下の式の事です。(この式の具体的な計算方法についてはIntro to Curl Noise [5]にあるPDFファイルを見て下さい)
滅茶苦茶簡単に言えば、この式は在る点にいる粒子の速度を計算しています。
この式を使用して流体の移動などを表すと凄い綺麗な動きになります。
ただしこの計算をするためには元になるVector場が必要になります。大抵はPerlin Noiseが使用されます。
以上になります。
ここからは想像ですがCurl Noise Force Moduleにある以下の二つのParameterは
Noise Strengthがベクトル場の大きさを表して、Noise Frequencyがベクトル場の複雑さを表してるじゃないでしょうか。
Intro to Curl Noise [5]にあるPDFファイルでPerlin NoiseのFrequencyの大きさについて述べています。
Noise Frequencyはこれを計算しているんじゃないんでしょうか?
試しにCurl Noise Force ModuleのScriptを開いて見てみました。
うーん。分からん。
分からんなりに見てみますと、Noise StrengthはSampled Noiseに掛けていますね。
となるとやっぱりNoise Strengthはベクトル場の強さに関係してそうですね。Noise Strengthがベクトル場の最大の高さを表すのではなくて、Sample Noise、恐らくPerlin Noiseの値を何倍にもするScaleの働きをしているんでしょうね。
Noise Frequencyの方も見てみます。
あれ、Age AdvancementってParticleが一定時間でどれだけ移動したかって事なんでしょうか?
良く考えたらもうPerlin Noiseの計算方法覚えていません。
Noise Frequencyに関しては、良く分からないですね。
公式のDocumentであるParticle Update Group [6] には以下の説明がされていました。
これ読むとPerlin Noiseはオプションみたいですね。
以下の部分で、元になるVector場をどっかから一気に持って来てる感じですね。
まあ、これ位分かれば今回は十分です。次のModuleを調べます。
<Drag Moduleについて>
このModuleに関しては単にParticleにDrag Forceを追加してるだけでしょう。
上記の例で言えば、Dragする方向に1を追加していると考えられます。
一応、Scriptも開いてみます。
あれ!
これは簡単です。これなら今の自分でも読んで理解出来そうです。
読んでみましょう。
最初の塊です。Linear Dragと書かれています。
最初のMap Getノードですが、何をしているのか分かりました。
このノードはこのEmitter内に存在してる全てのParameterにGet 関数としてAccess出来るんです。
因みにEmitter内に存在してる全てのParameterはNSのParametersに載っているparameterの事です。(この部分は間違っていました。Physics DragはTransientですが以下に示したNSのParametersには載っていません。)
今回Get関数としてAccessしたのはこのDrag ModuleにParameterとして有るDragと元から存在してるParameterであるPhysicsDragです。
このPhysicsDragこそがそれぞれのParticleのDrag 力を指定する変数なはずです。だからその変数にDrag ModuleのDragで指定した値を追加しているんです。
NiagaraFloatがTrueかFalseかは以下に示したDragにチェックが入っているかどうかによると思われます。
もしチェックが入っていればPhysicsDragの値は、PhysicsDragの値 + Drag ModuleのDragで指定した値になり、チェックが入っていなければPhysicsDragの値はそのままです。
Rotational Dragの塊の方も全く同じ事をやっています。
回転のDrag Forceを管理するのはPhysics Rotational Drag変数です。
公式のDocumentであるParticle Update Group [6] には以下の説明がされていました。
はい。Scriptで予測した通りです。
少しはScriptも読めるようになって来ました。
<Vortex Force Moduleについて>
このModuleは何をしているのか全く覚えていません。
外してみました。
もう一度追加します。
公転してます。
全てのParticleが在る点を中心として公転する力を追加しているようです。
Scriptの方はまあまあ複雑です。
Scriptの方はまあまあ複雑なので、今回はScriptの分析はパスします。
公式のDocumentであるParticle Update Group [6] には以下の説明がされていました。
ここで言われているVortex Axisが全てのParticleが公転する為の中心軸で、そのために必要な力はPhysics. Forceに追加されるようです。
<Scratch Moduleについて>
まっさらのModuleでScriptを自分の自由に書けるModuleです。
UE4 Answer HubのWhat is Scratch Pad Module? [6] にはScratch Pad Moduleについての解説がありますが、Scratch Pad ModuleはどうやらScratch Moduleの事みたいです。
このサイトに紹介されているYouTubeに飛ぶと更に詳しい説明がされています。一つだけ私が大切だと思った事を書いておくとScratch ModuleはTransientなのでその場だけしか存在しない事です。
もっと細かい内容については次の節で勉強します。
2.2 Scratch Moduleの中身の実装について
今度はScratch Moduleの内容について検証します。
<最初のサラの状態で存在するノードの機能や役割を調べる>
Scratch ModuleをクリックするとScratch Padが開きます。
Scratch Padには以下のBPが配置されています。
これはもう理解出来ます。Map Getは現存するParameterにaccessする為のもので、Map Setはそのparameterの値を変更するためのものです。Getter setterのBP版です。
<左脇に表示されているToolについて調べる>
以下のヤツです。
Scratch Padというやつです。
で色々探したんですがこれそのものを説明しているサイトは見つかりませんでした。
ので私が色んなサイトから集めた情報で解説します。
間違っている部分もあるかもしれませんが現時点で理解している範囲でベストをつくします。
<<Modules>>
+ボタンを押すと新しいScratch Moduleを作成します。
ほとんどのTutorialで解説してたのが、作成したScratch Moduleを普通のModuleとして永遠に保存する方法です。
以下の様にModuleをクリックしてEditorを表示させ、そこからCreate Assetを選択します。
すると以下の様な画面が表示されるので適切な名前を打ち込みSaveボタンをクリックする事で出来ます。
<<Dynamics Inputs>>
これは何なのか分かりません。
分からないなりに予測するとNiagara Emitterの方でParameterの値を指定する方法の一つにDynamic Inputというのがあります。
これを自作しているのだと思われます。
試しにLife timeにAdd Floatをセットします。
そしてそれを開いて見ます。
すると以下の様な形になっています。
今度は以下の様にDynamic Inputsに新しいInputを作成してそれを開いてみます。
はい。Add floatとほとんど同じ形をしていますね。
間違いないですね。
<<Scratch Script Parameters>>
ここはModule内で使用しているAttributeを表示しているようです。
試しにNormalized Ageを追加してみます。
Normalized Ageが表示されています。
所でParticlesタイプじゃなくてTransientタイプの追加方法を知りたいです。
<<Scratch Pad Selection>>
これは作成しているModuleのDetailを表しているんでしょうね。
個々の機能についてはまだ分からないですね。
一応、Cursorを添えると説明文が表示されるので必要に応じて勉強します。
以上です。
<作成したModuleの機能について解説する>
以下に先週作成したScratch Moduleを示します。
まずMap GetでNew Static MeshをInputに追加します。
そのStatic Mesh内からRandomな場所を選択します。
ここからは2つの作業をしています。
最初の作業です。
その場所をWSに変換します。
変換した値をPositionにセットします。
ここで生成されるParticleが指定したStatic Meshの形に生成されるようになるようにしています。
次の作業です。
その場所のUVを取ります。
UV値を元にvectorの値を2から4に増やします。
増やした値をDynamic Material Parameterにセットします。
ここではMaterialが一個一個のParticleに貼られるのではなくParticle全体に一個のMaterialが貼られるように指定しています。
以上です。
2.3 前回、作成しなかった残りのEffectも作成する。
これはあんまりやる意義を感じないんで止めます。
3. Cascade Systemの勉強
水のEffectの作成を勉強します。
Waterfall | Detailed Tutorial | Unreal engine 4 Particle Systems [7] を勉強します。
このTutorial、去年作成されたにも関わらず、Cascadeを使用しています。作者曰く、Patronで2年前に作成したWaterのParticle Effectの作り方に良く分からない所がある。と言われたので特別に作り直したそうです。
以下にしめしたEffectを作成するそうです。
3.1 Materialの作成
指定されたTextureをDownloadしました。
こんなやつです。
αには以下のイメージが入っていました。
RGBは全く同じイメージでした。
そのTextureからMaterialを作成します。
こんな感じです。
実装は以下の通りです。
まあ、Effect用のMaterialとしては普通の作り方ですね。
次のMaterialを作成します。
M_WaterfallSmokeと名付けました。滝の周りに発生する蒸気をするためのMaterialでしょうか?
こんな感じです。
実装は以下に示します。
ーん。
Radial Gradient Exponentialノードを前に使用した時は何を作成したんでしたっけ?もう忘れてしまいました。
2021-08-09のブログに綺麗にまとめられていました。
Densityは白い部分の濃さを表します。数字が小さい程黒く、つまり透明になります。
最後にM_WaterfallSmokeのInstanceを作成します。
以上でMaterialの作成は終わりです。
3.2 Particleの作成
Particleを作成します。名前はP_Waterfallとします。
まずData TypeをGPU Spritesにします。
次にRequired ModuleのMaterialをM_WaterfallMainに変更します。
Spawn ModuleのSpawn Rateの設定をDistribution Float UniformにしてMin とMaxを10にします。
これって、Min とMaxが同じ値ならConstantを使用して、あえてUniformを選択する必要無いと思うんですが。
前に公式のTutorialで「Uniformをあえて使用してもEffectの計算コストは変わらない。」みたいな話を聞いた事がありますが、Constantの代わりにUniformを使用するのは一般的なんでしょうか?
今度はLife Time Moduleの設定です。Distribution Float Uniformを指定してMin とMaxに3をセットしました。
ここでもUniformを使用していながらMin とMaxに同じ値をセットしてますね。
ちなみに今の状態でのReviewのイメージです。
全く滝には見えませんね。
今度はInitial Size ModuleのSizeの値を全部200に変更しました。
Initial Velocity ModuleのStart Velocityの値を以下の様にしました。
こんな感じです。とても滝には見えません。
次はSize by Life Moduleです。
このModuleは付属のModuleではないので自分で追加します。
以下の様な設定に変更しました。
グラフで見るとこんな感じです。
次にConstant Accelerationを追加します。
そしてAccelerationの値のzに-300 を追加します。
以下の様になりました。
なぜ-300にしたんでしょうか?
重力加速度なら-980にすべきでしょうに。
位置を変更すると突然Particleが消える事があります。直すためにBoundsを設定します。
しました。
以下のようになりました。
マイナーな調整でVelocityのMinの値を10にセットしました。
以下の様になりました。
かなり滝の水飛沫に似て来ました。
これで最初のEmitterの作成は終わりです。
次のEmitterを作成します。
まずEmitterをDuplicateします。
Required ModuleのMaterialをM_WaterfallSmokeに変更します。
先程Materialの所でInstanceを作成したのになんで使用しないんでしょうか?
このEmitter単体だと以下の様なイメージになります。
EmitterのSの箇所をクリックするとそのEmitterだけ表示するそうです。
これは知りませんでした。
これで完成だそうです。
Levelに実際に配置してみました。
雪のEffectと交わると結構、滝に見えます。
こんな簡単に出来るんですね。
これで終りと思ったらまだ続きがありました。
Starter Kitに付属のP_Steam_LitをcopyしてDuplicateします。
名前をP_Waterfall_Smokeにします。
これが滝が落ちた所に発生する蒸気を表します。
値を調整して蒸気っぽくします。
SubImage Index moduleの値です。
ここで値を以下の様に変更しました。
敢えて最初のin Valの値を0.1にする意味を忘れてしまいました。
Blogの何処かにこの理由を書いた記憶があるのですが見つかりません。後で探す事にします。
最後に
を追加して値を以下の様にセットしました。
これは滝が落ちる位置にこの蒸気を発生させるためだそうです。
これで終わりかと思ったらこっちのParticle Effectにも更にEmitterを追加します。
そしてRequired ModuleのMaterialにM_WaterFallSmoke_Instをセットします。
ここでInstanceを使用するんですね。
現状、こんな感じです。
Spawn ModuleのSpawn Rateを1にします。
Lock Axis Moduleを追加します。
OrientationのLock Axis Flagsの値をzにセットします。
このLock Axis ModuleはSpriteに貼りつけてある板の向きを常にカメラに向けるのではなく、Z軸に向けるようにするためのものだったはずです。
実際Previewを見てみるとZ軸に板が向いているように見えます。
更に以下の値も変更しました。
これでテストします。
こんな感じです。
この後でTutorialでは黒い影が蒸気に見えるのでそれを消すためにMaterialに変更を加えています。
私の蒸気には別に黒い影は見えないのでその部分はパスします。
3.3 BPの作成
ActorクラスからBP_WaterFallを作成します。
作成したBPに先程完成したP_WaterfallとP_WaterFallSmokeを追加します。
以下の様なイメージになっています。
位置を調整します。
BPを配置してみます。
うーん。下の蒸気が小さすぎます。後、色が少しだけ灰色になっています。
後はいいんじゃないでしょうか。
この後、Tutorialでは滝の音を追加していますが、ここまでで十分です。
3.4 まとめと感想
今回の Tutorialはかなり勉強になりました。やっぱり基本を理解してからこういう応用を勉強すると理解の深さが全然違います。
自分で更に工夫するとしたら
- 滝の色が白のみだが黒や灰色を追加する
- MeshにガラスのMaterialを貼り付けて滝に流して見る。
- 蒸気はもっと大きくして、他の色を追加する。
- 炎のEmberのような滝の周りに発生した水飛沫を追加する。
なんかが考えられると思われます。
4. 切符を道具として使用した場合の実装
先週、考えたように切符は道具で使用出来ないようします。
まず、切符は道具袋にしまってあるので道具袋にアクセスする時を全て抜き出します。
道具袋は、RPGGameInstanceのItemsにStringとして格納されています。
この変数にAccessする関数を全て抜き出します。
これらを一個ずつ虱潰しにチェックしていきます。
はい。見つけました。
Item Widgetでボタンをクリックされた時です。
そのItemを消費して、HPやMPを回復させます。
この後でチェックする事にします。
これでいけると思います。
テストします。
道具袋を開いて切符をクリックしましたが何も起きません。
出来ていますね。
これと同じやり方で全部やります。
今度は戦闘中にアイテムを選択した場合です。
ここで切手かどうかチェックします。
これで戦闘中にアイテムを使用する時も切手は選択出来ません。
テストします。
何回やっても出来ません。
何でと良く見たらContains関数のSubstringが切符ではなく切手になっていました。
直します。
もう一度テストします。
今度は何回クリックしても切符は選択出来ません。
出来ました。
はい。全部Itemsが使用される箇所をチェックしました。
これで完成です。
5. 駅のバグ出し
先週さらに沢山の機能を駅に追加しました。それらをテストしていきます。
5.1 駅員と話しをします
「駅員と話しをする」をクリックします。
駅員からの質問と、Playerが選択出来る回答が表示されます。
「この駅について」を選択します。
この駅は町から外の世界に出る為に建設されたので、駅員の回答は間違っています。
それぞれの駅で駅員の回答は変わるべきですがそのための機能はまだ作成していません。
5.2 その他の点について
他のバグは見つかりませんでした。
バグではありませんが、絵のキャラが駅員に見えません。直すべきです。
6. 実際のゲームに使用するmap1とテスト用のMap1を分ける
6.1 Map1をduplicateする
Map1は最初にPlayerが冒険するMapなんですが、テストをする為にあらゆるアクターが配置されていて実際のゲームのシナリオが作れません。のでDuplicateしてTest用のMap1とゲームに実際に使用するMap1に分ける事にします。
開いて中を確認します。
と書かれていますね。
やっぱり単にDuplicateしたら全部解決とはいかないようですね。
Playでmap1_Testの中身を確認します。
イキナリErrorです。
直していきます。
RPG Game Mode BPのBeginPlay関数でそれぞれのMapにおけるPlayerの操作するキャラの発生する場所を指定しています。
ここにMap1_Testの場合を追加する必要がありました。
しました。
もう一回テストします。
墜落して死んでしまいました。
良く見たらMap1と違う場所を指していました。
直します。
テストします。
Nav MeshのRebuildが必要とありますね。後でします。
音楽とかは普通に聞こえます。
Mapの移動が起こるEventはErrorが発生する可能性が高いので最初はそれ以外をテストします。
6.2 別なMapに移動するEventが発生しないEventをテストする
まずは道具屋です。
会話をしたり買い物をしたりしましたが普通に出来ました。
問題ありません。
武器屋です。
同じように問題ありません。
次はItemを拾ってみます。
これも問題無く出来ました。
武器を拾ってからの装備も問題無く出来ました。
今度はNPCに話しかけました。全員問題無く回答してくれます。
しかしNPCの会話は以下のParameterで指定された値で決まっています。今はMap1と同じ指定になっていますが後で調整が必要になると思います。
6.3 戦闘をしてみる
今度は、Mapの移動のあるEventを試してみます。
まずは戦闘をしてみます。
途中までは順調でしたが戦闘が終わって石像と話す場面になっても後ろの髑髏が消えません。
会話は普通に出来るみたいですね。
褒美をもらって元のMapに戻って来ました。
Map1とMap1_Testは見た目が一緒なのでどっちに戻って来たのか分かりません。
違いが分かるようにMap1_TestにはAnvil(金床)を置きました。目印です。
もう一回戦闘してみます。
戻って来ました。
Anvil(金床)がありました。Map1_Testに戻ってきてます。
戦闘が終わった時に空を回っている骸骨が消えないバグはMap1やLandscape4で戦闘した結果も考慮した後に直します。
6.4 宿屋に泊まる
宿屋は泊まって寝ている間は別なMapに移動しています。きちんとMap1_testに戻ってこれるか確認します。
宿屋に泊まってみます。
金貨の表示がオカシイですね。後で直します。
泊まりました。
Map1ではなくMap1_Testに戻っています。大丈夫みたいです。
ここで問題が発生しました。
敵のMonsterとの戦闘中に「逃げる」を選択して成功したら戦闘場で自由に動けるようになってしまいました。
ただし元のMapには戻れません。
バグ発見です。後で直します。
6.5 神殿でセーブする
今度は神殿でセーブしてみます。セーブした後にLoadでゲームを開始してMap1_Testから始まるか試します。
始まりませんね。以下に示した様にAnvil(金床)がありません。
Map1が開いています。
所持していたItemなどはどうなってるんでしょうか?
道具類は持っていますね。
Mapだけが違うようになっています。
しかしこれは設定によって何が正解か変わってくる可能性もあります。例えばH x Hのグリードアイランドではセーブした後で戻ってこられる場所は一か所だけです。
もう少し設定が煮詰まってから考える事にします。
6.6 駅での移動を試す
駅を使用してLandscape4に移動する事は可能なはずです。Landscape4からMap1_testに戻って来る事は出来ませんが。
試してみます。
出来ました。
6.7 Map1を開く
Nav MeshをRebuildしろと言うmessageがうざいので直そうと思ってMap1を開いたらUE4 Editorがクラッシュしました。
えっ。
もう一回開いて見ます。
今度は大丈夫ですね。
Playを押しても大丈夫でした。
後、Nav MeshをRebuildしろと言うMessageも消えています。やっぱりMap1のNav Meshが問題を起こしていたんですね。
6.8 Map1_Testで色々やってみる。
Map1の方はゲームの内容に合わせて色々変えてしますので、Map1_Testで今まで作成した機能の管理をします。のでもう少しMap1_Testで色々やってみます。
弓矢を装備しました。
隣村に着きました。
Blogにコメントを書いている間に夜になってしまいました。
Map1の方は一日に長さの調節も必要ですね。
隣村のGateから外に出ました。
村の外側を歩いていたら昔作成したWarp Gateがありました。
こんなの作成したのすっかり忘れていました。
石橋ですがTextureの貼り方がオカシイ部分があります。
倒したMonsterが消滅しません。
新たなバグ発見です。
これもMap1やLandscape4で試した後に直します。
6.9 Map1やLandscape4で戦闘をしてみる。
Map1_Testの戦闘で見つかったバグがMap1やLandscape4でも起きるのか確認します。
Map1で試してみます。
Map1で戦闘に勝利するとまず以下のWidgetが表示されます。
これはMap1_Testでは表示されませんでした。
閉じるボタンを押してWidgetを閉じます。
後ろの骸骨は消えていますね。
石像に話かけました。
ここからはMap1_Testも同じです。
Landscape4でも試してみます。
Landscape4でもMap1と同じ結果になりました。
今度はMonsterを倒した後、そのMonsterが消えているのかを確認します。
Map1に配置したMonsterと戦います。
倒したMonsterは消えています。
Landscape4でも試してみます。
Monsterが2体しかいない赤線で囲った場所で戦ってみます。
2体Monsterを倒したら赤線で囲った場所にはMonsterはSpawnしなくなりました。
出来ています。
6.10 Map1_TestでMonsterを倒した後に消滅しない理由
これは分かりました。
以下にMap1_TestのLevel BPのBeginPlay関数の一部を示します。
ここでこのLevelに生成するMonsterをRPG Game InstanceのArrayであるMonster Spawn Dataから得ています。しかしこのArrayはMap1のMonsterの生成のために作成されたものです。
RPG Game Mode BPで
の最後でRemove Defeated Monster from Map1が呼ばれています。
ここにMap1_Testを作成しないといけません。
しかしそのためにはRPG Game Instance BPにMap1_Test用のMonster Spawn Dataを作成して、Map1_Test Mapが開かれた時はそこからMonster を生成する必要があります。
更にMap1_Test用のMonster Spawn DataからMonsterを消滅する関数、Remove Defeated Monster from Map1_Testも必要です。
6.11 Map1_TestでMonsterを倒した後に消滅させる
まずは確認のためにRPG Game Mode BPのEventであるFightIsOverの最後で実装されている以下の部分にMap1_Testを追加しました。
これでMap1_Test上で戦闘してもMonsterは消えるはずです。
テストします。
戦闘後、倒したMonsterは消えていました。
だたし、今のままではMap1に移動しても同じ場所のMonsterは消滅しています。
Map1とMap1_Testは違うMapですのでMap1_TestでMonsterを倒してもMap1のMonsterが消滅してはおかしい訳です。
これを直していきます。
まずRPG Game Instance BPにMap1_Test用のMonster Spawn Dataを作成しました。
中身はMonster Spawn Dataと一緒です。Duplicateして作成しただけです。
今度はMap1_Test Mapが開かれた時にMonster Spawn Data Map1_TestからMonsterを生成するようにします。
Map1_Test MapのEvent Begin PlayのMonsterを生成する部分の実装を以下のように変更しました。
更にMap1_Test用のMonster Spawn DataからMonsterを消滅する関数を作成します。
出来ました。
中身はRemove Defeated Monster From Map1と全く同じで、赤線で囲った部分だけ変更しています。
この部分をParameterにして関数にパスするようにすれば、Remove Defeated Monster()関数は一個で済みますね。後で整理する時に考えてみます。
この関数を実装しました。
これでいけるはずです。
試してみましょう。
Map1_TestでPlayを開始しました。
Monsterは普通に生成されています。
Monsterを一体倒しました。
以下のWidgetも表示されるようになりました。
はい。倒したMonsterは消滅しました。
この後、銀河鉄道に乗って、Landscape4に移動して、更に銀河鉄道に乗ってMap1に移動しました。
Map1の同じ場所のMonsterは消滅していません。
出来ています。
6.12 新たなバグの発見
Map1からMap1_Testを開こうとするとUE4Editorがクラッシュします。逆もまたしかりです。
多分ですが、Map1とMap1_Testで同じMemoryを使用してる部分があるからだと思います。直接Map1からMap1_Testは開かないようにします。逆もしないようにします。
今週はここまでとします。
来週は更にMap1とMap1_Testで共同に使用しているData、例えばItemのSpawnのData、を別にしていきます。
今Map1で生成されている事全てがMap1_Testで生成されるようになったらMap1上で本当のRPGを作成していきます。
再来週位にはMap1上でRPGの物語が制作出来るようにしたいです。
裏切りの石像がくれる褒美がパスのままです。後で切符に直します。
これで今週のRPGの実装は終りにします。最後にこのまま閉じてもこのProjectが問題なく開ける事を確認しておきます。
普通に開けました。
大丈夫でしょう。
残りは来週やります。
7. 別なマップに移動してる間に別なアニメーションを表示する方法についての調査の続き
7.1 Async Loading Screens and Transition Levels | Unreal Fest Europe 2019 | Unreal Engine [8]の続きを勉強する
先週、途中まで勉強したUnreal Fest Europe 2019のAxel Riffard氏の講演の続きをやります。
IActionRPGLoadingScreenModuleはInterfaceなので実装出来ないはず。
と思ったらFActionRPGLoadingScreenModuleクラスがIActionRPGLoadingScreenModuleを継承してActionRPGLoadingScreen.cpp内で宣言と実装を一遍にやっていました。
以下に先週のBlogのその部分について述べた所を示しておきます。
Axel Riffard氏のスライドの説明から、予測した時は
となっていました。ActionRPGが抜けている以外はほぼ予測通りですね。
後、図ではIModuleInterfaceはFLoadingScreenModuleから呼ばれていますが
実際のコードでは
IActionRPGLoadingScreenModuleの親Interfaceで、FActionRPGLoadingScreenModuleから直接は呼ばれていませんね。
そうは言っても大体の予測は合ってます。
次の私の予測である
を調べて見ます。
SLoadingScreenはActionRPG内ではSRPGLoadingScreenと呼ばれていました。
あ、SCompooundWidgetを継承しています。これについては後で述べる事にします。
はい。
FActionRPGLoadingScreenModuleクラスのメンバー関数であるStartInGameLoadingScreen()と
CreateScreen()内で使用されています。
予測の通りでした。
更にSLoadingScreenからFLoadingScreenBrushが呼ばれると予測していますが、
ActionRPGにおけるSLoadingScreenBrushクラスにあたるFRPGLoadingScreenBrushクラスが呼ばれています。
これも予想通りでした。
また、
と予測しましたが実際は以下に示した様に
SRPGLoadingScreenクラスはSCompoundWidgetクラスの子クラス、RRPGLoadingScreenBrush構造体はFSlateDynamicsImageBrushの子でした。
大体のコードの実装方法が理解出来ました。
しかしこれThread使ってないですね。
まあこんだけ理解しておけば大丈夫でしょう。講義の内容に戻ります。
今度はSlateについてのようです。
SlateはSherif, William氏のUnreal Engine 4 Scripting with C++ Cookbook のAdding Slate Widgets to the screen [9]などで散々勉強したので大体分かるでしょう。
正直、UMGが存在している今、非ProgrammerがSlateを触るメリットはあんまりないと思っています。
私が初めてUnreal Engine 4 Scripting with C++ CookbookでSlateを勉強した時は、UMGとSlateの違いも知らなかったし、Game制作においてC++で書く必要がある部分とない部分の区別もついていませんでした。
今の私が当時の何も分からない状態でSlateを勉強している私にアドバイスを送るとしたら、
- Game内で表示するWidgetはUMGを使用して作成すべき。
- Slateを使用した方が良い場合は以下の2点のみ
- UE4Editorの形状に変化を追加したい場合
- UMGを使用するより速くWidgetを表示したい場合
です。
以下のサイトがSlateについて非常に分かり易く解説されているとありました。
覚えておきましょう。
次のSlideです。
今度はModule Logicの実装についてです。
Module Logicの実装って先程自分で考証した
についての実装なのかと思ったら
のメンバー関数についてでした。
ここで重要なのは
が呼ばれている事だそうです。
うーん。
色んな所で呼ばれていますね。
でもこの関数を呼ぶ事で別なアニメーションを表示出来る訳ですね。
それじゃこのGetMoviePlayer()関数の流れを実際のコードで追ってみますか。
まずFActionRPGLoadingScreenModuleが作成されます。
多分ですが、以下のメンバー関数、StartupModule()が最初に呼ばれるはずです。
するとStartupModule()関数内に実装されているHelper関数である
が呼ばれます。このCreateScreen()関数内には
GetMoviePlayer()関数が使用されています。
FActionRPGLoadingScreenModule内の次のメンバー関数である
でも全く同じ様にGetMoviePlayer()関数が使用されています。
ただしパスされるLoadingScreenのAttributeの値は微妙に変わっています。
CreateScreen()関数では
はTrueにセットされていますが、
StartInGameLoadingScreen()関数内では
となっています。
CreateScreen()関数が呼ばれた場合はLoadingが終わった瞬間にMovieの再生も終了しますが、StartInGameLoadingScreen()関数が呼ばれた場合はLoadingが終わったとしてもMovieの再生が終わるまでLoading Screenは継続すると考えられます。
これ位しか、理解出来ませんね。
次のSlideを見ます。
はい。これが聞きたかった。正直これまでの実装はMulti-threadにあんまり詳しくない私でも理解出来る内容です。
こっからが本番です。
えっ。
じゃあ、別に自分でFAsyncTask()関数を呼び出さなくて良いの?
更に次のSlideでこう述べられています。
え。どういう事。
つまり上記のコードをそのまま使用すればAsyncになってるって事?
次のSlideでSlateが実際に別なThreadとして作成されている証拠として実際のコードが表示されていました。
うーん。
成程。
この後のSeamless TravelはMulti player用のLoading Screenの作成方法だそうなので勉強するのはここまでとします。
実際にこの方法で作成するとなると、FActionRPGLoadingScreenModuleクラスをBP側で作成出来るのか、それともUEC++側でこのクラスを作成する、別なBPからCallableな関数が必要なのかを調べないといけませんが、それもActionRPGを見れば分かるでしょう。
うーん。思ったより簡単でした。
先週、勉強出来なかった残りの2つのTutorialも見てみます。
7.2 Unreal Engine 4 - Easy Loading Screen Tutorial - Blueprints Only [8] を勉強する。
まずサラッと全部みました。
それで知らない事が2個があったんです。
まずSub Levelです。
次にLoad Stream Levelノードです。
このノードでLevelを開くと
BackgroundでLevelをLoadしてくれるって説明されています。
あれ?
じゃこのノードの使い方さえ分かればAsync でLoading Screenを表示出来るんじゃね?
その後で、説明しているGame InstanceにDispatcher を作成して云々は、まあ誰でも思いつく方法だと思いますのでスキップします。
7.3 Load Stream Levelノードについて
予定を変更してLoad Stream Levelノードを調べる事にします。
公式のDocumentのLevel Streaming [12] に以下の解説ありました。
この下にある解説を読むと、分割して(恐らく複数のMapで)管理している広大な世界を、Seamlessな読み込みを行う事でPlayerにとっては一つの大きな世界のように見せる技術があるそうです。
それを可能にするための機能としてLoad Stream Levelノードが使用されているそうです。
へー。
MMOなんかで何千人が同時にPlayするゲームだと非常に広大なMapとかが必要になるんでしょうか?
それは兎も角、今週はLoad Stream Levelノードの使用方法だけ勉強出来れば十分です。
見つからない。
なぜかLoad Stream Levelノードの使用方法だけ説明しているTutorialが見つかりません。そう言えば公式のDocumentも見つかりません。
うーん。これはLevel Streamingを理解してから使用しろという啓示なんでしょうか?
来週、一寸考えてから決断します。
7.4 Sub Levelについて
Sub Levelに関してですが、まず定義が分かりません
広大な一個のMapを分割して、その分割したそれぞれのMapをSub Levelと呼んでいるみたいです。
公式のDocumentであるLevels [13]には
の二つでSub Levelの使用方法や作成方法についての解説をしています。
これらは複数のMapで管理している広大な世界をSeamlessな読み込みを行うための技術です。
私は単にLoading Screenで鉄道に乗っているシーンを写している間にBackgroundで次のLevelを読んでほしいだけです。Load Stream Levelノードで読み込むLevelは必ずSub Levelになってないといけないんでしょうか?
その辺が良く分かりません。
後、Mathew Wadstein氏のHTF do I? Loading Screens using Level Streaming in Unreal Engine 4 [14]を見つけました。Mathew Wadstein氏のTutorialは分かり易いので後で見てみます。
結論としては、これも来週考えます。
7.5 How To Make A Loading Screen In 5 Minutes Unreal Engine 4 Tutorial [9] を勉強する。
このTutorialは以下に示したPlug-inを使用して作成する方法を解説しています。
これはLoad Stream Levelノードの使用方法を理解した後で勉強したいです。
のでこれは来週に回します。
8. Good Skyの復習
のおおあ。
今週こそはこれを終わらせようとしたんですが時間が無くなってしまいました。
来週やります。
9. まとめと感想
Particle Systemの勉強に関してですが、Niagaraはかなり理解してきました。
Niagaraを使用したParticle systemの作成にはオリジナルなModuleの作成のようなProgrammer向きのTaskと炎のEffectの作成におけるEmber Emitterの作成のようなDesigner向きの Taskがある事が分かりました。
正直中々面白い分野だと思います。
Cascadeは今週は良く勉強できました。選んだTutorialが良かったんだと思います。
Cascadeはもうやらなくて良い気もしているんですが、今止めるとCascadeの使用方法を全部忘れそうなのでもう少しだけ勉強を続けます。
RPGの製作についてですが、以下の2点を行いました。
- 切符は道具として使用出来ないように実装した。
- Map1をDuplicateして片方をテスト用、もう片方を実際のゲーム用にした。
テスト用のMap1は今までのMap1と同じ挙動を示すためにはもう少し改造が必要です。来週も引き続きテスト用のMap1の改造を行います。それが完成したら実際のゲーム用のMap1を改造します。
Async なLoading Screensの作成方法については、最初のTutorialの勉強は終えました。2番目のTutorialはSteamlessなMapの読み込みに使用するLoad Stream Levelノードを使用してBackgroundでLevelをLoadしています。これを理解するためにはもう少しLevel Streamingの勉強が必要かもしれません。来週までに何を勉強するのかを決めます。
以上です。
10. 参照(Reference)
[1] Epic Games [Unreal Engine]. (2019, May 20). Async Loading Screens and Transition Levels | Unreal Fest Europe 2019 | Unreal Engine [Video]. YouTube. https://www.youtube.com/watch?v=ON1_dEHoNDg
[2] CGHOW. (2020, July 21). Nebula Effect | UE4 Niagara Nebula | Download Project Files [Video]. YouTube. https://www.youtube.com/watch?v=niSWXhWNyQQ&list=PLwMiBtF6WzsqC7_cJmD26ts0YDbtPCCfe&index=11
[3] Epic Games. (n.d.-c). Particle Spawn Group. Unreal Engine Documentation. Retrieved August 22, 2021, from https://docs.unrealengine.com/4.26/en-US/RenderingAndGraphics/Niagara/EmitterReference/ParticleSpawn/
[4] gameDev Outpost. (2020, December 24). UE4 - Niagara Static Mesh and Surface [Video]. YouTube. https://www.youtube.com/watch?v=lkdFu0rAV18
[5] Werner, P. (2021, August 16). Intro to Curl Noise. Shifting Sands. http://petewerner.blogspot.com/2015/02/intro-to-curl-noise.html
[6] Epic Games. (n.d.-d). What is Scratch Pad Module? - UE4 AnswerHub. UE4 AnswerHub. Retrieved August 22, 2021, from https://answers.unrealengine.com/questions/981653/what-is-scratch-pad-module.html
[7] Realtime 3D NowYoshi. (2020, July 28). Waterfall | Detailed Tutorial | Unreal engine 4 Particle Systems [Video]. YouTube. https://www.youtube.com/watch?v=ZzTsZWkXScQ
[8] Epic Games [Unreal Engine]. (2019b, May 20). Async Loading Screens and Transition Levels | Unreal Fest Europe 2019 | Unreal Engine [Video]. YouTube. https://www.youtube.com/watch?v=ON1_dEHoNDg
[9] Sherif, William. Unreal Engine 4 Scripting with C++ Cookbook (Kindle Locations 5126-5127). Packt Publishing. Kindle Edition.
[10] Reids Channel. (2019, December 15). Unreal Engine 4 - Easy Loading Screen Tutorial - Blueprints Only [Video]. YouTube. https://www.youtube.com/watch?v=lzGcVDUDU5g
[11] Uisco. (2021, March 3). How To Make A Loading Screen In 5 Minutes Unreal Engine 4 Tutorial [Video]. YouTube. https://www.youtube.com/watch?v=KpXmcqSITOg
[12] Epic Games. (n.d.-a). Level Streaming. Unreal Engine Documentation. Retrieved August 22, 2021, from https://docs.unrealengine.com/4.26/en-US/BuildingWorlds/LevelStreaming/
[13] Epic Games. (n.d.-b). Levels. Unreal Engine Documentation. Retrieved August 22, 2021, from https://docs.unrealengine.com/4.26/en-US/Basics/Levels/
[14] Wadstein, M. [Mathew Wadstein]. (2015, October 31). HTF do I? Loading Screens using Level Streaming in Unreal Engine 4 [Video]. YouTube. https://www.youtube.com/watch?v=QJVn2mW67YQ