20200301のUnityに関する記事は12件です。

ScriptableObject をパス指定ではなく検索して取得する。

作成した ScriptableObject をファイルパス指定ではなく
Assetフォルダ下のどこにあっても問題無いように検索して取得したい。

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

// 右クリック Tools/Test で任意の場所に Test_ScriptableObject.asset ファイルを作成できる
[CreateAssetMenu(menuName = "Tools/Test")]
public class Test_ScriptableObject : ScriptableObject
{
    public int value;
}

この Test_ScriptableObject.asset を実際のコード上で取得する時

const string FilePath = "Assets/Test_ScriptabkeObject.asset";

public Test_ScriptableObject Load()
{
   return AssetDatabase.LoadAssetAtPath<Test_ScriptableObject>(FilePath);
}

このように AssetDatabase.LoadAssetAtPath を使用してScriptableObjectを取得できますが、 パスを指定しているのが気に食わない。

パスで決め打ちするよりファイルを検索した方が気持ちいい。

ということで、以下の形はどうでしょうか。

public Test_ScriptableObject Load()
{
    var guid = AssetDatabase.FindAssets("t:" + nameof(Test_ScriptableObject)).FirstOrDefault();
    var filePath = AssetDatabase.GUIDToAssetPath(guid);
    if (string.IsNullOrEmpty(filePath))
    {
        // 場合により Exception や
        throw new System.IO.FileNotFoundException("Test_ScriptableObject does not found");

        // Log 出して return null
        Debug.LogWarning("Oh...");
        return null;
    }

    var test = AssetDatabase.LoadAssetAtPath<Test_ScriptableObject>(filePath);

    return test;
}
AssetDatabase.FindAssets("t:" + nameof(Test_ScriptableObject))

t:オブジェクト名 で Assetフォルダ以下の指定オブジェクトをすべて取得。
今回はnameofでクラス名を取得。

すべて検索ということで、配列が返ってくるから FirstOrDefault() で一番最初に見つかったものを取得しています。
しかしここは「複数見つかった場合ログを出す」ほうが親切ですね。

以上になります。
簡単ですが、UnityEditorの触りで自分が迷った箇所を書いてみました。


