LoginSignup
0
1

More than 1 year has passed since last update.

Unity練習[O1o0] インストールから

Last updated at Posted at 2022-11-05

Unity練習[O1o0]

What is This is
OS Windows
Tool Unity Hub 3.3.0 (あとでインストール)

手順

Step [O1o0] インストールから

Step [O1o1o0] インストール

👇 以下のホームページから、Unity をダウンロードしてほしい

📖 Unity

細かく言うと Unity Hub という名前の .exe ファイルかもしれない

👇 インストール先の例

C:\Program Files\Unity Hub

Unity Hub というアプリケーションが Windows にインストールされたと思う

英語版かと思うが、日本語化パッチのようなものは(あるのか知らないが)わたしは 当てない
英語の人口が 日本語の人口より多いのだから
エラーメッセージが出たとき検索して情報が多いのは 英語版 であることが多い経験則から そうする

Step [O1o2o0] 実行

Unity Hub アプリケーションを実行してほしい

プロジェクト一覧画面が出ているだろうか
これで Unity は使える状態になった

Step [O1o3o0] 学習(しない)

Unity Hub のメニューから Learn を探してクリックしてほしい
Learn - Featured が開いているだろう

科目のようなものが並んでいて、どれを選べばいいか分からないかもしれない

分からないときの最善の方法は すべてのメニューをクリックして 開けてみればよい

TIME TO COMPLETE:
XXX minutes

といった 完了するまでにかかる時間(分)が書いてあるだろう

1時間は 60 minutes(分)だということを思い出してほしい
たとえば Beginner Course2430 minutes だから 40日と30分かかる
これは大変だ、ということが分かったところで、いったん 着手せず、別の方法を探る

Step [O1o3o0] 学習 - 推奨(使わない)

Unity HubLearn - Recommended もクリックしてみよう

Sign in というリンクをクリックしてほしい

Unity ID というWebサイトに飛ばされる
アカウントを作り、ここに Email と Password を入れる

するとまた Unity HubLearn - Recommended に帰ってくる

Nothing here,yet (まだ、ここには何もない)と書いてある

Step [O1o4o0] 学習(しない)

Learn - Featured に戻ってくる

TYPE というものが薄い字で書かれてあり、その要素は以下のものがあるようだ

  • FOUNDATIONAL TUTORIAL (基礎チュートリアル)
  • BEGINNER COURSE (初級コース)
  • BEGINNER PROJECT (初級プロジェクト)
  • INTERMEDIATE COURSE (中級コース)
  • ADVANCED COURSE (上級コース)

基礎と初級はどっちを先にやればいいのか なんの説明もないが、

  • チュートリアル というものは ちょっとした操作説明ぐらいで大したことはやらない雰囲気がある
  • コース というものは そこに入ってしまうと長い雰囲気がある

とりあえず 手あたり次第 チュートリアルをクリックして開けて 説明だけ見ていってみる

Step [O2o0] Get started with the Unity Hub

Get started with the Unity Hub というフィーチャーが Foundational Tutorial で、 20 minutes のようだ

とりあえず これを実践してみよう

View on Unity Learn をクリックする

📖 https://learn.unity.com/tutorial/get-started-with-the-unity-hub#

👆 Web サイトへ飛ばされる。 Web サイトを読みながらやれ、ということらしい

じゃあ、やってみる

英語だが、ブラウザの機能で日本語訳すれば問題ない

どうも この Get started with the Unity Hub を読めるスキルレベルを習得すれば
わたしが Qiita で記事を書かなくても良さそうな感じだ

Step [O3o0] Unity projects

Unity のプロジェクトを どのフォルダーの下に作るかだが、デフォルトだと

C:\Users\{ユーザー名}

の直下に作ってしまった。
好きにすればいいが、わたしは次は

C:\Users\{ユーザー名}\Documents\UnityProjects

とでもフォルダーを切ってその下に置きなおした。やり方は以下の通り

  • Windows Explorer で 📂 UnityProjects を作成し、その下へ Essentials 3D project を移動
  • Unity Hub のプロジェクト一覧から、 Essentials 3D project の行の […] ボタンをクリック、 Remove project from list をクリックして一覧から消す
  • Unity Hub の Open ボタンで Essentials 3D project を選び直す

Step [O4o0] Explore the Unity Editor

👇 次は Explore the Unity Editor というフィーチャーを開けてみる。 Foundational Tutorial で、 10 minutes のようだ

📖 https://learn.unity.com/tutorial/explore-the-unity-editor-1

画面の見方、カメラの操作の仕方などが学べた

Step [O5o0] Create with Code

Learn - Featured に戻ってくる

Create with Code を開く。 Beginner Course で 2430 minutes だ。
期間が長いが、ちょっと かじってみて 嫌なら止めればいい。開けてみる

📖 https://learn.unity.com/course/create-with-code

いくつものパートに分かれているようだ

チュートリアルのWebサイトを見渡すと、
Select your Unity version というドロップダウンリストがある。
インストールしている Unity のバージョンに合わせる。
わたしは 2022年11月現在、 先行版の 2022.1 ではなく、 LTS の 2021.3 をインストールしているから、 2021.3 にする

2021.3 は 2021年3月 に見えるから 2021c にでもすればいいのに、LTS は 3 だから 2021.3 だ、という表記は Unityの作法として決まっている。

参考: 📖 3.Identify Unity versions

私は神ではないので仕方ない。人のやることを受け入れることにする。諦め、妥協し、不本意なことに従う

あとで分かったのだが この Select your Unity version、 記事毎に設定するもので、記事が存在しないバージョン番号もあるようだ。
多分、古い記事の内容と同じで、変更がなかったのだと思うことにしよう。 ユーザーからすれば「記事が無い!」という気持ちだが、まあ、いいだろう

Step [O5o1o0] Getting Started

  • Getting Started - Tutorial, Begginer, 35 Mins

さらにパートに分かれている

Step [O5o1o1o0] Course Introduction

  • Course Introduction - Tutorial, Begginer, 10 Mins

動画が2本あり、やる気を高める動画と、このコースの動画を上手く使える人がしている勉強方法について の2本のようだ

Tips:

動画は英語だし、字幕も 英語かスペイン語で 日本語はないかもしれない。
じゃあ 日本語話者は どうすればいいかというと、
動画の外の下側に Show transcript リンクがあり クリックすると 全文書き起こし のようなものが出てくるので
Web ブラウザーの機能で Web サイト全体に Google翻訳 をかけるといいだろう

例えば以下のように出る

00:00:12  たった一つなのに
          ちょっとしたアドバイス

00:00:14  大事なことなのでここでシェアします

00:00:17  私たちがこのビデオ全体を作ったこと
          それについてあなたに話すために。

00:00:20  ですから、これ以上苦労することなく、

00:00:22  ここに最も重要な3つがあります
          あなたが知っておくべき言葉

あとで分かったことだが、日本語が選べる動画もあるし、選べない動画もあるようだ。まあ、いいだろう。
また、ブラウザ翻訳すると、動画の上の字幕が日本語になるケースもあるし、ならないケースもある

Tips:

そんなことできないかも知れないが、教えておく

もし 英語のリスニング(聞き取り)をしながら、動画と、日本語訳の間を目で往復するのが 間に合わないときは、
動画を再生した直後に 一時停止し、
日本語訳を先に全部読んでしまってから(変な直訳で意味分からないが) 落ち着いて 動画を見るとよい

Tips:

動画は、ページに埋め込まれているとサイズが小さくて Unity Editor の文字が見えないので、全画面表示にして見るとよい

Step [O5o1o2o0] Install Unity Software

  • Install Unity Software - Tutorial, Begginer, 25 Mins

インストールしておけ、というものが説明されている

VS 2022 を使っているのに Microsoft Visual Studio Community 2019 なんか入れたくないが、
私は神ではないので仕方ない。人のやることを受け入れることにする。諦め、妥協し、不本意なことに従う

ほとんど 先にやってしまっていたことが書いてある。先に進む

Tips:

あとで分かったことだが、外部スクリプトを開くエディターは選ぶことができる

Step [O5o2o_1o0] Unit 1 - Player Control

あとで分かったことだが、 Unit 1 の目次を見て 少なそうだな、と思ったら 目次を開けると中に パートが沢山ある

Player Control は、すぐにゲーム開発に取り掛かるのではなく、 Unity Editor の基本操作を覚えていく感じ

Step [O5o2o0] Unit 1 - Introduction

  • Unit 1 - Introduction - Tutorial, Begginer, 5 Mins

確かに動画を1本見るだけで 5分もあれば終わる

Step [O5o2o1o0] Lesson 1.1 - Start your 3D Engines

  • Lesson 1.1 - Start your 3D Engines - Tutorial, Begginer, 1 Hour 20 Mins

ここから実技に入るのだろう

著作権で縛られていて商用不可の材料が .zip ファイルで置いてあるのでダウンロードする
Prototype 1 - Starter Files.zip ファイルがダウンロードされた

Tips:

Unity Project の名前が Prototype 1 なのだが、イケてないと思う。
Driving Car みたいな名前なら あとで思い出せると思うのだが、
レッスンをしばらくサボって 続きをやるかな、と思ってプロジェクト・リストに Prototype 1 という Unity Project が並んでいても
「なんだったかな、これ……?」と思う

じゃあ名前を変えればいいのかもしれないが、変えたら変えたで 後で分からなくなるのも嫌だ。
私は神ではないので仕方ない。人のやることを受け入れることにする。諦め、妥協し、不本意なことに従う

Step [O5o2o1o1o0] 1.Make a course folder and new project

動画ではデスクトップにフォルダーを置いていたが、
わたしは フォルダーの場所を 📂 "C:\Users\{ユーザー名}\UnityProjects\Create with Code" とした。

Step [O5o2o1o2o0] 2.Import assets and open Prototype 1

さっきダウンロードした Prototype 1 - Starter Files.zip ファイルをどこへでもいいので解凍する。
Prototype-1_Starter-Files.unitypackage というファイルが出てくる。
これは ユニティーパッケージ と呼ぶ

ユニティーパッケージを Unity Editor へインポートする方法は 動画で説明があるので、そうする

動画の中で同じことを2回やっているが、
同じことは2回できないという説明なのに 2回できてしまい、よく分からないので無視した

Step [O5o2o1o3o0] 3.Add your vehicle to the scene

とにかく 赤い車を置いた

Step [O5o2o1o4o0] 4.Add an obstacle and reposition it

箱を置いたり、X,Y,Z 座標をリセットしたり、ゲームオブジェクトの名前を変えたりした

Step [O5o2o1o5o0] 5.Locate your camera and run the game

カメラを選択する。プレイする

Step [O5o2o1o6o0] 6.Move the camera behind the vehicle

  • ギズモを使ってみる
  • 車の後ろにカメラを持っていく
  • マウスの左ボタンを押下しながら [Ctrl] キーをクリックすると何かできるそうだが、なんのことだか分からなかった

Step [O5o2o1o7o0] 7.Customize the interface layout

  • Unity Editor の、いろんなレイアウトを試す
  • My Layout を作ってほしい

Step [O5o3o0] Lesson 1.2 - Pedal to the Metal

  • Lesson 1.2 - Pedal to the Metal - Tutorial, Begginer, 1 Hour 10 Mins

  • まず1本の動画を見る。スクリプトの話しをする、という前振りだ

    • これから PlayerControl というものを C#言語で作りますよ、という前振り

Step [O5o3o1o0] 1.Create and apply your first script

  • C#スクリプト・ファイルを新規作成して、 それを Vehicle(赤い車)にアタッチするところまで

Step [O5o3o2o0] 2.Add a comment in the Update() method

  • Unity の補助画面として Visual Studio を開いて C# スクリプトをここに書く……、というところまで
    • Tips として、 ソリューション・エクスプローラーといったサイドビューを全て閉じ、画面全体をテキストエディターだけにするのが見やすいというテクニックを教えてくれる

Tips

📖 Openin unity script in visual studio does not work

[Edit] - [Preferences] - [External tools] - [External Script Editor]

👆 エディターを選べるらしい

Visual Studio Community 2022 を選んだ

Unity Editor で C# スクリプトファイルをダブルクリックすると、Visual Studio Community 2022 が出てきた

VSCode も選べるが、 Visual Studio の方が インテリジェンス機能が利くので良かった

Step [O5o3o3o0] 3.Give the vehicle a forward motion

  • トラックがすごいスピードで地平線まで走っていく

Step [O5o3o4o0] 4.Use a Vector3 to move forward

  • プログラムが数字で書かれているより、単語で書かれている方が見やすいよな、という話し
  • z の正の数は 必ず前方なのだろうか。自分から見た座標か?
  • z の単位は meter

Step [O5o3o5o0] 5.Customize the vehicle’s speed

  • トラックのスピードをゆっくりにする
  • Frame (いわゆる FPS)を知る

Step [O5o3o6o0] 6.Add RigidBody components to objects

  • トラックと障害物が突き抜けないように、物理的な設定をする
  • Mass の単位は killo-gram

Step [O5o3o7o0] 7.Duplicate and position the obstacles

  • 箱を増やす

Step [O5o3o8o0] 8.Lesson Recap

  • レッスン1.2のまとめ

Step [O5o4o0] Lesson 1.3 - High Speed Chase

  • Lesson 1.3 - High Speed Chase - Tutorial, Begginer, 50 Mins

  • これからすることの説明動画を観る

Step [O5o4o1o0] 1.Add a speed variable for your vehicle

  • Unity Editor のインスペクターを使って、ゲーム画面を見ながら変数の値を変更する方法を知る

Step [O5o4o2o0] 2.Create a new script for the camera

  • トラックとカメラのポジションを紐づける方法を知る

Step [O5o4o3o0] 3.Add an offset to the camera position

  • 近すぎるトラックとカメラのポジションを少し離す方法を知る

Step [O5o4o4o0] 4.Make the offset into a Vector3 variable

  • コードの書き方を少し綺麗にする

Step [O5o4o5o0] 5.Smooth the Camera with LateUpdate

  • 画面のカクツキの解消

Step [O5o4o6o0] 6.Edit the playmode tint color

  • 再生中に、編集中と思って間違って編集するのを防ぐ方法

Step [O5o4o7o0] 7.Lesson Recap

  • まとめ動画を見る

Step [O5o5o0] Lesson 1.4 - Step into the Driver's Seat

  • Lesson 1.4 - Step into the Driver's Seat - Tutorial, Begginer, 50 Mins

  • これからやることの動画を見る

Step [O5o5o1o0] 1.Allow the vehicle to move left/right

  • 左右への移動

Step [O5o5o2o0] 2.Base left/right movement on input

  • Turn Speed を 1 にするのを聞き逃さないでほしい
  • a, d キーや、 左、右キーを使って トラックを左右に移動できるようになる

Step [O5o5o3o0] 3.Take control of the vehicle speed

  • w, s キーや、上、下キーを使って トラックを前後に移動できるようにする

Step [O5o5o4o0] 4.Make vehicle rotate instead of slide

  • GlobalLocal ボタンの説明を聞いてやってみたが、 Local にしても Global と同じ動きだった。よく分からない
  • トラックの首振り(Rotate)メソッドの説明
  • Speed を 10、Turn Speed を 25 に変更

Step [O5o5o5o0] 5.Clean your code and hierarchy

  • ヒエラルキー・ウィンドウを整理する
  • コードを整理する

Step [O5o5o6o0] 6.Lesson Recap

  • 今までの振り返り動画を見る

Step [O5o6o0] Challenge 1 - Plane Programming

  • Challenge 1 - Plane Programming - Tutorial, Begginer, 30 Mins

  • 諦めるな、という感じの動画を見る

Step [O5o6o1o0] 1.Challenge 1 Overview

  • このチャレンジの全体像の説明動画を見る
    • バグがあるプロジェクトを修正するという内容
    • その答えは、そのチャレンジのページの下の方にあるから、分からなくなったときに見ろということ
    • それでも分からない場合の調べ方の紹介(Stack Overflow を見るとか、実際にプログラマーがやるような内容)
  • Prototype 1 に、 Challenge 1 カスタムパッケージをインポートしてみたものの、そのあと何をするのか?
    • とりあえず Instructions フォルダーに入っている動画を見たところで、このステップを完了にする
    • Scenes という名前のフォルダーを作ってくれていないだけで、 Challenge 1 というシーンが入っていたのでそれを開けてプレイする
      • 確かにバグがありそうだ。これを直すのがチャレンジらしい

Step [O5o6o2o0] 2.Warning

  • 説明が書いてある。バグを直せということと、分からなかったら下を読めということ
  • 下にはヒントが書いてあるだけで答えは書いていないので、下を読まなくても、自分のやり方で、先々、勝手に直していいと思う
    • あとでヒントを読むと、自分のやり方が想定されていない方法だと分かったので直した

Step [O5o6o3o0] 3.The plane is going backward

  • 確かに 後ろに進むように書いてあるので、前に進むように書き直した

Step [O5o6o4o0] 4.The plane is going too fast

  • 飛行機が速く飛んでいるので、遅く飛ぶようにスピードを遅めにした(Speedを0.5にした)が、ヒントを見ると、わたしの方法は想定された解答ではないようだ

Step [O5o6o5o0] 5.The plane is tilting automatically

  • 飛行機が機首を上げたり、下げたりするのを勝手にやっているから、ユーザーが上、下キーを押したときだけ機首を上げ、下げするように
    直せという課題だが、なぜ 機首が勝手に上がったり下がったりしてるのか分からない
    • プログラムに、機首を上げたり、下げたりしろと書いてあるのではないようだ
    • 発想の問題かと思う。一番シンプルだと思う方法で直した(飛行機のメッシュのチェックボックスを外した)が、ヒントを見ると、わたしの方法は想定された解答ではないようだ

Step [O5o6o6o0] 6.The camera is in front of the plane

  • カメラが なんにもない方向を向いているので、飛行機の方を向くといいと思う
  • この課題はもっと早いステップでやった方が、バグ探しが楽なんじゃないか?

Step [O5o6o7o0] 7.The camera is not following the plane

  • 飛行機は飛んでいくので、カメラが 飛行機と並走するように置くといいと思う

