チワ裏

とあるゲーム作りたい人のチラシの裏

開発日記 その4 自作迷路生成アルゴリズム step 1

第四回です。
今回はこのゲームの根幹となるアルゴリズムについて書いてきます。

迷路生成のアルゴリズムはネット上にかなりの数がありますよね。
しかし、そのまま利用しただけでは普通の迷路ゲームになってしまいます。
今回の制作するゲームとしては不十分ですし、再帰処理で行われてるとゲーム要素の注入が面倒になります。

そこで、前に書いた再帰分割法の「分割」という考え方や、既存の迷路のアルゴリズムを組み合わせてオリジナルのものを作っていこうと思います。

方針

このゲームでは迷路をランダムな形状で最短距離の指定の長さを保証し管理しやすい構造で、作っていくことが目標です。
上記を実現するためにこのアルゴリズムは 3つのstep に分かれています。

  • step 1 不定形セルの生成
  • step 2 セルの接続
  • step 3 セル内を迷路で埋める

今回はstep 1を解説していきます。

Step 1

まず、ランダムな形状を実現するために平面のフィールドを不定形なエリアに区切っていきます。
計算で不定形な図形を生成するのは非効率なので、乱数を使って実現します。


(フィールドのイメージ)

f:id:tiwaluna:20171210144331p:plain
このようにフィールドのある座標に開始点を付けます。(図では赤く着色)
上下左右の方向に確率で伸ばしていきます。
時間ともに成長していくので、「セル」と名付けます。



等間隔に開始点を発生させて、セルごとに伸びる確率をランダムに設定するとこうなります。
これによって、不定形な形状の区切りが作れました。
また、セルには通し番号をつけます。画像の場合、左下が0で右上が24になります。


次に情報の生成です。
次のstep 2では生成したセル同士を連結したいので、連結に必要な情報を抽出します。
必要な情報は、セル同士が隣り合う境界部分です。(画像の色つけしたセルの境界部分の座標)

  • 隣のセル番号
  • 座標
  • 連結方向

を取得します。

取得するタイミングは、セルを伸ばすときに別のセルに衝突したときに取得します。
そのときの衝突したセルの番号、伸ばした方向、座標を取得します。


境界部分を着色するとこうなります。
連結時には、連結先のセル番号を参照して、座標と方向から連結処理を行えます。


今回はここまでです...
次回の更新でstep 2の解説をします!(step 2の実装に詰まっている)

メモ

今回の記事で使用した画像は、アルゴリズムのデバック用のスクリプトで生成したものです。
step 1で生成されたセル番号を配列に格納しておいて、
それをスクリプトで参照して、値の変更が行われたら対応する色(マテリアル)で着色しています。
コルーチンを使ってオブザーバーパターン的に実装してます。

一からアルゴリズムを作っていくのは今回が初めてなんですが、こういった視覚的なデバックがあるととても便利ですね。
文字のみ(Debug.Log)だとレイアウトが崩れたり、柔軟な表示ができないので、デバックコードの重要性がわかりました。

(step 2の実装はデバック用のコードをはじめに作らなかったせいで5時間ほど無駄してしまった...)

開発日記 その3 巨大な迷路を3Dオブジェクトで表現する

第三回です。
意外と順調に更新ができているので自分でも驚きです。

さて、今回は1000*1000の迷路を3Dオブジェクトで表現する方法について書いていきます。

方法1 1000*1000個のゲームオブジェクトを用意する!

結論から言って無理です。
流石にこんな阿呆な実装をする人はいませんが、いちおう試して消費メモリを計測してみました。
f:id:tiwaluna:20171205210014p:plain

1.54GBになります。デデドン(絶望)
ちなみに本当に1000*1000個の生成をするとゲーム自体が起動しなかったので、上のスクショは500*500=250000個のゲームオブジェクトの生成したときの結果です。

想像通りの結果ですね。

目標のメモリ消費量

256MBです。
理由として、WebGLでゲームを作る際にこんな設定があります。 (ProjectSettings->Player->WebGL)
f:id:tiwaluna:20171205210901p:plain

ここで設定する値がブラウザ上で確保されるメモリ量だと思われます。256MBより多く設定すると32bit環境で動かないなどという警告がでますね。
なので超軽量なUnityゲームのメモリ消費量は256MB以下なんじゃないかなーと思ってます。
今回はカジュアルに遊べるようなスマホゲームを目指しているので目標はこれぐらいを目指しています。
Unity - マニュアル: WebGLを対象とした場合に必要なメモリへの配慮