いつでも読み込めるように関数定義しておいた方が便利かも

    void Hoge()
    {
        var instance = Load<Test_ScriptableObject>();
    }

    // 読み込みますよ
    public T Load<T>() where T : UnityEngine.Object
    {
        var guid = AssetDatabase.FindAssets("t:" + typeof(T).Name).FirstOrDefault();
        var filePath = AssetDatabase.GUIDToAssetPath(guid);
        if (string.IsNullOrEmpty(filePath))
        {
        }

        return AssetDatabase.LoadAssetAtPath<T>(filePath);
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Houdini to Unity備忘録

法線

primitiveとvertex(or point)の法線は独立しているので、normalノードで法線を反転させてもUnityに持って行っても面の表裏は反転せずにシェーディングに法線反転した法線が反映されるだけ。
Reverseノードで面の表裏を反転させられる。(pointやvertexではなくてprimitiveの法線を反転させられる。)
Houdini上でもvertexやpointの法線を整えたはずなのにシーンでの見え方がおかしい感じがする時もprimitiveの向きがおかしいのが原因だったりするのでReverseしておけばOK。

Material

1メッシュ内に複数のマテリアルを含んだ状態でモデルを出力したい場合はSOP内(geometryノード内)でmaterialノードをサブメッシュ毎に繋いでMaterialパラメータでそれぞれ適当にマテリアルを設定しておけば出力時にサブメッシュで分割される。
マテリアル自体は/mat/の位置にPrincipleShaderノードを置いておけばそれが前述のMaterialパラメータで選択出来る。

Tangent

Unityに持っていくと「このモデルはTangentとBinormalが無いよー」って警告が出る時がある。polyFrameノードでTangetは生成できるけどpointにTangent持たせても、vertexにTangent持たせても警告が出てくる。UnityのImportSettingsでimportやめて計算させて解決が無難そう。ようわからん。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity ミニマムなGPU Instancing

            #pragma multi_compile_instancing
            #include "UnityCG.cginc"
            #include "UnityInstancing.cginc"

            UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
            UNITY_INSTANCING_BUFFER_END(Props)
            struct appdata
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct v2f
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            v2f vert(appdata v)
            {
                v2f o;

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);// フラグメントシェーダーのインスタンス化したプロパティーにアクセスしたい場合にのみ必要

                 o.vertex = UnityObjectToClipPos(v.vertex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(i); // フラグメントシェーダーのインスタンス化したプロパティーがアクセスされる場合にのみ必要
                 return UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
            }


何となくの記憶で各記述の意味を説明すると、

UNITY_VERTEX_INPUT_INSTANCE_IDは実質uint instnaceID:SV_InstanceIDなので、直接instanceIDを扱いたい場合は書き換えて問題なさそう。

UNITY_SETUP_INSTANCE_ID(v);はモデル行列の配列からinstanceIDに対応するモデル行列をunity_ObjectToWorldに代入している。なのでUnityObjectToClipPos()とかモデル行列が関与する処理よりも前にこのマクロが来ないと意図していない挙動になるはず。

UNITY_TRANSFER_INSTANCE_ID(v, o);o.instanceID = v.instanceIDしているだけなのでここも書き換えて問題なさそう。

UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(Props)

は複雑なマクロで構造体を定義してる。UNITY_DEFINE_INSTANCED_PROP(○○, ××)の後ろに;を書くとエラーになる。やりがち。

UNITY_ACCESS_INSTANCED_PROP(Prop, ○○) = ...;みたいにプロパティアクセスを左辺に持ってくるとエラーが出る。これは言語とかGPUとか由来っぽい。そういう場面では一回変数に代入してから左辺値としてその変数を使えば良い。

もろもろの記述をちゃんと確認したい場合はUnityInstancing.cgincを読む。UnityCG.cgincも少し絡んでいたようんいなかったような。

あとはマテリアルのEnable GPU Instancingのチェックを付ければGPU Instancingが使える条件がそろっていれば同じマテリアルのオブジェクトがGPU Instancingされる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity 2019.3 と Oculus Integration で発生するメッシュの謎の点滅現象を解消する

なぜ起こっているのか切り分けまでできなかったので、タイトルは釣りの可能性があります m(_ _)m

点滅現象

なぜこうなるのか、何が原因でこうなるのかわかりませんが、あるメッシュが点滅するかのように、見え隠れします。
点滅.gif

とりあえずの解消方法

Plyaer Settings → Player → Others Settings の Compute Skinning(2018の頃は GPU Skinning という項目だった) の設定をオフにします。
Build_Settings_と_Donuts_-_HandTracking_-_Android_-_Unity_2019_3_0f6_Personal__Personal___Metal_.png

補足

  • Unity2019.3 + Oculus Integration で構築したからと言って 100% 起こる問題ではない
    • 新規プロジェクトでやってみたが、同じ現象に出くわさなかった
  • SkinnedMeshRenderer だからと言って問題になるわけではない。
    • Unity-chan の顔も同じメッシュ
  • 参考に記載した記事との関連を考えると、Android ビルドと関係があるのかも
    • 両眼用のカメラを配置している方が怪しい気もする。謎。

参考

Daydream+Unity で激しい点滅やらポリゴンメッシュ崩壊やら起こる件

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityを触ってみた

Qiita初投稿です…。
Unityでちょっとしたゲーム作ってみて、それとなく形になってきたので投稿してみます。
半分趣味でしたが、プログラムの勉強にもなりやってみて良かったです。
今回の投稿はUnity使った事無い人向けです。キャラの動かし方程度のレベルの話です。

「Unityでどうやってゲーム作るの?」「どうやってキャラが動いてるの?」

案外作るの難しくないですよーってのが伝われば良いかなと思います。

 

■Unityとは?

具体的な話に入る前に簡単にUnityについてお話します。
ゲーム制作ツールUnityではプログラム言語はC#を使用します。
C#でコードを記述したことはありませんでしたが、Java等と基本の文法は同じなのでJava等扱った事のある方は問題ないかと思います。
Unityはゲーム制作のツールなので、よくあるHPバーを作ったり、オブジェクトに重力をかけたり等もできます。
ほんとに出来る事が多く自分もちゃんとツールを把握しきれていません。笑

■ゲーム作るのって難しい?

ゲーム自体の難易度によりますが、オブジェクトの生成や移動方法が理解できれば、ある程度作れる気がします。
最初は紐づきが分からず苦戦しましたが、途中から想像通りに組み込めるようになってきました。

■どうやってキャラとか動かすの?

下記の流れでキャラを動かします。
1. C#スクリプト(C#コード)側でUnityに登録している画像を呼び出す
2. 生成したオブジェクトを配置する
3. 配置したオブジェクトを移動させる
4. ゆっくり動かすためにUpdate()メソッドを使用する

1. C#スクリプト側でUnityに登録している画像を呼び出す


◎下記のコードでオブジェクトを生成できます。
 GameObject original = (GameObject)Resources.Load("BlockPrefab") as GameObject;
 obj = Object.Instantiate(original) as GameObject;
 これは、ツール内に登録した画像(BlockPrefab)を読み取ってオブジェクト化しているコードです。

2. 生成したオブジェクトを配置します

◎下記のコードでオブジェクトの配置ができます。
 obj.transform.position = new Vector3(1, 1, 1);
 Unityにおいて、Vectorクラスという概念は重要です。 Vectorとはベクトル(方向)のことです。
 ざっくり言えば上記のコードは中心(0,0,0)から見た座標(1,1,1)に配置するということです。
 ※座標(x,y,z)です

3. 配置したオブジェクトを移動させる

◎下記のコードでオブジェクトを移動させることができます。
 obj.transform.Translate(0.1f, 0, 0);
 Translateというメソッドがあるみたいです。
 配置したオブジェクトの位置から指定した値だけ移動します。
 ※このコードは、配置のメソッドなので瞬間移動します。
  キャラがゆっくり動く原理については後述します。

4. ゆっくり動かすためにUpdate()メソッドを使用する

図5.PNG

◎Update()メソッドについて
 実はUnityでスクリプトを生成した場合、Start(){}とUpdate(){}がデフォルトで付いています。
 ツールでゲームを再生すると、一度Start(){}を読み込み、それからは常に繰り返しUpdate()の中の
 処理を読み込みんでいます

◎使用例
 例えば、こうやって使えます。
 Update(){
 obj.transform.Translate(0.1f, 0, 0); ←これが何回も読み込まれる
 }
◎上記のコードの結果
 obj.transform.Translate(0.1f, 0, 0);では0.1fだけ右に配置されるのでこれをUpdate()が繰り返す
 ので、徐々に右に移動しているように見える、という仕組みです。
 「小さな瞬間移動を繰り返し行うことによってゆっくり動いているように見せる」ってカンジです。
 
 
何となく簡単に作れそうってイメージ付きそうですかね…?
まあ触ってみないと分からないこと多いですよね。笑

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity SceneViewのカメラ操作感変更

Unity2018?系からSceneViewのToolbarからカメラ操作感を変えられるようになりました。
プロジェクトごとに最適なSceneカメラのスピードは違うので適宜変更すると良きです。

スクリーンショット 2020-03-01 18.28.21.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】【 Androidビルド向け環境構築】

さて、本日はUnityでARを試してみたい。
やりたいことは、Androidデバイスのカメラ機能に、指定の経度緯度にでっかい3Dモデルを出現させるというものだ。
既に先駆者がいるがそれはIPhonseだった。
【ARKit】GPSで特定の場所にARを表示する

ちなみに似た様なことをAndroidでやっている記事はあったので、この2つの記事を参考にしつつ、そのやりたいことを実現しようと思う。
UnityでARアプリの作り方1/4 環境構築+androidビルドからカメラ表示

そもそも僕はUnityでAndroid向けの開発はやったことないから、
まずは上記記事を参考にして環境構築から始める。

環境

Windows10
メモリ8G
SSD
Unity 2019.3.3f1

レッツゴー

まずはAndroid Studioを公式サイトからダウンロードして
ひたすらデフォルト的なインストールする。
https://developer.android.com/studio/install?hl=ja

次に、Unity Hub でAndroidビルド用のモジュールを追加する
image.png

そして、新たなUnity Projectを新規作成して(ターゲットはAndroidにして)
image.png

それを開く。
Edit > Preference > External Tools からSDKの位置を確認すると何やら警告が出ている。
image.png

警告メッセージ↓↓
You are missing the recommended JDK. Install the recommended verion using Unity Hub
You are missing the recommended SDK. Install the recommended verion using Unity Hub
You are missing the recommended NDK. Install the recommended verion using Unity Hub

あれ、、さっきのモジュール追加で入れたんちゃうんかと思って、このメッセージでぐぐると、
https://yanpen.net/unity/install_android-sdk-ndk_from_unity_hub/ こちらの記事がでてきた。
モジュール追加のときにトグル開いて、そのなかでSDKとかインストールする項目があったのね。
それ見落としててチェックし損なってた。

というわけで、
これを
image.png

こーして、再度インストール
image.png

少しインストールに時間かけてる間にポットでお湯を沸かして生姜湯を作成し一息つく(ふ~。)

インストールが完了したら先ほど作ったUnity Projectを再度開き、また
Edit > Preference > External Tools を開く。
諸々の警告は下記の様に消えている。
image.png

ほな、ビルドしてみましょう。
image.png

Build And Run を選択して、出力先フォルダとファイル名を指定する。
そして少し待たされる。

そして、無事PCにUSB接続したAndroid端末で作成されたapkファイルが実行された。
image.png

ひとまずOKというところ。
とくにつまづかずめだたしめでたし。

次号では、もう少し形にしたARアプリの記事を書く。
ではでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで動画を再生する方法(備忘録)

Unityにおいて動画を再生する方法

今回の目的は、2Dにおいて一画面に複数の動画を再生するということを目的としている。

手順

  1. HierarchyからPlaneを作成する。
  2. PlaneのInspectorにおいてVideoPlayerのcomponentの追加を行う。 コメント 2020-03-01 144508.png
  3. Video Clipに目的の動画を追加する。
  4. AssetsにRender Textureの作成を行う。 コメント 2020-03-01 144723.png
  5. HierarchyにおいてRawImageの作成を行い、RawImageのHierarchyのTextureに作成したRender Textureを追加する。
    コメント 2020-03-01 145505.png

  6. PanelにおけるHierarchyのVideoPlayerのTarget Textureを4で作成したRender Textureを追加する。
    コメント 2020-03-01 150315.png

これによって動画を再生することが出来るようになる。

音声の追加する方法

PlaneにAudio Sourceのコンポーネントを追加して以下のようにVideoPlayerのコンポーネントに追加する。
コメント 2020-03-02 143633.png

参考にしたURL
https://gametukurikata.com/basic/videoplayer
https://note.com/yukyu/n/ncdf99123eebd

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityの継承周りの苦痛をどうにかしたい

エネミーを実装しよう

image.png

なんてことはない、ごく普通のエネミーコンポーネントである。

すごいエネミーは移動する

image.png

普通に実装すれば、恐らくはこうなる。

public class SugoiiEnemy : MonoBehaviour {
  private void Update() {
    // 移動処理
  }
}

エネミーは動いたよ、やったね!

エネミーは体力が0を下回ると死亡する

苦痛の始まりである。

image.png

簡単だ、さっそく実装しよう。

public class Enemy : MonoBehaviour {
  public int hp = 10;
  private void Update() {
    if(hp <= 0) Destroy(gameObject);
  }
}

あれ? Enemy死なないよ?
おっと、間違えた。
SugoiiEnemyでもUpdateを実装しているから、こうでなくては。

public class Enemy : MonoBehaviour {
  public int hp = 10;
  protected virtual void Update() {
    if(hp <= 0) Destroy(gameObject);
  }
}

あれ? これでも死なない?
あ、SugoiiEnemyの方もも変えなくては。

public class SugoiiEnemy : MonoBehaviour {
  protected override void Update() {
    base.Update();
    // 移動処理
  }
}

苦痛だ。

苦痛だ

例はよくないかもしれないが、ニュアンスは伝わったはずだ。
今回はEnemyの派生先クラスがSugoiiEnemy 1つだったから、これだけで勘弁してもらえたものの、気合入れて20個作った、みたいな場合だともう目も当てられない。
継承のネストがもっと深い場合は、もう寝てよし。

三つ子問題

見返してみると、私のコードには3種類のUpdateがあった。

// 継承しておらず、かつ継承されない場合
private void Update(){}
// 継承される場合
protected virtual void Update(){}
// 継承する場合
protected override void Update(){base.Update()}

ギャグか? ギャグなのか?
何かしらの県の条例に違反してるとしか思えない所業である。

この3つのUpdateを状況に応じて使い分けるのがプログラマーの仕事であるのなら、私は今すぐプログラマーをやめたい。

なぜこんな問題が起こっているのか

MonoBehaviourにprotected virtual void Update(){}のようなコードがないからだ。
MonoBehaviourにprotected virtual void Update(){}のようなコードがないから、我々は3種類のUpdateを使い分けるなどという愚行ができてしまうのだ。

解決せねば。
解決するか、Unityをやめるかの二択だ。

解決法 全部定義してしまう

ないなら足せばいい。
<ProjectName>MonoBehaviourみたいなクラスを作って、MonoBehaviourの代わりに継承すればいい。

    public class ProjectNameMonoBehaviour : MonoBehaviour {
        protected virtual void Reset() {}
        protected virtual void Awake() {}
        protected virtual void OnEnable() {}
        protected virtual void Start() {}
        protected virtual void FixedUpdate() {}
        protected virtual void OnTriggerEnter(Collider other) {}
        protected virtual void OnTriggerEnter2D(Collider2D other) {}
        protected virtual void OnTriggerStay(Collider other) {}
        protected virtual void OnTriggerStay2D(Collider2D other) {}
        protected virtual void OnTriggerExit(Collider other) {}
        protected virtual void OnTriggerExit2D(Collider2D other) {}
        protected virtual void OnCollisionEnter(Collision other) {}
        protected virtual void OnCollisionEnter2D(Collision2D other) {}
        protected virtual void OnCollisionStay(Collision other) {}
        protected virtual void OnCollisionStay2D(Collision2D other) {}
        protected virtual void OnCollisionExit(Collision other) {}
        protected virtual void OnCollisionExit2D(Collision2D other) {}
        protected virtual void OnMouseEnter() {}
        protected virtual void OnMouseOver() {}
        protected virtual void OnMouseUp() {}
        protected virtual void OnMouseDrag() {}
        protected virtual void OnMouseDown() {}
        protected virtual void OnMouseUpAsButton() {}
        protected virtual void OnMouseExit() {}
        protected virtual void Update() {}
        protected virtual void LateUpdate() {}
        protected virtual void OnWillRenderObject() {}
        protected virtual void OnPreCull() {}
        protected virtual void OnBecameVisible() {}
        protected virtual void OnBecameInvisible() {}
        protected virtual void OnPreRender() {}
        protected virtual void OnRenderObject() {}
        protected virtual void OnPostRender() {}
        protected virtual void OnRenderImage(RenderTexture src, RenderTexture dest) {}
        protected virtual void OnDrawGizmos() {}
        protected virtual void OnGUI() {}
        protected virtual void OnApplicationPause(bool pauseStatus) {}
        protected virtual void OnDisable() {}
        protected virtual void OnDestroy() {}
        protected virtual void OnApplicationQuit() {}
        protected virtual void OnApplicationFocus(bool focusStatus) {}
    }

イベント関数の実行順を参考に列挙した。
これでコードは常にprotected override void XXX(){base.XXX();}に統一される。
詳しくは知らないが、パフォーマンスはやばそうである。

軽減法 コンポーネント指向

そもそも、Enemyを継承して別タイプのEnemyを作る、という手法自体が時代遅れなものだ(たぶんな)。
もっとスマートなやり方として、

  • ステータスコンポーネント
  • 死亡コンポーネント
  • 移動コンポーネント
  • 上記のコンポーネントを制御する頭脳コンポーネント(Enemyコンポーネント)

という三つのコンポーネントを作り、SugoiiEnemyというプレファブにつける、という手法がある。
この手法のメリットは、継承では対処が困難な、

  1. 歩行エネミー
  2. 飛行エネミー
  3. 歩行+飛行エネミー
  4. 不死身の飛行エネミー

のような、変則的な実装にもスマートに対応できるところだ。

ただし、各コンポーネントの設計が従来のやり方とは少し異なるため、慣れるまでは面倒に思うかもしれない。
責務とか責任とかをクラスごとにきっちり分けたい、イカしたエンジニアはこうしているはずだ。

このやり方であれば、少なくとも今回のような単純な場合においては、三つ子問題は起きない。
コンポーネントを継承して新しいコンポーネントを実装するときには起きるが、今までのやり方ほど影響範囲は大きくはないだろう。
しかし起きる可能性がある以上、問題は解決したとは言えない。

たせけて

本当はイカした方法があって、私が知らないだけだということを切に願う。
偉い人、愚かな私をたせけてください。

P.S. base.XXX();書くのしんどいです、自動で最初に呼び出してくれるイケメン機能はないですか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityでシューティングゲームを作る(6)

ここまでの進捗

  • 背景がループするようにした。
  • 普通の敵の動作を作成し、その敵が3秒ごとに生成される。
  • 瞬間移動する敵の動作を作成し、5秒ごとに生成する。
  • プレイヤーが画面の範囲外に行かないようにした。
  • 敵とプレイヤーが衝突したらプレイヤーが消滅する。
  • 分散攻撃の敵を実装する。
  • タイトルシーンとエンディングシーンを追加する。
  • プレイヤーがダメージ受けると点滅して数秒だけ無敵状態になる
  • プレイヤーが横に移動すると機体が傾くアニメーションをつける
  • エフェクトとBGMを追加する。

今後やること

  • ボスキャラの動作を実装する。

この記事では赤い部分を作成した。
いよいよボスの実装に入る

難易度Extremeの内容

それぞれの敵キャラを強化したものをボスにする
画面の一番上にボスの体力バーを表示する

画面の上部にボスの体力バーを設置した

これを参考にして作ったので今回は説明を少し端折る
qiita/SliderでHPバーを作る

コメント 2020-02-22 125026.png

ヒエラルキーで右クリックして[UI]→[Slider]を選択する
コメント 2020-02-22 125522.png

SliderのHandle Slide Areaはsliderのつまみの部分なので消す
コメント 2020-02-22 130128.png

sliderオブジェクトのInteractableはバーの長さをユーザーが変更できてしまうのでチェックを外す
Fill AreaとFillのRect transformの値をすべて0にする

Slider HpBar;

void Start() {
        BossMaxHp = 50;
        HpBar = hp.GetComponent<Slider>();
        HpBar.maxValue = BossMaxHp;
        HpBar.value = BossMaxHp;
    }

これによってSliderでボスのHPを設定して、ボスのOnOnTriggerEnter2D内でHpBar.valueの値を減らすことで体力が減ることを表す

構成

             ボスA  

        コメント 2020-02-15 021721.png

              ↓
       ボスB(ノーマルタイプのボス)

コメント 2020-02-15 021656.png
               ↓
         ボスC(テレポートのボス)
コメント 2020-02-15 021739.png
               ↓
         ボスD(分散攻撃のボス)
      コメント 2020-02-15 021757.png
              ↓
             ラスボス  
        コメント 2020-02-15 021721.png

すべてのボスの移動は画面上部をランダムに移動して、攻撃するときだけ止まる。
ボスはプレイヤーの弾を受けたら、攻撃をうけたことが分かるように点滅するようにする

ボスA

ここのボスはまだ簡単な攻撃を行うようにする
[攻撃1]ランダムの方向に攻撃する
[攻撃2]正面に太いビームを撃つ

ボスB

[攻撃1]分身を出してそれぞれの分身は固定の位置で攻撃してくる
[攻撃2]追尾してくる弾を撃つ

ボスC

最初は攻撃が通らず、2つのオブジェクトを破壊しないと攻撃が通らない
オブジェクトが破壊されるまでは基本的に瞬間移動してからプレイヤーに向けた弾を撃つ
[攻撃1]瞬間移動した後にプレイヤーに向けた弾を撃つ(5回くらい)
[攻撃2]5回程度瞬間移動して弾を置いていき、移動し終わったらプレイヤー方向に弾を飛ばす
[攻撃3]瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく

ボスD

[攻撃1]分散攻撃を連続でやってくる
[攻撃2]全方位にいくつもの弾幕(らせん状 or 等間隔)をはる(言葉で説明しずらい)
[攻撃3]弾が2段階くらい分裂する

ラスボス

ラスボスに入る前にライフを全快にして画面が赤く点滅する
ボスAの攻撃を進化させた以下の攻撃と今までのボスの攻撃をいくつかやってくる
[攻撃1]正面にビームを撃つ & ランダムの方向に攻撃する
[攻撃2]分身を出してそれぞれの分身は固定の位置で攻撃してくる
[攻撃3]瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく
[攻撃4]全方位の弾幕を2つ出す
[攻撃5]瞬間移動で弾を置いていき、移動し終わったらプレイヤーに向けた弾を撃ち、弾も途中で分裂する

ボスA

ランダムの方向に攻撃する

分散攻撃を利用する
分散の数を5にしてランダムでその5方向から1方向選んで発射する

public void Attack6(Transform bossA, int NwayCount) {

        float r = Random.Range(1, NwayCount + 1);
        float angle = -(NwayCount + 1) * 5 / 2 + r * 5;
        Instantiate(BossProjectilePrefab, bossA.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle)));

    }