Step [O5o6o8o0] 8.Bonus: The plane’s propeller does not spin

  • 飛行機のプロペラが回っていないことを確認する
    • プロペラだけ部品が分かれているので、ヒエラルキー・ウィンドウから プロペラをダブルクリックすると 再生中でもシーン・ビューで一瞬拡大できる
  • インスペクターで設定できるのか、スクリプトを書くのか、どちらか分からない
  • プロペラを1フレームに1回り 回すには 思ってるより大きな数字になることに気づくと なんとかできた

Step [O5o7o0] Lab 1 - Project Design Document

  • Lab 1 - Project Design Document - Tutorial, Begginer, 1 Hour 30 Mins

やる気を出す動画を観る

Step [O5o7o1o0] 1.Understand what a Personal Project is

将来のレッスンでやることも含んだ、計画書の書き方の動画を観る

Step [O5o7o2o0] 2.Review Design Doc examples

  • 計画書の記入例の動画を観る
  • Google Document 形式、Word 形式、 PDF 形式で 同じ計画書の テンプレートをダウンロードできる

Step [O5o7o3o0] 3.Complete your Project Concept V1

  • 計画書を観ながら、新しいプロジェクトを計画する例の動画を観る

Step [O5o7o4o0] 4.Complete your Project Timeline

  • タイムラインを書こう、という話しの動画を観る
  • 追加したい機能は Backlog に書く

Step [O5o7o5o0] 5.Complete your MVP sketch

  • オフィスソフトを用いたアイデアスケッチの作り方動画を観る

Step [O5o7o6o0] 6.Recap

  • まとめ動画を観る

Step [O5o8o0] Quiz 1

  • Quiz 1 - Quiz, Begginer, 15 Mins

クイズが10問ある。ラジオボタンを選択していく

Step [O5o9o0] Bonus Features 1 - Share your Work

  • Bonus Features 1 - Share your Work - Tutorial, Begginer, 1 Hour

Step [O5o9o1o0] 1.Overview

動画を観る。
難しいことをやらせるつもりで、このステップはスキップしてよいとの説明がある

Step [O5o9o2o0] 2.Easy: Obstacle pyramids

動画を観る。
障害物を積んでみろ、ということらしい。
あとで分かったことだが、ヒントは記事のページの下の方にある。とりあえず自分で調べてやる

確かに、ブロックを積む方法が分からない。宙に浮く

📖 オブジェクトをきれいに配置する

👆 [Ctrl] キーを押しながら 箱を動かしたりしてみるが、箱の表面に スナップするようなことはなく 突き抜けたりする

📖 Unityショートカット : エディタ上でオブジェクトを密着させて配置する

👆 [Shift] + [Ctrl] キーを押しながら 箱を動かしたりしてみるが、スナップしている感じはしない。食い込む
ただ、この操作が思っていることに一番近いから使う

  • Create Emptry で Pile と名前を付けて 箱を4つ入れ、 Pile を [Ctrl] + [D] で複製するなど 効率の良い作業方法を勝手にする

途中で飽きるので 完了 にする

Step [O5o9o3o0] 3.Medium: Oncoming vehicles

動画を観る。
対向車を走らせてみろ、ということらしい。
あとで分かったことだが、ヒントは記事のページの下の方にある。とりあえず自分で調べてやる

とりあえず 白いバスを置いてみるが、反対を向かせる方法が分からん。Y軸を固定して、回転すればいいのか。できた

走らせるには、スクリプトを新規作成して ゲームオブジェクトにアタッチして、
スクリプトの内容は Z軸の正の方向に進んでもらうようにすればいいと思う

BusController.cs みたいなファイル名でいいか?

📺 やってみた

途中で飽きるので 完了 にする

Step [O5o9o4o0] 4.Hard: Camera switcher

動画を観る。
キーボードのキーを押すと 運転席視点と、車の後ろからの視点を 切り替えろ、ということらしい。
あとで分かったことだが、ヒントは記事のページの下の方にある。とりあえず自分で調べてやる

とりあえず [Edit] - [Project Settings...] - [Input Manager] を見てみる
JumpSpace キーが割り当てられているので、とりあえず これを使ってみる

FollowPlayer.cs というスクリプトは、車の後ろからの始点 を意味するので、
これとは別に PerspectiveOfDriversSeat.cs というスクリプトを作ればいいのか?
Jump キーを受け取るスクリプトは どのゲームオブジェクトだ?

📖 Switch between 2 cameras

👆 2008年頃のフォーラムらしく、リンク切れしていて リンク先の URLが業者に乗っ取られている

Unity Hub から 現在のフォーラムへ移動

📖 https://forum.unity.com/

👆 検索の性能が悪く、欲しい記事が見つからない

チュートリアルの記事に コメントがあるので 読んでみるが ベスト・プラクティス を感じない

Hierarchy ビューの Main Camera に C# スクリプトを2つアタッチすることはできるようだ。
FollowPlayer.csPerspectiveOfDriversSeat.cs の2つのスクリプトを持たせた。
このスクリプトの違いは、 offset だけ

    private Vector3 offset = new Vector3(0.5f, 2, 1.5f);

👆 この x, y, z の数字は シーン画面、ゲーム画面を見ながら手調整していく

ゲーム中に Space キーを押すと、その2つのスクリプトのうち、片方のスクリプトだけが有効になるように
交互に切り替わってくれればいいわけだが

CameraSwitcher という C# スクリプト・ファイルを作るか
そして Main Camera ゲームオブジェクトへアタッチする

スペースキーを押したかどうかを、どうやって取得するか?

👇 例えば、これでどうか?

    // Update is called once per frame
    void Update()
    {
        var isPressSpaceKey = Input.GetKeyDown(KeyCode.Space);
    }

Main Camera にアタッチされているスクリプトの 活性/不活性 を切り替えたいが、
Main Camera にどうアクセスし、
Main Camera にアタッチされているスクリプトの 活性/不活性 にどうやってアクセスする?

インターネットで適当にググることにする

📖 他のスクリプトの変数を取得する

👇 こうすると、それっぽくなるが、思ってる感じとは違う。
思ってるのと違っていれば手調整する、の繰り返し

using System;
using UnityEngine;

public class CameraSwitcher : MonoBehaviour
{
    private int cameraView;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        var isPressSpaceKey = Input.GetKeyDown(KeyCode.Space);

        if (isPressSpaceKey)
        {
            // 0 と 1 を ひっくり返す
            cameraView = 1 - cameraView;

            var followPlayer = this.GetComponent<FollowPlayer>();
            var perspectiveOfDriverSeat = this.GetComponent<PerspectiveOfDriversSeat>();

            switch (cameraView)
            {
                case 0:
                    followPlayer.enabled = false;
                    perspectiveOfDriverSeat.enabled = true;
                    break;

                case 1:
                    followPlayer.enabled = true;
                    perspectiveOfDriverSeat.enabled = false;
                    break;

                default:
                    throw new Exception($"Unexpected cameraView:{cameraView}");
            }
        }

    }
}

📺 やってみた

Step [O5o9o5o0] 5.Expert: Local multiplayer

動画を観る。
画面を2分割して、左画面と 右画面で 別のカメラにして、車の操作も 1P と 2P で分けろ、ということらしい。
あとで分かったことだが、ヒントは記事のページの下の方にある。とりあえず自分で調べてやる

ヒントによると、カメラには Viewport Rect というプロパティがあるそうだ。確かにある

じゃあ とりあえず 2台目の カメラを 置いてみるか
Main Camera[Ctrl] + [D] キーで複製する。 2P Camera に名称変更

Vehicle[Ctrl] + [D] キーで複製する。 2P Vehicle に名称変更

2P CameraFollowPlayer スクリプトの Player プロパティと、
PerspectiveOfDriversSeat スクリプトの Player プロパティに Vehicle が入っているので、
これを 2P Vehicle に置き換える

Main CameraViewport RectX 0 Y 0 W 1 H 1 を、 X 0 Y 0 W 0.5 H 1 に置き換える
2P CameraViewport RectX 0 Y 0 W 1 H 1 を、 X 0.5 Y 0 W 0.5 H 1 に置き換える

カメラや 車の位置を シーン画面で調整する

PlayerController.cs が 1P のキー設定になっている。
これを [Ctrl] + [D] キーで複製して Player2Controller.cs に名称変更する。
ファイルを開くと クラス名が PlayerController なので、 Player2Controller に変える
Player2Controller.cs2P Vehicle にアタッチする。
もともと付いていた PlayerController.cs はリムーブする

        horizontalInput = Input.GetAxis("Horizontal");
        forwardInput = Input.GetAxis("Vertical");

👆 この "Horizontal" などの名称を変える必要があると思う。 Input Manager を調べてみる
プレイヤー1と プレイヤー2で 名称が分かれているわけでも無さそうだ

📖 Unityでオフラインの対戦プレイや協力プレイを実現する方法

👆 自分で Horizontal2 のように名前を変えなければいけないみたいだ、最初から設定しておいてくれたらいいのに……
Joy Num というのが プレイヤー番号に相当するのだろう

Negative Button などの設定を1つ1つ変えていく
Gravity とか Dead とか何だか分からないが 1プレイヤーと 2プレイヤーで 同じになるように設定を変えていく
最初の設定から けっこう変えないといけない

📺 やってみた

Step [O5o9o6o0] Share your work

画像をアップロードするといいらしいが、よく分からない
スクリーンショットを撮ってアップロードする

Submission Gallery というところに公開されるようだ

Step [O6o0] Unit 2 - Introduction

  • Unit 2 - Introduction - Tutorial, Begginer, 5 Mins

📖 Unit 2 - Introduction

案内役の人のテンションが上がっている動画を見る。
英語のちからがないので 直訳を読むが、 その言い回しで 何を伝えようとしているのか よく分からないが まあいいだろう

Step [O6o1o0] Lesson 2.1 - Player Positioning

  • Lesson 2.1 - Player Positioning - Tutorial, Begginer, 1 Hour

動画を観る。 if文の話しに触れてくれる

Step [O6o1o1o0] 1.Create a new Project for Prototype 2

動画を観る

Unity Hub を使って Prototype 2 プロジェクトを新規作成し、
.unitypackage をプロジェクトにインポートする

SampleScene は削除する
Prototype 2 シーンを開く

Step [O6o1o2o0] 2.Add the Player, Animals, and Food

動画を観る

  • キャラクターを配置する
  • 牧場の娘のゲームオブジェクト名を Player に名称変更する

Step [O6o1o3o0] 3.Get the user’s horizontal input

動画を観る

  • Assets フォルダーの下に Scripts フォルダーを作成する
  • Assets/Scripts フォルダーの下に PlayerController.cs ファイルを作成する
  • 水平方向の入力を変数に受け取るだけ

以下略

Step [O6o1o4o0] 4.Move the player left-to-right

動画を観る。
内容の趣旨は プロトタイプ1 と被る

  • キー入力を、プレイヤーの水平位置に連動させる

Step [O6o1o5o0] 5.Keep the player inbounds

動画を観る。
C# を初めて書く人に向けての動画なので、コードをコピペするだけでもいいが、一応観ていく

Step [O6o1o6o0] 6.Clean up your code and variables

動画を観る。
C# を初めて書く人に向けての動画なので、コードをコピペするだけでもいいが、一応観ていく

Step [O6o1o7o0] 7.Lesson Recap

まとめ動画を観るだけ

Step [O6o2o0] Lesson 2.2 - Food Flight

  • Lesson 2.2 - Food Flight - Tutorial, Begginer, 1 Hour 10 Mins

動画を観るだけ

  • Prefabs の説明

Step [O6o2o1o0] 1.Make the projectile fly forwards

動画を観るだけ

  • 既知の技術の反復練習

Step [O6o2o2o0] 2.Make the projectile into a prefab

動画を観る

  • Prefabs の使い方の実践
    • プレファブを、C#スクリプトから参照できるようにする

Step [O6o2o3o0] 3.Test for spacebar press

動画を観る

  • スペースバーを押下されたことを C#スクリプトで判定する if文コードブロックを書くところまで

Step [O6o2o4o0] 4.Launch projectile on spacebar press

動画を観る

  • Instantiate の説明
    • 1行書くだけ

📺 やってみた

Step [O6o2o5o0] 5.Make animals into prefabs

動画を観る

  • 動物もプレファブにする

Step [O6o2o6o0] 6.Destroy projectiles offscreen

動画を観る

  • 画面外に飛んでいったピザを削除する方法の説明(画面外に出ていった動物はまだ削除しない)
  • Prefabs を Override するのが要点
    • Hierarchy ビューのゲームオブジェクトにスクリプトをアタッチしても、プレファブには反映されない
      • Inspecter ビューの右上の Overrides ドロップダウンリストをクリックして、その中の Apply All ボタンをクリックすると、プレファブに反映されると説明される

Step [O6o2o7o0] 7.Destroy animals offscreen

動画を観る

  • 画面外に出ていった動物を削除する方法の説明
  • if ~ else 文の説明

Step [O6o2o8o0] 8.Lesson Recap

動画を観る

Step [O6o3o0] Lesson 2.3 - Random Animal Stampede

  • Lesson 2.3 - Random Animal Stampede - Tutorial, Begginer, 1 Hour

動画を観る

  • Ramdomness

Step [O6o3o1o0] 1.Create a spawn manager

動画を観る

  • Unity Editor で可変サイズの配列のプロパティを置く方法の説明

Step [O6o3o2o0] 2.Spawn an animal if S is pressed

動画を観る

  • Sキーを押すと 動物が発射される方法の説明

Step [O6o3o3o0] 3.Spawn random animals from array

動画を観る

  • 乱数の説明

Step [O6o3o4o0] 4.Randomize the spawn location

動画を観る

  • 乱数の説明(動物を生成する線上の点)

Step [O6o3o5o0] 5.Change the perspective of the camera

動画を観る

  • プロパティ1個分の説明
    • パースを付ける方法、パースを付けない方法の説明

Step [O6o3o6o0] 6.Lesson Recap

動画を観るだけ

Step [O6o4o0] Lesson 2.4 - Collision Decisions

  • Lesson 2.4 - Collision Decisions - Tutorial, Begginer, 50 Mins

動画を観る

  • 関数を初めて自分で書くという話し

Step [O6o4o1o0] 1.Make a new method to spawn animals

動画を観る

  • 関数を始めて自分で書くという話し
    • リファクタリングするだけ

Step [O6o4o2o0] 2.Spawn the animals at timed intervals

動画を観る

  • InvokeRepeating() を使う
    • タイマーを1行で書ける

📺 やってみた

Step [O6o4o3o0] 3.Add collider and trigger components

動画を観る

  • コライダー の説明
  • RigidBodyGravity の説明
  • Inspecter ビューの Overrides ドロップダウンリストの Apply All ボタンも必要なら使う

Step [O6o4o4o0] 4.Destroy objects on collision

動画を観る

  • OnTriggerEnter メソッドのオーバーライドの説明
  • きつねから 止まっているピザに当たりに来ても 衝突とは判定されない?
  • 動いているピザが きつねに当たると 衝突と判定される?

Step [O6o4o5o0] 5.Trigger a “Game Over” message

動画を観る

  • Debug.Log() の説明
    • Unity のステータスバーは暗くて文字が見えない(文字の色ではなく、ログが非表示になっている)
      • [Window] - [General] - [Console] からコンソール・ウィンドウを表示できるが、デバッグ・ログは出ていない
        • コンソールの右上の [吹き出しに?が描かれたアイコン] をクリックすると ログが表示された。分かりづらい
          • ステータスバーにも文字が表示されるようになった
  • シーンビューから ピザと動物を消す
  • ソースコードの自動フォーマットの説明

Step [O6o4o6o0] 6.Lesson Recap

動画を観るだけ

Step [O6o5o0] Challenge 2 - Play Fetch

📅 2022-12-03

  • Challenge 2 - Play Fetch - Tutorial, Begginer, 1 Hour

  • バグのあるプログラムを直していこう、というもの

Step [O6o5o1o0] 1.Overview

動画を観る

  • Challenge 2 - Starter Files.zip をダウンロードして解凍する。 Prototype-2_Starter-Files.unitypackage が入っている
  • Prototype 2 プロジェクトに、 Prototype-2_Starter-Files.unitypackage パッケージをインポートする

Project ビュー:

    📂 Assets
    └── 📂 Challenge 2
        ├── 📂 Instructions
        │    └── 📺 Challenge 2 - Outcome       # 完成形の動画
        └── 📄 Challenge 2                      # シーン

👆 インポートされる

Challenge 2 シーンをクリックする

Challenge 2 - Outcome 動画をクリックして動画を観る

Step [O6o5o2o0] 2.Warning

このチャレンジの説明

  • 以下のステップを続けて読んでいく

Step [O6o5o3o0] 3.Dogs are spawning at the top of the screen

画面の上で 犬が生成されているので、これをボールに変えること

Step [O6o5o4o0] 4.The player is spawning green balls instead of dogs

スペースキーを押すと 農夫が緑色のボールをスポーンしている。
代わりに 犬を生成するように変えること

Step [O6o5o5o0] 5.The balls are destroyed if anywhere near the dog

犬の近くでないのに、ボールが破棄されている。
代わりに、犬にぶつかったときにボールが破棄されているように見えてほしい

Step [O6o5o6o0] 6.Nothing is being destroyed off screen

生成されたボールと犬が破棄されていないので、
ボールは画面下に出たとき破棄してほしいし、犬は画面左の外に出たとき破棄してほしい

Step [O6o5o7o0] 7.Only one type of ball is being spawned

ボールが1種類しか落ちてこないおんで、
ボールが3種類落ちてくるようにしてほしい

Step [O6o5o8o0] 8.Bonus: The spawn interval is always the same

ボールが等間隔の時間で落ちてきているので、
3~5秒の間隔でランダムに落ちてきてほしい

Step [O6o5o9o0] 9.Bonus: The player can “spam” the spacebar key

犬を連射できるので、
連射の間隔を一定時間 開けてほしい

👇 こうやった

PlayerControllerX.cs:

using UnityEngine;

public class PlayerControllerX : MonoBehaviour
{
    public GameObject dogPrefab;

    /// <summary>
    /// 溜め時間
    /// </summary>
    private float chargeSecs = 1.0f;

    /// <summary>
    /// 経過時間
    /// </summary>
    private float elapsedSecs = float.MaxValue;

