パスワードを忘れた? アカウント作成
14328669 journal
日記

yumeの日記: Unity制作 メデューサ・ゲーム #2 1

日記 by yume

●補習
昨日のコレだが:

public int playerLayer = 9;
int layerMask = ~(1 << playerLayer); //Exclude layer 9

++C++;曰く、

・<<は左シフト(これはまぁ覚えてた)
・~は補数
例えば二進数000111の0と1を逆転し、111000にする。

で、例えば

int D (1 << 5) //32
int rD = ~D //-33

となるようだ。ここではintを使っているが、bitに直すと
0b11111111111111111111111111011111
になり、符号付10進数であるintは、これを-33と読むらしい。
たぶんUnityのLayerMaskのとらえ方はすごく賢くて、intで与えられた数字を二進数に読み替えている(厳密にいえば、機械はそもそも二進数で読んでいるんだろうけど)んだろう。
だから、0b11111111111111111111111111011111は「5番目だけが0、ほかは1」、つまりレイヤーマスク5以外すべてOKとして扱えるようだ。

--

●メデューサ・ゲーム

石化の続き
石化について実装したい機能をまとめて考える
・石化進行中は視覚的にそれがわかるようにしたい
・メデューサに見られていない間は、石化の進行が止まるのではなく、徐々に元に戻していきたい。
うーむ、現状では直接GetEvilSeeingメソッドを見られるたびに実行するが、そうではなく、見られているときは「邪視状態」をTrueにして、
邪視状態中は徐々に石化が進行する、ということにしようか。

private void CheckEvilSeeing()
{
        if (evilSeeing)
        {
                evilDamage += Time.deltaTime;
                if (evilDamage > resistPower)
                {
                        evilDamage = 0.0f;
                        Debug.Log("石化しちゃった!");
                }
                evilSeeing = false;
        }
        else
        {
                evilDamage = Mathf.Min(0.0f, evilDamage - Time.deltaTime);
        }
        evilSeeing = false;
}

このメソッドをFixedUpdateメソッドで実行すれば、毎Fixedフレームごとに、evilSeeing状態ならば石化ダメージを負い、evilSeeingでないなら石化ダメージを徐々に回復する。
フレームの最後で状態をfalseにしておけば、「メデューサに見られたフレーム」だけevilSeeing状態になるはず。

evilDamageに応じて、一旦スプライトの色を青色に変えていこう。evilDamageが0なら正常な色。

そんで、実際の石化処理を行う。
これは石化したSoldierをDestroyするか、「石化状態」というべき状態に切り替えてしまえばよさそうだが、どっちがよいだろう。
Destroyする場合:
・メデューサ側でStart時にキャラクターの数を数え、それを配列にしているが、この配列が可変にならなければならないので、Listにする必要がある。
・ListからDestroyされるタイミングで削除する。
・Destroy後に、自分の石化した状態のSpriteだけを持つGameObjectをそこに置く。

Destroyしない場合:
・自分の状態を「石化状態」にする。
・様々なコンポーネント(RigidBody2Dとか)を無効化する。
・Spriteを「石化状態」に変える。

ん~。後々Soldierが後から増えたりする場合は、どちらにせよリスト化する必要はでてくるし、「様々なコンポーネントを無効化」が後々大変そうなので、
ここはDestroy + Listから削除 + 死体オブジェクトを残す方式にしよう。

メデューサが持つcharactersリストには、個々のGameObject情報が入っている。そこにはインスタンスIDもある。
Soldierが破壊される直前に、メデューサのリストから自分のインスタンスIDを削除するよう命令しよう。
ただ、それをFixedUpdate中にやるといろいろややこしそうだ。
公式曰く、
どうやらイベントの中には「OnDestroy」なるものがある。ここでもろもろの処理をすればよいだろう。

