2014年12月18日木曜日

近況

今年の12月17日でロックマンが27周年だとか。スマブラにもロックマンが参戦したからマリカー8入り Wii U (白) を買って、スマブラも買ってしまったよ。それだけで Wii U のハードディスクはほとんどいっぱいになってしまった。なんてことだ。 マリカーは第一作は友人の家でよくプレイしていたが、それ以外は64版を1回やっただけだった。当然のことながらビジュアル面はものすごく進化してまったく別物だが、なんとなく走っているだけでも楽しいのは変わらない。スマブラは実は1作もやったことがなかったのだが、ロックマン出てるから買う以外しようがないね。

さて、そのロックマンはストリートファイター×鉄拳に出てきたような北米版のMEGAなおっさんではなく、欧州版の青年でもなく、日本版の少年で3Dモデルの出来栄えも素晴らしく、SEも本家から輸入しているものが含まれているので懐古厨も安心のクオリティ。粘土っぽい質感のイエローデビルが四角いブロックに分裂して移動し人型を再構成するのも雅である。

ゲームパートはどれをやってもいいが、桜井さんがやっているようにリアル8人集合で大乱闘するのがもっとも超エキサイティン!だろう。非リア充には厳しいが、ぐぬぬ。あと、サウンドテストでなつかしいあの曲 (どれ?) のオリジナル版やアレンジ版を聴くことができ、「もう BGM 聞いてるだけでいいんじゃないかな」という気分になるくらい素晴らしい。FE外伝推しの私としてはミラの加護とともに (セリカマップ1) の岩垂さんのアレンジ版トカ垂涎モノであった。

しかし、実際にはマリカー8もスマブラも半ば積みゲーと化しており、最近は『電装天使ヴァルフォース』という『電脳戦機バーチャロン』(以下バーチャロン)のクローンゲームをやっている。仮想ゲームセンターに接続して快適に対戦または観戦することができ、5年くらい前に出たゲームにもかかわらず毎夜の数人規模の野試合や毎月の10数人規模の大会が開かれている。

実は今年になってから有料の同人ゲームに手を出すようになり、Macbook Air 上の Parallels Desktop で Windows 8.1 仮想マシン走らせ、[erka:es] によるロックマンクローン作品である『Rosenkreutzstilette』、『Rosenkreutzstilette Freudenstachel』などをやっていた。過去にほんのちょろっとやって高速機動戦闘がいい感じだったバーチャロンについてクローンを探したらヴァルフォースが見つかって体験版を試したものの、仮想環境のおかげか体験版が(パッチが存在するにもかかわらず見つからない) エラーで正常に動作せずであったが、試行錯誤の末、ゲームのフォルダーをルートに配置したらなぜか動いたのでポチった。仮想マシンでゲームやろうとして起動できなくて投げた人がいるのかどうかは知らないが参考になれば。

つまり、その、アレだ。制作の進捗は… (文章はここで途切れている)

2014年11月4日火曜日

敵を発生させるスクリプト

またも間が空いてしまった。ブログの記事に時間を割くよりは制作を優先したらこうなった。今回は Nostalgia のマップに配置したタイルの ID に対応する敵をタイルの位置に発生させるスクリプトを晒す。マップコンポーネントを追加したゲームオブジェクトに以下のスクリプトを追加して使う。例によって品質は KS だ。

using UnityEngine;
using System.Collections;
using Nostalgia;

public class EnemySpawner : MonoBehaviour
{
  [SerializeField] private GameObject[] enemyPrefabs;
  private Map enemySpawnerMap;
  private Vector3[] tilePositions;
  private int[] tileIds;
  private Cell[] cells;

  void Start ()
  {
    enemySpawnerMap = GetComponent<Map> ();
    cells = enemySpawnerMap.cells;
    tilePositions = new Vector3[cells.Length];
    tileIds = new int[cells.Length];
    for (int i = 0; i < cells.Length; i++) {
      tilePositions[i] = enemySpawnerMap.MapPointToWorldPoint (cells[i].position);
      tileIds [i] = cells [i].tileID;
    }
    StartCoroutine (SpawnEnemy ());
  }