ほとんど分散攻撃のプログラムと変わらない
引数に分散の数とボスのtransformをいれて、ランダムの方向を決めて発射する

ezgif.com-video-to-gif (6).gif
やりたかった動作になってくれた

正面にビームを撃つ

ビームのスプライトはないので自分で以下のように作成した。
コメント 2020-02-18 153438.png

ボスの撃つ弾から四角く切り抜いた
これをある程度伸ばしてビームのスプライトとした

public void Attack7(Transform bossA) {

        Instantiate(BeamProjectilePrefab, new Vector3(bossA.transform.position.x - 0.32f, bossA.transform.position.y, 0), bossA.rotation);

        Instantiate(BeamProjectilePrefab, new Vector3(bossA.transform.position.x + 0.33f, bossA.transform.position.y, 0), bossA.rotation);

        Instantiate(BossBeamPrefab, new Vector3(bossA.transform.position.x + 0.03f, bossA.transform.position.y - 3.0f, 0), bossA.rotation);

    }

あまり難しいことはなく、プレファブを出現させているだけ

ezgif.com-video-to-gif (7).gif

ちょっとしょぼいけどこれもちゃんと実装できてよかった

行動パターン

(移動×3回 → 通常攻撃) × 5回 → 乱数で攻撃1か攻撃2のどちらかを選択し攻撃する