    // Update is called once per frame
    void Update()
    {
        elapsedSecs += Time.deltaTime;

        // On spacebar press, send dog
        if (Input.GetKeyDown(KeyCode.Space) && elapsedSecs > chargeSecs)
        {
            Instantiate(dogPrefab, transform.position, dogPrefab.transform.rotation);
            elapsedSecs = 0.0f;
        }
    }
}

Step [O6o6o0] Lab 2 - New Project with Primitives

📅 2022-12-03 sat 16:21

  • Lab 2 - New Project with Primitives - Tutorial, Begginer, 1 Hour

プリミティブ は、球体、立方体、平面 のような基本図形のこと

できあいのものをインポートせず、新規作成のプロジェクトから、作っていくという趣旨

動画を観る

Step [O6o6o1o0] 1.Create a new Project and rename your scene

動画を観る

  • Personal Project を作る

Step [O6o6o2o0] 2.Create a background plane

動画を観る

  • 背景、または床を置こう という話し
  • Plane オブジェクトの Inspector ビューの Transform コンポーネントの Reset ボタンを押そう、という話し

Step [O6o6o3o0] 3.Create primitive Player with a new material

動画を観る

  • 球体を シーン ビューに置く。名前は Player

Project ビュー:

    📂 Assets
👉  └── 📂 Materials    # 新規作成
👉      └── 📄 Blue     # 新規作成
  • 青いマテリアルを作って、シーン ビューに置いてある球体にドラッグ&ドロップする

Step [O6o6o4o0] 4.Position camera based on project type

動画を観る

  • カメラのポジションを決めよう、という話し

Step [O6o6o5o0] 5.Enemies, obstacles, projectiles & materials

動画を観る

  • 敵、障害物 等の配置
  • 着色

Step [O6o6o6o0] 6.Export a Unity Package backup file

動画を観る

  • バックアップの取り方の説明
    • *.unitypackage ファイルを作れる
    📂 UnityProjects    # 練習用フォルダー
👉  └── 📂 Backups      # 新規作成

Step [O6o6o7o0] 7.Lesson Recap

動画を観るだけ

Step [O6o7o0] Quiz 2

  • Quiz 2 - Quiz, Begginer, 15 Mins

概要は文章が長くて堅苦しいし、読み飛ばしていいかも。

Step [O6o8o0] Bonus Features 2 - Share your Work

  • Bonus Features 2 - Share your Work - Tutorial, Begginer, 1 Hour

Step [O6o8o1o0] 1.Overview

4つの難易度別の努力目標が与えられる

完成図の動画を観る

  • 以下のステップを続けて読んでいく

Step [O6o8o2o0] 2.Easy: Vertical player movement

動画を観る

  • プレイヤーを上下に動かせるようにする

Step [O6o8o3o0] 3.Medium: Aggressive animals

動画を観る

  • 動物を左右から出すようにする

  • 左右から出てきた動物に当たると、コンソールにゲームオーバーと表示する

  • プレイヤーに BoxCollider を付けると、ピザを発射した瞬間にプレイヤーが消えてしまう。また、動物が、動物に当たって消えてしまう

    • レイヤー という概念が必要
      • Player の Inspector の上の方の Layer ドロップダウンリストの Add Layer をクリック
        • 例えば User Layer 10Player と入力
        • User Layer 11Animal と入力
        • User Layer 12Projectile (発射物)と入力
      • Player ゲームオブジェクトの Layer ドロップダウンリストを Player レイヤーに変更
      • 各動物ゲームオブジェクトの Layer ドロップダウンリストを Animal レイヤーに変更
      • ピザの Layer ドロップダウンリストを Projectile レイヤーに変更
      • [Edit] - [Project Settings] - [Physics] -[Layer Collision Matrix]
        • PlayerAnimal のチェックがオンである
        • PlayerProjectile のチェックをオフにする
        • AnimalAnimal のチェックをオフにする
    • ピザを参考に、プレイヤー、動物にも RigidBody を追加。 Use Gravity のチェックは外す

DetectCollisions.cs:

using UnityEngine;

/// <summary>
/// 衝突の検出
/// </summary>
public class DetectCollisions : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    /// <summary>
    /// 自分: 動物
    /// </summary>
    /// <param name="other">農夫、動物</param>
    private void OnTriggerEnter(Collider other)
    {
        if (gameObject.name == "Player")
        {
            Debug.Log("Damage! Game Over!");
        }

        // このスクリプトの所有者を破棄
        Destroy(gameObject);

        // このスクリプトの所有者と衝突した方を破棄
        Destroy(other.gameObject);
    }
}

PlayerController.cs:

using UnityEngine;

public class PlayerController : MonoBehaviour
{
    public float horizontalInput;
    public float verticalInput;
    public float speed = 10.0f;
    public float xRange = 10;

    /// <summary>
    /// 発射するもの
    /// </summary>
    public GameObject projectilePrefab;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        // Keep the player in bounds
        if (transform.position.x < -xRange)
        {
            transform.position = new Vector3(-xRange, transform.position.y, transform.position.z);
        }
        if (transform.position.x > xRange)
        {
            transform.position = new Vector3(xRange, transform.position.y, transform.position.z);
        }

        horizontalInput = Input.GetAxis("Horizontal");
        transform.Translate(Vector3.right * horizontalInput * Time.deltaTime * speed);

        verticalInput = Input.GetAxis("Vertical");
        transform.Translate(Vector3.forward * verticalInput * Time.deltaTime * speed);

        // Launch a projectile from the player
        if (Input.GetKeyDown(KeyCode.Space))
        {
            Instantiate(projectilePrefab, transform.position, projectilePrefab.transform.rotation);
        }
    }
}

Step [O6o8o4o0] 4.Hard: Game user interface

動画を観る

やりたいことは次の通り

プレイヤーに Lives というプロパティーを持たせ、 3 から開始し、 0 になると ログに Game Over と出す。
動物とプレイヤーが衝突すると Lives が 1 減る。
Score というプロパティーがあり、 0 から開始し、 ピザと動物が衝突すると 1 増える。
LivesScore は コンソールに表示する

プレイヤーに LivesScore を持たせると、プレイヤーを破棄したときに LivesScore も画面から消えてしまう。
とりあえず GameManager ゲームオブジェクトを新規作成して、 GameManager C#スクリプトをアタッチすることにする

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public int lives = 3;
    public int score = 0;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

LivesScore が変化するタイミング、 Game Over と表示するタイミングを以下にリストする

  • 動物とプレイヤーが衝突したとき
  • ピザと動物が衝突したとき
  • Score が 0 になったタイミング

👇 とりあえず Lives を 1 減らすメソッドと、 Lives が 0 になったときに Game Over を表示するコードを GameManager.cs に追加する。
ついでに Score を 1 増やすメソッドも追加する

    /// <summary>
    /// ライフを1減らす
    /// </summary>
    public void DecreaseLive()
    {
        if (0 < lives)
        {
            lives--;
            Debug.Log($"Lives:{lives}");

            if (lives == 0)
            {
                Debug.Log("Game Over");
            }
        }
    }

    /// <summary>
    /// スコアを1増やす
    /// </summary>
    public void IncreaseLive()
    {
        score++;
        Debug.Log($"Score:{score}");
    }

👇 DetectCollisions.csOnTriggerEnter を書きかえる

    /// <summary>
    /// 
    /// </summary>
    /// <param name="other"></param>
    private void OnTriggerEnter(Collider other)
    {
        // レイヤー名を取得
        var myLayerName = LayerMask.LayerToName(gameObject.layer);
        var opponentLayerName = LayerMask.LayerToName(other.gameObject.layer);
        // Debug.Log($"myLayerName:{myLayerName} opponentLayerName:{opponentLayerName}");

        // プレイヤーが、動物に衝突したとき
        if (myLayerName == "Player" && opponentLayerName == "Animal")
        {
            var gameManagerScript = GameObject.Find("GameManager").GetComponent<GameManager>();

            // ライフを減らす
            gameManagerScript.DecreaseLive();

            if (gameManagerScript.lives < 1)
            {
                // プレイヤーを破棄
                Destroy(gameObject);
            }
        }

        // 発射物が、動物に衝突したとき
        if (myLayerName == "Projectile" && opponentLayerName == "Animal")
        {
            var gameManagerScript = GameObject.Find("GameManager").GetComponent<GameManager>();

            // スコアを増やす
            gameManagerScript.IncreaseLive();
        }

        // このスクリプトの所有者を破棄(プレイヤーを除く)
        if (myLayerName != "Player")
        {
            Destroy(gameObject);
        }

        // 衝突した相手の方を破棄(プレイヤーを除く)
        if (opponentLayerName != "Player")
        {
            Destroy(other.gameObject);
        }
    }

Step [O6o8o5o0] 5.Expert: Animal hunger bar

動画を観る

  • 大きな改造になるので、改造に取り掛かる前に Export Package で バックアップを残した方がよい

やりたいことは次の通り

各動物の上に [空腹バー] を表示する。
[空腹バー] は最初空っぽで、ピザを当てるごとに増えていく。 [空腹バー] が満たされると動物は破棄される。
動物の種類ごとに、ピザを何回当てればいいか異なる

思ったのは次の通り

GUI を作るのは スキルレベル が高すぎる。平面図形を使った簡単なものでいいか。
黒い平面と、緑色の平面を2枚重ねればいいのでは。
緑色の平面と、黒い平面の左側を揃えて、緑色の平面の横幅が 0~黒い平面の横幅 に伸びていくと考える

Scene ビュー:

    📄 HungerBar            # GameObject
    ├── 📄 Plane_Green      # GameObject with Material
    └── 📄 Plane_Black      # GameObject with Material

この HungerBar ゲームオブジェクトを 動物の真上に くっつけるにはどうすればいいか?
とりあえず、 HunberBar C#スクリプトを作って、 HunberBar ゲームオブジェクトにアタッチする

次に、動物に 空腹 カウントを持たせることにする。
動物は [空腹バー] を持っているのだから、空腹バーに 空腹カウントを持たせるのがよさそう。
現在値の value、 最大値の max があればいいだろうか。

空腹バーのためのコードを追加

次に、ピザが動物に当たったときに、 満腹度を 1 増やしたい

次に、緑色の平面の横幅を変更したい。
どうやって 緑色の平面 を取得するのか?
HungerBar ゲームオブジェクト の子要素の Plane_Green ゲームオブジェクトを取るには?
ヒエラルキー・ビューのゲームオブジェクトを指すフルパスのようなものは無いらしい

動物から、 [空腹バー] への紐づきが無いことに気づく。
Animal C#スクリプトを作成し、 各動物にアタッチする

コードは以下の通り

Animal.cs:

using UnityEngine;

public class Animal : MonoBehaviour
{
    /// <summary>
    /// 空腹バー
    /// </summary>
    public GameObject hungerBarPrefab;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

SpawnManager.cs / SpawnRandomAnimal method:

    public GameObject hungerBarPrefab;
    private float spawnRangeZ = 10;
    private float spawnPosX = 30;

    void SpawnRandomAnimal()
    {
        // 進行方向
        // -1: to right
        // 0: to down
        // 1: to left
        var angleType = Random.Range(-1, 2);

        // Randomly generate animal index and spawn position
        Vector3 spawnPos;

        if (angleType == 0)
        {
            spawnPos = new Vector3(Random.Range(-spawnRangeX, spawnRangeX), 0, spawnPosZ);
        }
        else if (angleType == 1)
        {
            spawnPos = new Vector3(spawnPosX, 0, Random.Range(-spawnRangeZ, spawnRangeZ));
        }
        else
        {
            spawnPos = new Vector3(-spawnPosX, 0, Random.Range(-spawnRangeZ, spawnRangeZ));
        }

        // 動物
        int animalIndex = Random.Range(0, animalPrefabs.Length);
        var animal = Instantiate(animalPrefabs[animalIndex], spawnPos, animalPrefabs[animalIndex].transform.rotation);
        animal.transform.Rotate(Vector3.up, (angleType * 90.0f + 360.0f) % 360.0f);// 向きを変える
        var animalScript = animal.GetComponent<Animal>();

        // 空腹バー
        var hungerBar = Instantiate(hungerBarPrefab, spawnPos, new Quaternion(0.0f, 0.0f, 0.0f, 0.0f));
        var hungerBarScript = hungerBar.GetComponent<HungerBar>();

        // 動物の種類に応じて、空腹バーの最大値を変更する
        if (animal.name == "Animal_Fox_01(Clone)")
        {
            hungerBarScript.max = 1;
        }
        else if (animal.name == "Animal_Doe_01(Clone)")
        {
            hungerBarScript.max = 2;
        }
        else if (animal.name == "Animal_Moose_01(Clone)")
        {
            hungerBarScript.max = 3;
        }

        // 動物と空腹バーを相互紐づけ
        hungerBarScript.precededPrefab = animal;
        animalScript.hungerBarPrefab = hungerBar;
    }

DetectCollisions.cs / OnTrigger method:

    private void OnTriggerEnter(Collider other)
    {
        bool isDestroySelf = false;
        bool isDestroyOpponent = false;

        // レイヤー名を取得
        var myLayerName = LayerMask.LayerToName(gameObject.layer);
        var opponentLayerName = LayerMask.LayerToName(other.gameObject.layer);
        // Debug.Log($"myLayerName:{myLayerName} opponentLayerName:{opponentLayerName}");

        // プレイヤーが、動物に衝突したとき
        if (myLayerName == "Player" && opponentLayerName == "Animal")
        {
            var gameManagerScript = GameObject.Find("GameManager").GetComponent<GameManager>();

            // ライフを減らす
            gameManagerScript.DecreaseLive();

            if (gameManagerScript.lives < 1)
            {
                // プレイヤーを破棄
                Debug.Log($"Destroy player. Life none.");
                isDestroySelf = true;
            }
        }

        // 発射物が、動物に衝突したとき
        if (myLayerName == "Projectile" && opponentLayerName == "Animal")
        {
            // スコアを増やす
            var gameManagerScript = GameObject.Find("GameManager").GetComponent<GameManager>();
            gameManagerScript.IncreaseLive();

            // 動物から、空腹バーを取得
            var animalScript = other.gameObject.GetComponent<Animal>();
            var hungerBar = animalScript.hungerBarPrefab;
            var hungerBarScript = hungerBar.GetComponent<HungerBar>();

            // 満腹度を増やす
            hungerBarScript.IncreaseValue();

            // 満腹になっていれば、その動物オブジェクトと、空腹バーの両方を破棄
            if (hungerBarScript.IsFull())
            {
                Debug.Log($"Destroy animal. value:{hungerBarScript.value} max:{hungerBarScript.max}");
                isDestroyOpponent = true;
                Destroy(hungerBar);
            }

            // 発射物を破棄
            Debug.Log($"Destroy food. Because it hit an animal.");
            isDestroySelf = true;
        }

        // このスクリプトの所有者を破棄
        if (isDestroySelf)
        {
            Destroy(gameObject);
        }

        // 衝突した相手の方を破棄
        if (isDestroyOpponent)
        {
            Destroy(other.gameObject);
        }
    }

GameManager.cs:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    public int lives = 3;
    public int score = 0;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {

    }

    /// <summary>
    /// ライフを1減らす
    /// </summary>
    public void DecreaseLive()
    {
        if (0 < lives)
        {
            lives--;
            Debug.Log($"Lives:{lives}");

            if (lives == 0)
            {
                Debug.Log("Game Over");
            }
        }
    }

    /// <summary>
    /// スコアを1増やす
    /// </summary>
    public void IncreaseLive()
    {
        score++;
        Debug.Log($"Score:{score}");
    }
}

HungerBar.cs:

using UnityEngine;

/// <summary>
/// 空腹バー
/// 
/// - 満腹度
/// </summary>
public class HungerBar : MonoBehaviour
{
    /// <summary>
    /// 先行プレファブ
    /// 
    /// - 張り付き先のゲームオブジェクト
    /// </summary>
    public GameObject precededPrefab;

    /// <summary>
    /// 現在値
    /// </summary>
    public int value = 0;

    /// <summary>
    /// 最大値
    /// </summary>
    public int max = 1;

    // Start is called before the first frame update
    void Start()
    {
        // 緑色の平面の横幅を変更する
        this.Repaint();
    }

    // Update is called once per frame
    void Update()
    {
        // 先行プレファブと、位置を同じくします
        if (precededPrefab != null)
        {
            gameObject.transform.position = precededPrefab.transform.position;
        }
    }

    /// <summary>
    /// 満タンか?
    /// </summary>
    /// <returns></returns>
    public bool IsFull()
    {
        return max <= value;
    }

    /// <summary>
    /// 1 増やす
    /// </summary>
    public void IncreaseValue()
    {
        if (value < max)
        {
            value++;
            Debug.Log($"Nice taste! value:{value} max:{max}");
        }

        // 緑色の平面の横幅を変更する
        this.Repaint();
    }

    /// <summary>
    /// 緑色の平面の横幅を変更する
    /// </summary>
    private void Repaint()
    {
        if (precededPrefab != null)
        {
            var rate = (float)value / (float)max;

            var greenPlain = gameObject.transform.GetChild(0);
            greenPlain.transform.localScale = new Vector3(rate, greenPlain.transform.localScale.y, greenPlain.transform.localScale.z);
            // Debug.Log($"Repaint. greenPlain.name:{greenPlain.name} greenPlain.transform.localScale:{greenPlain.transform.localScale} rate:{rate} value:{value} max:{max}");
        }
        //else
        //{
        //    Debug.Log("[Error] Repaint failed. Because precededPrefab is null.");
        //}
    }
}

見栄えが悪いところはあるが、大変なので先に進むことにする

Step [O6o8o6o0] 6.Hints and solution walkthrough

ヒントが書いてある

Step [O6o8o7o0] Share your work

📅 2022-12-04 sun 18:30

作品動画をアップロードすることができる

📺 やってみた

Step [O6o9o0] Unit 3 - Introduction

  • Unit 3 - Introduction - Tutorial, Begginer, 5 Min

📖 Unit 3 - Sound and Effects

Step [O6o9o1o0] 1.Unit 3 - Introduction

動画を観るだけ

  • おじさんがジョークを言うようになってきた
  • BGM、効果音、パーティクルの前振り

Step [O6o9o2o0] Lesson 3.1 - Jump Force

📅 2022-12-04 sun 18:42

  • Lesson 3.1 - Jump Force - Tutorial, Begginer, 1 Hour 30 Mins

