たくみん成長日記

開発したアプリやプログラミングの備忘録を不定期に上げていきます。

Unityちゃんを英雄王にしてみた

こんちか!! たくみんです。この記事は、SLP KBIT Advent Calendar 2018の18日目の記事になります。

qiita.com

本題に入りますが、Fateに興味をもちまして、Fate/stay night [Unlimited Blade Works]を視聴しました。

一応最後まで見たんですが、「王の財宝」がかっこよすぎるんですよね。まず、「王の財宝」と書いて、「ゲートオブバビロン」というのが、かっこいいし、迫力も凄いし、もう半端じゃないって思いました。 f:id:takuminv:20181216215245j:plain:w400

そして、思いました。

そうだ、Unityでやろう。

「王の財宝」の確認

まず、「王の財宝」を確認して、どのような要素で構成されているのか、確認してみましょう。 確認すると、「王の財宝」は3つの要素で構成されていることがわかります。

  1. 宝具(剣とか槍とか)
  2. 宝具を射出するやつ
  3. 英雄王本人

f:id:takuminv:20181216220921j:plain:w400

これをUnityで再現します。 つまり、「Unityちゃんが「王の財宝」を使って、的に対して攻撃する」を再現します。 この記事では、「王の財宝」を生成する部分まで紹介します。

Assetの準備

まずは、「王の財宝」を再現するための、Assetを探します。

1. 宝具

宝具に見えるAssetは、下記のものにしました。これなら、無料で使用できますし、それっぽいですからね。 assetstore.unity.com

2. 宝具を射出するやつ

宝具を射出するやつは、下記のものにしました。AssetStoreには、無料でいいものがなかったので、こちらにしました。 ktk-kumamoto.hatenablog.com

3. 英雄王本人

英雄王は、Unityちゃんにしました。ほとんど初めてのUnityなので、下記のQiitaの記事を参考にしました。

qiita.com

assetstore.unity.com

assetstore.unity.com

今回作成したObjectの紹介

今回Scene上に生成するObjectは以下です。

  • Player(Playerが操作するUnityちゃん)
  • target(「王の財宝」の的になるObject)
  • aura_ground(宝具を射出するやつ)
  • sword(宝具)
  • mask_sword(後述)

「王の財宝」を生成するスクリプト

生成するスクリプトは、PlayerにAddしているSwordGeneratorというスクリプトです(くそコードなのは許してください)。とりあえず、Hキーを押すことで、「王の財宝」を生成することにしました。

public class SwordGenerator : MonoBehaviour {

    GameObject sword;
    GameObject mask_sword;
    GameObject sword_circle;
    GameObject player;
    GameObject target;
    // Update is called once per frame