これを繰り返す

ボスB

分身を出してそれぞれの分身は固定の位置で攻撃してくる

if (c == 0) {
            //3つの分身を出す
            for (int i = 0; i < 3; i++) {
                float rand_x = Random.Range(-3.0f, 3.0f);
                float rand_y = Random.Range(0.0f, 4.5f);
                Instantiate(AvatarPrefab, new Vector2(rand_x, rand_y), Quaternion.identity);
            }
            c = 1;
        }else if (c == 1) {
            //分身が全滅するまで本体はランダムの方向に弾を撃つ
            if (GameObject.FindGameObjectsWithTag("Avatar").Length > 0) {
                float r = Random.Range(1, 6);
                float angle = -15 + r * 5;
                Instantiate(BossProjectilePrefab, bossA.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle)));
                AudioSource.PlayClipAtPoint(BossShotSE, transform.position);
            } else if (GameObject.FindGameObjectsWithTag("Avatar").Length == 0) {
                AttackFlag = 0;
                c = 0;
            }
        }

上がプログラムとなり、変数cによって分身を発生させる処理を1回だけ行うようにしてあとは分身は生成された場所で弾を撃ち続け、本体はランダムの方向に弾を撃つようにする

ezgif.com-video-to-gif (9).gif

追尾してくる弾を撃つ