Step [O6o9o2o1o0] Overview Video

動画を観るだけ

  • 放物線を描くには Translate() では困難なので、Unity の物理演算 AddForce() を使おう、という話し

Step [O6o9o2o2o0] 1.Open prototype & change background

動画を観る

  • 新しいプロジェクト Prototype 3 を作って、 Prototype-3_Starter-Files.unitypackage をインポートする
  • SpriteRenderer の説明

Step [O6o9o2o3o0] 2.Choose and set up a player character

動画を観る

  • Player ゲームオブジェクトの準備

Step [O6o9o2o4o0] 3.Make player jump at start

動画を観る

  • GetComponent<T>() の使い方
  • RigidBodyAddForce() の使い方

Step [O6o9o2o5o0] 4.Make player jump if spacebar pressed

動画を観る

  • スペース・バーを押下してジャンプする
  • ジャンプの上昇をすぐにする ForceMode.Impulse 定数

Step [O6o9o2o6o0] 5.Tweak the jump force and gravity

動画を観る

  • Physics.gravity フィールドの説明
  • *= 演算子の説明

Step [O6o9o2o7o0] 6.Prevent player from double-jumping

動画を観る

  • bool 型の説明
  • && 演算子の説明
  • OnCollisionEnter メソッドの説明

Step [O6o9o2o8o0] 7.Make an obstacle and move it left

動画を観る

  • 画面を固定して、障害物や、背景の方を高速に移動させる説明

Step [O6o9o2o9o0] 8.Create a spawn manager

動画を観る

  • SpawnManager の作成

Step [O6o9o2o10o0] 9.Spawn obstacles at intervals

動画を観る

  • 障害物の生成をリピート
  • 衝突時に物体が傾く軸方向のフリーズの説明

Step [O6o9o2o11o0] 10.Lesson Recap

📅 2022-12-04 sun 22:03

動画を観るだけ

📺 やってみた

Step [O6o9o3o0] Lesson 3.2 - Make the World Whiz By

  • Lesson 3.2 - Make the World Whiz By - Tutorial, Begginer, 1 Hour 10 Mins

Step [O6o9o3o1o0] Overview Video

動画を観るだけ

  • このレッスンでやる内容のあらすじ

Step [O6o9o3o2o0] 1.Create a script to repeat background

動画を観る

  • 背景がスクロールしているように見える仕組みの説明
  • RepeatBackground C#スクリプトを Background オブジェクト(背景)にアタッチ

Step [O6o9o3o3o0] 2.Reset position of background

動画を観る

  • 背景がリピートするスクリプトを書く

Step [O6o9o3o4o0] 3.Fix background repeat with collider

📅 2022-12-06 sun 19:42

動画を観る

  • 背景の横幅を正確に計算する

Step [O6o9o3o5o0] 4.Add a new game over trigger

動画を観る

  • Tag の作り方、付け方
  • 地面と、障害物を区別する
    • CompareTag()

Step [O6o9o3o6o0] 5.Stop MoveLeft on gameOver

動画を観る

  • スクリプトから、別のゲームオブジェクトにアタッチされているスクリプトの取得方法の説明

Step [O6o9o3o7o0] 6.Stop obstacle spawning on gameOver

動画を観る

  • 同様

Step [O6o9o3o8o0] 7.Destroy obstacles that exit bounds

動画を観る

  • 通り過ぎて行った障害物を削除する

📺 やってみた

Step [O6o9o3o9o0] 8.Lesson Recap

📅 2022-12-06 sun 20:56

動画を観るだけ

Step [O6o9o4o0] Lesson 3.3 - Don't Just Stand There

📅 2022-12-07 sun 19:11

  • Lesson 3.3 - Don't Just Stand There - Tutorial, Begginer, 1 Hour

Step [O6o9o4o1o0] Overview Video

動画を見るだけ

  • AnimationController の前振り

Step [O6o9o4o2o0] 1.Explore the player’s animations

動画を見る

  • Animatoion Controller の説明
  • いろいろダブルクリックして覗き見るだけ
    • Player ゲームオブジェクトの Animator コンポーネントの Controller 欄をダブルクリックする

Step [O6o9o4o3o0] 2.Make the player start off at a run

動画を見る

  • キャラクターが走るようになる
    • テキストボックスに、数値を入れる

📺 やってみた

Step [O6o9o4o4o0] 3.Set up a jump animation

動画を見る

  • PlayerController C#スクリプトから、 Player ゲームオブジェクトの持つ Animator コンポーネントへアクセスする

Step [O6o9o4o5o0] 4.Adjust the jump animation

動画を見る

  • 空中を走るのを直す

Step [O6o9o4o6o0] 5.Set up a falling animation

動画を見る

  • スクリプトを書いて、倒れ込む演技へ、状態遷移

Step [O6o9o4o7o0] 6.Keep player from unconscious jumping

動画を見る

  • 倒れ込んだあとにまだジャンプできる不具合を修正する

Step [O6o9o4o8o0] 7.Lesson Recap

📅 2022-12-07 sun 21:48

動画を見るだけ

Step [O6o9o5o0] Lesson 3.4 - Particles and Sound Effects

📅 2022-12-08 sun 19:21

  • Lesson 3.4 - Particles and Sound Effects - Tutorial, Begginer, 1 Hour

Step [O6o9o5o1o0] Overview Video

動画を見るだけ

  • Particle や、 SoundListener の説明

Step [O6o9o5o2o0] 1.Customize an explosion particle

動画を見る

  • Player ゲームオブジェクトへ、 パーティクルをアタッチする方法

Step [O6o9o5o3o0] 2.Play the particle on collision

動画を見る

  • パーティクルの再生方法
    • (プロジェクト ビューではなく)ヒエラルキー ビューの方のパーティクルを、 PlayerController C#スクリプトへアタッチする

Step [O6o9o5o4o0] 3.Add a dirt splatter particle

動画を見る

  • 足元のパーティクル
  • ジャンプ中に障害物に当たると、足元のパーティクルは消えないようだ?

Step [O6o9o5o5o0] 4.Add music to the camera object

動画を見る

  • カメラは音楽を聴くための耳も兼ねている
  • AudioSource の説明

Step [O6o9o5o6o0] 5.Declare variables for Audio Clips

動画を見る

  • 効果音を PlayerController C#スクリプトへアタッチする

Step [O6o9o5o7o0] 6.Play Audio Clips on jump and crash

動画を見る

  • 効果音を鳴らす

📺 やってみた

Step [O6o9o5o8o0] 7.Lesson Recap

📅 2022-12-08 sun 22:05

動画を見るだけ

Step [O6o9o6o0] Challenge 3 - Balloons, Bombs, & Booleans

📅 2022-12-10 sun 11:42

  • Challenge 3 - Balloons, Bombs, & Booleans - Tutorial, Begginer, 1 Hour

  • エラーを直すというもの

  • このチャレンジ3は、最初の動かない不具合を除けば、チャレンジ2より簡単

Step [O6o9o6o1o0] 1.Overview

動画を見る

  • Challenge 3 starter files ユニティーパッケージを Prototype 3 へインポートする

👇 2022年12月10日現在、このソースには NullReferenceException という例外がある不具合があるようで、以下のように修正する必要がある

PlayerControllerX.cs:

    void Start()
    {
        // ...
        playerRb = GetComponent<Rigidbody>(); // 追加
        // ...
    }

Step [O6o9o6o2o0] 2.Warning

この章の説明を読むだけ

Step [O6o9o6o3o0] 3.The player can’t control the balloon

  • 「プレイヤーがスペースバーを押すと、風船が浮き上がるようにしてほしい」とのことだが、スペースバーを押すと浮き上がる。
    ヒントを見てみると、 NullReferenceException を直せ、というチャレンジだったようだ。もう直した

Step [O6o9o6o4o0] 4.The background only moves when the game is over

  • ゲームオーバーのとき背景が動くようになっている。これを、ゲーム開始時に背景が動き、ゲームオーバーのとき背景が止まるようにしてほしい
    • 条件判定の真偽が逆なのだろうと当たりを付けて、すぐ直す

Step [O6o9o6o5o0] 5.No objects are being spawned

  • お金や爆弾といったゲームオブジェクトが画面上に生成されてこないので、数秒ごとに生成されてくるようにしてほしい
    • ゲームオブジェクトを生成するのは SpawnManager C#スクリプトだろうと当たりを付けて、すぐ直す

Step [O6o9o6o6o0] 6.Fireworks appear to the side of the balloon

  • 「風船の近くに火花が現れているので、風船のところで火花が現れてほしい」ということだが、ゲーム画面を見ても 何が悪くて どう直したらいいのか説明からは分からない
    • 爆発と 火花は 別のパーティクルのようだ
    • シーン ビューで、遠景にすると 遠くで火花が出ていることが分かった
      • ポジションの数値を直すだけでいいのか?

Step [O6o9o6o7o0] 7.The background is not repeating properly

  • 背景のスクロールのリピートのつながりがおかしいから、つながりの切れ目が見えないように直してほしい
    • C#スクリプト全体を にらめっこしてると 問題個所が分かる

Step [O6o9o6o8o0] 8.Bonus: The balloon can float way too high

  • 風船が高く浮きすぎているから、高く浮きすぎないようにしてほしい
    • 風船が高く浮きすぎるのは、与える力が大きすぎるか、風船が軽すぎるか、それ以外かの理由だろうか?
      • 風船の軽さは、 Player ゲームオブジェクトの持つ RigidBody コンポーネントの mass で質量をキログラム単位で指定すればいいと思うが、風船は重いものではないだろう
      • 与える力は、 Player ゲームオブジェクトの持つ PlayerControllerX C#スクリプトの floatForce を指定すればいいと思う
        • じゃあ、バイナリーサーチでちょうどいい感じの数を探そうかと思ったが、風船が軽すぎるのか floatForce は 1 違うだけでだいぶ違う。少しずつ ずらして加減を調整する

Step [O6o9o6o9o0] 9.Bonus: The balloon can drop below the ground

📅 2022-12-10 sun 14:24

  • 風船が画面下の外へ落ちて行ってしまう。これを、画面下で跳ね返るようにしてほしい。跳ね返るとき効果音も付けてほしい
    • C#スクリプトを書けばいいのかもしれないが、チュートリアルのチャレンジ問題にしては手間が混み過ぎる。発想の問題。ヒントを見る
      • PlayerController C#スクリプトの OnCollisionEnter() メソッドを思い出す
      • Ground ゲームオブジェクトに、 Ground タグを付ける。 Box Collider コンポーネントも付ける

参考:

        if (other.gameObject.CompareTag("Ground"))
        {
            // 接地
            // Debug.Log("Is ground!");
            playerRb.AddForce(Vector3.up * floatForceOnGround);
            playerAudio.PlayOneShot(boingSound, 1.0f);
        }

📺 やってみた

Step [O6o9o7o0] Lab 3 - Player Control

📅 2022-12-10 sun 14:54

  • Lab 3 - Player Control - Tutorial, Begginer, 1 Hour

  • ほとんど復習

Step [O6o9o7o1o0] Overview Video

動画を見るだけ

Step [O6o9o7o2o0] 1.Create PlayerController and plan your code

動画を見る

  • 基本操作の復習といった内容
    • Unity Hub から Personal Project プロジェクトを開く
    • Player ゲームオブジェクトへ PlayerController C#スクリプトをアタッチする

Step [O6o9o7o3o0] 2.Basic movement from user input

動画を見る

  • 説明がどんどん省略され始めてきている
  • 十字キーを押したら、玉が転がるところの説明
    • PlayerController C#スクリプトの書き方の復習

Step [O6o9o7o4o0] 3.Constrain the Player’s movement

動画を見る

  • プレイヤーが転がらないように Player ゲームオブジェクトが持つ Rigidbody コンポーネントの [Constraints] - [Freeze Rotation] で制約を付ける
  • 玉が画面外に転がりでないように、左右に壁を置くというもの
    • Purple 材質を追加、壁を紫色にする
  • 玉が画面外に転がりでないように、上下に PlayerController C#スクリプトで制限するというもの

Step [O6o9o7o5o0] 4.Code Cleanup and Export Backup

動画を見る

  • Environment ゲームオブジェクトを作って、その下へ Wall ゲームオブジェクトを入れる、といった片づけ
  • Update() メソッドの中の処理を、 MovePlayer() メソッドや ConstrainPlayerPosition() メソッドに分割するなどの、片づけ

Step [O6o9o7o6o0] 5.Lesson Recap

📅 2022-12-10 sun 17:39

動画を見るだけ

Step [O6o9o8o0] Quiz 3

  • Quiz 3 - Quiz, Begginer, 15 Mins

概要は読まなくていいかも

Step [O6o9o9o0] Bonus Features 3 - Share your Work

📅 2022-12-10 sun 18:01

  • Bonus Features 3 - Share your Work - Tutorial, Begginer, 1 Hour

難しい課題に挑めるやつだ

Step [O6o9o9o1o0] 1.Overview

完成図の動画を観る

Step [O6o9o9o2o0] 2.Easy: Randomize obstacles

動画を観る

  • 柵だけでなく、積み上げた箱や、ドラム缶も生成しよう、というもの
    • 変数を配列に変える。インデックスの選択には乱数を使う

Step [O6o9o9o3o0] 3.Medium: Double jump

動画を観る

  • 2段ジャンプを実装しよう、というもの
  • 2段ジャンプ中かどうかのフラグを付けたらいいのだろうか? フラグ名は isFirstJump にしてみる

要点:

        if (Input.GetKeyDown(KeyCode.Space) && (isOnGround || isFirstJump) && !gameOver)
        //                                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        {
            // ジャンプ
            isFirstJump = !isFirstJump;
            //            ^^^^^^^^^^^^
        }

Step [O6o9o9o4o0] 4.Hard: Dash ability and score

動画を観る

  • プレイヤーが特定のキーを押下している間、ダッシュするよう実装してほしい
    • 例えば D キーとか
    • フラグを使うか? フラグ名は isDash とかにするか?
    • プレイヤーではなく、障害物と背景のスピードを変えなければいけないのが難しい
      • MoveLeft C#スクリプトの方に、 PlayerController C#スクリプトを参照するように変数を作ればいいのか? と思ったら gameOver フィールドを見るために既に有った
      • PlayerController C#スクリプトに speed フィールドを追加するか?
  • また、スコアを実装してほしい。ダッシュ中はスコアが2倍になる。ゲームオーバーでスコアのカウントアップを止める
    • スコアは単純な実装にする

MoveLeft.cs:

        var speed = playerControllerScript.speed;

        if (playerControllerScript.gameOver == false)
        {
            transform.Translate(Vector3.left * Time.deltaTime * speed);
        }

PlayerController.cs:

        if (Input.GetKeyDown(KeyCode.D) && isOnGround && !gameOver)
        {
            isDash = !isDash;

            if (isDash)
            {
                // ダッシュ
                Debug.Log("Dash!");
                speed = speedHigh;
                playerAnim.SetFloat("Speed_f", 2.0f);
            }
            else
            {
                // ダッシュを止める
                Debug.Log("Walk!");
                speed = speedNormal;
                playerAnim.SetFloat("Speed_f", 1.0f);
            }
        }

        if (isDash)
        {
            score += 2;
        }
        else
        {
            score++;
        }

📺 やってみた

Step [O6o9o9o5o0] 5.Expert: Game start animation

動画を観る

  • ゲーム開始直後から走っているが、これを、最初は背景が止まっていて、画面の外から歩いてきて、そして走る、というゲーム開始時の演出を付けてほしい
    • SpawnManager C#スクリプトの Start() メソッドで行っている、 InvokeRepeating() による繰り返し処理を遅らせた方がいいのか?
  • ヒントを見る
    • lerping というのは、動作A, B の間を補完することを言うらしい
  • 読者のコメントを見る
    • 愚直にやっているようだ。簡単にできないか?
    • とりあえず、イントロを 3秒 と固定して、プログラムを書いてみる
      • MoveLeft C#スクリプトと SpawnManager C#スクリプトを無効化したまま初めて、 3秒経過してから enable にする、といったことはできるか?
        • GameManager C#スクリプトを新規作成した方が楽そう
        • タイマーを作ったことはあっただろうか?

👇 チャレンジ2で、経過時間をカウントしていたことを思い出す

    /// <summary>
    /// 経過時間
    /// </summary>
    private float elapsedSecs = float.MaxValue;

3 秒間かけて 画面外から 所定の位置へ歩いてくる、というのは どうやって作る?
TimeLine というものがあるらしいが使い方が分からない

Vector3.Lerp() というメソッドもあるらしいが、よくわからない

📖 [Unity] Vector3.Lerpの使い方

  • ひとまず、プレイヤーの初期位置を 画面左端の外側になるよう、 Ground ゲームオブジェクトの左端に置く。 position x=-10 とする。重力で端から落ちないように気を付ける
  • GameManager C#スクリプトで、3秒経過したら、 position x=0 とする
  • これだと、3秒後に瞬間移動するわけなので、その途中を補完するように見せたい
  • 足元の土煙(パーティクル)は、 enabled ではなく、 playOnAwake チェックボックスのチェックを外して、あとで Play() で開始するようにする
    • 接地状態だと Play() してしまうので、アニメーション・ステートを確認する

👇 以下で、だいたい うまくいった

GameManager.cs:

using UnityEngine;

public class GameManager : MonoBehaviour
{
    /// <summary>
    /// ゲーム開始時の演出時間(秒)
    /// </summary>
    public float introductionSecs = 2.0f;

    /// <summary>
    /// 経過時間(秒)
    /// </summary>
    private float elapsedSecs;

    private GameObject player;
    public ParticleSystem playerDirtSplatter; // 足元の土煙
    private GameObject background;
    private GameObject spawnManager;
    private Animator playerAnim;
    private bool isGameStart;
    private Vector3 preStartPos;
    private Vector3 startPos = new Vector3(0.0f, 0.0f, 0.0f);

    // Start is called before the first frame update
    void Start()
    {
        player = GameObject.Find("Player");
        background = GameObject.Find("Background");
        spawnManager = GameObject.Find("SpawnManager");
        playerAnim = player.GetComponent<Animator>();

        preStartPos = player.transform.position;
    }

