by Max Neet Games
 適当で汚い実装が多いので、
参考にすべきかは微妙な感じです。
(なら何故公開する!?)
 このスライドの内容は前作FarmFury!やブ
ログで解説した事はあまり載せていませ
んので、良ければそちらも参照ください。
 Farm Fury! 技術資料
 最後に参考リンクが貼ってあります
 Max Neet Gamesについて
 ゲーム紹介
 開発環境
 技術解説
 Max Neet Games
 (訳: Hatarakitakunai-De-Gozaruuuu!!)
 活動概要
 まったりとゲーム作ってます。きっと、多分、恐らく
 作ったゲームの資料も公開していく予定
 ホームページ
 http://maxneet.web.fc2.com/
 メンバー
 大熊猫 : プログラム、惰眠担当
 まる : アート、雑用担当
 プラットフォーム
 PlayStation Mobileにて108円で配信中
 プレイ人数 : 1
 ジャンル: ホラー
 Point & Click Adventure的ホラーゲームです
 想定プレイ時間: 20~60分
 PC
 XNA 4
 PlayStation Mobile
 PsmStudio
 MonoGame
 全体
 LuaInterface (C#でLuaを使う為のライブラリ)
 Tortoise svn, Audacity, Paint.Net, Excel, etc
 ファイル管理
 Dropbox上にsvnのリポジトリ作成し、Dropboxのフォルダを共有
 まずはXNAでPC用に作り、そこからPsm対応しました。
 MonoGame についてはFarmFury!の技術資料でザックリと説明しています。
 このゲームのデータ類(文言、キャラの設定、
日記の内容指定、等)はExcelで作成しています
 Excelで作成したテーブルはその後に自作のコンバー
ターを使ってxmlに変換しています。
 今回xmlはXNAのIntermediate Serializerを使用せず通常
のC#の機能で扱っています。
 実装の紹介 - C#メモ – xml読み込みの汎用化
 正直C#の言語機能を使うと遅いのでバイナリーに変
換すべきでしたが、面倒なのでやってません(おぃ)
 今回のゲームはデータ量事態そんなに多くないので、
日記以外(とDebugビルド時のデバッグ用テーブル)の
テーブルは起動時に読み込んでいます。
 C# + OpenXMLで作成しています。
 プログラムの引数に変換するファイルを探す
場所と吐き出し場所を指定する様にしていま
す。
 探す場所に指定したディレクトリにある全て
のxmlsファイルと、子フォルダにある全ての
xmlsファイルを変換する。
 クラスの名前はファイル名をそのまま使って
います。
 毎回Excelファイルを D & Dするのは面倒な
ので、バッチで纏めてやる様にしていま
す。
 バッチの内容は、指定ディレクトリにあ
る全てのファイルを変換し出力するのを
Windows版、Psm版、Windows版ビルド先
のデータフォルダに行っています。
 SET EXCEL_CONVERTER=%HORROR_ROOT%ExecutableExcelConverterExcelConverter.EXE
 SET PSM_CONTENT_DIR=%HORROR_ROOT%/Program/Psm/Horror/Content/Tables
 SET WIN_CONTENT_DIR=%HORROR_ROOT%/Program/Windows/Horror/HorrorContent/Tables
 SET WIN_EXE_DIR=%HORROR_ROOT%/Program/Windows/Horror/Horror/bin/x86/Debug/Content/Tables
 "%EXCEL_CONVERTER%" "%CD%" "%PSM_CONTENT_DIR%"
 "%EXCEL_CONVERTER%" "%CD%" "%WIN_CONTENT_DIR%"
 "%EXCEL_CONVERTER%" "%CD%" "%WIN_EXE_DIR%"
 例のバッチはテーブルが置いてあるフォルダ
に置いてあり、今のフォルダのディレクトリ
をファイルを探す場所としてConverterに渡
しています。
 Converterがあるパスの定義と、3つの吐き
出し先を定義し、3回コンバート処理を行っ
ています(効率?ナンデスカソレ?)
 事前準備として、%HORROR_ROOT% の環境
変数をプロジェクトのルートフォルダに定義
しています。
set DIR=%CD%
setx HORROR_ROOT %DIR%
 これを行い、他の人のパソコンでプロ
ジェクトをチェックアウトしても同じ様
に動く様にしています。
 周りクドイ実装ですが、変数保存用のクラスを作り、enum でその
変数の種類を定義しています。
 変数の保持自体はDictionaryに変数の種類を保存し、文字列をkeyに
使っています。
 文字列は定義用のenumをToString()で変換すると言うアホをやって
います(intとかでよかったんじゃね?)。
 保存用のクラスにはセーブ用にToString()をオーバーライドして中
身を書き出せる様にしてあります。
 余談ですが、enumの定義は必要以上に多く取って置き、後で増や
す必要が出てもセーブデータのサイズが変わらない様にしています。
 なので実はセーブデータ(と書き込む内容)を80%ぐらい削れる気がする
public classVariableTable<T>
{
protected Dictionary<string,T> m_variables;
//…変数足したり、設定したり
public override stringToString()
{
string data = "";
foreach (KeyValuePair<string,T> kvp in m_variables)
{
data += kvp.Value.ToString() + "n";
}
return data;
}
}
public enum Flag
{
// -- ドア系
BasementDoorLock, // 地下室のドア使える?
BathDoorLock, // 風呂のドア鍵が掛かってる?
KitchenDoorLock_0, // 廊下から台所のドア鍵が掛かってる?
KitchenDoorLock_1, // 書斎から台所のドア鍵が掛かってる?
LibraryDoorLock, // 書斎へのドア鍵が掛かってる?
FrontDoorLock, // 外へのドア鍵が掛かってる?
// …その他色々
// -- イベント系
HasWatchedNormalEnding, // 通常エンディングを見た?
IsTrueEnd, // 良いエンディング
Max = 100 // 本当は30以下だけど多めに取る
}
[Serializable]
public class GameData
: Singleton< GameData >
{
public const bool _DEFAULT_FLAG = false;
publicVariableTable<bool> Flags { get; private set; }
// …その他の宣言
public GameData()
{
Flags = newVariableTable<bool>();
// 空データ追加
for ( int i = 0; i < (int) Flag.Max; ++i )
{
Flags.Add( ((Flag)i).ToString(), _DEFAULT_FLAG );
}
// …その他の初期化
}
public override stringToString()
{
string data = Flags .ToString();
// …その他のデータ
return data;
}
}
 ゲームオブジェクト = ゲーム内に存在している物
 ただ、このゲームの場合は基本的にはゲーム内で描画される
物 = ゲームオブジェクト、みたいな扱いになっています
 UnityやらCocosやらその他色々で使われている親子関係っぽ
い物を使用し、親から情報を受け取ってます。
 ただ、実装時に怠けた為位置関係のみ親から受け継ぐと言う
アホ仕様
(はい、反省します・・・)
 コンポーネント等を使用しない為、基礎クラス内に位置やシ
ンプルな描画、クリック/タッチ確認処理を実装
 木構造の基本的な実装
 List<GameObject> Child
見たいに直接の子供一覧を保持
 更新描画は再起的に呼び出して木構造全体で
呼ばれる様にしています
 ローカル座標(自身の位置)と
ワールド座標(親の影響を受けた後の位置)
両方を取れる様にしています
 更新処理:
protected List<GameObject> m_child;
public virtual void Update(float delta)
{
for ( int i = 0; i < m_child.Count; ++i )
{
m_child[i].Update( delta );
// … その他の処理
}
}
if ( m_child[i].IsDead )
{
m_child[i].ClearAll();
m_child.RemoveAt( i );
i--;
}
}
}
 部屋の背景は1枚絵で表示しています
 部屋自体もGameObjectで、子供としてキャラ等を配置
 それ以外にも各種個別に使用したり、何らかの判定をしたりする物
