Day 13 - Create Enemy
自分で設定した完成に近づいてきました。 現バージョンはver1.3。残るバージョンは ver1.4 敵モンスターの作成 戦闘機能追加 - 月曜まで ver1.5 クエスト機能 -火曜まで となっております。 ver1.4 敵モンスターとの戦闘機能を追加します。 敵が居なければ戦闘も出来ないので、まずはモンスターを作る所から。 自分でモンスターを作っても良いのですがモンスター(ただの四角い箱)だとちょっと味気ないので アセットストアからいい感じのモンスターを持ってきましょう。 ↓ https://www.assetstore.unity3d.com/jp/ ...
自分で設定した完成に近づいてきました。 現バージョンはver1.3。残るバージョンは
ver1.4 敵モンスターの作成 戦闘機能追加 - 月曜まで ver1.5 クエスト機能 -火曜まで
となっております。
ver1.4
敵モンスターとの戦闘機能を追加します。 敵が居なければ戦闘も出来ないので、まずはモンスターを作る所から。
自分でモンスターを作っても良いのですがモンスター(ただの四角い箱)だとちょっと味気ないので アセットストアからいい感じのモンスターを持ってきましょう。 ↓ https://www.assetstore.unity3d.com/jp/#!/content/77703
kawaiiですね。 Level1っていう名前も最初の村の近くに居るモンスターとしては最適でしょう。
Set spawn point
設定としては、村を脅かす巨大モンスターが外フィールドに一体いるのでそれを倒さなければならない。 (ver1.5ではそのモンスターを倒してくれ、というクエストを受注できる機能を追加する予定です。)
つまりモンスターの出没する場所はver1.3で作ったフィールドです。
とりあえず配置してみた画像がこちら
つよそう。 このうさぎにHPと攻撃力、当たり判定を追加してあげます。
何から作るべきか、とりあえず作り慣れている当たり判定から作ってあげましょう
Hit box
この敵には、体の物理判定と体の周りにあるダメージを受ける範囲の判定の二種類をつけます。 体のほうはアセットに含まれているので、ダメージを受ける範囲を設定してあげます。
うさぎの周りにある緑の円に入ると、プレイヤーがダメージを受けるようなスクリプトを組み込みます。 OnTriggerEnterに、1秒間に一度プレイヤーのHPを減らすという処理を入れる感じでしょうか
そうしようとするとまずはプレイヤーのHPを設定しなければなりませんね。
Player's HP
これは凄く簡単で、ただHPというstatic変数を定義してあげて数値を代入すれば大丈夫ですね。 PlayerHPというスクリプトを作ってあげて、設計図通りにHPは100と設定してあげましょう。
public class PlayersHP : MonoBehaviour { public static int HitPoint; void Start() { HitPoint = 100; } }
さて、前に作った左上のUIにHPの表示がありますね (MPっぽいゲージも一応左上のUIに表示されてますが今回これは飾りという形です)
しかし、このUIとプレイヤーのHPの数値が現在全く関係を持っていません。 HPが100の時は、100%のawidth表示。HPが10の時は、10%のawidth…という形で連動させたい。
多分、HPバーのインスペクターの数値を取得してそこにHPの値を代入してやるようなスクリプトを組み込んでやればいいはず。 HPバーの幅や高さの値を取得して変更するには
https://forum.unity3d.com/threads/modify-the-awidth-and-height-of-recttransform.270993/
こちらの
RectTransform rt = UICanvas.GetComponent (typeof (RectTransform)) as RectTransform; rt.sizeDelta = new Vector2 (100, 100);
というコードから、UICanvas.を抜いてVector2の数値を(プレイヤーのHPの変数,30)にしてやれば 今回の場合では実装することができます。
void Update () { RectTransform rt = GetComponent(typeof(RectTransform)) as RectTransform; rt.sizeDelta = new Vector2(PlayerHP.HitPoint, 30); }
こんな感じで書いて上げればいけるでしょう。 では実行!
プレイヤーがゲーム開始と同時に即死しました。南無三。 絶対にこれうまくプレイヤーのHPを取得出来ていないか幅のところに数値が代入出来ていないかのどちらかですね。
前にも似たようなことがあったような気がする。 スペルミスなんていう簡単なミスだったけどまさか今回も・・・
public class PlayersHP : MonoBehaviour
PlayersHP
rt.sizeDelta = new Vector2(PlayerHP.HitPoint, 30);
PlayerHP
OMG. スペルミス、気をつけなくては… もう少しわかりやすい名前にするとか短い名前にするのを意識しましょう。
PlayersHPにアクセスするクラス名を直して試しに初期設定でHPを50にしてあげて実行すると
完璧ですね。 では戻ってうさぎに触れたらダメージを受けるコードを書いていきましょう。
void OnTriggerEnter(Collider other) { PlayersHP.HitPoint -= 10; }
とりあえず書いてみましたが、これだと一回触れた時にHPが10減る。 というだけの処理しか出来ません。触れ続けていると1秒間にダメージを受ける、みたいな処理にしたい。
1秒間ごとに処理を繰り返すような処理にしたい。
なので、触れたらダメージを受ける処理はなかったことにして 接触をbool変数で判定して接触している間はwhileでダメージを受け続けるようにしてみます。
private bool HitUsagi; void OnTriggerEnter(Collider other) { HitUsagi = true; } void OnTriggerExit(Collider other) { HitUsagi = false; } void Update() { while(HitUsagi == true){ PlayersHP.HitPoint -= 10; } }
あ~これだと触れた瞬間にめちゃくちゃ体力減ってしまう気がするな~ でも、面白そうだから一回起動して動作チェックしてみよう!
1 minutes later
甘かった。
僕は甘かった。 触れた瞬間面白い動作をするどころかフリーズしてしまったのである そらそうだ、秒間とかそういうレベルじゃない速度で繰り返し処理がされているのだ・・・
さて、フリーズを解消して秒間ダメージにするには繰り返しの一回一回に1秒のクールタイムを設けてやればいいと思う。 1秒の処理待ちをするためにはどうすればいいんだろう
その為に必要なのが 「コルーチン」 というものらしい。 IEnumeratorを使ってコルーチンを作り、コルーチンを呼び出すことによってそのコルーチンを実行する。
意味が分からない、けど実際に使ってみよう。
void Update() { while (HitUsagi == true) { PlayersHP.HitPoint -= 10; StartCoroutine("Wait"); // Waitというコルーチンを実行する } } IEnumerator Wait() { yield return new WaitForSeconds(1.0f); // 1秒間待つ }
ちなみに、直接void Updateの中にコルーチンの処理を書こうとしたらエラーを吐かれた。 IEnumeratorで作って、void Updateの所で呼び出して上げるという面倒くさいことをしなければちゃんと動かないみたい。 しかし、なるほど。これで1秒間待つ処理が出来たみたいです。
では、早速実行することに。
2 minutes later
フリーズ。 なんで?
これ、whileくんが1秒待ってくれていない状態ですかね…? こちらのサイトによるとvoid Updateでコルーチンを使う場合に気をつけなければいけない点があるとのこと。 http://qiita.com/kazz4423/items/73219068684e87adc87d
void Updateはフレーム毎に処理される。 つまり、コルーチンは1秒待ってくれてるけどコルーチンを呼び出す所まではvoid Updateが勝手に繰りしていると。
Bool is GOD
これを解決するために必要なもの。それはbool型変数。 便利すぎる。なんだこいつ。
void Update() { while (HitUsagi == true) { PlayersHP.HitPoint -= 10; StartCoroutine("Wait"); } } IEnumerator Wait() { if (isRunning) // isRunningがtrueならば { yield break; // 以下は実行されない } isRunning = true; // コルーチン実行中はisRunningがtrueになる yield return new WaitForSeconds(1.0f); isRunning = false; // 終了と同時にisRunningがfalseになる }
これで、void Updateが勝手に何度も中の処理を繰り返したりするのを阻止出来ます。 では、実行してチェックしてみましょう。
3 minutes later
フリーズ。 いやよく考えたら、コルーチンが実行されないだけで全体の処理そもそもが止められてないや…
Thinking time
一旦整理します。 今したいことは
近づいたら → ダメージを受ける → まだ近づいているなら → 1秒後にダメージを受ける
全体の処理を止めるために、whileの外でコルーチンを呼び出してやらなければならない。 しかもこれ、whileである必要が無いのでは?ifでいいのでは。
書き直します。
void Update() { StartCoroutine("Wait"); if (HitUsagi) { PlayersHP.HitPoint -= 10; } } IEnumerator Wait() { if (isRunning) yield break; isRunning = true; yield return new WaitForSeconds(1); isRunning = false; }
これで実行してみた結果がこちらです
フリーズはしなくなりましたが、触れた瞬間即死していますね。 やっぱりvoid Updateがそもそも止められていないのが原因っぽいですね
Add if statement
ならば、if文をもう一個追加してやり、isRunningがfalseの場合のみ中のif文が実行されるようにしてやればどうだろう?
void Update() { if (!isRunning) // isRunningがfalseならば以下が実行 { if (HitUsagi) { PlayersHP.HitPoint -= 10; StartCoroutine("Wait"); } } }
これで、
最初にHPが10減り → コルーチンが実行 → 1秒間はisRunningがtrueとなるため、処理が実行されない → 1秒後isRunningがfalseとなり、処理がもう一度実行されHPが10減る
という処理になるはずです。
実際に動かしてみましょう。
I did it.
やった・・・やったぞー! 1秒毎にダメージを受けています。
void Updateの使い方がまだまだ慣れていない感じですね、でもなんとか出来ました。 まだまだ先は長いのに時間をかなり使ってしまった…頑張っていきます。
Rabbit's HP
今度はうさぎ側のHPを設定してあげます。 プレイヤー側のHP設定と同じように組み込んだスクリプトに変数の宣言をしてあげてHP設定は完了です。
Punch
次に、プレイヤーに攻撃機能を追加してあげましょう。 とりあえず近づいたらこちらもダメージを与え続けるという仕様にします。
if (!isRunning) { if (HitUsagi) { PlayersHP.HitPoint -= 10; UsagisHP.usagiHitPoint -= 30; StartCoroutine("Wait"); } } if (UsagisHP.usagiHitPoint < 0) { gameObject.SetActive(false); }
これで、ダメージを与えて敵のHPを減らすことが出来るようになりました。 しかし、流石にモーションも無くただ近づくだけでダメージを与え合うゲームなんてつまらない…
しかし、明日までの期限が迫っているので今回はここまでにします。 明日ver1.5のクエスト機能が完成次第、攻撃モーションを追加してあげましょう。 今日はここまで。