    // Update is called once per frame
    void Update()
    {
        elapsedSecs += Time.deltaTime;

        if (!isGameStart)
        {
            // 中間補完
            player.transform.position = Vector3.Lerp(preStartPos, startPos, elapsedSecs / introductionSecs);

            // End
            if (elapsedSecs > introductionSecs)
            {
                // 演技
                playerAnim.SetFloat("Speed_f", 1.0f);   // 走るポーズ
                playerDirtSplatter.Play();  // 足元の土煙

                background.GetComponent<MoveLeft>().enabled = true;
                spawnManager.GetComponent<SpawnManager>().enabled = true;

                isGameStart = true;
            }
        }
    }
}

PlayerController.cs:

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Ground"))
        {
            // ...

            if (playerAnim.GetCurrentAnimatorStateInfo(0).IsName("Run_Static"))
            {
                dirtParticle.Play(); // 土煙
            }
        }// ...
    }

📺 やってみた1
📺 やってみた2

Step [O6o9o9o6o0] 6.Hints and solution walkthrough

📅 2022-12-10 sun 23:35

読むだけ

Step [O6o10o0] Unit 4 - Introduction

📅 2022-12-10 sun 23:40

  • Unit 4 - Introduction - Tutorial, Begginer, 5 Min

📖 Unit 4 - Introduction

  • このコース全体で、 Unit 4 が1番難しいとのこと
    • 難しいというのは、ベクトル計算を使うことを言ってるのか? ベクトル計算得意なら、むしろ簡単
      • ボーナス フューチャーの章がベリーハード

Step [O6o10o1o0] Lesson 4.1 - Watch Where You’re Going

📅 2022-12-11 sun 10:33

  • Lesson 4.1 - Watch Where You’re Going - Tutorial, Begginer, 1 Hour

Step [O6o10o1o1o0] Overview Video

動画観るだけ

  • カメラ操作の話しか?
  • Focal Point
    • 島が回転するのではなく、カメラが島の中心を軸にして島の外周を回る

あとで分かったことだが、カメラの回転というのは、カメラが首を振るのではなく、焦点を中心に周囲を回ることらしい

Step [O6o10o1o2o0] 1.Create project and open scene

動画観る

  • 新しいプロジェクト Prototype 4 を作って、 Prototype-4_Starter-Files.unitypackage をインポートする

  • ゲームプレイすると、パーティクルが動いている。画面は暗いし、カメラは動かない。

Step [O6o10o1o3o0] 2.Set up the player and add a texture

動画観る

  • 球体を置いて、プレイヤーとする

Step [O6o10o1o4o0] 3.Create a focal point for the camera

動画観る

  • 島の中心を焦点として、カメラが島の周りを回るようにする方法
    • Focal Point ゲームオブジェクトを作って、その下へ Main Camera を移動する
    • RotateCamera C#スクリプトを作って (Main Camera の方ではなく) Focal Point ゲームオブジェクトの方へアタッチ

Step [O6o10o1o5o0] 4.Rotate the focal point by user input

動画観る

  • 左キー、右キーに応じて、カメラを軸回転できるようにする

Step [O6o10o1o6o0] 5.Add forward force to the player

動画観る

  • 上キー、下キーに応じて、プレイヤーを操作できるようにする
    • PlayerController C#スクリプトを作る

Step [O6o10o1o7o0] 6.Move in direction of focal point

動画観る

  • プレイヤーを、前後に移動するのではなく、フォーカルポイントが向いている方向と、移動する方向を揃える、といった振る舞いに変更する

Step [O6o10o1o8o0] 7.Lesson Recap

動画を観るだけ

📅 2022-12-11 sun 13:02

Step [O6o10o2o0] Lesson 4.2 - Follow the Player

📅 2022-12-11 sun 16:08

  • Lesson 4.2 - Follow the Player - Tutorial, Begginer, 1 Hour

Step [O6o10o2o1o0] Overview Video

動画を観るだけ

  • 敵を作るという前振り
  • ベクトル計算の紹介

Step [O6o10o2o2o0] 1.Add an enemy and a physics material

動画を観る

  • 敵を作る。敵は、プレイヤー(球)を弾き返すだけの物理的な設定がある
  • Physics Materials フォルダーを作る
    • Bouncy 物理素材を作る
      • アタッチする

Step [O6o10o2o3o0] 2.Create enemy script to follow player

動画を観る

  • プレイヤーを追いかける振る舞いを C#スクリプトで書き、敵にアタッチする
  • Enemy C#スクリプト作成、 Enemy ゲームオブジェクトへアタッチ
  • ベクトル計算を使う
    • normalized を使う

📺 やってみた

Step [O6o10o2o4o0] 3.Create a lookDirection variable

動画を観る

  • コードが読みにくくなったので、読みやすくしようという話し
  • lookDirection 変数を作る

Step [O6o10o2o5o0] 4.Create a Spawn Manager for the enemy

動画を観る

  • 敵を生成する SpawnManager を作る準備
  • Prefabs フォルダーを作る
    • Enemy ゲームオブジェクトを Prefabs フォルダーへドラッグ&ドロップ
      • Hiererchy ビューの Enemy ゲームオブジェクトは削除
  • Spawn Manager ゲームオブジェクト作成、SpawnManager C#スクリプト作成

Step [O6o10o2o6o0] 5.Randomly generate spawn position

動画を観る

  • 敵を常に同じ位置に生成している。これを、ランダムな位置に生成するよう変えたい

Step [O6o10o2o7o0] 6.Make a method return a spawn point

動画を観る

  • コードを整える

Step [O6o10o2o8o0] 7.Lesson Recap

動画を観るだけ

📅 2022-12-11 sun 18:00

Step [O6o10o3o0] Lesson 4.3 - PowerUp and CountDown

  • Lesson 4.3 - PowerUp and CountDown - Tutorial, Begginer, 1 Hour

  • パワーアイテムの話しのようだ

Step [O6o10o3o1o0] Overview Video

動画を観るだけ

  • コルーチンの前振り

Step [O6o10o3o2o0] 1.Choose and prepare a powerup

動画を観る

  • Hierarchy ビューの [Course Library] - [Picups] フォルダーの Gem_01 プレファブを Scene ビューにドラッグ&ドロップする
    • Hierarchy ビューの Gem_01Powerup にリネームする
  • Powerup タグ作成
  • Scene ビューの Powerup ゲームオブジェクトを、 Project ビューの Prefabs フォルダーへドラッグ&ドロップする

Step [O6o10o3o3o0] 2.Destroy powerup on collision

動画を観る

  • プレイヤーがパワーアップに衝突したら、パワーアップを消す
    • PlayerController C#スクリプトの OnTriggerEnter() メソッド作成
    • hasPowerup フラグ作成

Step [O6o10o3o4o0] 3.Test for enemy and powerup

動画を観る

  • パワーアップ状態で敵に当たってみて衝突判定をテストする
    • Enemy ゲームオブジェクトに Enemy タグを付ける
    • PlayerController C#スクリプトに OnCollisionEnter() メソッド追加

OnTriggerEnter は、 Box Collider の当たり判定に接触したとき?
OnCollisitionEnter は、 図形に衝突したときか?

Step [O6o10o3o5o0] 4.Apply extra knockback with powerup

動画を観る

  • パワーアップしているときに敵に接触すると、敵が飛んでいく(ノックバック)ようにしたい
    • ベクトル計算で、方向は算出できる。それを力として大きくして、加えるだけ

Step [O6o10o3o6o0] 5.Create Countdown Routine for powerup

動画を観る

  • パワーアップは永遠に続く。そこで、パワーアップが時間経過で切れる仕組みを作る
    • コルーチンの書き方を覚える

Step [O6o10o3o7o0] 6.Add a powerup indicator

動画を観る

  • パワーアップ状態が切れたことがユーザーには分からない。そこで、パワーアップ状態が切れたことを分かるようにする
  • active フラグの説明
  • + new Vector3(...) という書き方

📺 やってみた

Step [O6o10o3o8o0] 7.Lesson Recap

動画を観るだけ

📅 2022-12-11 sun 20:50

Step [O6o10o4o0] Lesson 4.3 - Lesson 4.4 - For-Loops For Waves

📅 2022-12-14 wed 18:40

  • Lesson 4.4 - For-Loops For Waves - Tutorial, Begginer, 5o Mins

  • 敵を複数生成しよう、という話し

Step [O6o10o4o1o0] Overview Video

動画を観るだけ

  • Forループ の前振り

Step [O6o10o4o2o0] 1.Write a for-loop to spawn 3 enemies

動画を観る

  • Forループ の説明

Step [O6o10o4o3o0] 2.Give the for-loop a parameter

動画を観る

  • 関数の引数 の説明

Step [O6o10o4o4o0] 3.Destroy enemies if they fall off

動画を観る

  • 落ちた敵を破壊する
  • FindObjectsOfType<T> メソッドの説明

Step [O6o10o4o5o0] 4.Increase enemyCount with waves

動画を観る

  • 徐々に敵の数を増やす

Step [O6o10o4o6o0] 5.Spawn Powerups with new waves

動画を観る

  • パワーアップアイテムも出す

📺 やってみた

Step [O6o10o4o7o0] 6.Lesson Recap

動画を観るだけ

📅 2022-12-14 wed 20:04

Step [O6o10o5o0] Challenge 4 - Soccer Scripting

  • Challenge 4 - Soccer Scripting - Tutorial, Begginer, 1 Hour

概要読むだけ

  • 不具合があるプロジェクトを直すという話し

Step [O6o10o5o1o0] 1.Overview

動画を観るだけ

  • Challenge 4 starter files を準備する
    • Lesson 4 の material が外れるなどの不具合が起こったが直した

Step [O6o10o5o2o0] 2.Warning

この章の説明を読むだけ

  • 以下のステップを続けて読んでいく

Step [O6o10o5o3o0] 3.Hitting an enemy sends it back towards you

📅 2022-12-15 wed 20:07

  • 敵を攻撃すると、敵がわたしに向かって送り返されてくる。これは不具合なので、敵をわたしから遠ざけるようにしてほしい という課題

    • ベクトルの正負が逆なのでは?
  • しかしプレイすると NullReferenceException が出て動かない

EnemyX.cs:

private GameObject playerGoal;

👆 このフィールドがヌルなのでは? 入れてみる

    void Start()
    {
        // ...
        playerGoal = GameObject.Find("Player Goal");
    }

👆 NullReferenceException はこれで直った

PlyaerControllerX.cs:

    private void OnCollisionEnter(Collision other)
    {
        if (other.gameObject.CompareTag("Enemy")) { // ...
            // Vector3 awayFromPlayer =  transform.position - other.gameObject.transform.position;
            Vector3 awayFromPlayer =  other.gameObject.transform.position - transform.position;
            // ...
        }
    }

👆 これで直った

Step [O6o10o5o4o0] A new wave spawns when the player gets a powerup

  • プレイヤーがパワーアップを取ると、敵の波が生成される。これは不具合なので、敵を全て破壊したときに、敵の波を生成してほしい という課題
    • パワーアップ・アイテムと衝突したときに、敵を生成する関数を呼び出しているのではないか?もしそうなら、それを削除すればどうか?
      • そのようなコードは見当たらない
    • じゃあ、パワーアップ・アイテムが全部破壊されると 敵を生成するように書いているのでは?

SpawnManager.cs:

    void Update()
    {
        enemyCount = FindObjectsOfType<Enemy>().Length;

        if (enemyCount == 0)
        {
            waveNumber++;
            SpawnEnemyWave(waveNumber);
            Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation);
        }
    }

👆 一見すると、敵の数を数えているように見える

敵には Enemy タグが、 アイテムには Powerup タグが付いている。これも正しいように見える

SpawnManagerX.cs:

    void Update()
    {
        enemyCount = GameObject.FindGameObjectsWithTag("Powerup").Length;

        if (enemyCount == 0)
        {
            SpawnEnemyWave(waveCount);
        }

    }

👆 見ているスクリプトが違ったので見直すと タグが間違っているので修正する。これで直った

Step [O6o10o5o5o0] 5.The powerup never goes away

  • パワーアップ期間が切れない。これは不具合なので、一定時間経過すると消えるように直してほしい
    • コルーチンが動いていれば消えるはず。どうなっているか確認する
      • IEnumerator PowerupCooldown() の呼出し箇所が 0 箇所だ

PlayerControllerX.cs:

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Powerup"))
        { // ...
            // 追加
            StartCoroutine(PowerupCooldown());
        }
    }

👆 過去のコードを参考に追加する。これで直った

Step [O6o10o5o6o0] 6.2 enemies are spawned in every wave

  • どの波でも敵が2つ増える。これを、1回目の波では1、2回目の波では2、3回目の波では3,というようにしてほしい という課題
    • SpawnEnemyWave() の呼出し箇所を調べてみる。 waveCount 分、増えるようだ
      • waveCount の使用箇所を調べてみる

SpawnManagerX:

    void SpawnEnemyWave(int enemiesToSpawn)
    {// ...
        // Spawn number of enemy balls based on wave number
        for (int i = 0; i < 2; i++)
        {
            Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
        } // ...
    }

👆 途中、上記のような箇所を見つけた。 ハードコーディングの 2waveCount に変えてみる。多分直った

Step [O6o10o5o7o0] 7.The enemy balls are not moving anywhere

  • 敵は動かない。これは不具合なので、プレイヤー側のゴールを目指して動いてほしい
    • 敵から見てプレイヤー側のゴールの向きを、それを力の向きと捉えて、その力の向きを小さくして、毎フレーム、敵に与えればいいのでは

EnemyX:

    void Update()
    {
        // Set enemy direction towards player goal and move there
        Vector3 lookDirection = (playerGoal.transform.position - transform.position).normalized;
        enemyRb.AddForce(lookDirection * speed * Time.deltaTime);
    }

👆 それっぽいコードはもう書いてある。だったら、speed が0なのでは? ビンゴ。 Enemy プレファブの持つ EnemyX C#スクリプトの Speed フィールドに 30 ぐらい入れる。直った

Step [O6o10o5o8o0] 8.Bonus: The player needs a turbo boost

  • スペースバーを押している間中、プレイヤーは速くなりたい。そしてブーストしている間、パーティクル効果を付けてほしい
    • PlayerControllerX C#スクリプトの Update メソッドの中で スペース・キーの押下判定をして、Speed の2倍ぐらい 力を余分に加えればいいか?
    • パーティクルは、シーンビューで Player ゲームオブジェクトの子として加えて、 setActive() でオン/オフを切り替えればいいか?

PlayerControllerX.cs:

    void Update()
    { // ...

        // Turbo boost
        if (Input.GetKey(KeyCode.Space))
        {
            Debug.Log("Boost!");
            var turbo = 3;
            playerRb.AddForce(focalPoint.transform.forward * turbo * speed * Time.deltaTime);
        }

        // ...
    }

👆 これで向いている方向に力が加わっていると思うが、実感はない。Input.GetKeyDown() は押下にしか反応しないので、 Input.GetKey() を使えば押しっぱなしを検知できる

  • Smoke_Particle という素材が入っていたので、 Player ゲームオブジェクトの子として置く。Player の子にすると、回転してしまう。焦点カメラの子にする
    • Play On Awake のチェックは外しておく
      • Prototype 3 の土煙を参考にする
    public ParticleSystem dirtParticle;

    dirtParticle.Play(); // 土煙
    dirtParticle.Stop();

👆 上記のコードをうまく使えばいいはず

  • Play() メソッドを使っても、パーティクルの動画再生が始まらない
    • Project ビューにあるパーティクルではなく、 Scene ビューにあるパーティクルを PlayerControllerX C#スクリプトにアタッチすると、再生された

PlayerControllerX.cs:

    // Turbo boost
    public ParticleSystem smokeParticle;
    public float spamSmokeSecs = 3.0f;
    public float spamSmokeSecsCountdown;

    void Update()
    { // ...
        // Turbo boost
        if (Input.GetKey(KeyCode.Space))
        {
            Debug.Log("Boost!");
            var turbo = 3;
            playerRb.AddForce(focalPoint.transform.forward * turbo * speed * Time.deltaTime);

            if (spamSmokeSecsCountdown < 0.5f)
            {
                smokeParticle.Play();
                spamSmokeSecsCountdown = spamSmokeSecs;
            }
        }

        // Turbo boost
        spamSmokeSecsCountdown -= Time.deltaTime;

        // ...
    }

👆 これで OK

📅 2022-12-15 thu 22:04

Step [O6o10o5o9o0] 9.Bonus: The enemies never get more difficult

  • 敵の速度は増えません。そこで、新しい波ごとに増えるようにして、ゲームを難しくしてください

    • 敵の数が増えるだけでも難しいが……
  • 敵の速度は Enemy C#スクリプトの Speed 変数に設定されている。さっき自分で設定したやつだ

    • SpawnManagerX C#スクリプトの SpawnEnemyWave メソッドで、敵の速度を設定すればいいような気がする

SpawnManagerX.cs:

    void SpawnEnemyWave(int enemiesToSpawn)
    { // ...
        for (int i = 0; i < waveCount; i++)
        {
            var enemyGo = Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
            var enemyScript = enemyGo.GetComponent<EnemyX>();
            enemyScript.speed *= waveCount;
        }

        // ...
    }

👆 簡単にやるなら、こう?

📅 2022-12-15 thu 22:29

📺 やってみた1
📺 やってみた2

Step [O6o10o6o0] Lab 4 - Basic Gameplay

  • Lab 4 - Basic Gameplay - Tutorial, Begginer, 1 Hour 30 Mins

📅 2022-12-17 sat 13:20

概要読むだけ

  • 復習なので退屈な章。次のクイズの章のためにやる感じか

Step [O6o10o6o1o0] Overview Video

動画を観るだけ

Step [O6o10o6o2o0] 1.Give objects basic movement

動画を観る

  • 現在、球だけを上下左右キーで動かすことができ、それ以外の図形は動かない状態だ。そこで、敵オブジェクトを動かす方法の基本を説明する

  • 非プレイヤーオブジェクトを Hierarchy ビューでまとめて選択し、 Rigidbody コンポーネントをアタッチする

  • MoveDown C#スクリプトを作成

    • 非プレイヤーオブジェクトを Hierarchy ビューでまとめて選択し、 MoveDown C#スクリプトを まとめてアタッチする