発射してから1.0秒間だけプレイヤーに向けて進む弾
ボスからプレイヤーへのベクトル = ボスBのposition - プレイヤーのposition
これを正規化したものを6倍することによって速度を保っている

public void Track(Transform t) {
        Vector2 vec = player.transform.position - t.position;
        rb.velocity = vec.normalized*6;
    }

ezgif.com-video-to-gif (8).gif

簡単によけられてしまうかもしれないけどできた!

行動パターン

基本的にはボスAと同じ
(移動×3回 → 通常攻撃) × 5回 → 乱数で攻撃1か攻撃2のどちらかを選択し攻撃する

ボスC

ezgif.com-video-to-gif (13).gif

ボスCの特徴として2つのシールドを破壊しないと本体には攻撃が通らないようになっている
シールドは2つ破壊されてから60秒経つと復活する

瞬間移動した後にプレイヤーに向けた弾を撃つ(5回くらい)

瞬間移動はtransform.Translateで移動する
(瞬間移動 → 攻撃) ×5 → 0.5秒静止

ここはあまりかからなかった

ezgif.com-video-to-gif (10).gif

この動画だと瞬間移動しないけど本来ちゃんとする

5回程度瞬間移動して弾を置いていき、移動し終わったらプレイヤー方向に弾を飛ばす