    private void Start()
    {
        sword = (GameObject)Resources.Load("Prefabs/WeaponSword002");
        mask_sword = (GameObject)Resources.Load("Prefabs/Cylinder");
        sword_circle = (GameObject)Resources.Load("Prefabs/aura_ground");
        player = GameObject.Find("Player");
        target = GameObject.Find("target");
    }
    void Update () {
        if (Input.GetKeyDown(KeyCode.H) )
        {
            //---- arua_ground(宝具を射出するやつ)の生成
            Vector3 pos = new Vector3(Random.Range(-20.0f, 20.0f) + player.transform.position.x, player.transform.position.y + Random.Range(2.0f, 12.0f), player.transform.position.z);

            Vector3 pos_circle = pos;

            Vector3 aim = target.transform.position - pos;
            
            Quaternion look = Quaternion.LookRotation(aim);

            pos_circle = pos_circle + (look * new Vector3(0f, 0f, 1.1f));
            Instantiate(sword_circle, pos_circle, look);

             //---- sword(宝具)とmask_swordの生成
            switch(Random.Range(0,11))
            {
                case 0: sword = (GameObject)Resources.Load("Prefabs/WeaponSword000"); break;
                case 1: sword = (GameObject)Resources.Load("Prefabs/WeaponSword001"); break;
                case 2: sword = (GameObject)Resources.Load("Prefabs/WeaponSword002"); break;
                case 3: sword = (GameObject)Resources.Load("Prefabs/WeaponSword003"); break;
                case 4: sword = (GameObject)Resources.Load("Prefabs/WeaponSword004"); break;
                case 5: sword = (GameObject)Resources.Load("Prefabs/WeaponSword005"); break;
                case 6: sword = (GameObject)Resources.Load("Prefabs/WeaponSword006"); break;
                case 7: sword = (GameObject)Resources.Load("Prefabs/WeaponSword007"); break;
                case 8: sword = (GameObject)Resources.Load("Prefabs/WeaponSword008"); break;
                case 9: sword = (GameObject)Resources.Load("Prefabs/WeaponSword009"); break;
                case 10: sword = (GameObject)Resources.Load("Prefabs/WeaponSword010"); break;
            }

            Vector3 axis_x = new Vector3(1f, 0f, 0f);
            Quaternion q_x = Quaternion.AngleAxis(90f, axis_x);

            Vector3 axis_y = new Vector3(0f, 1f, 0f);
            Quaternion q_y = Quaternion.AngleAxis(90f, axis_y);

            look = Quaternion.LookRotation(aim, Vector3.up);
            look = look * q_x * q_y;

            Instantiate(sword, pos, look);
            Instantiate(mask_sword, pos, look);

        }
        
    }
}

ひとつずつ説明していきます。

aura_ground(宝具を射出するやつ)の生成

aura_ground(宝具を射出するやつ)の生成を行います。 このコードで行っている処理は、以下の通りです。

  1. playerの座標と、Random.Range()により、aura_groundを生成する座標を決定
  2. aura_groundの座標とtargetの座標の差と、Quaternion.LookRotation()によりtargetの方向を向くようにする
  3. aura_groundの座標を、targetがいる方向に1.1fずらす(宝具の先っぽの方で、aura_groundを生成するため)。
  4. aura_groundの生成
 //---- sword(宝具)を射出するやつの生成
//-- 1.
Vector3 pos = new Vector3(Random.Range(-20.0f, 20.0f) + player.transform.position.x, player.transform.position.y + Random.Range(2.0f, 12.0f), player.transform.position.z);
Vector3 pos_circle = pos;
//-- 2.
Vector3 aim = target.transform.position - pos_circle;
Quaternion look = Quaternion.LookRotation(aim);
 //-- 3.
pos_circle = pos_circle + (look * new Vector3(0f, 0f, 1.1f));
//-- 4.
Instantiate(sword_circle, pos_circle, look);

1. aura_groundを生成する座標を決定

aura_groundを生成する座標を決定します。Unityちゃんの周りに生成してほしいため、Unityちゃんの座標とRandom.Range()を使用します。これにより、Unityちゃんの座標が中心として、一定の範囲内にarua_groundが生成されるようになります。また、今回はZ座標は、Unityちゃんの座標のままにしているので、aura_groundは、XY平面上に生成されます。 イメージとしては、下図のような感じです。赤丸がUnityちゃんだとすると、赤四角の範囲内に、aura_groundが生成されます。

f:id:takuminv:20181217003216p:plain:w500

2. targetの方向を向くようにする

targetの方向を向かせるために、以下のサイトを参考にしました。

code.hildsoft.com

3. targetの方向に座標をずらす

Vector3型のベクトルにQuaternionを掛け合わせることで、ベクトルを回転させることができます。これにより、「現在の座標」に「target方向に向いた大きさ1.1fのベクトル」を足すことで、座標をずらしています。

4. aura_groundの生成

Instantiate()で生成しているだけです。

sword(宝具)の生成

sword(宝具)の生成を行います。 このコードで行っている処理は、以下の通りです。

  1. 生成するsword(宝具)をランダムで決める
  2. aura_groundと、Object自体の向いている方向?が異なっているため、補正
  3. sword(宝具)とmask_swordの生成