は子供を追加する時に別のリストにも追加しています。
 部屋のインスタンスはゲーム全体の状態遷移管理をしている中にあ
る、ゲーム中状態(と呼べば良い?)の中で生成され、管理されてい
ます。
 部屋の移動用データは部屋作成時に読み込んで設定しています(移
動処理の説明時に紹介します)。
 部屋の電気が切れてる状態等の演出用に他のオブジェクトの前に描
画出来るフィルター的な物を設定出来る様にしています。
 背景オブジェクト(画像有り)
 表示の為だけに配置し、状況によっては追加したり、しなかったりする
 選択出来るオブジェクト(画像有り)
 カーソルがルーペに変わる置物。ドア、棚、アイテム等はこれです。
 トリガー(画像無し)
 表示はされませんが、キャラが触ったら処理を呼び出すオブジェクトで
す。腕発見時等に使用しています。
 追尾者用トリガー(特殊)
 プレイヤーに付いて来るキャラが触ったら処理が呼び出されるオブジェ
クトです。主に成仏イベント用です。
 フィルターの前に描画するオブジェクト
 フィルターで電気がついていない演出をしている時に電気のスイッチを
前に持ってくる時等に使用しています。
 部屋の構造、背景、等をどうするかは部屋を作成
した際にその部屋用のスクリプトを走らせて初期
化しています。
 フラグの状態によって結構中身が変わるので、