MoveDown.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class MoveDown : MonoBehaviour
{
    public float speed = 5.0f;
    private Rigidbody objectRb;

    // Start is called before the first frame update
    void Start()
    {
        objectRb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        objectRb.AddForce(Vector3.forward * -speed);
    }
}

Step [O6o10o6o3o0] 2.Destroy objects off-screen

動画を観る

  • 現在、画面外に出たオブジェクトはずっと残っている。これを、画面外に出たオブジェクトを破壊するようにしたい

  • z 座標が -10 より下がったら破壊する、という発想

MoveDown.cs

    private float zDestroy = -10.0f;

    void Update()
    { // ...
        if (transform.position.z < zDestroy)
        {
            Destroy(gameObject);
        }
    }

Step [O6o10o6o4o0] 3.Handle object collisions

動画を観る

  • Powerup ゲームオブジェクトの Capsule ColliderIs Trigger チェックボックスにチェックを付け忘れていたので、チェックしておく(ぶつからず、通過するようになる)

  • Phisycs Materials フォルダー作成

    • Bouncy 物理素材作成
      • Bounciness1 に変更
      • Bounce CombineMultiply に変更
    • Bouncy 物理素材を、Player ゲームオブジェクトの Sphere Collider コンポーネントへアタッチ(Bouncy 物理素材を、Player ゲームオブジェクトへドラッグ&ドロップすればよい)
  • Enemy タグ作成

  • Powerup タグ作成

  • Player, Enemy, Powerup ゲームオブジェクトへ、それぞれ Player, Enemy Powerup タグを設定

  • PlayerController C#スクリプトへ、 OnCollisionEnter() メソッド作成

  • PlayerController C#スクリプトへ、 OnTriggerEnter() メソッド作成

PlayerController.cs:

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.CompareTag("Enemy"))
        {
            Debug.Log("Player has collided with enemy.");
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.gameObject.CompareTag("Powerup"))
        {
            Destroy(other.gameObject);
        }
    }

Step [O6o10o6o5o0] 4.Make objects into prefabs

動画を観る

  • ゲームプレイ中にインスタンス化するには、プレファブにしておく必要があるという説明

  • Project ビューに、 Prefabs フォルダー作成

    • Enemy 1Powerup ゲームオブジェクトを Prefabs フォルダーへドラッグ&ドロップ
    • Scene ビューの Enemy 1Powerup ゲームオブジェクトは削除

Step [O6o10o6o6o0] 5.Make SpawnManager spawn Prefabs

動画を観る
11分もある長い動画

  • SpawnManager ゲームオブジェクト作成
  • SpawnManager C#スクリプト作成。SpawnManager ゲームオブジェクトへアタッチ

SpawnManager.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpawnManager : MonoBehaviour
{
    public GameObject[] enemies;
    public GameObject powerup;

    private float zEnemySpawn = 12.0f;
    private float xSpawnRange = 16.0f;
    private float zPowerupRange = 5.0f;
    private float ySpawn = 0.75f;

    private float powerupSpawnTime = 5.0f;
    private float enemySpawnTime = 1.0f;
    private float startDelay = 1.0f;

    // Start is called before the first frame update
    void Start()
    {
        InvokeRepeating("SpawnRandomEnemy", startDelay, enemySpawnTime);
        InvokeRepeating("SpawnPowerup", startDelay, powerupSpawnTime);
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    void SpawnRandomEnemy()
    {
        float randomX = Random.Range(-xSpawnRange, xSpawnRange);
        int randomIndex = Random.Range(0, enemies.Length);

        Vector3 spawnPos = new Vector3(randomX, ySpawn, zEnemySpawn);

        Instantiate(enemies[randomIndex], spawnPos, enemies[randomIndex].gameObject.transform.rotation);
    }

    void SpawnPowerup()
    {
        float randomX = Random.Range(-xSpawnRange, xSpawnRange);
        float randomZ = Random.Range(-zPowerupRange, zPowerupRange);

        Vector3 spawnPos = new Vector3(randomX, ySpawn, randomZ);
        Instantiate(powerup, spawnPos, powerup.gameObject.transform.rotation);
    }
}

Step [O6o10o7o0] 6.Recap

動画を観るだけ

📅 2022-12-17 sat 16:57

📺 やってみた

Step [O6o10o8o0] Quiz 4

  • Quiz 4 - Quiz, Begginer, 15 Mins

概要は読まなくてもいいや

📅 2022-12-17 sat 17:22

Step [O6o10o9o0] Bonus Features 4 - Share your Work

  • Bonus Features 4 - Share your Work - Tutorial, Begginer, 1 Hour

概要読むだけ

  • 簡単な問題から、難しい問題まで出てくるやつ

Step [O6o10o9o1o0] 1.Overview

動画を観る

  • 完成例の動画

Step [O6o10o9o2o0] 2.Easy: Harder enemy

📅 2022-12-18 sun 10:46

動画を観る

  • 敵の種類を増やして、ランダムに生成してほしい という話し

    • サンプル動画からは 意図がよく分からないが……
  • Materials フォルダーを作って、 Material を作るか

  • SpawnManager C#スクリプトの enemyPrefab フィールドを配列にすればどうか?

    • インデックスは乱数にする

SpawnManager.cs:

    public GameObject[] enemyPrefabs;

    void SpawnEnemyWave(int enemiesToSpawn)
    {
        for (int i = 0; i < enemiesToSpawn; i++)
        {
            int randomIndex = Random.Range(0, enemyPrefabs.Length);
            Debug.Log($"randomIndex:{randomIndex}");
            GameObject enemyPrefab = enemyPrefabs[randomIndex];

            Instantiate(enemyPrefab, GenerateSpawnPosition(), enemyPrefab.transform.rotation);
        }
    }
  • 敵毎に強さを設定するのは Rigidbody コンポーネントの Mass フィールドをいじる(1 mass = 1 Killo-gram)
    • Scale をいじっても 大きさを変更できる

Step [O6o10o9o3o0] 3.Medium: Homing rockets

動画を観る

  • ホーミング・ロケットを発射できるパワーアップアイテムを追加してほしい という話し

    • 要件:非パワーアップ時は ワンウェイ、パワーアップ時は全ての敵に向けて発射してほしい
  • SpawnManager C#スクリプトの powerupPrefab フィールドを配列にすればどうか?

    • 通常と、ホーミングの違いを見分けるには?

SpawnManager.cs:

    public GameObject[] powerupPrefabs;

    void SpawnPowerup()
    {
        int randomIndex = Random.Range(0, powerupPrefabs.Length);
        GameObject powerupPrefab = powerupPrefabs[randomIndex];

        Instantiate(powerupPrefab, GenerateSpawnPosition(), powerupPrefab.transform.rotation);
    }

PlayerController.cs:

    public int powerupType;

    void Update()
    { // ...
        if (hasPowerup && powerupType == 2 && Input.GetKey(KeyCode.Space))
        {
            Debug.Log("TODO ここで球を発射したい");
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Powerup"))
        {
            if (other.name.StartsWith("Powerup 2")) // 実際は "Powerup 2(Clone)" になっている
            {
                Debug.Log("パワーアップ2型");
                powerupType = 2;
            }
            else
            {
                Debug.Log("パワーアップ1型");
                powerupType = 1;
            } // ...
        }
    }

👆 パワーアイテムは、名前で区別できるだろう

  • 球の発射は、前にピザを飛ばしたチュートリアルを参考にする
    • 球の図形は、カプセルを寝かせれば作れるか。名前は Bullet にする
    • RigidbodyMass を大きくすれば、相手は吹っ飛ぶだろう
    • 水平に飛んでいってほしいので、 Use Gravity は外す
    • 水平回転以外の回転を止めたいので Freezexz をフリーズ

📖 プレーヤーの向いている方向に発射する

  • 発射口というゲームオブジェクトを作ってしまうのが好手らしい
    • Player から見て Z=1 の位置に Cube 図形を置く。名前は Launcher とかにしておく
  • LauncherController C#スクリプトを作って、 Launcher ゲームオブジェクトへアタッチ

LauncherController.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 発射口のコントローラー
/// </summary>
public class LauncherController : MonoBehaviour
{
    public GameObject bulletPrefab;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        if (
            // hasPowerup && powerupType == 2 &&
            Input.GetKey(KeyCode.Space))
        {
            // Debug.Log("TODO ここで球を発射したい");
            SpawnBullet();
        }
    }

    void SpawnBullet()
    {
        Instantiate(bulletPrefab, transform.position, transform.rotation);
    }
}

👆 これだと、ジャムって噴水みたいに暴発する。スパムを止める必要がある

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 発射口のコントローラー
/// </summary>
public class LauncherController : MonoBehaviour
{
    public GameObject bulletPrefab;
    public float spamCountdownSecs;
    public float spamSecs = 0.5f;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        spamCountdownSecs -= Time.deltaTime;

        if (
            // hasPowerup && powerupType == 2 &&
            Input.GetKey(KeyCode.Space) && spamCountdownSecs < 0.0f)
        {
            // Debug.Log("TODO ここで球を発射したい");
            SpawnBullet();
            spamCountdownSecs = spamSecs;
        }
    }

    void SpawnBullet()
    {
        Instantiate(bulletPrefab, transform.position, transform.rotation);
    }
}

👆 スパムは止まる

  • HomingBullet C#スクリプトを作り、 Bullet ゲームオブジェクトへアタッチ
    • HomingBullet C#スクリプトに speed, targetPrefab フィールド作成

HomingBullet.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HomingBullet : MonoBehaviour
{
    public float speed = 100.0f;
    private Rigidbody myRb;
    public GameObject targetPrefab;

    // Start is called before the first frame update
    void Start()
    {
        myRb = GetComponent<Rigidbody>();
    }

    // Update is called once per frame
    void Update()
    {
        if (targetPrefab == null)
        {
            return;
        }

        Vector3 lookDirection = (targetPrefab.transform.position - transform.position).normalized;
        myRb.AddForce(lookDirection * speed);

        // 盤から落ちてたら破壊
        if (transform.position.y < -10)
        {
            Destroy(gameObject);
        }
    }
}
  • シーン上のすべての敵のポジションを知りたい
        enemyCount = GameObject.FindGameObjectsWithTag("Powerup").Length;

👆 前のチュートリアルのこれが使えるのでは?

LauncherController.cs:

    void SpawnBullet()
    {
        // シーンの敵のポジションを知りたい
        GameObject[] enemiesGo = GameObject.FindGameObjectsWithTag("Enemy");

        foreach(var enemyGo in enemiesGo)
        {
            GameObject homingBulletGo = Instantiate(homingBulletPrefab, transform.position, transform.rotation);
            HomingBullet homingBulletScript = homingBulletGo.GetComponent<HomingBullet>();
            homingBulletScript.targetPrefab = enemyGo;

            Vector3 lookDirection = (enemyGo.transform.position - homingBulletGo.transform.position).normalized;
            homingBulletGo.GetComponent<Rigidbody>().AddForce(lookDirection * Time.deltaTime * homingBulletScript.speed);
        }
    }

👆 基本的な考え方はこう

📺 やってみた

📅 2022-12-18 sun 14:33

Step [O6o10o9o4o0] 4.Hard: Smash attack

動画を観る

  • ジャンプして着地の衝撃で敵を遠ざけてほしい。近くの敵の方が衝撃が大きいはず という課題

  • ジャンプをどう実装するか? AddForce() でなんとかなるのか?

  • Unity Learn Webサイトのコメントを参考にする

    • 難しいので部品を分ける
      • SmashAttack というゲームオブジェクト作成
      • SmashAttack C#スクリプトを作り、 SmashAttack ゲームオブジェクトへアタッチ

SmashAttack.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SmashAttack : MonoBehaviour
{
    public GameObject playerPrefab;

    public float smashMultiplier = 10f;
    public float forceRadius = 100f;
    public float jumpForce = 500f;
    public float waitForSeconds = 3.0f;
    public float spamCountdownSecs;
    public float spamSecs = 0.5f;

    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        spamCountdownSecs -= Time.deltaTime;

        if (Input.GetKeyDown(KeyCode.S) && spamCountdownSecs < 0.0f)
        {
            StartCoroutine(Smash());
            spamCountdownSecs = spamSecs;
        }
    }

    /// <summary>
    /// コルーチン
    /// </summary>
    /// <returns></returns>
    IEnumerator Smash()
    {
        // すぐジャンプ
        playerPrefab.GetComponent<Rigidbody>().AddForce(Vector3.up * jumpForce, ForceMode.Impulse);

        // 1.5秒待機
        yield return new WaitForSeconds(waitForSeconds);

        // シーン上のすべての敵に対して
        foreach (var enemy in FindObjectsOfType<Enemy>())
        {
            // プレイヤーから見た敵の位置
            Vector3 awayFromEnemy = enemy.transform.position - playerPrefab.transform.position;

            if (awayFromEnemy.magnitude < forceRadius)
            {
                // 敵に力を加える
                enemy.GetComponent<Rigidbody>().AddForce((forceRadius - awayFromEnemy.magnitude) * smashMultiplier * awayFromEnemy, ForceMode.Impulse);
            }
        }
    }
}

👆 重さと 力のパラメーターを丁度よく調整するのが うまくいかない

📺 やってみた

Step [O6o10o9o5o0] 5.Expert: Boss battle

📅 2022-12-18 sun 15:22

  • 一定数の波のあと、発射物を発射する敵など、ボスのような敵をプログラムしてほしい という話し

    • このゲームは 操作性の悪さから 波を1つクリアするのも難しいので この課題は難しい
  • とりあえず、 Enemy 3 ゲームオブジェクトを追加する。特長は でかい ということにする

    • 第2派のときに enemyPrefabs[2] が必ず出てくるようにプログラムしてみる

📺 やってみた

📅 2022-12-18 sun 15:45

Step [O6o10o9o6o0] 6.Hints and solution walkthrough

ヒントが書いてあるだけ

Step [O6o11o0] Unit 5 - Introduction

📅 2022-12-18 sun 15:59

  • Unit 5 - Introduction - Tutorial, Begginer, 5 Min

📖 Unit 5 - Introduction

動画を観るだけ

Step [O6o11o1o0] Lesson 5.1 - Clicky Mouse

Lesson 5.1 - Clicky Mouse - Tutorial, Begginer, 1 Hour

概要は読まなくていいや

Step [O6o11o1o1o0] Overview Video

動画を観るだけ

  • マウスクリックの前振り

Step [O6o11o1o2o0] 1.Create project and switch to 2D view

動画を観る

  • Prototype 5 プロジェクトをいつも通り作る
  • 2D ボタンをクリックして、 2D ビューにする

Step [O6o11o1o3o0] 2.Create good and bad targets

動画を観る

  • Good 3つ、 Bad 1つ の射撃の目標のプレファブを作る

    • Rigidbody, Box Collider をアタッチ
  • Target C#スクリプトを作る。射撃の目標ゲームオブジェクトへアタッチ

  • Prefabs フォルダーを作って 射撃の目標を移動。 Hiererchy ビューからは削除

Step [O6o11o1o4o0] 3.Toss objects randomly in the air

動画を観る

  • 射撃の目標を 空中に放り投げるという話し
  • トルク とは、ねじること
    • AddTorque() メソッドの説明

Step [O6o11o1o5o0] 4.Replace messy code with new methods

動画を観る

  • コードを読みやすくするという話し
    • ヘルパーメソッドを作る

Step [O6o11o1o6o0] 5.Create object list in Game Manager

動画を観る

  • Game Manager ゲームオブジェクト作成
    • GameManager C#スクリプトを作成する。アイコンは特別に、歯車になる。設定ファイルとしてよく使うので
    • Game Manager ゲームオブジェクトへ GameManager C#スクリプトをアタッチ
  • List<> の説明

Step [O6o11o1o7o0] 6.Create a coroutine to spawn objects

動画を観る

  • 射撃の目標の生成のタイミング
  • コルーチンを使う
  • while ループの説明

Step [O6o11o1o8o0] 7.Destroy target with click and sensor

動画を観る

  • OnMouseDown() の説明
    • Collider BoxIs Trigger がチェックされていると効く?
  • OnTriggerEnter() の説明
    • シーンの底に Sensor ゲームオブジェクトが最初から置かれてあって、そこに衝突したら破壊する
  • ySpawn を -2 へ変更

Step [O6o11o1o9o0] 8.Lesson Recap

動画を観るだけ

📅 2022-12-18 sun 18:17

Step [O6o11o2o0] Lesson 5.2 - Keeping Score

📅 2022-12-18 sun 18:24

  • Lesson 5.2 - Keeping Score - Tutorial, Begginer, 1 Hour 10 Mins

概要読むだけ

  • スコアを表示するのだろうか?

Step [O6o11o2o1o0] Overview Video

動画を観るだけ

Step [O6o11o2o2o0] 1.Add Score text position it on screen

動画を観る

  • TextMeshPro の説明

  • Hierarchy ビューから、 [Create] - [UI] - [TextMeshPro - Text] をクリック

    • 初めて TextMeshPro を使うなら、 Inport TMP Essentials ボタンをクリック
      • Canvas というゲームオブジェクトが自動で作られる。その下に TextMeshPro -Text が置かれる
  • Anchor の使い方

Step [O6o11o2o3o0] 2.Edit the Score Text’s properties

動画を観る

  • 見栄えの良いテキストになるようにプロパティを設定する話し
  • UGUI は ユーグイと読む

Step [O6o11o2o4o0] 3.Initialize score text and variable

動画を観る

  • テキスト表示
  • using 文の説明
    • using TMPro;
    • .text フィールド

GameManager.cs:

    public TextMeshProUGUI scoreText;

    void Start()
    { // ...
        score = 0;
        scoreText.text = "Score: " + score;
    }

Step [O6o11o2o5o0] 4.Create a new UpdateScore method

動画を観る

  • スコア表示を更新されるようにする

Step [O6o11o2o6o0] 5.Add score when targets are destroyed

動画を観る

  • 射撃の目標を破壊したときにスコアを更新したいという話し

Target.cs:

    private GameManager gameManager;

    void Start()
    { // ...
        gameManager = GameObject.Find("Game Manager").GetComponent<GameManager>();
    }

    private void OnMouseDown()
    { // ...
        gameManager.UpdateScore(5);
    }

Step [O6o11o2o7o0] 6.Assign a point value to each target