//---- sword(宝具)とmask_swordの生成
//-- 1. 
switch(Random.Range(0,11))
 {
    case 0: sword = (GameObject)Resources.Load("Prefabs/WeaponSword000"); break;
    case 1: sword = (GameObject)Resources.Load("Prefabs/WeaponSword001"); break;
    case 2: sword = (GameObject)Resources.Load("Prefabs/WeaponSword002"); break;
    case 3: sword = (GameObject)Resources.Load("Prefabs/WeaponSword003"); break;
    case 4: sword = (GameObject)Resources.Load("Prefabs/WeaponSword004"); break;
    case 5: sword = (GameObject)Resources.Load("Prefabs/WeaponSword005"); break;
    case 6: sword = (GameObject)Resources.Load("Prefabs/WeaponSword006"); break;
    case 7: sword = (GameObject)Resources.Load("Prefabs/WeaponSword007"); break;
    case 8: sword = (GameObject)Resources.Load("Prefabs/WeaponSword008"); break;
    case 9: sword = (GameObject)Resources.Load("Prefabs/WeaponSword009"); break;
    case 10: sword = (GameObject)Resources.Load("Prefabs/WeaponSword010"); break;
 }
//-- 2.
Vector3 axis_x = new Vector3(1f, 0f, 0f);
 Quaternion q_x = Quaternion.AngleAxis(90f, axis_x);

Vector3 axis_y = new Vector3(0f, 1f, 0f);
Quaternion q_y = Quaternion.AngleAxis(90f, axis_y);

 look = Quaternion.LookRotation(aim);
 look = look * q_x * q_y;

//-- 3.
Instantiate(sword, pos, look);
Instantiate(mask_sword, pos, look);

1. ランダムでsword(宝具)を生成

Random.Range()とswitch文により、生成するPrefabを変えています。でもこの書き方はちょっとダサい気がするので、何かいい方法を知っていれば教えてください。

2. 向きの補正

「王の財宝」を再現するためには、sword(宝具)とaura_groundが同じ方向を向いている必要があります。では、以下の2つの画像を見てください。この2つの画像では、2つとも同じ上方向を向かせているつもりなのですが、ObjectのRotationを確認すると、まったく違う方向を向いていることがわかります。そのため、ただ、targetの方向を見るだけでは不十分であり、向きの補正が必要なのです。

f:id:takuminv:20181217005629p:plain:w300f:id:takuminv:20181217005635p:plain:w300

4. 宝具の生成

sword(宝具)をInstantiate()するわけですが、このままでは、aura_groundの裏側にもsword(宝具)が表示されてしまいます。「王の財宝」を再現する場合、以下の画像のように、aura_groundの裏側になる部分は、隠れていないとダメなのです。

f:id:takuminv:20181217195247p:plain:w400

では、どのようにして、指定の部分のみを非表示にするのか。その答えが以下の記事にありました。 つまり、別のオブジェクトを重ねることで、見かけ上隠すということです。

nn-hokuson.hatenablog.com

今回は、swordより少し大きいCylinderをswordに重ねることで、隠すことに成功しました。

f:id:takuminv:20181217225523p:plain:w400

スクリプトの実行結果

実行してみると、かなりそれっぽくなっていることがわかりますね。個人的には大満足です。 f:id:takuminv:20181217014143p:plain:w500

感想

ほとんど初めてのUnityだったので、Prefabってなに?Assetってなに?からのスタートでした。一番わからないのは、Quaternionですね。定義やリファレンスを見てもワケワカメ状態でした(そもそもベクトルがわからん)。3Dゲームを作るとなると、数学の知識が必要になってくるらしいので勉強しないとまずいかなって感じです。

おわりに

とりあえず「王の財宝」の生成までの記事を書いてみました。次は、射出する話であったり、背景と地形(Unlimited Blade Works再現)の話などを書いていこうかなと思います。ではでは。