出来るだけプログラム内に進行に関わる処理を含
めない為にスクリプトに投げています。
 本当はエディター作ろうかと考えてましたが、
基本的に自分(プログラマー)がイベント実装して
いたので、面倒になり妄想で終わりました
(またまた反省しています・・・)
 このゲームは移動先を選択、もしくはオブジェクトを選択し
た際にそこを目的地として移動します。
 目的地に着いた際、選択先がオブジェクトの場合そのオブ
ジェクト内の処理が呼ばれます。
 ドアについた場合、まず退出演出(ドアにズームイン等)、そ
の後に部屋を保持しているオブジェクトに部屋の切り替えし
ろ指令を出しています。
 ちなみに、ドア以外についた場合は、大体の場合はオブジェ
クト生成時に指定しているスクリプトを呼び出しています。
 部屋を移動した際のキャラの位置は元々来た部屋のIDを保持
し、その部屋へ繋がっているドアを探しそこの周辺に設定し
ています。
 部屋移動時の演出にズームとフェードがあり
ます
 理由としてはズーム時にはドアを黒い四角に
表示を変えているのですが、角度があるドア
や、階段等では上手く合わせるのが面倒 &
違和感が出る為です
 ズームして拡大した時の表示もかなり違和感が出
てるので、そこも大きいです
 なので違和感を覚える様な所はフェードで誤
魔化しています・・・ごめんなさい
 このゲームではLuaを使用してイベントの実
装や部屋の初期化を行っています。
 コアルーチンをPsmで楽に動かす方法が解ら
なかったので、泣く泣く毎フレームUpdate()
的な物を回していました・・・orz
 超面倒なのでやめましょう・・・!
 スクリプトの使い方としては、C#内にイベ
ント等で使用出来るメソッドを用意し、Lua
内から呼べる様にしているだけです
 部屋にオブジェクトを追加、削除
 進行によっては鍵等のアイテムを配置
 クリック対応範囲の指定もスクリプトから生成時に行って
います。
 キャラの移動、速度変更、等の設定変更
 フラグ設定、変数設定
 SE再生、BGM切り替え、一枚絵演出、等演出系全般
 別のスクリプトを次に呼ぶ予約
 部屋に入ったらイベントを開始したい場合、部屋の初期化
の最後に次のスクリプトを指定して予約する
 ゲーム内には大まかに分けると、3種類のスクリプトが有り
ます
 1. 部屋の初期化
 既に説明した様に部屋に入った際に初期化を行う
 2. オブジェクト選択時用のスクリプト
 部屋に配置するオブジェクトにプレイヤーが辿り着いた時に呼
び出すスクリプト。中で通常のセリフ等を呼ぶか、イベントを
開始するかの判断をしている時もあります。
 3. イベントのスクリプト
 主にプレイヤーが操作出来なくなる演出処理の進行を行ってい