動画を観る

  • 射撃の目標ごとに、スコアを変えたいという話し

Step [O6o11o2o8o0] 7.Add a Particle explosion

動画を観る

  • 射撃の目標は、クリックしたら爆発してほしい、という話し

Target.cs:

    public ParticleSystem explosionParticle;

    private void OnMouseDown()
    { // ...

        Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation);

        //...
    }

Step [O6o11o2o9o0] 8.Lesson Recap

📅 2022-12-18 sun 21:22

動画を観るだけ

📺 やってみた

Step [O6o11o3o0] Lesson 5.3 - Game Over

📅 2022-12-18 sun 21:28

  • Lesson 5.3 - Game Over - Tutorial, Begginer, 1 Hour 10 Mins

概要読むだけ

Step [O6o11o3o1o0] Overview Video

📅 2022-12-19 sun 18:47

動画を観るだけ

  • EventListener の前振り

Step [O6o11o3o2o0] 1.Create a Game Over text object

動画を観る

  • テキストの配置の基本操作の説明

Step [O6o11o3o3o0] 2.Make GameOver text appear

動画を観る

  • テキストの表示/非表示についての話し
  • gameObject.SetActive(true) 使用

Step [O6o11o3o4o0] 3.Create GameOver function

動画を観る

  • 良いオブジェクトが、画面下のラインに当たった時に Game Over を表示するようにしてほしい、という話し
  • Bad タグ作成

Step [O6o11o3o5o0] 4.Stop spawning and score on GameOver

動画を観る

  • オブジェクトの生成と スコアの更新を止める
  • isGameActive フラグ

Step [O6o11o3o6o0] 5.Add a Restart button

動画を観る

  • ボタン作成の話し
  • Button ではなく、 Button - TextMeshPro の方を使う

Step [O6o11o3o7o0] 6.Make the restart button work