で、リストから自分のインタンスIDに合致するものを削除する、だが
リスト自体はGameObject型のリストで、GameObject型からインスタンスIDをGetする関数はあるものの、
直接リストからRemoveするメソッドがないかな。Remove()はGameObjectそのものを指定しなきゃならないし。

public void DeleteFromList(int instanceID)
{
        for (int i = 0; i < characters.Count; i++)
        {
                int checkID = characters[i].GetInstanceID();
                if (checkID == instanceID)
                {
                        characters.RemoveAt(i);
                        return;
                }
        }
}

で、これを石化時にSoldier側から呼び出す。呼び出す場合はまずメデューサを名前で検索してから……。
なんだか回りくどいな。例えばGameManagerみたいなオブジェクトがいて、すべてのCharacterのリストはそいつが持ってて、すべてのキャラクターはそいつへの参照をもって生まれたらもうちょいスマートかな。でもとりあえずこれでいこう。

これで動くかな?と思ったがダメ。エラー:
NullReferenceException: Object reference not set to an instance of an object
EvilEye.EvilEyeSeeing () (at Assets/Script/EvilEye.cs:38)
リスト削除メソッドの起動を、Destory時ではなくFixedUpdate中に移すとうまくいった。
さてなぜだろう。
OnDestroyでの処理の流れは:
・FixedUpdate(Player):Soldier[i]のevilSeeingをTrueに。
・FixedUpdate(Soldier[i]):evilSeeingがTrueなので、石化ダメージを追加。
・同:石化ダメージが抵抗力を超えたので、SoldierをDestroy
・OnDestroy(Soldier[i]):
・デバッグログを流し
・メデューサの名前で検索
・メデューサのリストを含むスクリプトを取得
・スクリプトにDeletFromListを実行させる

ところが、OnDestroyのデバッグログが流れる直前にエラーがでる。

ん~。例えば、あるフレームにおいて、SoldierのDestroyが実行された後に、PlayerのFixedUpdateが実行されて、そのあとにSoldierのOnDestroyが実行される、という順序になった場合、Player側では「すでにDestroyされたオブジェクトを探す」ということをやろうとしてしまうか。
一方、OnDestroyを使わない場合は
SoldierのFixedUpdateでリストからの削除も同時にされ、そのあとPlayerのFixedUpdateが走ってもすでにリストにはないので、問題はない。
逆に、PlayerのFixedUpdateで検索した後に、Soldier側から削除命令が出ても、やはり問題はない……か。
なら順序を問わない方法がいいか。

それで、OnDestroy時にStatueオブジェクトを生成する。
Qiita @Teach曰く、
・Instantiate(obj, this.transform.position, Quaternion.identity); みたいな感じでその場に生成できる。

生成はできたが、Unityになんか怒られる。

テラシュールブログ曰く、
・OnDestroy中にInstatiateするとゴミが残る。
・シーン停止時に、すべてのオブジェクトでUnityがOnDestroyを実行するため
だそうだ。回避策は、OnApplicationQuitかどうかを判別するか、単にOnDestroyでなんか生成するのをやめればよい。やめよう。

良い感じ。

  • by Anonymous Coward on 2020年09月16日 1時50分 (#3889842)

    「~は補数」のところ、正しくは「~はビット反転」です

    補数には「1の補数」とか「2の補数」ってのがあって
    1の補数がビット反転で計算できるだけです

    「n番目だけが0、他は1」な値を作る時に
    「n番目だけが1、他は0」という値を (1<<n)で生成して (nはゼロオリジン。値は0,1,2,3,4...)
    それのビット反転を行うことで「n番目だけが0、ほかは1」という整数値を作ります

    これはプログラムの常套手段です
    詳しいことは「ビット演算」とかでググってください

    これを補数と言ってしまうと多くの人は混乱します
    なぜなら、計算しているのは「ビット反転した値」であって補数ではないからです
    (たまたま1の補数と一致してるだけ)

    ここに返信
typodupeerror

普通のやつらの下を行け -- バッドノウハウ専門家

読み込み中...