  IEnumerator SpawnEnemy ()
  {
    while (true) {
      for (int i = 0; i < cells.Length; i++) {
        bool b1 = true; // 待ち時間以外の敵インスタンスの出現条件を書く
        bool b2 = true; // 敵インスタンスの向き変更条件を書く
        if (b1) {
          GameObject enemyInstance = (GameObject)Instantiate (enemyPrefabs [tileIds[i]], tilePositions[i], (b2 ? Quaternion.identity : Quaternion.Euler (0f, 180f, 0f)));
        }
      }
      yield return new WaitForSeconds (5f); // 待ち時間
    }
  }
}

敵の種類だけ異なるタイルを作るわけだが、全部透明の同じグラフィックにするとインスペクターなしで区別できない。これでは扱いづらいのでどうしたものか。それにしても Tile Component の使いどころがわからんな〜。

2014年10月14日火曜日

ルビを表示する

制作が止まっているわけではないのだが、また長らく間があいてしまった。ドット絵を打ったり。プログラムを書いたりしていた。

テキストにルビを振りつつ 1 文字ずつ表示するのを作った。基本的なアイデアは大きさと間隔を調整した GUI Text を並べ、表示速度を調整するだけ。テキストは XML 文書というか以下のような HTML 文書から読みこんで使う。指定したノード番号の dl 要素に含まれる、dt 要素をルビ 1 ページ, dd 要素を本文 1 ページとして表示する。


<html>
<head><title>タイトル</title></head>
<body>
<dl>
<dt>ほんじつ  せいてん     
ほんじつ  せいてん    </dt>
<dd>本日は晴天なり
本日は晴天なり</dd>
<dt>じゅ  げ む      じゅ  げ む
  ご  こう    す   き </dt>
<dd>寿限無   寿限無 
五劫の擦り切れ </dd>
<dt>かいじゃ  り  すいぎょ  
すいぎょうまつ   うんらいまつ  ふうらいまつ</dt>
<dd>海砂利水魚の
水 行 末 雲来末 風来末</dd>
<dt>  く   ね  ところ  す  ところ
やぶ  こう  じ    やぶこう  じ </dt>
<dd>食う寝る処に住む処
藪ら柑子の藪柑子   </dd>
<dt>            
                      
                            </dt>
<dd>パイポパイポ
パイポのシューリンガン
シューリンガンのグーリンダイ</dd>
<dt>                          
                   ちょうきゅうめい         ちょう すけ</dt>
<dd>グーリンダイのポンポコピーの
ポンポコナーの  長 久 命の 長  助</dd>
</dl>
</body>
</html>

KS コードは以下の通り。Canvas を 2 つ (本文用、ルビ用) 用意して、currentTextType をそれぞれ TextType.Body, TextType.Ruby としておき、それぞれの recoveryTime, fontSizeFactor を調整する。例えば、本文の recoveryTime を ルビの recoveryTime の 2 倍にし、本文のフォントサイズがルビのフォントサイズの 2 倍になるように fontSizeFactor を調整する。あとは、それぞれの LoadSelectedList (int i)を呼べばいいんじゃないかな。


using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Xml;

public class DisplayLetterByLetter : MonoBehaviour
{

  Text textarea;
  float timer;
 // 次の文字が表示されるまでの間隔。適当に調整
  public float recoveryTime = 0.1f;

  XmlDocument xmlDocument;

  // 読み込む XML (TextAsset として)
  public TextAsset xmlTextAsset;
  XmlNodeList[] DefinitionLists;
  XmlNodeList currentList;
  int currentPage;
  string fullText;
  int fullTextLength;

  // true のときテキストを表示
  public bool displayingText = false;

  int numberOfLettersDisplayed;

  // 全文字表示してから自動ページ送りするまでの秒数
  float autopagingTime = -2;
  bool paging;

 // 自動ページ送り
  public bool autopaging;


  public enum textType
  {
    Body,
    Ruby,
  };

  // 本文 Body, ルビ Ruby
  public textType currentTextType = textType.Body;

  // 画面の高さに対する割合
  public float fontSizeRatioToScreenHeight = 0.01875f;