ます。基本的にはイベント番号 = 進行度で管理しています(単純
なゲームで良かった・・・ホッ)
function Update()
-- 進歩獲得
progressID = GetProgressID()
-- データ読み込み
LoadTexture( "Textures/Rooms/room0/background", "bg0" )
LoadTexture( "Textures/Rooms/room0/Hammer", "Hammer" )
-- 背景設定
SetBackground( "bg0" )
-- カメラ移動出来るかの設定
SetScrollable( false )
-- === 物配置 ===
if IsPlaceHammer() then
AddBackgroundObject( "Hammer", 701, 381, 0, 0, "" )
End
-- 工具箱
AddObject( 151, 316, 178, 158, "Objects/Room0/Toolbox0" )
if progressID == 0 then
-- ゲーム開始時
else
-- 出口追加
AddStairs( GetCorridowID(), false, 475, 155, 137, 252, "" )
-- 棚
AddObject( 665, 0, 142, 476, "Objects/Room0/Shelve0" )
-- ポスター右
AddObject( 213, 105, 97, 102, "Objects/Room0/Poster0" )
-- ポスター真ん中
AddObject( 101, 105, 97, 150, "Objects/Room0/Poster1" )
-- ポスター真ん左
AddObject( 0, 61, 73, 164, "Objects/Room0/Poster2" )
end
-- スクリプト終了
if progressID == 0 then
PlayNextScript( "Events/Event0000" )
else
OnEndScript()
end
 ゲームの進行度を見る用にIDを保持
 先ほど紹介した変数の所で保持
 主に現在の進行度とフラグ設定をスクリ
プト内で見て、各設定を行っています
 イベント中はカーソルやタッチの入力に
対応しない様にし、セリフの更新はスク
リプトから確認、対応しています
 移動処理にはA* (えーすたー)と言う経路探索
を使用しています。
 移動できる場所を保持し、現在地から目的地
への経路を探索します。
 現在地はキャラの位置、目的地は選択した位
置 or オブジェクト
 移動範囲は部屋をマス目で区切り、移動出来
るマスにはソレ用の値を設定し、出来ない箇
所には値が無い用にしています。
 別の適当なプログラムを
でっち上げました
 部屋を32 x 32のマスで区切
る
 ピンクになっている所が移
動可能なマス
 左クリックで移動可能に
 右クリックで不可能に
 設定を書き出したり、読み
込んだり出来ます。
 吐き出したファイルはそのま
まゲームに使用されています。
 ゲーム内のマップの各マスには、位置、歩けるか、隣接して
るマス、目的地までの距離、道筋に追加したマスへの参照、
既に確認/計算が行われたか?、等が保持されています
 目的地用は随時計算
 やる事は開始位置のマスから隣接している各マスを延々と回
し続け、「一番目的地に近いマス」を探し続けて道のりを計
算します
 移動可能経路の総当りでは無く、大まかな距離計算を行う事
で、検索する範囲を狭めた上で早い経路を見つけるアルゴリ
ズムです
 目的地計算の為、「通れるマス」のリストと、「一度確認し
たマス」のリストを計算時に保持し、使用しています。
 1.探索に使う値とリストを全部リセット
 2.開始位置と目的地までの大よその距離を
計算します。その後に通れるマス一覧に追加
します
 Heuristic Function (H) と言う方法で計算します
 基本以下の様に計算します:
private float Heuristic(Point point1, Point point2)
{
return Math.Abs(point1.X - point2.X) + Math.Abs(point1.Y - point2.Y);
}
 3.現状通れる事になっているマス一覧を見る物が無くなる
まで処理を回す(whileとかで)
 初回は開始地です
 4.一覧の中から目的地に一番近いマスを探す
 一覧が空だったり、何も見つからなかったら検索を終了し、検
索結果として空値を返す
 debug時はassertとかでもいい気がします
 1度目は開始地点なので、探すも何も無いです
 5.獲得したマスが目的地の場合、道のりを再度定義して結