動画を観る

  • リスタートボタンを働かせるという話し
  • `using UnityEngine.SceneManagement;
    • SceneManager.LoadScene(SceneManager.GetActiveScene().name) 使用
      • Restart Button ゲームオブジェクトの Inspector ビュー の On Click イベントの使い方

Step [O6o11o3o8o0] 7.Show restart button on game over

動画を観る

  • ゲームがアクティブな間は リスタートボタンをオフにしてほしいという話し
  • using UnityEngine.UI 使用

Step [O6o11o3o9o0] 8.Lesson Recap

動画を観るだけ

📅 2022-12-19 mon 20:50

📺 やってみた

Step [O6o11o4o0] Lesson 5.4 - What’s the Difficulty?

📅 2022-12-20 sun 20:02

  • Lesson 5.4 - What’s the Difficulty? - Tutorial, Begginer, 1 Hour

概要読むだけ

Step [O6o11o4o1o0] Overview Video

動画を観るだけ

Step [O6o11o4o2o0] 1.Create Title text and menu buttons

動画を観る

  • タイトル、3つのボタンの置き方の説明

Step [O6o11o4o3o0] 2.Add a DifficultyButton script

動画を観る

  • 難易度ボタンを働かせる準備
  • DifficultyButton C#スクリプト作成、3つのボタンへアタッチ

Step [O6o11o4o4o0] 3.Call SetDifficulty on button click

動画を観る

  • ボタンに働きを持たせる前振り
  • button.onClick.AddListener(メソッド名) 使用
  • デバッグ方法の説明

Step [O6o11o4o5o0] 4.Make your buttons start the game

動画を観る

  • ボタン押下でゲーム開始する話し

Step [O6o11o4o6o0] 5.Deactivate Title Screen on StartGame

動画を観る

  • タイトルを非表示にする話し

Step [O6o11o4o7o0] 6.Use a parameter to change difficulty

動画を観る

  • ゲームの難しさを変える話し
  • Maximize On Play ボタン(Play Maximized ボタン)の説明

📺 やってみた

Step [O6o11o4o8o0] 7.Lesson Recap

動画を観るだけ

📅 2022-12-20 mon 22:14

Step [O6o11o5o0] Challenge 5 - Whack-a-Food

📅 2022-12-21 mon 18:58

  • Challenge 5 - Whack-a-Food - Tutorial, Begginer, 1 Hour

概要は読まなくてもいいかも

  • バグを直そう、という課題

Step [O6o11o5o1o0] 1.Overview

動画を観る

  • Challenge 5 の準備をする

Step [O6o11o5o2o0] 2.Warning

読むだけ

Step [O6o11o5o3o0] 3.The difficulty buttons look messy

  • ボタンのテキストがずれているので、水平方向、垂直方向ともにボタンのセンターに揃えてほしい という話し
    • テキストの寄せボタンをクリックして解決

Step [O6o11o5o4o0] 4.The food is being destroyed too soon

  • 現状、マウスが近づいたら食品が破棄されてしまう。これを、マウスでクリックしたときに破棄するようにしてほしい という話し

  • MonoBehaviour のメソッドを見てみるか

    • TargetX C#スクリプトに OnMouseEnter() メソッドがあるので、 OnMouseDown() メソッドにリネームする

Step [O6o11o5o5o0] 5.The Score is being replaced by the word “score”

  • 現状、 score としか表示されてない。 これを、 Score: に直し、その後ろに点数を表示してほしい という話し

GameManagerX.cs:

    public void UpdateScore(int scoreToAdd)
    {
        score += scoreToAdd;
        // scoreText.text = "score";
        scoreText.text = "Score: " + score;
    }

👆 これで十分そう

Step [O6o11o5o6o0] 6.When you lose, there’s no way to Restart

  • リスタートボタンを付けてほしいという話し

GameManagerX.cs:

    public void GameOver()
    {
        gameOverText.gameObject.SetActive(true);
        // restartButton.gameObject.SetActive(false);
        restartButton.gameObject.SetActive(true);
        isGameActive = false;
    }

👆 これで十分そう

Step [O6o11o5o7o0] 7.The difficulty buttons don’t change the difficulty

  • 難易度を変えるボタンがいつもハードなので、イージーならゲームスピードが遅く、ハードなら速くなるようにしてほしい という話し

DifficultyButton.cs:

    void SetDifficulty()
    {
        Debug.Log(button.gameObject.name + " was clicked");
        // gameManagerX.StartGame();
        gameManagerX.StartGame(difficulty);
    }

GameManagerX.cs:

    // public void StartGame()
    public void StartGame(int difficulty)
    {
        // spawnRate /= 5;
        spawnRate /= difficulty;
        // ...
    }

👆 これで十分そう

Step [O6o11o5o8o0] 8.Bonus: The game can go on forever

  • 現状、ゲームが永遠に続いているので、タイマーを 59 から初めて、 0 になったらゲームオーバーになるようにしてほしい という話し

  • なんの用意もされていないので、一から作れということらしい

    • ヒントを見ると Unity Count down timer C# でググれとある
      • Update() メソッドの中で Time.deltaTime を減算していけばいいということらしい
        • Mathf.Round() メソッドで小数点を四捨五入できるらしい

GameManagerX.cs:

    public TextMeshProUGUI timerText;
    private float timeLeftSecs = 60;

    private void Update()
    {
        if (isGameActive)
        {
            timeLeftSecs -= Time.deltaTime;

            if (timeLeftSecs < 0)
            {
                GameOver();
            }
            else
            {
                timerText.text = "Timer: " + Mathf.Round(timeLeftSecs);
            }
        }
    }

👆 これで十分そう

📺 やってみた

📅 2022-12-21 mon 20:07

Step [O6o11o6o0] Lab 5 - Swap out your Assets

  • Lab 5 - Swap out your Assets - Tutorial, Begginer, 1 Hour 30 Mins

📅 2022-12-24 sun 11:22

概要は読まなくてもいいかも

  • 退屈なプリミティブな図形を、豪華なアセットに置き換えよう、という話し

Step [O6o11o6o1o0] Overview Video

動画を観るだけ

Step [O6o11o6o2o0] 1.Import and browse the asset library

動画を観る

  • アセット・ライブラリのインポートの話し

  • Create with Code - Course Library.zip をダウンロードする

  • コースライブラリーをインポートする

Step [O6o11o6o3o0] 2.Replace player with new asset

動画を観る

  • プリミティブなジオメトリーを、 アセットへ置き換えるという話し
    • Hirarchy ビューで、 Player ゲームオブジェクトを Project ビューの Prefabs フォルダーへドラッグ&ドロップし、 プレファブ化する
      • Project ビューの Player プレファブをダブルクリックする
        • Player プレファブの子要素として コースライブラリーのフォルダーから、動物のプレファブ をドラッグ&ドロップする
          • 球に付いていた メッシュや、コライダーの Active のチェックを外す
          • 動物に コライダーを付ける

Step [O6o11o6o4o0] 3.Browse the Asset store

動画を観る

  • Unity Asset Store の紹介
    • Pricing で Free Asets をチェックする(無料)
    • Publisher で Synty Studios を検索(チュートリアルのオススメ)
    • Low Poly にすると、電池の消費が少ないなど、ゲーム用に向く
    • [Add to My Assets] ボタンをクリック
      • [Open in Unity] ボタンをクリック
        • Package Manager 画面が出てくる。色んな商品が並んでいるので、 Webサイトで選んだ商品を探す
        • [Download] ボタンをクリック
        • [Import] ボタンをクリック

📖 Asset Store

フォルダーを作って、その下に置くといい

    📂 Assets
    └── 📂 Asset Store
        └── 📂 (ダウンロードしたアセット)

Step [O6o11o6o5o0] 4.Replace all non-player primitives

動画を観る

📺 やってみた

Step [O6o11o6o6o0] 5.Replace the background texture

動画を観る

  • テクスチャーの質感を上げる説明

Step [O6o11o6o7o0] 6.Recap

動画を観る

📅 2022-12-24 sun 14:17

Step [O6o11o7o0] Quiz 5

  • Quiz 5 - Quiz, Begginer, 15 Mins

📅 2022-12-24 sun 14:38

Step [O6o11o8o0] Bonus Features 5 - Share your Work

  • Bonus Features 5 - Share your Work - Tutorial, Begginer, 1 Hour

概要読むだけ

Step [O6o11o8o1o0] 1.Overview

動画を観るだけ

Step [O6o11o8o2o0] 2.Easy: Lives UI

動画を観る

  • 失敗を3つまでしてもゲームオーバーにならない、という Lives: というカウンターを画面に付けてほしい、という話し

  • とりあえず Score: を複製して、そのあとのことは そのあと考える

  • private int lives = 3; 変数でも増やしてみよ

Target.cs:

    private void OnTriggerEnter(Collider other)
    {
        Destroy(gameObject);

        if (!gameObject.CompareTag("Bad"))
        {
            gameManager.GameOver();
        }
    }

👆 Target.cs の方に ゲームオーバーの決定権があるの つらい
せめて gameManager.DecreaseLive() みたいなメソッドを通したい

GameManager.cs:

    void Start()
    {
        livesText.text = "Lives: " + lives;
    }

    public void DecreaseLife()
    {
        if (isGameActive)
        {
            lives--;
            livesText.text = "Lives: " + lives;

            if (lives<1)
            {
                GameOver();
            }
        }
    }

👆 用意する

Target.cs:

            // gameManager.GameOver();
            gameManager.DecreaseLife();

👆 これでよさげ

Step [O6o11o8o3o0] 3.Medium: Music volume

  • 音量を下げるボリュームを付けてほしい、という課題

  • とりあえず スライダー・バーを置いてみる。使い方は そのあと考える

  • とりあえず BGM を付ける、しかし BGM なんか どこにある?

    • プロトタイプ3 からインポートする。このとき、 📂 Sound フォルダーだけチェックする
    • カメラに AudioSource をアタッチして、そこへ サウンドファイルをドラッグ&ドロップする
    • スライダーバーと、オーディオソースの紐づけ方が分からん
      • スライダーバーは、 Inspector ビューを見ると OnValueChanged イベントを持っているようだ

📖 Slider.onValueChanged

  • とりあえず、 Audio Manager ゲームオブジェクトと、 AudioManager C#スクリプトを作るか

AudioManager.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class AudioManager : MonoBehaviour
{
    private AudioSource audioSource;
    private Slider volumeSlider;

    // Start is called before the first frame update
    void Start()
    {
        audioSource = GameObject.Find("Main Camera").GetComponent<AudioSource>();
        volumeSlider = GameObject.Find("Volume Slider").GetComponent<Slider>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    public void UpdateVolume()
    {
        audioSource.volume = volumeSlider.value;
    }
}

Step [O6o11o8o4o0] 4.Hard: Pause menu

動画を観る

  • ボタンを押すと 一時停止と再開を切り替えられるような、ポーズボタンを付けてほしい、という課題

  • 物理的に飛んでいる物体を、どうやってポーズするのか?

    • ヒントを見ると、 Time.timeScale を使うらしい

📖 Time.timeScale

GameManager.cs:

    private bool isPause;

    void Update()
    {
        // 一時停止/再開 切替
        if (isGameActive && Input.GetKeyDown(KeyCode.Space))
        {
            isPause = !isPause;

            if (isPause)
            {
                Time.timeScale = 0;
            } else
            {
                Time.timeScale = 1;
            }
        }
    }

👆 機能だけなら これでいけるが、 画面への Pause 表示と、 BGM の一時停止が残ってる。 ポーズ中にブロックが崩せるのもよくない

GameManager.cs:

    void Update()
    {
        // 一時停止/再開 切替
        if ((isPause || isGameActive) && Input.GetKeyDown(KeyCode.Space))
        {
            isPause = !isPause;
            isGameActive = !isPause;

            if (isPause)
            {
                Time.timeScale = 0;
            } else
            {
                Time.timeScale = 1;
            }
        }
    }

👆 雑に解決すると、こう

AudioManager.cs:

    public void Pause()
    {
        audioSource.Pause();
    }

    public void Play()
    {
        audioSource.Play();
    }

GameManager.cs:

    public TextMeshProUGUI pauseText;
    private GameObject directionalLight;
    private AudioManager audioManagerScript;

    void Start()
    { // ...
        directionalLight = GameObject.Find("Directional Light");
    }

    void Update()
    {
        if ((isPause || isGameActive) && Input.GetKeyDown(KeyCode.Space))
        { // ...
            if (isPause)
            { // ...
                directionalLight.SetActive(false);
                pauseText.gameObject.SetActive(true);
                audioManagerScript.Pause();
            } else
            { // ...
                directionalLight.SetActive(true);
                pauseText.gameObject.SetActive(false);
                audioManagerScript.Play();
            }
        }
    }

👆 ポーズ時に画面を暗くする。ポーズテキストを出す。音を一時停止させる。それを解除させる

  • Pause の文字は、 Game Over Text ゲームオブジェクトを複製して いじって作る
    • Inspector ビューの public TextMeshProUGUI pauseText フィールドへ、 Pause Text ゲーム・オブジェクトを アクティブにして ドラッグ&ドロップするのと、 非アクティブにして ドラッグ&ドロップするのとで アタッチされるのが ゲームオブジェクトなのか、テキストなのかが違うので確認すること

Step [O6o11o8o5o0] 5.Expert: Click-and-swipe

  • マウスクリックに代えて、マウスドラッグで 対象を破壊できるように変えてほしいという課題

  • ペンみたいな軌跡をどうやって作れるのか分からん。 答えを見て進むことにする

    • Unity Learn のコメント欄を見る
      • Good 1Good 3 ゲームオブジェクトへ、 Good タグを付ける
// Author: VladStoyanoff
// Arranged by: Muzudho
// CursorTrail script, attached to Main Camera (creates the trail):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CursorTrail : MonoBehaviour
{
    public Color trailColor = new Color(1, 0, 0.38f);
    public float distanceFromCamera = 5;
    public float startWidth = 0.1f;
    public float endWidth = 0f;
    public float trailTime = 0.24f;

    Transform trailTransform;
    Camera thisCamera;
    GameObject trailObj;

    // Start is called before the first frame update
    void Start()
    {
        thisCamera = GetComponent<Camera>();

        // 新規作成
        trailObj = new GameObject("Mouse Trail");
        trailTransform = trailObj.transform;
        TrailRenderer trail = trailObj.AddComponent<TrailRenderer>();
        trail.time = -1f;
        MoveTrailToCursor(Input.mousePosition);
        trail.time = trailTime;
        trail.startWidth = startWidth;
        trail.endWidth = endWidth;
        trail.numCapVertices = 2;
        trail.sharedMaterial = new Material(Shader.Find("Unlit/Color"));
        trail.sharedMaterial.color = trailColor;
    }

    // Update is called once per frame
    void Update()
    {
        MoveTrailToCursor(Input.mousePosition);
    }

    void MoveTrailToCursor(Vector3 screenPosition)
    {
        // スクリーン座標を、ワールド座標へ変換
        trailTransform.position = thisCamera.ScreenToWorldPoint(new Vector3(screenPosition.x, screenPosition.y, distanceFromCamera));
    }

    public void SetVisibility(bool isVisibility)
    {
        trailObj.SetActive(isVisibility);
    }
}

Targets.cs:

// Author: VladStoyanoff
// Arranged by: Muzudho
// Changes in Targets.cs script on the OnMouseDown() function:

    private void OnMouseOver()
    {
        if (gameManager.isGameActive && Input.GetMouseButton(0))
        {
            if (gameObject.CompareTag("Good"))
            {
                Destroy(gameObject);
                gameManager.UpdateScore(pointValue);
                Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation);
            }
            else if (gameObject.CompareTag("Bad"))
            {
                Destroy(gameObject);
                gameManager.DecreaseLife();
                Instantiate(explosionParticle, transform.position, explosionParticle.transform.rotation);
            }
        }
    }

GameManager.cs:

// Author: VladStoyanoff
// Arranged by: Muzudho
// Changes in the GameManager.cs script regarding the GameOver() and StartGame() functions: 

    private CursorTrail cursorTrailScript;

    void Start()
    { // ...
        cursorTrailScript = GameObject.Find("Main Camera").GetComponent<CursorTrail>();
    }

    void Update()
    { // ...
        if (Input.GetMouseButton(0))
        {
            cursorTrailScript.SetVisibility(true);
        }
        else
        {
            cursorTrailScript.SetVisibility(false);
        }
    }

    public void StartGame(int difficulty)
    { // ...
        cursorTrailScript.enabled = true;
    }

    public void GameOver()
    { // ...
        cursorTrailScript.enabled = false;
    }

📺 やってみた

Step [O6o11o8o6o0] 6.Hints and solution walkthrough

ヒントが載っている

📅 2022-12-24 sun 18:05

Step [O6o12o0] Next Steps - Introduction

  • Next Steps - Introduction - Tutorial, Begginer, 5 Mins

概要読むだけ

📖 Next Steps

Step [O6o12o1o0] 1.Next Steps - Introduction

動画を観るだけ

Step [O6o12o2o0] Lesson 6.1 - Project Optimization

  • Lesson 6.1 - Project Optimization - Tutorial, Begginer, 30 Mins

概要読むだけ

  • プロジェクトの改善をするらしい

Step [O6o12o2o1o0] 1.Variable attributes

動画を観る

  • Prototype 1 の復習
  • [SerializeField] アトリビュートの説明
  • アクセス修飾子の説明

Step [O6o12o2o2o0] 2.Unity Event Functions

動画を観る

  • Prototype 1 の復習
  • Secondary Camera の作成
  • Update()Start() 以外の関数の話し
    • FixedUpdate() - 物理的な計算をするものへ
    • LateUpdate() - カメラへ
    • Awake()

Step [O6o12o2o3o0] 3.Object Pooling

動画を観る

  • Prototype 2 の復習
  • この説明をそのまま実行すると、破壊してしまう。バックアップを取っておくこと
    • 上書きせず、差分を調べて 自力でソース読んで マージすること
    • インスタンスの生成と破棄より効率的な、プーリングの説明

なんどか修復してやってみたが 壊れた。この節はスキップする

DestroyOutOfBounds.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DestroyOutOfBounds : MonoBehaviour
{
    private float topBound = 30;
    private float lowerBound = -10;

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        if (transform.position.z > topBound)
        {
            // Instead of destroying the projectile when it leaves the screen
            //Destroy(gameObject);

            // Just deactivate it
            gameObject.SetActive(false);

        }
        else if (transform.position.z < lowerBound)
        {
            Debug.Log("Game Over!");
            Destroy(gameObject);
        }

    }
}

DetectCollisions.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class DetectCollisions : MonoBehaviour
{
    void OnTriggerEnter(Collider other)
    {
        // Instead of destroying the projectile when it collides with an animal
        //Destroy(other.gameObject); 

        // Just deactivate the food and destroy the animal
        other.gameObject.SetActive(false);
        Destroy(gameObject);
    }

}

ObjectPooler.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ObjectPooler : MonoBehaviour
{
    public static ObjectPooler SharedInstance;
    public List<GameObject> pooledObjects;
    public GameObject objectToPool;
    public int amountToPool;

    void Awake()
    {
        SharedInstance = this;
    }

    // Start is called before the first frame update
    void Start()
    {
        // Loop through list of pooled objects,deactivating them and adding them to the list 
        pooledObjects = new List<GameObject>();
        for (int i = 0; i < amountToPool; i++)
        {
            GameObject obj = (GameObject)Instantiate(objectToPool);
            obj.SetActive(false);
            pooledObjects.Add(obj);
            obj.transform.SetParent(this.transform); // set as children of Spawn Manager
        }
    }

    public GameObject GetPooledObject()
    {
        // For as many objects as are in the pooledObjects list
        for (int i = 0; i < pooledObjects.Count; i++)
        {
            // if the pooled objects is NOT active, return that object 
            if (!pooledObjects[i].activeInHierarchy)
            {
                return pooledObjects[i];
            }
        }
        // otherwise, return null   
        return null;
    }

}

PlayerController.cs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    private float horizontalInput;
    private float speed = 20.0f;
    private float xRange = 20;
    public GameObject projectilePrefab;


    // Update is called once per frame
    void Update()
    {
        // Check for left and right bounds
        if (transform.position.x < -xRange)
        {
            transform.position = new Vector3(-xRange, transform.position.y, transform.position.z);
        }

        if (transform.position.x > xRange)
        {
            transform.position = new Vector3(xRange, transform.position.y, transform.position.z);
        }

        // Player movement left to right
        horizontalInput = Input.GetAxis("Horizontal");
        transform.Translate(Vector3.right * Time.deltaTime * speed * horizontalInput);


        if (Input.GetKeyDown(KeyCode.Space))
        {
            // No longer necessary to Instantiate prefabs
            // Instantiate(projectilePrefab, transform.position, projectilePrefab.transform.rotation);

            // Get an object object from the pool
            GameObject pooledProjectile = ObjectPooler.SharedInstance.GetPooledObject();
            if (pooledProjectile != null)
            {
                pooledProjectile.SetActive(true); // activate it
                pooledProjectile.transform.position = transform.position; // position it at player
            }
        }



    }
}

Step [O6o12o2o4o0] 4.Lesson Recap

オブジェクト・プーリングはスキップ

📅 2022-12-24 sun 20:29

Step [O6o12o3o0] Lesson 6.2 - Research and Troubleshooting

  • Lesson 6.2 - Research and Troubleshooting - Tutorial, Begginer, 1 Hour

Step [O6o12o3o1o0] 1.Make the vehicle use forces

動画を観る

  • スピードメーターを付けよう、という話し

  • 加速と減速

    • transform.Translate() をやめて、 playerRb.AddForce() に変える
    • Speed をやめて、 HorsePower を付ける
  • 空間の GlobalLocal

    • Googling Unity add force in local space
      • AddForce() の代わりに AddRelativeForce() を使う
  • Prototype 1 プロジェクトのバックアップを取っておく

Step [O6o12o3o2o0] 2.Prevent car from flipping over

動画を観る(15:50 もある、長い)

  • 平行移動から 物理的な移動に変えたので 車がよくひっくり返るようになってしまった。これを ひっくり返らないようにしてほしいという話し
    • 車の形に見えるが、車輪が完全な円をしていて、回らなければ つっかえるので 車体はひっくり返る。 Wheel Colliders の説明
    • Rigidbody.centerOfMass (重心)を下げる、という説明
      • Center of Mass ゲームオブジェクトを作る

📺 やってみた

余計 転がるようになったが、レッスンの先に進む

📅 2022-12-24 sun 22:39

Step [O6o12o3o3o0] 3.Add a speedometer display

動画を観る(12:54 もある、長い)

  • スピード・メーターは、テキストで実装するようだ
  • マグニチュード(キロメートル毎時) から マイル毎時 に変換している?

Step [O6o12o3o4o0] 4.Add an RPM display

動画を観る

  • 毎分回転数って何だ。タイヤが1分間に何回回ってるか知りたいらしい
  • 剰余の説明 Modulus, Remainder

Step [O6o12o3o5o0] 5.Prevent driving in mid-air

動画を観る

  • 空中で加速できるので、できないようにしてほしい、という話し
  • 車輪4つを、1つ1つ接地判定する必要がある
    • foreach 文の説明
      • wheel.isGrounded 使用

Step [O6o12o3o6o0] 6.Lesson Recap

読むだけ

📅 2022-12-24 sun 16:48

Step [O6o12o4o0] Lesson 6.3 - Sharing your Projects

  • Lesson 6.3 - Sharing your Projects - Tutorial, Begginer, 30 Mins

ビルドして、 WebGL 用にエクスポートする話しらしい

Step [O6o12o4o1o0] 1.Install export Modules

  • Export Module というものを追加する話し

Step [O6o12o4o2o0] 2.Build your game for Mac or Windows

  • 終了方法の分からないゲームだと、終了方法が分からないから注意

📺 やってみた

Step [O6o12o4o3o0] 3.Build your game for WebGL

  • 以下のようなエラーが出て動かない
Failed to download file Build/Prototype1_webgl.data.gz. Loading web pages via a file:// URL without a web server is not supported by this browser. Please use a local development web server to host Unity content, or use the Unity Build and Run option.

📖 【Unity】WebGLビルドしてブラウザで表示する時にハマった件

[Edit] - [Project Settings] - [Player] - [Publish Settings] - [Decompression Fallback] チェックボックスにチェックを入れる

Visual Studio Code の live server エクステンションで Webサーバーとして起動する

📺 やってみた

ゲームのアップロード先には、以下のようなサイトがあるらしい

📖 Unity Play
📖 itch.io

Step [O6o12o4o4o0] 4.Lesson Recap

読むだけ

📅 2022-12-24 sun 17:41

Step [O7o0] Django で Unity を動かすには

📖 DjangoでUnityを動かしたい

    📂 static
    ├── 📂 warabenture_vol1o0
    │   └── 📂 Builds
    │       └── 📂 AirHockey
👉  │           ├── 📂 Build
👉  │           ├── 📂 StreamingAssets
👉  │           └── 📂 TemplateData
    📂 templates
    └── 📂 warabenture_vol1o0
        └── 📂 Builds
            └── 📂 AirHockey
👉              └── 📄 index.html

👆 index.html を templates フォルダーの下へ、 それ以外を static フォルダーの下へ
このとき、 Build という名前のフォルダーが git 操作で無視されることがあった。無視されていないか、要確認

index.html:

<!-- 冒頭に追加 -->
{% load static %}

<!-- 削除
        <link rel="shortcut icon" href="TemplateData/favicon.ico" />
        <link rel="stylesheet" href="TemplateData/style.css" />
    ↓ 追加(パスは編集すること)
-->
        <link rel="shortcut icon" href="{% static 'warabenture_vol1o0/Builds/AirHockey/TemplateData/favicon.ico' %}" />
        <!--                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -->
        <link rel="stylesheet" href="{% static 'warabenture_vol1o0/Builds/AirHockey/TemplateData/style.css' %}" />
        <!--                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -->

        <script>
            // ↓ パスを編集すること
            // var buildUrl = "Build";
            var buildUrl = "{% static 'warabenture_vol1o0/Builds/AirHockey/Build' %}";
            //              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

            var loaderUrl = buildUrl + "/AirHockey.loader.js";
            var config = {
                dataUrl: buildUrl + "/AirHockey.data.unityweb",
                frameworkUrl: buildUrl + "/AirHockey.framework.js.unityweb",
                codeUrl: buildUrl + "/AirHockey.wasm.unityweb",
                streamingAssetsUrl: "StreamingAssets",
                companyName: "DefaultCompany",
                productName: "Air Hockey",
                productVersion: "0.1",
                showBanner: unityShowBanner,
            };

        </script>

Step [O8o0] DOTS Best Practices

  • DOTS Best Practices - Course, Advanced, 1 Hour 55 Mins

📖DOTS Best Practices

いったん パス

Step [O9o0] Unity Essentials

📅2023-01-10 tue

📖Unity Essentials

👆 ページのレイアウトが ビギナーコースと違っていて 使い方が分からん

Step [O9o1o0] Get started with Unity

📖Get started with Unity

  • Get started with Unity - Mission, Foundational

ビギナーコースより エッセンシャルズを先にやればよかったのか

Step [O9o1o1o0] Welcome to Unity Essentials

  • Get started with Unity - Tutorial, Foundational, 0 Mins

先人の話しを聞け、みたいなことをするらしい

Step [O9o1o1o1o0] 1.Overview

動画観るだけ

トランスクリプトが無い。翻訳できね~

Step [O9o1o1o2o0] 2.What's next

これから Unity をインストールしよう、という前振り

Step [O9o2o0] Get the setup wizard

📖Get the setup wizard

  • Get the setup wizard - Tutorial, Foundational, 30 Mins

インストールはもうしてるけどなあ。成績上げたいので読んでいく

Step [O9o2o1o0] 1.Overview

前振り文章

Step [O9o2o2o0] 2.Before you begin

Unityが使える環境の説明文章

Step [O9o2o3o0] 3.Download the setup wizard

ダウンロードの案内の説明文章

📖Unity Download Page

Step [O9o2o4o0] 4.Next steps

次のステップの説明文章

Step [O9o3o0] Unity Plans: What's right for me?

  • Unity Plans: What's right for me? - Tutorial, Foundational, 25 Mins

なんか 計画だろうか

Step [O9o3o1o0] 1.Overview

個人か、企業かで サブスクリプション(料金)プランが違うという前振り文章

Step [O9o3o2o0] 2.What are Unity Plans?

動画観るだけ

この動画には英語のトランスクリプトが付いてる

サブスクリプション・プランの違いの説明

Step [O9o3o3o0] 3.Unity Plans in detail

サブスクリプション・プランの違いの説明の詳細な文章

Step [O9o3o4o0] 4.Unity Education Plans

教育者、学生向けの説明文章

Step [O9o3o5o0] 5.Next steps

次に Unity Hub をインストールする流れらしい

Step [O9o4o0] Install Unity and the Hub

  • Install Unity and the Hub - Tutorial, Foundational, 45 Mins

📅2023-01-10 tue 19:30

Step [O9o4o1o0] 1.Overview

インストールの前振り文章

Step [O9o4o2o0] 2.Install Unity 2019.4 LTS

Unity Hub をインストールして開き、
Unity Hub を使って Unity をインストールしよう、という説明文章

Step [O9o4o3o0] 3.What is a Unity ID?

Unity ID の説明

Step [O9o4o4o0] 4.Create your Unity ID

Unity ID の作り方の説明

Step [O9o4o5o0] 5.Select a Microgame

Unityの最初の体験として、 LEGO マイクロゲームをインストールしよう、という話し

しかし Unity Hub に Select Microgame というメニューは見当たらない。記事が古いのか?
ググると見つかるが

📖LEGO® Microgame

インストールが進まないが、できたつもりで先に進む

Step [O9o4o6o0] 6.Launch Unity Editor

Microgame をインストールしたあとの話し。
インストールしてないが、先に進む

Step [O9o4o7o0] 7.Install Unity without the wizard

インストール・ウィザードを使わずにインストールする話し

Step [O9o4o8o0] 8.Next steps

Unity Hub と、 Unity Editor と、 Unity ID の準備ができたという話し

Step [O9o5o0] Explore the Unity Editor

  • Explore the Unity Editor - Tutorial, Foundational, 45 Mins

📅2023-01-10 tue 19:53

Unity Editor と パッケージ・マネージャーの説明をするらしい
トランスクリプトも .zip ファイルで提供しているようだ

Step [O9o5o1o0] 1.Overview

Microgame の説明の前に、 Unity Editor の説明をするという前振り

Step [O9o5o2o0] 2.Before you begin

始める前に、今開いているチュートリアルを閉じろとのことだが、そんな画面は開いていない
先に進む

Step [O9o5o3o0] 3.Unity Editor: First impressions

動画がある

最初に Unity を触った時の感想みたいな内容

Step [O9o5o4o0] 4.Introduction to the Unity Editor

Unity Editor の構成の紹介

5つの要素

  1. Scene ビューと、 Game ビュー
  2. Hierarchy ウィンドウ
    • GameObject が並んでいる
  3. Project ウィンドウ
    • Asset が並んでいる
  4. Inspector ウィンドウ
    • GameObject の詳細情報
      • コンポーネントが並んでいる
  5. ツールバー

Step [O9o5o5o0] 5.Review the Editor layout options

Unity Editor の前述の5つの要素の配置レイアウトは変えれるという話し

Step [O9o5o6o0] 6.Using scenes in your project

Unity Editor のプロジェクトは、複数のシーンで構成されるという話し

シーン1つ分は、体験1つ分と考えるとよいとのこと。
Unity プロジェクトの要件は、シーンを1つ以上含んでいるということだけ

Step [O9o5o7o0] 7.Practice navigating the scene

Scene ビューのウィンドウ内でのナビゲーションの説明

  1. Pan
    1. 手のひらツールでシーン・ビューをドラッグ
  2. Zoom
    1. Alt キーを押したまま、右クリックでドラッグ
  3. Orbit
    1. Alt キーを押したまま、左クリックでドラッグ
  4. Focus(Frame Select)
    1. ゲーム・オブジェクトが選択されているときに、シーン・ビューで F キー押下

フライスルー・モードは、一人称視点でシーン・ビューをナビゲートする方法

  • Flythrough mode
    • クリックして、右マウスボタンを押し続ける
    • WASD キーが left/right/forward/backward に対応
    • QE キーがビューの up/down に対応
    • 選択して Shift を押し続けると動くのが速い

Step [O9o5o8o0] 8.Unity Editor tips and tricks

動画観るだけ

Step [O9o5o9o0] 9.Summary

まとめ文章読むだけ

📅2023-01-10 tue 20:38

Step [O9o6o0] Explore a Microgame

  • Explore a Microgame - Tutorial, Foundational, 30 Mins

マイクロゲームはインストールしてないが……
コード無しで ゲーム体験をするらしい

Step [O9o6o1o0] 1.Overview

文章

Step [O9o6o2o0] 2.Open a new Microgame

Unity Hub の NEW ボタンから 4つの Microgame を選べる??
確かに 選べそうだ
テンプレートと呼ぶらしい。ダウンロードしてみる

Step [O9o6o3o0] 3.Re-open the tutorial window

チュートリアル・ウィンドウの開き方の説明だが、よく分からない

インストールが出てきたらチュートリアルが出てきた。

先に進めないゲームを、先に進めるように改造していくチュートリアルだ。
チュートリアルを進めていくと、エレベーターを作る所で 何をすればいいのか分からなくなり 嫌になったので やめた

📅2023-01-10 tue 21:45

Step [O9o6o4o0] 4.Complete the in-Editor tutorials

文章。

完了してないけど先に進む

Step [O9o6o5o0] 5.Explore the submissions

マイクロゲームの提出の仕方の説明

Step [O9o6o6o0] 6.Submit your Microgame in our Gallery

チュートリアルを完了すると、マイクロゲームを提出する方法を習得できるらしいが、
LEGOのチュートリアルが嫌になったので ここで終わる

Step [OA10o0] Creative Core: UI

📅2023-01-11 wed 18:56

  • Creative Core: UI - Project, Beginner, 3 Hours 45 Mins

コースから外れてしまったので、適当に選んで始めてみる

と思ったが、タイトルを見ると もうやったビギナーコースと重複してるかもしれない。パスする
ほかのも見てみるが ビギナーコースと重複してそう

Step [OA11o0] DOTS Best Practices

  • DOTS Best Practices - Course, Advanced, 1 Hour 55 Mins

📖DOTS Best Practices

とりあえず もう一回見てみる

どうも内容が 高速化、最適化のもののようだ。興味無いのでパス

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1