  void Start ()
  {
    textarea = GetComponent ();
    textarea.fontSize = (int)(Screen.currentResolution.height * fontSizeRatioToScreenHeight);

    xmlDocument = new XmlDocument ();
    xmlDocument.LoadXml (xmlTextAsset.text);
    XmlNodeList xmlNodeList = xmlDocument.SelectNodes ("/html/body/dl");
    int numberOfDefinitionLists = xmlNodeList.Count;
    DefinitionLists = new XmlNodeList [numberOfDefinitionLists];
    for (int j = 1; j <= numberOfDefinitionLists; j++) {
      DefinitionLists [j - 1]
      = currentTextType == textType.Body ?
        xmlDocument.SelectNodes ("/html/body/dl[" + j + "]/dd") :
        xmlDocument.SelectNodes ("/html/body/dl[" + j + "]/dt");
    }
  }


  public void LoadSelectedList (int i)
  {
    currentList = DefinitionLists [i];
    currentPage = 0;
    LoadCurrentPage ();
  }
  //
  void LoadCurrentPage ()
  {
    fullText = currentList.Item (currentPage).InnerXml;
    fullTextLength = fullText.Length;
    timer = recoveryTime * (currentTextType == textType.Body ? 1 : 2);
    numberOfLettersDisplayed = 0;
  }
  void Update ()
  {
    // jInput を使っていない人は普通に Input.GetButtonDown ("お好みのボタン") 
    paging = jInput.GetButtonDown (Mapper.InputArray [5]);
  }
  void FixedUpdate ()
  {
    if (displayingText) {
      Display (autopaging);
    }
  }

  void Display (bool autopaging)
  {
    textarea.text = fullText.Substring (0, numberOfLettersDisplayed);
    timer -= Time.deltaTime;
    if (numberOfLettersDisplayed < fullTextLength) {
      if (paging) {
        timer = 0;
        paging = false;
      }
      if (timer < 0) {
        numberOfLettersDisplayed++;
        timer += recoveryTime;
      }
    } else {
      if (paging) {
        OnPageEnd ();
        paging = false;
      } else if (timer <= autopagingTime && autopaging) {
        OnPageEnd ();
      }
    }
  }

  void OnPageEnd ()
  {
    if (currentList [currentPage + 1] != null) {
      currentPage++;
      LoadCurrentPage ();
    } else {
      CloseMessageWindow ();
    }
  }

  void CloseMessageWindow ()
  {
    displayingText = false;
  }

}

再生の様子を動画でご覧あれ。表示位置とタイミングは半角スペースやら全角スペースやら改行やらを使って調整するという力業なのでいろいろ微妙。力こそパワーの精神でひとつヨロシク。

ルビの表示 displaying ruby text from hide behind on Vimeo.

各文字の位置から表示するタイミングを計算すればもう少しマシになるんだろうかねぇ。

2014年10月5日日曜日

デザイン変更。

しばらく間が開いてしまったが、ドット絵素材をチマチマと作成していた。全然進んだ気がしない。

夜中に真っ白な背景だと目に堪えるので、背景色を変更した。ちょっとはマシな配色になっただろうか。

ドット絵をやっていると滅入るので、気分転換にフリーの音楽素材を探して整理したりもしていた。ドメイン名/(必要に応じて)ユーザー名/ みたいな感じで分けた。ジングルというかサウンドロゴというかがなかなかイメージに合うものが見つけられない。部分的に切り取るとかも考えた方がいいかも知れない。チップチューン系だと素材探しが大変で、そういうのでないやつだと自作のドット絵がショボすぎて負けるような気がするので品質をあげないとナー。とはいえ KS 品質でもいいから全部のドット絵素材を用意する方が優先順位は高いけども。

2014年9月25日木曜日

DRN.008 エレキマンの行動パターンを考える。

まえがき

連載第6回はエレキマンを取り上げる。ちょっとよくわからない。