弾を置いたらプレイヤーの向きになるようにする

瞬間移動は上の攻撃1と同じ
移動先に弾を設置して1.0秒経ったらそれぞれの弾がプレイヤーに向かって飛んでいく

speed=3.0f;
//プレイヤーまでの方向ベクトルを取得
                Vector3 dir = player.transform.position - transform.position;
                //角度を取得
                float angle = Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
                Vector3 euler = new Vector3(0, 0, angle + angleOffset);
                transform.rotation = Quaternion.Euler(euler);
                rb.velocity = dir.normalized * speed;

上は現在地からプレイヤーまでの向きを求めてその向きに進むスクリプト
Mathf.Atan2で現在地からプレイヤーの位置までの角度をラジアンで表している
Mathf.Rad2Degはラジアンから度に変換している
角度の表し方は右を0度として半時計周りに増えていく、追跡する弾は最初真下を向いているので90度プラスすることで基準の方向を0度の方向にする必要がある
ここの説明は以下を参照したほうが分かりやすい
Hatena Blog/あるオブジェクトを指定の方向に向ける

rotationは度ではなくてQuaternionという構造で表されているのでVector3のeulerをQuaternion.Eulerで変換することで向きを変える

ezgif.com-video-to-gif (11).gif

このようになった

瞬間移動で爆弾を置いていき爆弾が爆発すると3方向に弾が飛んでいく