方法2 オブジェクトプールの考え方を用いる

オブジェクトプールとは、単純にいうとオブジェクトを使いまわすことです
具体的には、初めに十分な数のオブジェクトを用意しておいて、アクティブと非アクティブの切り替えを行うことをします。
これにより、毎回DestroyやInstantiateをすることがなくなるので効率化することが可能です。

また、使いまわすという考え方を応用して、以下のような方法を考えます。
f:id:tiwaluna:20171205212934p:plain

この考え方を用いると、カメラの視界に収まる数のオブジェクトのみで迷路のフィールドを表現することが可能です。

まず、カメラの視界内に収まる十分な量のオブジェクトを生成します。今回は10*10個用意しました。
プレイヤーが右に動いた場合、左端の列が視野外に出ます。また、右端の列にオブジェクトが表示されていない状態になります。
ここで、図に示したように、左端の列を右端に移動することによってグラフィックが途切れないようにすることが可能です。
その他の方向についても同様になります。

続いて、迷路の壁の表現について考えます
今回のゲームではいわゆる「壁マス」というものは存在せず、マスとマスの間に壁が存在します。
壁がどこに存在するかについては、マスごとに判定しています。
各マスごとに壁のある方向、「下」または「右」を設定して壁を作っています。
ここで、オブジェクトプールの考え方を利用して、あらかじめ下と右方向の壁を用意しておきます。
各マスの壁の方向をデータで保持して、必要に応じて表示、非表示を切り替えていきます。
図にすると以下のようになります。
f:id:tiwaluna:20171205214411p:plain

これらの考え方を利用すると、オブジェクト数が、3 * 10 * 10 = 300個で済みます。
データは
GameObject配列 [300]
方向用データ [1000 * 1000]
になりました。今回のゲームでは方向のデータをビットフラグを利用してint型1つで表現しています。

最後にメモリ消費量の測定結果です。
f:id:tiwaluna:20171205214748p:plain

約63MBに削減できました。
これから色々な要素を詰め込んでいきたいのでこの軽量さはとてもうれしいですね。

おわりに

実際に動かした結果

わざとオブジェクトの移動が見えるようにしていますが、実際にはカメラの視界やマスの大きさを調整することで自然な感じにできます。

今回はここまで。
過去最高の文字数の記事になったかも。

ようやくアルゴリズムの改善方法がまとまってきたので、次回はアルゴリズムの紹介をしたいと思います。

開発日記 その2 迷路の生成アルゴリズムとゲームデザイン

第二回です。
今回は迷路を生成するアルゴリズムとか今考えてるゲームデザインとかについて書いていきます。

再帰分割法

今回のゲームでは再帰分割法というアルゴリズムをうまく使っていきたいなーーと思っております。

参考元
Buckblog: A Better Recursive Division Algorithm


このアルゴリズム良い所は、マップを分割しながら作っていくというところですね。
参考元のサイトの迷路生成のアニメーションをぜひ見てもらいたいです すごくえっち...

ゲームデザイン

今回のゲームでは迷路にRPG要素をぶっこんでいきたいのです。
なので、こんな感じに
f:id:tiwaluna:20171203143407p:plain

分割されたマップ(エリア)ごとに色々と処理を加えていきたいと考えています。
上図では、エリアごとに敵のレベルを分けています。

あと、純粋な迷路生成では邪道?なのかもしれませんが、迷路にループを取り入れたいですね。
なにも考えずにループさせると迷路の複雑さがバラバラになるので、ある程度作為的に制御できるような形にしたいところです。

そのほかに今回のゲームではスタート地点とゴール地点の位置は固定にしようかなと思っています。
RPGなので、目的地に向かう途中によ寄り道したりする要素と、迷路をさまよいながら進む要素を合体できたらなーというのが理想です。

実現性?

んなこと知るか

上で色々と述べたんですが、このアルゴリズムが自分には難しいので実装できるかどうかわからんのですね。
結局実現したいことは「迷路をエリアごとに分けること」、「迷路の形状をコントロールできること」なので、再帰分割法でなくてもいい気もしてます。