エレキマン本体の行動パターン

  • 非負の整数を k として、移動した回数 n が n = 4 * k のとき
    1. 部屋の右側へ移動
  • 非負の整数を k として、移動した回数 n が n = 4 * k + 1 または n = 4 * k + 3のとき
    1. 部屋の中央に移動
  • 非負の整数を k として、移動した回数 n が n = 4 * k + 2 のとき
    1. 部屋の左側に移動/li>
  • 特定の x 座標に到達したとき
    1. サンダービームを発射
  • 自機がショットしたとき
    1. ジャンプする。
  • 壁に接触したとき
    1. ジャンプする。
  • 被弾したとき
    1. 行動をキャンセルし、ノックバック
    2. 一定時間無敵になる

こちらから攻撃しない限りはアイスマンと同様に部屋の右側、中央、左側を行ったり来たりしながら、指定 x 座標に到達するとサンダービームを発射する。ガッツブロックに引っかかったり、ノックバックさせたりで指定座標への到達できないとくり返し同じ座標に向かおうとするので、どうやら指定座標に到達してはじめて次の目標地点が変わるようだ。

サンダービームの行動パターン

  • 発射されたとき
    1. 直線運動
    2. 自機に当たると拡散

攻略法

サンダービームの予備動作中にタイミングよくショットを当てると、ハメられる。エレキマンステージ、ワイリーステージ2の両方で可能。動画を参照。

Defeat ELEC MAN without walking, jumping from hide behind on Vimeo.

あとがき

ハメると初期位置から一歩も動かずに撃破することもできるが、高火力のアイススラッシャーを上回る超火力のため事故りやすい。ノックバックするのが唯一の救い。

2014年9月24日水曜日

DRN.007 ファイヤーマンの行動パターンを考える。

まえがき

連載第5回はファイヤーマンを取り上げる。fireman は消防士のことなんだが、コイツは廃棄物処理ロボットである。

ファイヤーマン本体の行動パターン

  • 自機との距離が一定以上のとき、一定確率で?
    1. 自機の方を向いてファイヤーストームを撃つ
  • 壁に接触したとき
    1. 向き反転
  • 被弾したとき
    1. 一定時間無敵になる
    2. 自機の方を向いてファイアーストームを撃つ
  • 上記以外のとき
    1. 移動

離れているとファイヤーストームを撃ってくるが、間合いを詰めたままだと攻撃しない限りは移動するだけである。攻撃すると ファイヤーストームで反撃する。

ファイヤーストームの行動パターン

  • 発射されたとき
    1. 自機狙い、直進
    2. 自機足下に火がしばらく残る。

攻略法

ファイヤーマンステージでは、ある程度距離を詰めてファイヤーストームを封じつつ、ショットとジャンプを同時押しでジャンプショット、着地直後にまたジャンプショットのくり返しでハメられる。

ワイリーステージ4はワープカプセルがあって部屋が狭い。ワープカプセルの外に出て同様にする。

Defeat FIRE MAN with ROCK BUSTER from hide behind on Vimeo.

あとがき

攻撃面ではとにかく撃ちまくればよかった他のボスに対するアンチテーゼとでもいう位置づけか、弱点を衝けばゴリ押し可能なこのゲームでは珍しく、弱点を衝いてもパターンを無視してゴリ押ししようとすればゴリ押し返されることがある。また、なるべく敵から離れるという定石が裏目に出るのも嫌らしい。

2014年9月23日火曜日

ミサイルは渡り移るもの。

『ロックマン』の6ボスまで予約投稿にしたのでここで一段落。制作中のゲームの動画をさらす。

ミサイル渡りjump from one missile to another. from hide behind on Vimeo.

ミサイルからミサイルへ飛び移っているのは全部敵キャラクターなのだが、敵キャラクターのグラフィックがまだないのでプレイヤーので代用。プレイヤーキャラクターはミサイルにぶら下がっているやつ。ぶら下がり状態で左右に移動できるのだが、グラフィックがまd (以下略)。爆煙やらバックファイアを書きたいが、デカいのを書くと目障りになってしまうのをどうしたらよいかな。

そんなつもりはなかったが、クラウド・ストライフと同じようなカラーリングになっちゃった。イメージイラストもなくいきなり雑なドット絵打っているので、こうしてさらしておいたらエレガントなイラストを誰か書いてくれたりしませんかネェ。プレイヤーのドット絵もまだまだ足りない。被弾時のとぶら下がり移動、壁張り付きと壁張り付き移動は 最低でも必要だ。