瞬間移動と爆弾を置く操作は上の攻撃と同じ
置かれた爆弾は1.0秒後に3方向に分散する弾を出す

ezgif.com-video-to-gif (12).gif

行動パターン

(瞬間移動 → 通常攻撃) × (2つのオブジェクトが壊れるまで) → 乱数で攻撃1か攻撃2か攻撃3のどちらかを選択し攻撃する → 時間経過(60秒)したら2つのオブジェクトが復活してループ

ボスD

分散攻撃を連続でやってくる

分散攻撃は以前のものを利用するが、分散の中央の弾がプレイヤーの方向になるようにする
分散の数はランダムで3か5になるようにした
ezgif.com-video-to-gif (15).gif

らせん状に弾が飛んでいく

この攻撃も分散攻撃を改良していく
分散数を300などにしてそれぞれの弾の間隔も小さくして、次の弾を発射するまでの待機時間を0.02秒にした
これを実装するためにこの攻撃は関数ではなくてコルーチンを用いた

//らせん状に弾が飛んでいく
    IEnumerator Attack14(int c) {

        for(int i = 1; i < c; i++) {
            float angle = -(c + 1) * 10 / 2 + i * 10;
            NwayShot(angle);
            yield return new WaitForSeconds(0.02f);
        }
        AttackFlag = 0;
    }

ezgif.com-video-to-gif (14).gif

それっぽくなって結構感動した
てか分散攻撃が汎用性高すぎて驚いてる

弾を発射したら数秒後に2段階弾が分裂する

弾を発射した後に少し時間経過したら分裂させたかったのでコルーチンを採用した
Instantiateで作成したインスタンスを変数として保持しておき、waitForSecondsで待機させた後に分裂させた
分裂先の方向も7方向からランダムにすることで分裂できる範囲を広くさせた

//弾を発射したら数秒後に2段階分裂する
    IEnumerator Attack15(int NwayCount) {

        //最初の弾(p1として保持)
        float r1 = Random.Range(1, NwayCount + 1);
        float angle1 = -(NwayCount + 1) * 5 / 2 + r1 * 5;
        GameObject p = Instantiate(BossProjectilePrefab_slow2, transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle1)));
        AudioSource.PlayClipAtPoint(BossShotSE, transform.position);

        yield return new WaitForSeconds(0.2f);

        //最初の弾からの1段階の分裂(分裂した弾をp2として保持)
        float r2 = Random.Range(1, NwayCount + 1);
        float angle2 = -(NwayCount + 1) * 5 / 2 + r2 * 5;
        GameObject p2=Instantiate(BossProjectilePrefab_slow2, p.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle2)));
        AudioSource.PlayClipAtPoint(BossShotSE, transform.position);

        yield return new WaitForSeconds(0.5f);

        //1段階で分裂したそれぞれの弾が分裂(p1とp2からそれぞれ分裂)
        float r3 = Random.Range(1, NwayCount + 1);
        float angle3 = -(NwayCount + 1) * 5 / 2 + r3 * 5;
        Instantiate(BossProjectilePrefab_slow2, p.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle3)));
        AudioSource.PlayClipAtPoint(BossShotSE, transform.position);

        float r4 = Random.Range(1, NwayCount + 1);
        float angle4 = -(NwayCount + 1) * 5 / 2 + r4 * 5;
        Instantiate(BossProjectilePrefab_slow2, p2.transform.position, Quaternion.Euler(new Vector3(0.0f, 0.0f, angle4)));
        AudioSource.PlayClipAtPoint(BossShotSE, transform.position);

    }