正直、アルゴリズムがカッコイイという理由のみで決めています。
なので、ちょっとやって無理そうだったら、ランダムに領域を分ける処理とバックトラック法の迷路生成でどうにか賄うことも考えてます。

最後に再帰分割法で作った30*30サイズの迷路の動画↓

結構30*30のサイズでも大きく見えますね。
キャラクターの移動速度をいい感じに調整して、可能な限り大きな迷路を作ったらやりごたえのあるゲームになるかもしれません。

ただ、N*Nの平面だとゲームオブジェクトの量がとんでもないことになりますね。
このへんの解決策も考えついてはいるんですが、実装が間に合ってないので次回に紹介します。

開発日記 その1

そろそろ12月に入り暇になるので開発日記のようなものをつけようかなと。
今回はマジでただの日記です。

動機とか

自分はUnityでゲームを作っているのですが、あまり制作状況をメモすることが無いので、制作中の記憶が結構飛んでいることがあったりします。
特に、今年4月~8月に作ってたゲームは某コンテストへのエントリーもあり、デスマしたので開発終盤あたりの記憶がゴッソリなくなってます。

12月から次回作の制作を始めるにあたって、前回の反省を踏まえて開発日記としてメモを残したいなーと思ってます。
あと文章を人並に書くための練習としても...

なに作るの?

今月はちょくちょくゲームの企画をまとめて企画書もどきを作ってました。 
ガバガバ文章力のおかげで到底ブログで見せられるもんでは亡くなった

今回の開発では

「迷路」+「RPG

的なものを作っていきたいなと思ってます。人にウケるかどうかは分かりません!


色々と元地にするアルゴリズムの選択とかはすでにやっているんですが...
そのへんは小出しにしていかないと日記を続けられる気がしないのでまた次回ということで。 

土日中にその2を投稿したいナー

Unityで3Dモデルを動かして剣で敵をぶっ飛ばす実装メモ

3Dモデルをちゃんと動かすゲームを作ってみたくなったので試しに作ってみました。

実装手順

まずは使用したアセット(無料のやつです)

キャラクター Strong Knight (つよそう)
いい感じの騎士?のアセット
https://assetstore.unity.com/packages/3d/characters/humanoids/strong-knight-83586

スクリプト Free Footsteps System
モデルの動きやアニメーションを思い通りに実装できるアセット
https://assetstore.unity.com/packages/tools/audio/free-footsteps-system-47967

アニメーションやエフェクトは適当にどうぞ

ヒエラルキーの構成

f:id:tiwaluna:20170919220127p:plain

・top_down_camera_pivot
Standert AssetのMultipurposeCameraRigみたいなやつっぽい。
Targetにtop_down_controllerを設定する。
f:id:tiwaluna:20170919220548p:plain

・top_down_controller
このオブジェクトの子にキャラクターの3Dモデル(kinghtprefab)を配置する。
f:id:tiwaluna:20170919220553p:plain

上記はFree Footsteps Systemのデモシーンのオブジェクトに既に設定されているので流用して改造できます。

アニメーションの実装

最終的なアニメーション図 f:id:tiwaluna:20170919221336p:plain

Free Footsteps Systemのスクリプトを改造していきます。
アセットに付いている、TopDownController.csでモデルの制御ができます。
これをアタッチすると、移動キーに合わせたオブジェクトの移動と回転が実装されます。すげぇ

次に任意のアニメーションを再生できるようにします。
TopDownController.csのコードでは歩行時にAnimatorのパラメーターのmoveをtrueにすることでアニメーションを遷移させています。
このスクリプトを以下のように改造して対応するアニメーションを増やします。


void UpdateAnimator() {
  …
  <既存のコード>
  …
  //移動キーのいずれかが押されていればtrue、押されてないならfalse
  isMoving = Input.GetButton(“Horizontal”) || Input.GetButton(“Vertical”);  

   //tureかfalseが設定される
  thisAnimator.SetBool(“move”, isMoving); //tureかfalseが設定される
}

これを応用すれば、
isRunning = isMoving && Input.GetKey(KeyCode.LeftShift);
isAttack = Input.GetKey(KeyCode.Z);

thisAnimator.SetBool(“run”, isMoving);
thisAnimator.SetBool(“attack”, isRunning);

みたいな感じでダッシュ、攻撃のアニメーションフラグを設定できます。 AnimationControllerにちゃんとパラメーターを用意するのも忘れないようにしましょう。