果として返します
 マスに保存してある自身を追加したマスの参照を辿り、来た道
のりを逆算します。その位置情報を順番にList<Vector2>として返
し、これを移動の道のりとしています
 違った場合は6へ続きます
 6.もし獲得したマスが通るマス一覧、もしくは
確認済みマス一覧に無かった場合、獲得したマス
の全ての隣接したマスで以下を行う(移動可能の
マスのみ)
 開始位置からの距離をマスに書き込む
(開始から現在のマスまでの移動距離 + 1 )
 ゴールまでに掛かる距離を書き込む
(上記の値 + Heuristic )
 その隣接マスを通れるマス一覧のリストに追加
 その隣接マスをリストに追加したマスとして、現在の
マスへの参照を設定
 [*]既に追加済みの場合は7へ飛ぶ
 7.もし今見ている隣接しているマスの
目的地までの距離が現在の移動距離より
長かった場合、6と同じ事をする
 正し、移動予定と確認済みの一覧リストには
追加しない。
 8.[4]で獲得した今見ているマスを確認
済みリストに追加する
 9.ループ一回りが終わり、この時点で
は前回のマスの隣接マスで移動出来る物
が移動予定リストに入っているので、そ
こからまた4~8を繰り返します。
 後は4の失敗か、5の成功まで繰り返し
ます。
 長い上に解りづらい・・・
 移動する際、選択した位置のマスを計算し、経路
探索を行っています。
 オブジェクトは移動出来ない場所にある場合が多
いので、各オブジェクトの選択時に移動先を獲得
する処理が入っています
 デフォルトでは、オブジェクトの真ん中の一番下の位
置を返しています。
 そこから移動先マスを計算する際、同じ位置にあるマ
スか、そこから下方向にマップを検索していき一番最
初に見つかった移動出来るマスを返す様にしています。
 もし移動先が無かった場合、左右に一列づつずれて同
じ様に下方向に検索する事を繰り返しています。
 経路検索で獲得したList<Vector2>のリストを頭らから順番に
目的地として移動して行きます。
 ここは特に特別な事はしておらず、目的のマスへの向きを計
算し、向き * 移動速度で移動しています。
 キャラが画面奥に移動すると小さく、手前に来ると大きくな
る様にしています
 これは単純にY座標と適当な数値を架けてスケールを計算してい
ます
 NPCもプレイヤーと同じ目的地に向かって移動しているだけ
です
 速度を遅くして誤魔化しています・・・
 でも移動をやめるとプレイヤーと重なります orz
 ゲームの状態(ステート、シーン)はブログで紹介
しているゲームの状態変更の実装方法 を元に作成
しています
 日記を開いた際には新たに日記用の状態を追加し、
そこに切り替えています
 日記画面の描画時に元々の画面も表示する様にし
て元の画面の上に表示する様にしています
 日記終了時に元々の状態に戻す様にしています
 気になる方がいるか解
りませんが、ページを
めくる時の処理は全部
2Dでやっています。
 3D メンドイ・・・
 まず空のページをめくる側の位置に新たに空のページ画像を
作ります。
 それを徐々に小さくしていきます
 左側の場合はサイズと位置を調整
 右の場合はサイズのみ
 同時に、めくっているページの画像もサイズと位置を変更し
ていく
 と同時にフェードもしていく
 ページが真ん中(横幅0)になったら、今度はページの画像表
示を反転した上で横幅を増やしていきます。
 ナイスごまかし!
 Psmのサンプルと某ゲームを参考にしています。
 フラグ等を管理しているクラスの情報をToString()で書き出し、
byte[]に変換しています。
 Byteに変換したデータをxmlに保存しています。
 書き込み時に画面が動かないと止ってる感があるので別ス
レッドで作業をし、待ってる間は描画を微妙に変える様にし
ています
 [Saving…] の点の数を1~3の間で変えて行ってます
 実装はブログで紹介しているマルチスレッドでコンテントの読