ezgif.com-video-to-gif (16).gif

行動パターン

(移動×3回 → 通常攻撃) ×  → 乱数で攻撃1か攻撃2か攻撃3のどちらかを選択し攻撃する 

ラスボス

今までのボスの攻撃を組み合わせたりして改良したものが多いので、説明を省くものが多い

正面にビームを撃つ & ランダムの方向に攻撃する

ビームはボスAのビームを利用して、同時にランダムの方向への攻撃をするようにした
これは既存のものを組み合わせただけなので説明は省く

分身を出してそれぞれの分身は固定の位置で攻撃してくる

これもボスBの攻撃において分身の数を4体にしただけ

瞬間移動で弾を置いていき、少し経つとプレイヤーに向けて弾が飛んでいく

ボスCの攻撃2を利用して画面の右側と左側に弾を設置して時間が経つと、それぞれの弾がプレイヤーの方向に向かっていき途中で分裂する
弾が分裂するのもボスDの攻撃3を用いて、発射された弾に対してそれぞれ適用した

全方位の弾幕を2つ出す

ボスDのらせん状の弾幕を2つに増やした

瞬間移動で爆弾を置いていき、移動し終わったら弾を撃ち、弾も途中で分裂する

ボスCの攻撃3を利用して画面の左右に爆弾を置き、それぞれから弾を発射したときにその弾に対してボスDの攻撃3の弾が分裂する動作を適用した

これですべての実装が終わった
このゲームが最初に作ったゲームになるが、最初とはいえ2Dのゲームでもこんなに作るのが大変だとは思わなかった
次は3Dのゲームで何か作れればと思っている

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

パチンコの保留動作を再現する

パチンコの保留を再現したい。

保留の動作をいざ再現したいと思ったけど、
意外と再現するのに苦戦したので紹介。
以下を再現
horyu.gif

フローチャート

まず、プログラムを考えるときに必要なフローを考えてみた。
アニメーションなどは考えず表示、非表示で保留の動きを考えてみる。
後のことを考えて配列で保留を管理します。

  • 例: 保留が0と等しいときは保留がない状態。
  • 保留が1のときは白保留。
  • 保留が2のときは青保留
  • 保留が3のときは赤保留
  • etc キャプチャ3fffffffffff3.PNG

これにアニメーションを追加すると完成。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

photonnetwork.instantiateでgamepobject型を生成してからステータスや値を参照する方法

主に前回の記事で起きたことの修正です。
前回↓
https://qiita.com/wttk05/items/5f8cef73a5aa5ec3e06a

では本題です。(タイトルがまとまってなくてすいません)

まず前回のIllegal view ID:0ですがphotonnetwork.instantiateを使わないと出るエラーらしいです。逃れられないのでphotonnetwork.instantiateを使用した後値を参照することにしました。

GameManager.cs
    void CreateCardPrehub(Transform transform,int cardID)
    {
        // Prehubを生成
        playerInfo = PhotonNetwork.Instantiate("Cards", new Vector3(100, 100, 1), Quaternion.identity,0);// Photonオブジェクト生成
        playerInfo.transform.SetParent(playerFieldTransform.transform); // 親を設定
        playerInfo.transform.localScale = new Vector3(1.5f, 1.5f, 1.5f); // カードのサイズを設定
        playerInfo.transform.position = new Vector3(510, 270,0);// カードの位置を設定

        //CardControllerを参照してInitを実行
        playerInfo.GetComponentInParent<CardController>().Init(cardID); 
    }

SnapCrab_NoName_2020-3-1_2-4-2_No-00.jpg

CardViewでの値はinstantiaceするオブジェクトに付いているテキストやImage等をアタッチしています。

カードの位置の設定等はもっといい方法があるかもしれませんが今の自分では無理だったので裏で動く脳みそに任せて
成功を祝おうと思います。

GameManager.cs
CreateCardPrehub(playerPrehab.transform,1);

これ1行で幸せになれる。

まだ色々適当なので少しずつソースコードを壊れない程度に整理しつつ、カードゲームをオンライン化していこうと思います。
ちなみにコード一つ一つ解説するより大まかにサンプルを上げたほうが他人から見ても自分から見てもわかりやすいと思いってるので
このスタイルを続けます?

今回はこの辺で終わります。
アドバイスやコメントくださってる方本当にありがとうございます。
Unityしてる友達がいないのでとても助かってます。

Twtter@wttk05

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む