剣で敵を吹き飛ばせるようにする

キャラクターのモデルをこんな感じで改造してみました。

f:id:tiwaluna:20170919222829p:plain

変更した点は元モデルでは剣のモデル(dagger)を腰につけているんですが、daggerをR_elbowの子の位置に設定します。
僕の使ったアニメーションではこれで剣を振るう動きが作れました。
また、不要なパーツは非表示か削除しておきます。

daggerのメッシュに敵が当たったときに攻撃判定としたいので、daggerにMesh Colliderをアタッチします。
また、Mesh ColliderはOnTriggerEnterを使って接触の判定をしたいので、isTriggerを設定します。
そして敵を吹き飛ばすスクリプトを作ってアタッチします。

ブログ用 当たったオブジェクトを吹き飛ばすUnityスクリプト

解説

OnTriggerEnterで大体の処理を行ってます。

  • 敵の判定
    今回は敵にenemyタグをつけて判定するので、 if (other.gameObject.tag == “enemy”)で判定。

  • 攻撃アニメーションの判定
    if( (animatorStateInfo.IsName(“attack”) || animatorStateInfo.IsName(“jump attack”) )
    で攻撃アニメーションが再生されているかどうかを攻撃アニメーションの名前で判定します。

    • また、AnimatorStateInfoの情報は取得する前に
      rootAnimator.Update(0);
      で更新しないと最新のAnimatorの情報を取得できないので注意。
  • 攻撃判定
    0.2f < animatorStateInfo.normalizedTime && animatorStateInfo.normalizedTime < 0.8f
    animatorStateInfo.normalizedTimeで再生中のアニメーションの進捗を取得できます。
    0%(再生開始) ~ 100%(再生終了)
    今回は20%~80%の間で、剣を振っている最中のみ攻撃判定を出すようにします。

  • 吹っ飛ばす!
    rigit.AddForce(transform.root.forward * 25, ForceMode.Impulse);
    敵にRigitBodyがある前提ですが、こいつで吹き飛ばします。ForceMode.Impulseでは相手の質量(Mass)の影響を受けて、瞬間的に力を加えます。適切な値を設定します。

  • その他
    effectの部分は適当にすきなものをどうぞ。
    effectはプレハブを剣と敵の中間の位置に生成されます。


以上で実装は終わりです。正直ただのアセット紹介なんだよなぁ・・・。
説明が抜けていたらすみません。

完全にブログ放置してたからこれからは徐々に記事を書いていきたいなぁ…

Unityでカレンダー作ったメモ

2017/4/28 記事内容をめちゃくちゃ修正

Unityでこんな感じのカレンダーを作りました。
(WPFでやれとは言わない)
作ってみて、C#のDateTimeについて知らないことが思ったより沢山あったのでそれのメモ。
あと、UniRxを久しぶりにちょろっと使ってみて、結構忘れてたのでメモ。

作り方

まず、カレンダーの日付のマスをHierarchyのCreate->UI->Buttonで作ります。
7×6行で42個あれば十分です。

あとはこれらのButtonの親に空オブジェクトを設定しておきます。
また、カレンダーの月を変えるためのNext、Prevボタンも適当な場所に配置しておきます。
f:id:tiwaluna:20170217211911p:plain
Hierarchyはこんな感じになります。
Calendar、Row0~5が空オブジェクトになります。それぞれのRowオブジェクトの子にButtonオブジェクトが7つ入ります。この親子関係を利用して、Calendarにスクリプトを設定します。

2017/4/28 修正
Hierarchyの親子関係を複雑にすると後でいじくったりする際に面倒なことになります。
42個のButtonをまとめる親オブジェクトを一つ用意するだけでOKです。
Hierarchyはこんな感じになります(修正後)
f:id:tiwaluna:20170428191533p:plain

カレンダーの管理スクリプト

Calendarオブジェクトにアタッチします。
UIとは別の場所に空のオブジェクトを作成して、それにAdd Componentします。
日付を表示するButtonはスクリプトから読み込むため、Buttonの親オブジェクトを指定しておきます。
f:id:tiwaluna:20170428191725p:plain

解説
  • 初期設定

まず、Unity上でnextButtonとprevButtonに先ほど作ったNext、PrevのButtonオブジェクトを指定しておきます。

  • InitCalendarComponent
     InitCalendarComponentメソッドで各ButtonにCalendarButtonをアタッチします。ついでにオブジェクトとコンポーネントを保存しておきます。
     各Buttonの取得はGetChildを使って取得します。このスクリプトがアタッチしている空オブジェクトがrootオブジェクトなので、先ほどのHierarchyの構造を活用して取得します。
     何番目のRowオブジェクトのColumnであるかでそのButtonが何行目の何列目かわかります。このカレンダーは1行あたり7列で固定されているので、7×行数 + 列数で左上のButtonから数えて何番目なのかが分かります。これを利用してobjDays配列およびDays配列に保存しておきます。

     
     先ほども書いたとおり、↑の実装だとオブジェクト構造を変更することができなくなるので、ソースコードにあるように単純に連番で番号をつけます。

  • SetCalendar
     SetCalendarメソッドで各CalendarButtonオブジェクトに対応した日付を指定していきます。 
     first変数はSystem.DateTime型で、今月の1日目の日付を表します。DateTimeは(年, 月, 日)をint型で指定して作れるのでcurrentの年月を指定して、日にちに1を入れます。
     また、先月、来月の取得はcurrent.AddMonths()を使います。current.AddMonths(1)で来月、current.AddMonths(-1)で先月を取得します。
     このカレンダーでは空いたマスに先月、今月の日にちを表示したいので、そのための変数を用意します。来月の分は1から数えますが、先月の場合はまず、DateTime.DaysInMonthメソッドで先月の日数を取得します。その日数から空いたマスの日数分を減らします。空いたマスは今月の一日の曜日から計算します。曜日の取得はfirst.DayOfWeekから取得します。DayOfWeekは列挙型なのでint型にキャストします。DateTimeの曜日は日曜~土曜の間で(0~6)となっているので、先月の日数 - 今月の1日の曜日 + 1で空いたマスを計算できます。
     あとは条件に合ったマスに日時を指定していきます。

  • UniRx部分について
     まず、自分が深い理解ができてないので、非常にざっくりとした説明しかできないので申し訳ないですorz。
     nextButton.onClick.AsObservable()という部分でNextボタンが押されたかどうかを常に監視します。もし押された場合、Subscribe内の処理が行われます。Prevボタンも同様です。
    完璧に使い方を忘れていたので参考のリンク
    【OnClickAsObservable】UniRxでボタンのクリック回数をテキストに表示する【SubscribeToText】 - Qiita

  • 日付マスのスクリプト

    各Buttonにアタッチします。(数が多いのでCalendarManagerから動的にアタッチする。)

    解説


  •  UniRxで値の変動を監視します。各マスの設定された日付はDateValueに保存されていて、CalendarManagerから変更されます。UniRxのObserveEveryValueChangedを使ってDateValueの値の変動を取得します。ここではDateValueの値が変更された場合、.Subscribe内の処理が行われます。
    【UniRx】ObserveEveryValueChangedでフレーム間での値の変動を監視する - Qiita
     値が変動したらテキストの値を変えて、土日などは色を変えたいので、DateValueの値を使って色の判定をGetColorDayOfWeekメソッドでします。


  • 以上のスクリプトを踏まえて、他にも年や月を表示する処理を作れば冒頭のgifみたいなカレンダーができます。(たぶん)
     わざわざUnityで作らなくても良かった気もしますが、一応、Unityのオブジェクトとしてカレンダーを作ったのでここから更にカレンダーを他のUnityのアプリに組み込み易いかもです(こなみ)。
    あと傾けたりとか。
    f:id:tiwaluna:20170217234902p:plain

    おわり

    とりあえず実質的な初めてのブログ記事。
    正直誰に向けて書いてんだか良く分からない感じになってしまいました。(必要なUnityの前提知識が多い)
    あと、普段は感覚的になんとなくの理解でプログラミングしているので(UniRx関係とか)、ブログ記事で文章にしてみて、人に説明するのが難しいというか説明できないのをすごく実感したので精進したいと思います。
    こんな記事で書くのに3時間かかってしまったのでこれからは無駄な説明は省いていかないときつそう。

    2017/4/28 追記
    ブログを放置していたのでコードを改善したのに記事を更新してなかった、反省。

    はじめ

    ブログの目的

    趣味のプログラミングでやったことを忘れないようにメモしていきたい。

    ついでに文章を書く練習もする。