み込み とXNAの機能を使ってセーブする方法 の似たような処理
を行っています(セーブ/ロードにはXNA的な機能は使ってません)
 非同期処理をデータ読み込みと同じ様にセーブ管理の基礎クラ
スで行い、継承したクラスのProcess()内でセーブをやっています。
 ゲーム内から1ボタン/キーでデバッグメ
ニューに飛べる様にしていました
 デバッグ画面中はゲームの進行は止って
います(更新、描画が呼ばれていません)
 面倒だったので、見かけと操作は適当で
す・・・
 進行度表示/設定
 言語設定
 結局日本語しかないので、基本未使用
 イベント選択
 指定したイベントを再生
 部屋のやキャラの位置に不一致が起こるので、専用のテーブルを用意し、それを参照する様にして
設定を行っています
 なので、ある意味手書きで設定しています・・・
 フラグ設定
 フラグのTrue/Falseを切り替えられます
 変数(数値)設定
 ゲーム内の変数の数値を変更できます
 アイテム設定
 アイテムの追加、削除が出来ます。ゲームでは各アイテム1つのみしか手に入りませんが、一応最
大数まで増やす事も出来ます( 0~99)
 UIにも反映する処理を行っています。
 テーブル再読み込み ( 次のスライドへ続く・・・)
 テーブルを再度読み込む事で、ゲーム実行中でもデー
タ修正を反映出来る様にしています。
 やってる事自体は簡単で既に読み込んでるデータを破
棄し、再度読み込むだけです。
 Excelで修正 > バッチ使う > 再読み込み > 反映!
 最初の方に説明したバッチで出力先にビルド先のフォ
ルダも含めているのは、この流れを簡単に行う為です。
 ちなみに、Luaのスクリプトもスクリプト実行時に読
み込んでいる為、編集して再度イベントを開始すれば
ゲーム起動したまま修正確認が出来ます。
 前のスライドの画像にある様に、部屋の情報
表示を切り替えられる様にしています。
 歩ける範囲/マス
 ピンク色の部分
 オブジェクトの当たり範囲/クリック範囲
 緑と、青の部分。
 クリック可能の場合はルーペに変わる範囲
 画像にはありませんでしたが、トリガーも表示
 表示は1x1の画像を当たり範囲に描画しています
 Farm Fury! 技術資料
 XML読み込み
 Xml読み込みの汎用化
 メニューの作り方
 メニューの作り方
 メニューの作り方3:サイズが変わるメニュー
 ゲームステート(シーン遷移)の作り方
 ゲームの状態変更の実装方法2 (クラス編)
 ゲームの状態変更の実装方法3 (クラスの再利用化)
 会話のテキスト表示系
 流れるテキストの作り方
 自動改行するテキストボックスの作り方
 エフェクト
 振動エフェクトの作り方
 パーティクルについてはFarmFury! の時と同じです
 カメラ
 2Dゲーム用のカメラの作り方
 アイテムを拾った時の演出等で使うTween系(Lerp系)処理
 Lerp系処理を流用する方法
 Tween(Lerp系)処理後に何かをする方法
 当たり判定
 2Dゲーム用の当たり判定
 デバッグ
 FPSカウンターの作り方
 自動通しプレイ機能
 デバッグ大事! 1度進行不可で審査落ちた!
 エディター
 スクリプト全部書くとミスしやすい
 他の人に丸投げ出来ない
 Luaだとインテリセンス(ゆとり)が無いから・・・
 リアルタイムでのライティング
 画面の暗さ調整がアホみたいだった・・・
 翻訳対応
 翻訳する時にそなえて英語用のテーブルや画
像を変える処理もデータのフォルダや仮デー
タも準備はしてありますが、結局使っていま
せん。
 自分でやるのはメンドイです
(英語書くのは苦手・・・)
 実装は言語によって読み込み先を変える様に
しています
 紹介されていない物で知りたい事や、質
問、疑問、ツッコミ、等があればご連絡
ください
 Twitter: @ookumaneko_XD
 ブログ: http://ookumaneko.wordpress.com/

幽霊の棲む家 技術資料