ゲームに限らず、プログラミングに関しては書ききれないほどのTipsが有る。
ただ、いきなり全部書いてもしょうがないので、まず基礎的な話から書いてみる。
■概要
ゲーム作りの基本を解説する。ソースコードは一部SourceForgeにて公開してるので、各自チェックのこと。
まず注意すべきは「迷ったら過去の遺産を使うこと」であり、自分でモジュールやエンジンを作ろうと思わないこと。
プログラミングの鍛錬とゲーム製作を両立させることは非常に困難である。
プログラミング能力を上げたいなら、それ相応の勉強や、ソフトウェア会社への就職の方がよっぽど効果的である。
一挙両得を狙うとどっちつかずになるので注意。
もちろん趣味でプログラミングも知りたいというなら止めないが、当然茨の道となる。ここら辺は目的次第。
■ゲームエンジンとライブラリ
昨今、ゲームエンジンの興隆がめざましく、新規で開始する場合の選択の幅が広がりつつある。筆者はC++/DXLib派なので解説はしないが、参考までに。
目的別にまとめると下記となる;
統合エンジン系
最初から必要なライブラリが揃っており、開発環境と一体化しているもの。ちょっとしたゲーム製作には最強と言える。
逆に大規模化したり、特殊な機能を付けようとすると途端に壁にぶつかる。
Unity
○プログラミングする量を大幅に減らせる○3Dならほぼこれ一択
○JavaScrpit/C#がメジャー
×枯れていないため、アプデなどによる影響を強く受ける
×同様に、ネット上での情報は少ないか目的に沿わない。英語必須。
×自分で作るよりアセットを厳選する方がマシな世界
×アセットについてのノウハウが無いと厳しい
UE4
○ほぼUnityと同一×元々プロ向けだったため、アマチュア向け展開はやや出遅れた。
その他エンジンは省略(Cryエンジンとか)
RPGやADVについてもアセットが出ている(宴とか)。ここら辺はもう情報つかんだもの勝ちのパイオニア畑となる。
2Dエンジン系
一応3Dも使えるが、メインは2Dのエンジン。GameMaker
BBやCCなどが作られたエンジン○作成開始から完成までの時間がかなり短い
○老舗エンジンであるため、豊富なチュートリアルがある。
×VerUpにより、ファイルアクセス系で問題があるもよう(ここら辺は様子見?
ClickteamFusion2.5
○バーを置いてプロパティを編集するタイプなので、プログラミングが不要×逆に言えばプログラミングできないため、特殊すぎて学習コストが高いアクションゲームツクール
UnholySanctuaryなどが作られたエンジン○あんまりない
×キー配置が変
×特殊な動作はほぼ無理
アクションエディター
生贄の箱庭などが作られたエンジン○直感的(らしい
×リソース制限がきつい
×特殊な作り方になるので、学習コストは0ではない
×アクションの動きが悪くなりがち。自然な挙動にならない
他は筆者が知らないので省略
いずれも言えることだが、規模がでかくなるとリソース周りで制約を受けることも多い。
ライブラリ系
Siv3D
近年知ったライブラリ。IPA参画PJのようで、最新のC++にも対応している(C++11とか13)○VS2015など、最新のVSでビルド出来る
○UIの強化が半端ではない。直感的に近代的なアプリを作製出来る
○Win32APIを叩く処理にも強く、ファイル操作などI/O処理が全体的に強い
×変更が激しく、枯れるには相当時間が必要
DXLib
過去の遺産があるのでここから離れることはあんまり無さそうな○C++と親和性が高い
○2D関連は大体機能がそろっている
○C++のモジュールも当然利用出来る
×C++べた書きに近いため、エラーも当然自分の責任で発生する
ほぼC++で書くことになるので、最高の融通性と、100%技術依存の問題を同時に得ることになる。
リソース管理も自分で行うため、いきなりC++でやるのはかなり分が悪い。
SDL
マルチプラットフォームライブラリ(ラッパー)として一時知られていたもの。今でも一部では使っているかも知れないが、ハッキリ言ってDirectX5時代の能力がメイン。
3DはOpenGLになる。
本ページではC++/DXLibでの作り方に限定する。
他のエンジンについては、知らない事の方が多いので書けない。
他ジャンル系
ADV:吉里吉里、LiveMakerSLG:SLG作成ツール(DLsiteにあるやつ)、SRC
RPG:ツクール、ウディタ
ここら辺は食指が動かないので触っていない。
ただ、ARPG(ゼルダ風)はツクールやウディタではほぼ無理。ツクールMVは未知。いずれ出来るかも知れない。
ツール群
エンジンではないが、今後重要になりそうなツール及び技術群メタセコイア、MMD、Blender
要するに3D。○3Dゲームへの需要が散見される
○表現の幅が大幅に上がる
×モーションに嘘がつけない。作成コストが高くなる(適当に作ると2Dより酷くなる)
Spine,SpriteMaker
つまりパーツアニメ。最近はメッシュ変形(自由変形、立体変形)も出てきており、表現の幅が増えつつある。
○動きが付くだけでぐっと良くなる
○分担できるため、製作の負担が分散できる
×モーションについては3Dと同様嘘がつけない。下手に作ると酷い
×学習コストは3Dと同じかそれ以上
3Dもだが、単調な前後モーションだけではNGで、緩急を付けたり、残像などをこうりょしたものにしないとシュールなモノになる危険性が高い
Live2D、emote
AfterEffect、XXCake
要するに1枚絵を変形させて、動きを付けるもの
○息づかいや前後の挙動が付けられる
○セルアニメーションよりは難易度が低く、嘘も付きやすい(セルアニメに比べれば。)
×これも下手にやると妙な感覚になる(ひんまがったりする)
×AEはソフトもそれなりにする。
×学習コストがLive2D/emoteより高い(つまり表現の幅が高いとも言える)
最近は2Dでも動きを付ける動きが活発になってきている(それだけ習熟者が増え、ツールも使いやすくなってきているということ)
基本的にはどれも学習コストは安くない。やりたいと思うモノにマッチする物が有ったら挑戦してみよう。
表現の幅が高まると、嘘をつけなくなる。良い塩梅を見つけるのも重要だし、最初はドットから始めるのも良い。
■ゲーム動作の基本
ループを作る
何度か書いた話だが、ゲームというのは大体下記の流れで動く;- 初期化
- データのロード
- シーンの選択
- 操作読み込み
- シーンの表示
- シーンの動作
- (シーンの切り替え)
- ウエイト
- OS割り込み処理
- 上記4へ戻る
シーンについて
「シーン」(Scene)という考えはゲーム上重要で、簡単に言うと画面がかかれたカードに相当する。(この「カード」という概念はMacのハイパーカードが分かりやすい)
タイトルというシーンがあって、そこからゲーム本編やゲームオーバー画面、クリア画面、スコア画面…といったように、作る必要の有る画面=シーンが思い浮かぶ。
まずこの「シーンを『全て』書き出す」こと、そして「シーンを管理すること」がそれなり以上の規模のゲーム製作では必須となってくる。
シーンの設計が一個でも足りなければ、ゲームは完成しない。
シーンが1,2枚ならそれでもOK。
シーンはクラスを作って管理するのが一番簡単で、アブストラクトクラスを作ってI/Fを決定。アブストラクト関数から継承して処理を構築するのがえびせんスタイル。
シーンクラス
class AbstractScene {シーン切り替え時は、
public:
// 識別ID。enumとかで管理
int m_nSceneType;
public:
// 仮想関数。継承先で実装する。
virtual void init()= 0;
virtual void quit()= 0;
virtual void process() = 0;
};
・前のシーンで暗転処理(process())
・前のシーンのquit()
・次のシーンのinit()
・もし必要ならフェードイン処理(process())
と言う流れになる。
管理クラスもあった方が良い
class SceneManager {ここでポインタにしてるのがミソで、要するにポリモーフィズム(上位関数から継承サキの関数が呼べる)
public:
mapm_mapScenes;
public:
// Getter/Setterは省略
};
ポリモーフィズムとかオブジェクト指向とかはくいなちゃんの記事を読もう。ほぼ完全に説明されている。てか何者?
正直いきなり言われても分からないと思うが、最初は「int nSceneType = 0;」とかでswitch分岐で処理を分けても良い
switch(nSceneType) {みたいな。変数の共有がまずくなるので次はこれをクラスに分けて、最後は上記ポリモーフィズムにする。
case 0:
doTitle();
break;
case 1:
doGame();
break;
}
方法はこれだけではないが、大抵最後はこの方式が多い。(タスクシステムも結局はこれ)
オブジェクトの管理
次はオブジェクト。オブジェクトは継承継承アンド継承が基本。
いちいち同じパラメータを書くのはバグを産みやすい。
かといって全部親クラスに書くと融通が利かなくなる。選別は経験で行うことが多い。
オブジェクト単体クラス
目安的には下記;・基礎オブジェクトクラス
・中心座標
・存在範囲
・動くオブジェクトクラス←基礎から継承
・移動量(velocity)
・ゲームオブジェクトクラス←動くから継承
・当り判定
・生き死にフラグ
・種類
・オブジェクトクラス(敵、ショット、)
・拘束されているかどうか、しているかどうか、相手は…
などなど
・プレイヤークラス←ゲームObjから継承
・状態異常
などなど
ここはHPLShootingObject.hが詳しい。
でもって、これを種類分けする。
あまり細かすぎても、大まかすぎても問題がある。ゲームに応じて選択する。
(特にエフェクトとショットとオブジェクトと敵は曖昧になりがち)
種類分けする利点欠点
○細かいほど全体の制御や前後表示処理を扱いやすい。
(キャラより上に表示するオブジェクトと下のオブジェクトを分ける、動かない敵や動く敵を分ける、など)
○種類による条件分岐を書く必要が少なくなるので、オブジェクト指向的利点が高まる
×クラスが増えるのでコーディング時間が滅茶苦茶増える
×同じような処理を複数のクラスで書く場合があり、まとめた共通関数が必要になったりして分けた意味が有るのか不明な場合がある。
管理クラス
作ったら管理する。いろいろ有るが、個人的にはlistが早くて良い。vectorは追加削除がO(n)になるので、最悪O(nlogn)となりよろしくない。
ランダムアクセスはvectorが早いので、固定なら考えても良い。
listは連結してるので、基本イテレータでアクセスする。
class EnemyManager{ public: list・追加m_lstEnemies;};
Enemy* lpEnemy = new Enemy();※敵のループ中に敵を生成するとおかしくなるので、そういう時は別のリストを作って後で追加する
this->m_lstEnemies.push_back(lpEnemy);
・参照
for(list::iterator it = this->m_lstEnemies.begin(); it != this->m_lstEnemies.end(); ) {
Enemy* lpEnemy = *it;
it++;
}
・削除
if(削除する){
delete lpEnemy;
it = this->m_lstEnemies.erase(it);
} else {
it ++;
}
eraseの前にdeleteするのを良く忘れるので注意。
状態遷移
基本キャラクタは状態によって見た目を変える必要がある;・いつ(どのタイミングで)
・どこで(場所やシーン)
・何が(キャラクタの種類)
・どのように(これはまあ、状態管理なので「立ち」「歩き」「ジャンプ」とかに対応して画像番号を設定すれば良い)
上記あたりを満たせばゲームになる。
ここまでで十分ゲームになる。次は鬼門となる項目と、その対処方法(方針)。
■鬼門と対処
ゲームプログラムには鬼門が幾つかある。・画像管理
・地形との当り判定
・拘束処理
画像処理
画像はハンドラ/ハンドル(整数)で管理することが多いが、何百枚もあったらどう管理するのかという話になる。(ハンドル=取っ手、つまり、ここを掴めば欲しいモノが手に入ったり、動かしたり出来るもの)
ここら辺で重要なのは「紐付け」
通し番号→画像ハンドラ
画像をDXLib/LoadGraph()でロードするとハンドラが出る。これに0,1,...と番号を付ける。
番号はenumで管理すればいいので、150番目の画像を引っ張れば、画像ハンドルが得られる。
ここまでで一旦管理クラスを用意。
class ImageHandleManager{
public:
vectorm_vecHandles;
};
シーケンスデータ→通し番号
アニメーションはシーケンス、すなわち絵の連続である。アニメ1枚目→画像No.120
Nフレーム(x1/60秒)表示
アニメ2枚目→画像No.121
Mフレーム表示
…
といった具合に処理することで、アニメーションさせられる。
画像1枚毎に表示時間と表示範囲があるので;
class ImageInfo {で、アニメーションを管理するのはこっち;
public:
int m_nHandle;
// 待ちフレーム数(1/60秒)
int m_nWaitFrame;
// 切り出し範囲
HPLRect m_rSrcRect;
};
class Sequence {あとは、このシーケンス情報をアニメ種別番号(立ち、歩き、ジャンプ)と紐付ければOK。
public:
vectorm_ImageInfos;
// 現在のフレーム数(待ちフレーム用)
int m_nStepCounter;
// 今何枚目か
int m_nImageIndex;
// 現在のループ回数
int m_nLoopNum;
// 最大ループ数(-1で無限とか。
int m_nLoopNumMax;
};
class Sprite {public:vectorm_vecSequences; };
そしてスプライトも管理クラスを作る。
class SpriteManager{ public: vectorm_vecSprites;};
namespace Sprites {こんなもんでOK。
enum Sprites {
SpriteA,
};
}
namespace Sequences {
namespace CharacterA {
enum CharacterA {
};
}
}
状態遷移と紐付けても良いが、大抵状況によって処理を変えたりする。
そのため、最終的には下記が現実的;
状態遷移(int nStateType):立ち状態、歩き状態
→アニメ種別(Sprite):立ち状態、瀕死立ち状態、歩き状態、走り状態、…
→シーケンス(Sequence):立ち状態アニメシーケンス
→画像情報(ImageInfo):画像1枚毎の情報
「状態遷移→アニメ種別」はもうスイッチ文でも良い。外部化しても良い。好きにする。
ゲームを作っているうちに、状態遷移の種類は100や200を越えて来るので、もう面倒になってきたりする。
地形との当り判定
そのうち書く拘束処理
拘束処理はエロリョナ、あるいはそれを想起する一般ゲームでは必須の処理となる。やり方は幾つかある
A:位置を調整して重ね合わせる
B:合体した1枚の絵にしてしまう
融通が利くのがA。安定度や確実性はB。
基本構造としては;
・互いに情報を交換して拘束状態にする
・表示時に気を付ける(片方のみ表示、位置を常に固定する)
・アニメーションに合わせて、音を出したりダメージを与えたり解放したりする。
情報交換
・浅い結合相手の生成通し番号/種類/オブジェクトタイプ
・深い結合
相手のポインタを持つ
○相手の情報にフルアクセス出来る
×相手が死んでたりすると存在しないアドレスへの参照となり得る
→NULLなら強制解放とかでもいい
よほど相手が固定されているのでなければこれは使わない
表示処理
拘束されてたら表示するしないとかくらいで、特に注意点は無い。あんまり個別処理を入れまくると後で何がなんだかになるので、どちらかに統一したりするのが良い。
アニメーション連携と解放
SEのタイミング合わせは本項とは関係無いので省略。SequenceのImageIndexとStepCounterを見れば良い。拘束アニメはループとかもあるので細かく分け直すので、このアニメシーケンスが終わったら次は何になって、どのアニメシーケンスになったら解放(拘束解除)にするのかを記載する必要がある。
規模にもよるが、個人製作ゲームならハードコーディングで良い。
注意点
1.どっちが拘束情報を持つのか(それとも両方が持つ?)2.どっちが位置の起点となるのか(瞬間移動を防ぐ)
3.どっちのアニメーション処理で解放までの処理をするのか
[1]拘束の種類が複数有る場合、どっちが情報を持つのか。
どっちでもいいのだが、プログラムを書く上ではどっちが早いかと言う話にもなる。
大量に存在する敵を検索するよりは、プレイヤーを検索(というかアクセスするだけ)の方が早い。
しかし、どこの処理からアクセスするかによっては、逆も有り得る。
同じ情報を双方で持つというのも手。(その場合どっちのみ表示するかなどの設定も要る)
[2]例えばどっしり構えてる敵がいきなりプレイヤーの位置に来るのはおかしい。
逆に小さいモンスター側にプレイヤーが移動するのもなんかおかしい。
なので、どっちを起点にするのかは拘束情報として持たせるのもアリ
[3]アニメーションデータを敵が持ってるのかプレイヤーが持っているのか。
それによって処理が分かれるので、管理しやすくすること。
(面倒名のでハードコーディングすることが多い)
0 件のコメント:
コメントを投稿