フリーゲーム・フリーソフトの開発過程を記録していく、TDtechnic公式ブログです。製品はカテゴリの「ダウンロード場」からダウンロードして頂けます。
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

こんばんは。ようやく不死女の公開まで漕ぎ着けました。ふりーむ!様、フリーゲーム夢現様での公開となります。以下のリンクからどうぞ!

ふりーむ!様
フリーゲーム夢現様
スポンサーサイト

[2015/09/09 19:44] | 不死女 -Immortal girl-
|

レビューをさせていただきました。
キャベツでエビ釣る
初めまして
事後になってしまいましたが
今回、不死女 -Immortal girl-を誠に勝手ながら
ブログの方でレビューさせていただきました。
何か問題点などありましたら遠慮なくいってもらえると
助かります。
どうぞよろしくお願いします。

レビューありがとうございます!
Shiro
ブログを拝見しました!記念すべき第一回に不死女を選んでいただけるとは光栄です。不死女のポイントがしっかり解説されていて素晴らしい記事たと思います。レビュー&ご報告ありがとうございました。

お久しぶりです
Sva
こんにちは。twitterで拝見してからずっと気になってました!!遂に、遂に完成したのですね^^おめでとうございます。
今は学校のパソコンなのでプレイできませんが、是非やりたいです。
イラストのお話もいただいていたのに、何せ自分もこの年はストレスからの胃炎、過敏性大腸炎、胃カメラにお世話になっていたのでお引き受けできず申し訳なかったです…
Shiro様も体調にはお気をつけて頑張ってくださいね

お久しぶりです!
Shiro
こんばんは。完成した、といいますか、どうにか完結させた…という感じですが(笑)、ありがとうございます。
私もたまにTwitterを拝見して心配していたのですが、Svaさんもいろんな症状が出ているようで…お大事になさって下さい。まあ、退院後すでに3回気胸を再発させている私の言えたことではないかもしれませんが…。

コメント:を閉じる▲
 こんばんは。不死女、ようやく完成しました…というか完成ということにしましたって感じです。なんでこんな言い回しかというと、当初予定していたボリュームより大幅に薄っぺらいからです。まあ毎度のことかも知れませんがね…。決定的な敗因は、やはり私はイベントとかアイテムといった「ゲーム本編」を考えるのが苦手だということでしょう(なんでゲームプログラマーやってんだって話ですけど…)。

 でもアイデアが浮かばないのはそれだけが原因じゃないと思ってます。汚いコードと不自然な設計のために柔軟な開発が難しかったことも大きかったでしょう。やっぱりこういう「ストーリーがあるゲーム」を作るときは、マップエディタまで作るぐらいの勢いでやらないとダメですね。でも私は恥ずかしながらつい半年前ぐらいまでハードコード大好きプログラマーだったんで、コードが外部ファイル化を全く想定しておらず、この点では完全に詰んでたんですね。次回こそ、はじめから外部エディタを前提とした設計にしようと思います(それを早く始めたいから、不死女を早く終わらせたというのもある)。

 ネガティブなことばっかり言ってますが、一応完結したことについては喜ばしいことだと思っています。あちこちに未成のイベントやアイテムの痕跡がありますが、とりあえずクリアはできてエンドロールも見れます。タイムアタックモードも完備。あとは説明書付けてテキスト版のクレジット付けてテストプレイしてふりーむさんにうpしようと思います。正直売れ行きは記憶ほど行かないと思ってます。記憶は「ホラー」という言葉に釣られて期待してプレイして下さった方が多くみえたのですが、今作は堂々とFPSと名乗ってますから、ジャンルの人口を考えれば記憶のようにはいかないでしょう。まあいいんです。上記のように完成度は作者も低いと思ってますし、そもそもこのゲームを作った目的は「初めての技術に積極的に挑戦する」ことですから。IME、スキンメッシュアニメーション、プログラマブルシェーダー…これらを練習できただけでも、すでに目的は達成しているのです。そう言って不死女をここで開発終了とすることを正当化しているのです(笑)というわけで、公開までもう暫くお待ち下さい。

[2015/09/08 23:23] | 不死女 -Immortal girl-
|
お待たせしております。Shiroでございます。

不死女は、あとはラスボスのAIを作れば最低限クリア出来るようになるんですが、こういう時に限ってAIのパターンが思い浮かびません。イメージしているものはあるんですが、ラストステージのマップ自体から見直さないといけないと思います。やはりステージと敵はセットで考えるべきですね。

不死女が進まないとたいてい別のプログラムを書いています。この間はあいしやす.fla様のゲーム「ぱちぇコンウォーズ」のマップエディタを勝手に作り(今のところ配布はしておりません)、最近は自制のために寝る時間になったらPCを半強制的にシャットダウンするプログラムを作りました。もう、アイデア勝負のゲームを一人で作るのは懲り懲りです。不死女が完成したら、次回作はアイデアよりもゲーム性を中心に勝負できるゲームにします。レースゲームとか戦略シミュレーションとか。

[2015/07/03 09:45] | 不死女 -Immortal girl-
|
こんにちは。5面も最低限出来て、いざ6面を作ろうという段階に入ったんですが、思わぬ壁にぶち当たりました。6面では闇の表現をするために黒色のフォグをかける必要があるんですが、こいつが次から次へと問題を投げかけてきたのです。

最初は、固定機能のフォグを利用しました。やってみると、プログラマブルシェーダと同時に使用しても特に問題無いように見えました。これを見て数日は喜んでいたんですが、なんとフルスクリーンとウィンドウの切り替えや画質切り替えのためにResetメソッドを呼ぶと解除されてしまうことに気づきました(ただし、一旦デバイスロストさせてResetで復帰するとなぜか復活する)。まあーぬか喜びさせるひどい仕様だと思いましたが、やっぱりプログラマブルシェーダを使うならフォグも自分で書くのが正しいのでしょう。シェーダコードによるフォグ実装に踏みきりました。

幸いフォグのシェーダコードのサンプルが「DirectX9実践プログラミング」に載っていましたので、早速全部定数でそいつを入れてみる。よし動いた。じゃあ次は定数を各種変数にして、と…あれ?

…3D部が何も描画されなくなりました。

いやいや、と変数を定数に戻してみる。やっぱり動く。ちゃんと「均一なフォグ」がかかりました。同じ式で、変数にしてみる。映らない。はぁ?
なにがおかしいって、その時は試験的にフォグの対象を「テクスチャのある面」に限定していたんですね。もちろん定数フォグはその面だけ反映される。ところがバグった時は「テクスチャのない面」も含めて全部表示されなくなる。これはつまり、フォグの値がおかしいのが原因じゃないということです。もし値がおかしいだけなら、表示されなくなるのは「テクスチャのある面」だけのはずです。

しかしながら、この様に「コード上で影響を与えないはずの部分」に影響が出るということは、相当低水準な問題だと思いました。ひょっとしてサンプルが間違っているのか?いや、サンプルコードに限らず、どうやら変数を使うコードを書くとバグるようだ。うーん…試しにコードを減らしてみる。あれ?変数使っても大丈夫だ!もう一度コードを増やす。真っ暗だ!

どうやら命令数が多いと映らず、少ないと映るということが分かりました。というわけでこれは恐らく「命令数が上限を越えた時の症状」だったと思われます。どこにも載ってないので確証はないんですが、とりあえずそう思っておけば実用上は問題ないと思います。しかし、命令数多すぎるんならコンパイルしたときにエラーにしてくれればいいものを…。高水準プログラマがこういう挙動に出くわすと、かなり狼狽しますね。C/C++では見たことがないバグり方だったので。

ところで、命令数の多寡がバグの原因なのは良いとして、「定数だと動いて変数だと動かない」というのは何だったのでしょうか?実のところこれもはっきりしたことは分かっていません。あくまで推測の域を出ないのですが、恐らく定数の場合はコンパイル時に計算してその結果だけを定数として埋め込んでいるために命令数が減ったのだと思われます。これであれば「命令数が多いと真っ暗になる」仮説とも辻褄が合います。ただ、なぜか最適化をオフにしてもこの症状が出たので、その点では疑問が残ります(定数展開は、最適化オフでも実行される!?)。

最後に、晴れてフォグのかかった表示が出来たんですが、ところどころかかり方がおかしいところがある。コードを見てもさすがにこのバグり方はシェーダのせいじゃないな。モデルデータと比べてみると、どうやら「隣の面と頂点を共有していない面」で発生しているようだということがわかりました。なるほど、分割が粗い面と細かい面が連結されず並んでいる場合、細かい方はなめらかなのに対し粗い面は均一な範囲が多いから確かに境目で不自然になりますね。というわけで前述のような面を張りなおしてテスト。よしいい感じだ。
ちなみに、固定機能のフォグの時はこの症状は発生しませんでした。なぜなら固定機能で行っていたのは「ピクセルフォグ」という、画素ごとにかけるフォグだったからです。対して今回私が書いたようなフォグは「頂点フォグ」と呼ばれるものです。こちらは頂点単位で計算されるものですから、必然的に頂点の数によってかかり方が変わってしまったというわけです。

というわけでプログラマブルシェーダとも喧嘩をしては少しずつ仲良くなっていっている感じです。そのうちもっとシェーダを中心としたシステムも作ってみたいと思っています(というか次回作はそうなるかも!?)。

[2015/05/22 10:14] | 不死女 -Immortal girl-
|


skallo
フォグのグラフィックはどんどん進化していくね
煙や土埃、吹雪の表現を追求したゲームは多いけど闇のフォグとはまた珍しい・・


Shiro
> skallo さん
まともに光の計算をしていると重すぎるので、簡単なフォグで代用した次第です。ひょっとするとあなたの想像しているような美麗なフォグとはかけ離れたものかも知れません…。

コメント:を閉じる▲
そろそろ広告が出るので更新します。最近はツイッターでちまちま進捗を報告しているので、こっちの更新は疎かになりそうです。技術的な難関はほぼクリアしちゃってますし、ほんとにあとはゲームデザインばっかなんで。

というわけで今回はツイッターでの報告のまとめになっちゃうんですがよろしくお願いします。まず、前回発表したスクリプト化計画は一応成功しまして、まずまずの出来です。機能的には貧弱なもので、安全性も低いのですが、これらは以下の点から問題無いと考えました。

①スクリプトはあくまで「処理手順」を示すものなので、「処理内容」まで記述できる必要はない
現状の私のスクリプト言語には、「変数」「命令」「ジャンプ」「停止」しかありません。正直言って言語機能としては非常にしょぼいと思ってます。しかし、そもそも私は「処理の順番の制御」を簡潔にしたかっただけなので、今のところは意外とこれだけで十分です。これ以上の制御をしたい時こそ、プログラム側にコーディングする目安だと思ってます。

②スクリプトの間違いは読み込んだ時に検出できるので、危険性よりも利便性を重視できる
私のスクリプトは原始的な設計ですので、普通の言語からしたら相当無茶な仕様です。ジャンプはできる、変数スコープはない、当然オブジェクト指向プログラミングの欠片もない。しかし、①で述べたとおりできることも少ないので、致命的な過ちを犯すことがそもそもできません。どんなに気を抜いて書いたとしても、それを読み込むプログラムが大事なところをきちんと管理してくれる限り、プログラム自体をフリーズさせてしまうような間違いはできないのです。よって安全性のために面倒な文法を用いる必要はなく、ゲームシナリオに集中して書くことが出来ます。
(そう考えると、C#やJavaといった超高級言語は、C++などのガチ言語とスクリプトのような緩い言語の間を取った感じに思えますね。「機能はC++並、気軽さはスクリプト並」とでも言いましょうか。)

そして肝心の進捗ですが、既存の3面に加え、新たに4面(最低限のマップですが…)も含めてスクリプト化が完了しております。あとは、5,6,ラス面だけだ…!(でも、これから細かい遊びを色々追加しないといけないから、本当の完成にはまだ遠いか…?)今ちょっと迷ってるのは、ラス面までクリアできるようになった時点で一旦体験版や試作品として公開するか、完成までとっておくかということです。まあ今見てる限り、一部の友人を除いてそんなに世間から求められてる感じもしないので(笑)、発表は完成してからでもいいですかね。

[2015/05/14 19:16] | 不死女 -Immortal girl-
|
お久しぶりです。4面のマップができず月日が流れてしまいましたが、進まないのは4面が直接の原因ではなかったかもしれません。そう、またも製作方法が間違っていた可能性があるのです。

先日にも言っていたことなんですが、不死女はゲームシナリオやマップのプログラムもすべてハードコーディング(ソースコード内に記述)していました。巷ではダメなプログラムの典型と言われているのですが、私自身がその必要性を認められなかったのでそのままにしていました。実際、結構な規模になるまではその方が楽な面もありますし。

ところが、4面のマップを考えているうちに気づきました。ここで詰まるのは、単にマップが思いつかないからというだけでなく、マップデザインがしにくい環境なのもいけないのではないかと。あるいは、思いついても実装が面倒だからと無意識のうちにボツを量産しているのではないかと。というわけで、不死女もついにスクリプトへの道を進むことにしました。

<スクリプト化のメリット>
①簡単な内容を変えるだけなら、再コンパイルする必要がない。(スクリプトだけ再読み込みすればいい)
②致命的なバグを起こしにくい。(スクリプトの間違いは読み込んだ時に対処できるため)
③C++の記法に囚われずにプログラミングができる。

<スクリプト化のデメリット>
①スクリプトのコンパイラを作るのが面倒。(名前の二重化が大量発生する)
②スクリプト上で行うこととソースコード上で行うことの線引きが難しい。

要するにスクリプト化というのは簡単なプログラミング言語を自作して実行時に読み込むことですが、不死女におけるメリット・デメリットはこんな感じです。一番大きいのは、メリット③でしょうか。C++にはC++の思想があり、クラスの初期化などの手順は決まっているのですが、それらはゲームオブジェクトのように「ほとんど同じ」ものを「大量に」作るには往々にして冗長なものです。それを無理にC++で書いてきたため、ソースコードは同じような初期化コードで膨れ上がり、見るのも嫌な状態になっていました。スクリプト化ができれば、本当に必要なことだけを書けばオブジェクトが作れるので(というかそういう仕様に「私が」すればいい)、マップを作る作業がしやすくなることが期待できます。作業がしやすくなるということは作業が捗るということです。作業が捗るということはゲーム作りが楽しくなるということです。コードを見たくもない状況とは大違いですね。

デメリットというかスクリプト化を妨げる最大の要因はデメリット②です。これの感覚がわからないため、今までほとんど手を付けることもできませんでした。あまりに不自由ではスクリプト化する意味がありませんし、かといって何もかもスクリプト上でできるようにすると本体のプログラムがいらなくなってしまいますし(笑)、ゲームにおける「目安」がよくわかりません。とりあえず、当面の基準として私は「クラスを直接「使用」する最終段階」をスクリプト化することにしました。もう少し詳しく表現すると「クラスの種類、個数、順序、引数などのみによって制御できる部分」ですかね。これってまさに「ゲーム」のプログラムの本質じゃないでしょうか。例えば敵のプログラムにはモデルの読み込み・アニメーション・人工知能などいろいろありますが、それだけでは部品でしかなくて、やはり適切な「キャラ」「人数」「場所」などを設定して初めてゲームの要素となる。そしてそれこそがスクリプト化すべき部分なのではないか、というのが今回の結論です。

というわけでシステム部分をまた作り直すわけですが、今までの数回の大改造と比べたらかなり楽な方です。なぜなら、今までのものは不死女のプログラムの「全ての基盤」の修正だったのですが、今回は先ほど述べた通り上っ面の部分だけが対象です。よって今まで作った画像クラスとかキャラクタークラスとかは今後も使うことができます。やったぜ。

[2015/04/15 23:15] | 不死女 -Immortal girl-
|


skaiio
素人が読むとふりだしに戻るのかとヒヤヒヤしました
頑張ってくだせぇ


Shiro
大丈夫です、ゲームシナリオのプログラムをメモ帳に書き出すだけの改造ですから。

ただ、ネタ不足なのは相変わらずなので、なにか面白い謎解きとか仕掛けとかイベントが思いついたら教えていただけると嬉しいです。

コメント:を閉じる▲
こんばんは。いやあこんな時期にねえ、まさかの部分の進展がありました。

…当たり判定!

今になって、当たり判定に劇的な改良が施されました。

今までの当たり判定の弱点は、以下の様な状況でした。
collision.jpg
上図のように、鈍角(90°より広い角度)のカドに突っ込むと、ガクガク振動していたのです。同図にも書いてありますが、今までは二枚の壁の打ち消しベクトルを単純に足していたため、足し算の結果である押し返しベクトルがご覧のとおりもとの速度(上図v)よりも大きくなってしまっていたからです。つまり「打ち消す」どころか「跳ね返し」てしまい、それがプレイ時には小刻みな振動として現れていたのです(ちなみに鋭角~直角のカドの場合、打ち消しベクトルがもとの速度を超えることはありませんので無問題です)。

壁とキャラの当たり判定では跳ね返りは考慮しないので、打ち消しベクトルがvより大きくなるのは当然異常でして、これは夏に当たり判定を作った時からわかっていた問題でした。にもかかわらず今まで一切修正されなかったのは、他ならない、

…私の力が及ばなかったからです。

もちろん、今までの間、この修正には何度も挑戦しました。いろんな方法を試しました。ノート端や、大学のホワイトボードに思いつく限りを描き連ねて、考えました。けれどもどの方法も、効果があまりなかったり、振動は止まったが逆にカドから出られなくなるというものだったり、散々な結果でした。正直これにあまりこだわっていてもゲームは進まないので、仕様として諦めて完成に漕ぎ着けるつもりでした。

しかし、私の心を変える言葉に、昨日出会いました。

ゲームプログラマになる前に覚えておきたい技術ゲームプログラマになる前に覚えておきたい技術
(2008/11/15)
平山 尚(株式会社セガ)

商品詳細を見る

上の本は、私が最近趣味で読んでいるものです。技術的な復習と、純粋に楽しむための読書を兼ねています(筆者が面白い!)。
さて、こちらの本の546頁より、引用させていただきます。

…建物の角に突っ込むとキャラクターがガクガク振動するゲームをよく見るが、…(中略)…直す技術力がないのか、直すことに価値を認めないのかは知らないが、ゲームを遊ぶ側から見れば立派な不具合であり、言い訳できるものではあるまい。

図星でした。耳が痛かったです。ここまでやっと完成に近づいてきたつもりなのに、これを直せないままなのは私も悔しかったです。それを、まさにピンポイントで、バグの例として挙げられたものですから、ゾッとしました。
ここまで言われて黙っているわけにはいかないでしょう。私は、本気で、今まで以上に本気で、こいつを直すことに決めました。

結論を言うと、鍵は発想の転換でした。今まではあくまで物理的根拠のある一本の数式を見つけるんだと意気込んでいたのですが、それは諦め、幾らかのif文を含むことを許容して方策を練ったのです。まあ、そうだとしても、どのように分岐するかというのは探らなければならなかったわけですが。では、私の答えを以下に述べましょう。

①最初は問答無用で打ち消し
一つ目の壁は普通に判定します。ただ、打ち消しベクトルはすぐに反映せずに、どこかに保存しておきます。

②二回目以降は壁ズリできるかどうかを判定
二つ目の壁もまずは打ち消しベクトルを求めます。ここで、「一つ目の打ち消しベクトルと速度ベクトルの外積」と「二つ目の打ち消しベクトルと速度ベクトルの外積」を求め、異符号だったら壁ズリ不可、同符号だったら壁ズリ可能と判定できます。これはつまり、「角に向かっている場合」と「角に近い場所だが離れる方向に進んでる場合」という意味になります。詳しくは文字や口で表現しにくいので語りませんが、作図すると実際にこうなります。

③壁ズリできる場合は、より向き合ってる壁の打ち消しベクトルを採用
②で壁ズリできると決まった場合、部分打ち消しベクトルが必要なわけですが、ここでまた二つの足し算とかするとまた振動します(一フレームだけ)。ここでは、二つのうちから適切な法線を選びます。だってこれは「角から離れる場合」なんですから、実際にはズッている方の壁しか判定する必要ないわけですからね。ズッていない方の壁からは離れていっているはずです。ではこれをどう判定するかと言うと、さっき求めた外積を使いまわせます。外積の大きさはsinθに比例しますから、それを求めてsinθの絶対値がより小さい方の壁を選べば、それがズッている方の壁(= より向き合ってる壁 = 法線ベクトルと速度ベクトルの角度が小さい壁)のはずです。結局こちらの壁にしか当たってなかったということにしていいわけですから、その打ち消しベクトルを選びます。

④壁ズリできない場合は、全力で止める
②で壁ズリできないとわかった場合は、もう動かす必要はありません。打ち消しベクトルに(-v)を代入して終わりです。これ以降どのような状況であると分かっても、カドに向かっていっている人がどうにも動きようがないのは変わりませんからね。

⑤万が一、三つ目以降の壁が検出されたら②に戻る
三つ以上の壁に同時にぴったり当たるというのはなかなか無いことなんですが、三つ目以降も同様の処理で大丈夫なはずです。不死女にはそうなり得る地形がないので試していませんが、③で採用された方の壁を一つ目としてまた相応しい壁を選べばいいだけですからね。

⑥最後に、今までの集大成の打ち消しベクトルを速度ベクトルに加算する
ここまで残った打ち消しベクトルで実際に速度を打ち消します。ちなみに、キャラ同士の判定の方はこれを使わなくてもめったに振動しないっぽいので、キャラ同士判定の打ち消しベクトルはまた別に保持しておいてここで一緒に足すのがいいでしょうね。

以上です。これでついに不死女のマップはどの壁も滑らかに壁ズリできるようになりました。

[2015/03/16 23:33] | 不死女 -Immortal girl-
|
こんにちは。頑ななハードコーディングに定評のあるShiroも、とうとう局所的にテキストの外部ファイル化に踏み切りました。といっても、よく考えたら今までだって画像データやサウンドデータは外部ファイルだったわけで、テキストデータだって同じようにするだけだったんですけどね。

ただ、テキストには他と違う難しさがあります。それは「ファイルの内容をそっくり読みこめばいいわけではない」という点です。そう、ゲームのテキストでは、どうしてもプレイヤーの名前などを埋め込みたい場合が出てきます。そして不死女は名前を任意にしていますから、もちろんその名前を実行時に拾ってきて置換しなければなりません(「俺」「あなた」などでごまかすこともできますが、そうすると名前を任意にした意味がなくなります…笑)。

ではどうするか。ハードコーディングなら、普通にsprintfで%sするだけのことです。しかし外部ファイルからだと、%sとは打つことはできますが肝心の引数を渡せません。

方法① printf系の「引数順指定」を使う
sprintfなどの関数では、「%1$s」のようにすることで使う引数をフォーマット上で選択できるようになっています(ただし直接サポートしている処理系は少なく、例えばVSならsprintf_pという別の関数で対応している)。埋め込みたい文字列の種類というのはたかが知れていますから(むしろ今のところ名前ぐらいしかない)、使いそうな引数をみんな渡しておき、テキスト側で指定すれば必要な文字列を埋め込むことができます。
しかしこれは非常に難しいということがわかりました。なんでも引数順指定を使う際は、「すべての引数を必ず使わなければならない」「同じ引数を違う型で変換してはならない」という制約があるようなのです。これは厳しいです。どう考えても毎回すべての引数なんか使いませんし、ダミーで「%1$.0s%2$.0s%3$.0s…」などとしても、文字列でないものが含まれたらアウトです。どうしてもこの関数を使うなら、予め使っている引数と使っていない引数を調べ、使っていないもののみに対してダミーの変換指定子を付け加える…などということになりますが、こんな複雑なパーサーを作るくらいならもはや簡易sprintfを自作したほうが早いわけで(笑)、引数順指定を使う方法はボツとなりました。

方法② sprintfを作る
①でちらっと書いたのですが、結局文字列置換する関数を自作したほうが良さそうという結論に至りました。ただ、printf系の信頼と実績の変換指定子があるのに、それを一から自作するのはもったいないです。ここで気づいたのは、変換指定子の「%s」「%d」などはデータによって決まっているということです。「プレイヤーの名前を埋め込む」といったら「%s」しかないわけです。よってテキスト側で変換指定子まで決めれるのは冗長であり、むしろテキストを書く側からしたら面倒です。というわけで「プログラム側では「名前と変換指定子と引数」をセットで保持しておいて、テキスト側で名前を指定したら適切にsprintfして埋め込んでくれる」という設計にしてみました。これが大成功。テキスト側では埋め込む場所に名前を書くだけですし、細かい書式指定が必要な場合はプログラム側の変換指定子をそのようにすればsprintfがやってくれます。テキスト中に散りばめられた奇妙な文字列は消え、それでいて必要な自由度は保っている。十分実用的なスクリプトエンジンになったのではないでしょうか。


<本日のスクリーンショット>
動作テストを兼ねて関係ないモデルを読み込ませて遊んでみました。権利関係が怖いので製品版には一切(おまけとかにも)搭載しませんが(ただ、プレイヤーさんが自己責任で用意したモデルを読み込む機能くらいなら付けてもいいかも…?)。
けいおん

[2015/03/12 13:32] | 不死女 -Immortal girl-
|
こんばんは。面倒なバグに遭ったりもしてますが、相変わらずじわじわ作ってます。

さて、今回のメインの更新点は、独自形式モデルファイルのフォーマットです。今までモデルファイル(.model)には頂点やボーンの情報だけを格納して、当たり判定データ(.hit)は別途用意していました。元となるデータはいずれもPMDですが、当然のようにモデルと判定形状を別々のPMDに出力して、それぞれをコンバータにかける、という方法で生成していました。が、

…めんどくさい。

「めんどくさいはプログラマーのはじまり」を信条とする(嘘)私は、これをなんとかしてひとまとめに出来ないかなと思いました。そしてやってみたところ、案外2~3時間で出来ました。

鍵は「テクスチャファイル名」です。これはもちろんテクスチャのファイル名を指定する文字列ですが、自分のコンバータにかける分には別の意味を持たせたっていいわけです。というわけで、「テクスチャファイル名が「colligion.png」(適当に決めました)となっている材質が割り当てられている面は判定形状データ」という約束をしまして、コンバータでその仕様通りに判定形状を抽出するように改良しました。その材質の面を取り出して既存のコンバータ(の移植)にかけ、モデルデータからそれを削除する、というわけですね。

これで、
メタセコイア→PMD出力→判定形状PMD出力→コンバータ→判定形状コンバータ→不死女で読み込む
という作業をしていたのが、
メタセコイア→判定形状ごとPMD出力→コンバータ→不死女で読み込む
となり、作りやすくなりました。

ここまで来ると、メタセコイアのmqoファイルを直にコンバータにかけたい気もするのですが、それは簡単にはいきません。なぜならキャラクターのデータは(ボーンなどをそのまま利用しようと思うと)メタセコイアで編集できず、PMDでしかやりとり出来ないからです。mqo用とPMD用に分けたりすると、維持が面倒かな…と思ったりするのです。mqo→PMDをメタセコがやってくれるだけでもすごくありがたいので、もう十分な開発効率だと思います。

なぜここでモデルデータの改良に手を出したかというと、お気づきかもしれませんが、ほんとにほんとにステージ制作に入りたいと思ったからです。プログラムの方がようやくステージを成立させられるくらいになってきて、今度はデータの整備が追いつかなくなってきたのです。つまりこれは間違いなく…進歩です

[2015/03/06 22:51] | 不死女 -Immortal girl-
|
こんにちは。ついに、フォーカスシステムを使っていない入力系の殲滅に成功しました。これで、操作できては困るボタンなどをいちいち無効化しなくてすむようになり、スッキリしました!

度々根幹のシステムを「改良」しては迷走(たまに瞑想?)をしてきておりますが、こればっかりは成功の部類に入るんではないでしょうか。今まではボタンを増やすたびに有効化・無効化の処理を各所に追加するという、いかにもバグを含みそうな設計でしたからね。しかも、無理やり他を停止させて排他的にしているわけではなく、優先順位を付けて一番上の人だけに反映させるという設計なので、「一フレームの闘い」も発生しにくいです。

また、今まではフラグを一フレーム単位で建てることで制御していたコールバック(風)メソッドも、デリゲートを採用することで見やすく&書きやすくなりました。デリゲートはコールバックのみならず、「処理関数が異なるだけで基本的な構造は同じ」ような処理をするクラスにも有効でした。これで、処理関数をオーバーライドするためだけにいちいち継承する必要がなくなり、可能な限り基本クラスのまま使用出来るようになりました。これも有益な「改良」だと思います。

とはいえ、フォーカスもデリゲートも各サイトを参考にしつつ自作したものなので、本来のC++の機能ではないんですよね。ということは「正しい」C++とは外れているような気も…でもこれ使わないともっとカオスになりますし…C++のゲームって、普通はどうするんでしょうね。

[2015/02/27 16:34] | 不死女 -Immortal girl-
|
こんにちは。今日は概念的な話です。

プログラミングは「イベントドリブン型」と「手続き型」に分けられるとしばしば言われます。厳密にはこれらは対照される関係ではないらしいですが、ここでは「状態変化の瞬間に注目した設計」と「その時の状態に注目した設計」と捉えましょう。

例えば、キーボード入力について考えてみましょう。イベントドリブンの代表とも言えるWindowsプログラムでは、これらは「キーが押されたよ!」「離されたよ!」というような「メッセージ」として送られてきます。そしてプログラマーはそれに対応する処理を書くのです。OpenGL用ライブラリ「GLUT」などもこの形式です。
対してDirectInputは、あくまで「その時キーが押されているか」という形式で取得する仕様です。言い方を変えれば、好きなときにキーの状態を知れるというわけです。

イベントドリブンは、「操作→処理」の対応関係が明確であるという利点があります。しかし、処理どうしに関連性がある場合(例えば、処理Aは処理Bが終わった後に実行されることを前提としている場合)、一箇所でも対応漏れがあると(処理Bをしなくても処理Aが実行出来てしまう操作が存在すると)辻褄が合わなくなりがちです。つまり、その処理をするときにどういう状態になっているかというのは保証しにくいのです(これが原因とみられるバグを持つプログラムをいくつも見ています…)。

手続き型は、処理の流れがほぼ一本にまとまっています。その中でいじったものしか変化するはずないので、実行時の状態を把握しやすいメリットがあります。しかし、実行される可能性がある処理全てを一つにまとめるため、操作と処理の対応がわかりにくく、また実行するものとしないものの区別に大量のフラグが必要となります。つまり、こちらはある「瞬間」を捉えるのが面倒なのです。

どっちがいいのでしょう?不死女にも迷いがありました。そして下された一つの結論は…

どっちも使えばいいじゃない。

得手不得手があるなら、適材適所にすべきなのです。どっちか一辺倒になるとやっぱり無理が出ます。ここをゴリ押しでやるか立ち止まるかTPO次第ですが、不死女は趣味兼プログラミングの勉強なのでゆっくり考えていきます。ちょうどこの間作ったデリゲートはイベントドリブンの実装に最適です。また手続き部分には従来のシステムを使っていけば良いのです。仲良く共存させて、より適したプログラムを目指します!

[2015/02/24 13:41] | 不死女 -Immortal girl-
|
こんにちは。操作系を作っていると、必ず絡んでくるのが「優先順位」です。例えば、ダイアログボックスが表示されている時、その真下にあるボタンなどが押せてしまっては困りますね。このように操作系では、画面上での上下関係を実際の入力にも反映させる必要があります。

Windowsでの標準のウィンドウ類(コントロール)には、「フォーカス」という概念があり、このへんの処理を全て裏でやってくれています。よって、例えばWordの上にExcelを重ねて作業している時に、Excel上での操作がWordにまで反映されるということはありません。当たり前のようですがこれもWindowsが裏で処理してくれているからです。

実は、記憶・不死女を始めとするTDtechnicのゲームにはこの機能はありませんでした(他のソフトとの上下関係はWindowsがやってくれます)。そのため、今まではボタンやウィンドウが極力重ならないように設計するか、大量のフラグでゴリ押しして対処していました。

しかし今回はここも無視しません。自前でフォーカスシステムを作りました。蓋を開けてみれば意外と簡単、入力を受け付けるオブジェクトをリストに登録し、リストの最後になっている時のみアクティブ(操作が反映される)にする、という方法で出来ました。アクティブか非アクティブかは入力受付時に自動で判定するため、受付処理を書く側は一切意識する必要ありません(非アクティブの時は無効な値が返ってくるだけです)。ただし元々の入力システムは既にあちこちで使われており、一気に換装するのは危険なため、従来システムも共存出来るように設計しました。なんだか、旧道を残しながら新道を作る道路工事みたいで面白いですね(当方道路マニアにつき、未成道や廃道に多大な興味あり)。全てが新システムに移行出来たら旧システムを廃棄します。

[2015/02/20 16:09] | 不死女 -Immortal girl-
|
突然ですが、皆さんはC++で無性に関数を引数で渡したいときはありませんか?C形式の関数なら関数ポインタで普通にできますが、これがメンバ関数だと途端に複雑になります。そう、メンバ関数ポインタを使う場合は何らかの形で手動でthisポインタを渡してやる必要があり、さらにそのために所属するクラスを区別しなければならないんですね。これではせっかくポインタとして扱ってもCのように汎用的には使えず、まるで役に立ちません。

と思っていた時期が私にもありました。C++版デリゲート(委譲)を知るまでは。「C++をもってC++を制す」とはこのことですね。

デリゲートは、まさに上記問題を解決するものです。メンバ関数も関数ポインタのように持ち回ったり代入したり呼んだり呼ばれたりラジバンダリできるようにしてくれます。C#は標準装備で、前作「記憶」の時はちょくちょく使った(ような気がする)んですが、私はどうせC++でそんな高級なことは出来ないだろうと思い込んでいました。

もちろん、普通のC++にデリゲートはありません。しかし、工夫次第でどうとでもなるものだったのです。鍵は「テンプレート」「継承」「仮想関数」と、C++の機能をフル活用ですね。例によってマルペケ様を主に参考にさせていただきました。

まず、呼び出すためのオブジェクトが要ります。そして、目的のメソッドへのポインタが要ります。ここでオブジェクトの型(及びメソッドの型)が不定ですね。よってテンプレートクラスDelegateObjectを作って誤魔化しました。
次に、テンプレートでコロコロ型が変わっては一つのデリゲートで保持しようがありません。よって「実行する」という仮想関数だけを持つインターフェイスIDelegateObjectを作り、それを先のテンプレートに継承させます。これで、テンプレートでどんなに化けようが、このインターフェイスへのポインタで保持していれば仮想関数から実行出来ますね。

原理はこれだけです。あとは自分が使いやすい設計になるよう細かい仕様を策定していきます。とりあえず、ポインタを包ませるためのテンプレートやらインターフェイスやらを見たくないので、専用のDelegateクラスを作ってインナークラスにしてやりました。ついでに()演算子をオーバーロードして、「オブジェクト名(引数)」とするだけで実行出来るようにしました。あたかもDelegateオブジェクトそのものが関数のようですね。
また、Delegateオブジェクトに実際にメソッドを渡す際、IDelegateObjectポインタを得るためにいちいち「Delegate::Create(this, &Foo::Bar)」などと書きたくありません。そこで、IDelegateObjectポインタを持つ新たなクラスFuncを作り、この人がコンストラクタでnewして自身のポインタに保存、そしてDelegateオブジェクトにはこのFuncオブジェクトを渡すようにしました。もちろんDelegate側のデストラクタでdeleteしますよ。これで「Func(this, &Foo::Bar)」というようにちょっと短くなりました。あ、副次効果として、引数などで型を指定するときもいちいち「Delegate::IDelegateObject* pFoo」などと書かずとも単に「Func foo」と書けるようになりました(まあ、このへんはもっと短い名前を使えば我慢出来たかもしれませんが…)。
さらに、たまにデリゲートするメンバ関数に引数を渡したいことに気づきました。そこで、DelegateクラスとFuncクラスごと更にテンプレートにし、引数の型を指定出来るようにしました。引数の数は常に一つですが、複数渡したい場合は構造体などにすればよいですね。引数がいらない場合はダミーでint型などを受け取ります(ここの使い勝手はC#の類に遠く及びませんね…もうちょっとなんとかならないかな)。可変長引数も考えましたが、型の安全性を重視してこうしました。

さあこれで、メンバ関数といふものを受け渡し出来るようになりました。これまで関数を自由定義させる方法としては仮想関数ばかり用いていましたが、これがあればいちいち継承しなくても、メインのクラスで好きに作った関数を実行させることができます。しかも自分のクラスのメンバにアクセスし放題です。派生オブジェクトをメンバに持つ方式だと、コンストラクタでいちいちメインのクラスのポインタを渡さないとメンバにアクセスすることすらままなりませんし、だからといってメインのクラス自体が継承する方式だと、同じクラスの機能を複数パターンで使うことができません。

画期的なデリゲートですが、今回製作するに当たって見たことも聞いたこともないような機能は一切使っていません。今までのC++の知識で作れるはずだったのです。やはりプログラミングというのは発想・工夫が大切なんですね。わかりやすい解説をされていた皆様に敬意を表します。

[2015/02/17 00:54] | 不死女 -Immortal girl-
|
こんにちは。期末も終わったのでそろそろ再開しようかと思います(一つほぼ確実に死んだ科目ががががが)。なんか、最近は長期休みごとにしかできないのでなかなか進みませんね。でも!2年はコマ数が半分程度になるっぽいので(そう、コマ数だけはね…)、ひょっとしたら時間ができやすいかもしれません。

当面の目標は、まず弾丸を作ること(旧バージョンからの移植)。次に二面を作ること(その過程にステージ作りも含まれる)。以降はまたその時に考えよう、といった感じですね。今年の夏休みこそをめどに完成させたいところです(あ、でも自動車学校行くなら夏も完全フリーではない…!)。

ちなみに不死女のステージは完全にハードコーディング(マップデータなどを作らず、直接プログラムに書く)してます。マップを変えるたびに再コンパイルする時間はかかりますが(数秒ほど)、なんといっても最高の自由度が魅力でついつい書いちゃいます。でも、ちゃんとマップデータでやれば、マップエディタを一緒に配布して自作マップを作れるようにしたり…とかもできて面白そうなんですけどね。

[2015/02/10 10:51] | 不死女 -Immortal girl-
|
こんばんは。今回は細かいけどクオリティを左右する大事な要素をクリアーしました。

①だいたいあってる高速アルファブレンディング
②V-Syncとフレームスキップ

①だいたいあってる高速アルファブレンディング
アルファブレンディングとZバッファはほんとに仲が悪いです。最も正しいのはZバッファしないで遠いポリゴンから順に描画する「Zソート」ですが、FPSでそんな悠長なことをしている暇はありません。ゲームにおける十分条件として私は

・不透明なポリゴンは完璧な順序で描画される
・透明なポリゴンが後ろのポリゴンを透かしてしまう『透明マント状態』にはならない

を設定して考えてみました(ここでいう透明は半透明を含みます)。
一つ目は、普通にZバッファを使って不透明ポリゴンを全部描画すれば出来ます。この時、透明なポリゴンが途中に入っていると二つ目の「透明マント状態」が回避できませんので、不透明を先に全部描画して、その後で透明を描画すればよいです。これはマテリアルごとに透明/不透明フラグでも立てて管理すれば実現できます。

これでもほぼ要求通りですが、これだけでは透明なポリゴンどうしの「透明マント状態」が回避できません。そこで透明なポリゴンを描画する際はZバッファをオフにします。ただし完全オフだと不透明ポリゴンに隠れるはずのポリゴンが見えてしまいますので、Z評価は有効のままでZ更新を無効にします。これはSetRenderState(D3DRS_ZWRITEENABLE, FALSE)で実現できます(こちらの記事を参考にさせて頂きました)。
これらにより満足した要素をまとめると、

・「不透明と不透明」、「不透明と透明」の完璧な遠近関係
・「不透明と不透明」、「不透明と透明」「透明と透明」の透明マント完全回避

逆に容認した仕様は、

・「透明と透明」の遠近関係

ですね。ただし、透明ポリゴンというのはもともと後ろが透けて見えるものですから、透明どうしで遠近が少々おかしくてもそんなに目立ちません。

②V-Syncとフレームスキップ
フレーム制御は大事です。フレーム制御を一切しなければ、同じゲームでもPCによって15FPSだったり300FPSだったりしてしまいます(実測値)。そこでゲームは通常「早過ぎたらちょっと待つ(ウェイト側調整)」「遅過ぎたら描画を省略する(スキップ側調整)」という制御をしてゲームシステムのスピードを一定にします(他の流儀もありますが、不死女はこちらの方法を採用しています)。

さて、フレーム制御はtimeGetTimeなどで自作してもいいのですが、もう一つ、PC自体に搭載されているフレーム制御機構も忘れてはなりません。それはV-Sync(垂直同期)です。

V-Syncもフレーム制御に使えるのですが、こいつには「FPSをいくつにするかはゲーム側から設定できるとは限らない」「ウェイト側は調整するが、スキップ側は一切調整してくれない」という欠点があります。特に後者は厳しいです。描画が重くなるとゲーム全体のスピードまで重くなるわけですから、このままではよほど軽いゲームでしか使えません。

ではV-Syncは完全に無視して自前制御だけすればいいように思われますがそれも問題があります。もともとV-Syncは「
画面を一気に描画するタイミングを知らせる信号」ですから、V-Syncに合わせないと「あるフレームを描画中に次のフレームを描画してしまう」という現象が発生します。これは「ティアリング」といい、その描画中だった部分で画面がちぎれているように映ります。静止している場面では境目の前後で画面が変わりませんから全くわかりませんが、動いている場面、特に一方向にスクロールしているような場面では非常に目障りなノイズと化します。

不死女は長らく自前制御とV-Syncを同時に使ってそこそこの結果を得ていました。ただ自前制御とV-Syncのタイミングは完全には一致しませんから、60FPS出ている時にも誤差により無駄なフレームスキップが発生したりします。これは「定期的に1フレームだけカクつく」という現象を引き起こすので、これはあまり気持ちのよいものではありません。

そこで今回は自前制御の方の判定を甘くしました。つまり今までは「(自前実測値で)60FPS超えてたらウェイト、60FPSを下回っていたらスキップ」という判定でしたが、「70FPSを超えてたらウェイト、50FPSを下回っていたらスキップ」というように余裕を持たせたのです。50~70の間では60FPSのV-Syncが働いているものと仮定して自前制御は一切手出ししません。これにより、V-Syncだけで60出るマシンではカクつき・ティアリングの一切ないなめらかな映像が得られ、30FPS程度しか出ないマシンではサクサク軽快な動作が得られました。

[2014/12/30 23:23] | 不死女 -Immortal girl-
|
こんにちは。やってしまいました。ついにTDtechnicもプログラマブルシェーダの導入に踏み切りました。

今まで導入をためらっていた理由は2つあります。一つは慣れ親しんできた固定機能から離れたくなかったから。もう一つは、動作環境が更に限られるからです。

Visual Studioの都合上、プログラム自体はXP以降のみ対応となっております。また、DirecrX9を要求しますから、XPの方はそれがインストールされているのも条件です(インストーラは同梱してますが)。プログラマブルシェーダを使うと、これに加えて「シェーダーバージョン2.0以上」がほぼ必須となってしまうのです。もちろん最低バージョン(1.0かな?)のシェーダを使えばバージョンに関係なく動くことになりますが、1.x系のコンパイラはすでにMicrosoftが配布しておらず、性能も2.0以降と比べてかなり落ちてしまいますので、さすがに非現実的です。とりあえず、バージョンは2.0以上を前提として、対応していなかったらソフトウェア処理で代用(できるのかな?確認できる環境がない…)するという設計にしました。

あとは全ての描画機構をプログラマブルシェーダ用に換装して、固定機能用のステート設定群を削除して…かなりスッキリしました!描画処理を直接プログラムできるわけですから、ステート切替を駆使して処理方法を設定する時代は終わりです。さて、これでついにハードウェア頂点処理でスキンメッシュアニメーションができるようになりました。前回のステージデータに加えて敵(もちろんスキンメッシュアニメーションします)を5体同時に表示してみると…なんと!300FPS超え安定です。なるほどこれなら頂点が5倍程度になっても60FPSは出ると…。恐るべし、GPUの計算能力。

プログラマブルシェーダのコンパイラにはD3DX付属のものを使用しておりますが、例によってコンパイル結果のデータを予め保存して起動時にそれを読み込んでいるだけなので、不死女自体はやっぱりD3DX不要です。素のWindowsで起動できるって気持ちいいですね!

ちなみに、もう一つの私の動作確認環境であるネットブックは、そもそも頂点シェーダが積まれていないためプログラマブルシェーダとか関係なくハードウェア頂点処理は不可能です。それでも、前回の最適化による恩恵は直接受けられるわけですから、上記と同じ内容でテストしてみたところ、カックカクではあるものの何が起こっているかわからないほどではない速度が(15FPS程度)出ました。もちろんフレームスキップしてますからゲーム自体の速度は60FPS一定ですよ。

[2014/12/29 14:29] | 不死女 -Immortal girl-
|
こんにちは。今回は大躍進です。なんと描画負荷をおよそ1/4にすることに成功しました。

事の発端はステージ作りです。デスクなどを配置している最中だったのですが、十数個ほど並べただけで1Fの頂点数が約44000となり、ソフトウェア頂点処理とはいえ、そこそこ性能の良い私のPCでもカクつくという状態になってしまいました。

こいつでカクつくなら、ちょっと古めのPCならもっとカクつくでしょう。TDtechnicは動作環境の多さを売りにしているのに、要求スペックをそんなに上げるわけにはいきません。

まずやったのは、モデルの簡素化です。デスク一つ当たり約1300頂点あったのを、涙をのんで削減して約550にまでしました。全体ではおよそ18000にまで減少され、かなりマシになりました。ところが!これでもまだ、私のPCでカクつくのです(50FPS程度)!つまり今の描画ルーチンでは、ステージを18000頂点以下で作らないといけないというのですか?ステージ製作は始まったばかりですから、それは困ります。どうやら根本的に描画速度を上げる必要があるようです。

高速化の心当たりとしてはソフトウェア頂点処理をハードウェア頂点処理にすることがあります。普通はハードウェアにするんですが、私はスキンメッシュを固定機能で実装しているためそれが出来ません。ここの差はかなり大きいです(実際、スキンメッシュのないモデルで比べてみると違いは歴然です…)。とうとう固定機能を諦め、プログラマブルシェーダに移行する時が来たのかもしれません。

とりあえず、準備としてD3DXMeshを使ってみることにしました。これを使いたかった訳は、ハードウェアのメモリ容量に関わる分割アルゴリズムを考えたくなかったからです(笑)。要するにハードウェアにはいっぺんに全部の情報を置けないため、プログラマブルシェーダでやる場合、うまいこと幾つかに分けて描画する必要があるのですが、この「うまいこと」が難しいのです。ここはMicrosoftが作ってくれたConvertToIndexedBlendedMeshという関数を利用しましょう。まずはD3DXMeshを作ってみて、まだ分割は使わないで固定機能で描画して、おお、出来ました。カクカクですがちゃんとアニメーションもできています。次にConvertToIndexedBlendedMeshで分割して、やっぱり固定機能で描画して、…おお!?

なんということでしょう。分割を適用したところ、固定機能のままで200FPS出ました。もちろんソフトウェア頂点処理です。どうやら、分割によってメッシュ自体がかなり最適化され、根本的な効率が上がったようです。1ループが食う時間の殆どは描画時間ですから、およそ1/4の時間で描画できていることになります。逆に言えば、今の3倍くらいの頂点になってもちゃんと60FPSで描画できる…ってことですよね!たぶん。

というわけで、今回はプログラマブルシェーダに移行しないで済みました。まあここまで来たら挑戦してみたい気持ちもありますが、まだわかりません。ところで、D3DXMeshを利用すると再びD3DXのインストールが必要なように思われますが、そのようなことはありません。実はConvertToIndexedBlendedMeshは、PMDファイルを自作形式のモデルデータに変換するプログラムの中で使うだけで、モデルデータにD3DX依存のデータは一切含まれませんので、不死女本体はD3DX非依存のままです。今後も素のDirectX一本でプレイしていただけます。

<本日のスクリーンショット>
たまにはスクリーンショットを上げましょうかね。今回晴れて描画できるようになったデスク群です。もちろんこれだけでなくもっと色々置かねばならないので、描画速度との戦いはまだまだ続きますがね。
desks.jpg

[2014/12/28 12:26] | 不死女 -Immortal girl-
|
こんにちは。Shiroです。

また基盤を改良してしまいました。タスクシステム・バージョン3です。バージョンを重ねるごとに素直な実装になってゆきます(初版は無駄に演算子オーバーロード・テンプレート多用、前版は継承使いすぎ)。あと、DirectX関係を整理しました。なんと!png読み込みをlibpngで実装、数学関数を(ネットとにらめっこしながら)自前で実装することにより、D3DXが不要になりました。これにより、Windows Vista以降でプレイする場合はインストール作業は一切必要なくなりました!XPでもほとんどの場合必要ありません)さらにDirectInputとDirectSoundを排除すれば、GCCでコンパイルすることすら可能になります、が…これは直接には大した効果が無いので後回しですね。

内容の進捗も報告しておきましょう。今までステージは仮組みで壁以外ほぼ何もありませんでしたが、階段・蛍光灯・トイレ・窓という最低限の設備を追加した事によりまともな建物になりました。デスクや棚などの什器はまだ設置していませんが、少なくとも空きビルとしては完成しました(まだ1Fのみですが…)。あとプレイヤーの視野判定(グラフィックを軽くする用)を改良したことにより、グラフィックの省略しすぎによる不自然な動きがなくなりました。


[2014/12/23 19:39] | 不死女 -Immortal girl-
|
こんばんは。Shiroです。

不死女、進めたいのですが迷っている部分があったり課題があったりSIREN動画を作ってたりでなかなかやれません。とりあえず頑張って視野判定用のトリプルディスパッチを作りました。おぞましい数の仮想関数になってちょっと怖いですが、視野は見る人と見られる人の型だけでなく障害物の型にも依存するので、型だけで場合分けするにはこうするしかありません。

あと細かいですが、リロードにパックマンぽいプログレス表示を作りました。要するに円グラフです。でもDirectXで円グラフを表示するのは意外と大変で、扇型を描画する関数がないものですからテクスチャでやるしかありません。しかも、テクスチャを扇形に伸ばすようなこともできませんから、扇型のテクスチャ2枚及びマスキング用の透明なテクスチャをうまく重ねあわせてなんとか実装しました。労力の割に重要性は高くないかもしれませんが(笑)、作ってて面白かったです。

[2014/11/23 19:08] | 不死女 -Immortal girl-
|

こんばんは
Sva
お互い大変なのは一緒ですね…↓
自分もやらなきゃやらなきゃとは思うのですが、結局自分に甘いのでしょうね^^;


Shiro
趣味に苦しんでは本末転倒ですしね。適度に後回しにしつつ、楽しめるペースで進めていかなくては。
ちなみに今年のコンテストには到底間に合いそうにないです…ここはじっくり作りこんで、来年度によりよい作品を完成させようと思います。

コメント:を閉じる▲
こんばんは。不死女はじわじわ作っています。タスクシステムの仕様が変わったので、全体に影響することに…こういうふうに一つ崩れると全部調整みたいな設計はほんとはしたくないんですが、みんなが共通規格を持ってくれてるほうが結果的に自由度が高いこともあるのでこうなってます。まあ、新システムに移行するときに今までのコードの見直しもできますし、悪いことはないだろう(プロになったらこんな悠長なコーディングはできないでしょうがね…趣味だからできるささやかな贅沢です)。

ざっくりと何が変わったかというと、簡潔にしようとしてC++のマニアックな機能で無茶して実装していた部分を、素直に書くようにしました。あと、今まですべてのタスクはnewして動的に作っていましたが、稀にポインタがダングリングして外部からアクセスする際に存在が保証できないことと、存在したとしても型が確実に判定できない(dynamic_castすれば一応できなくもないですが…)という問題を抱えていましたので、メンバなどで静的に作ったものも使用できるようにしました。

今年中に完成するかな…(笑)ではまた。

[2014/10/24 22:52] | 不死女 -Immortal girl-
|
こんばんは。夏休み中は到底無理でしたが、じわじわ進んでます。今回も(個人的に)革新的な進歩が見られましたよ。それは、「視野判定三点セット」です!前回チラッと言いましたがAI製作には視野判定が必要です。また、視野判定を作ろうとして思ったのですが、これを「プレイヤーが見える敵」にも応用すれば、壁に隠れて見えない敵の描画を省略でき、かなり体感FPSを上げられるはずです。そんなこんなで要求仕様が複雑になりましたが、なんとか視野判定が完成しましたのでそれぞれについて報告します。

①敵用視野判定システム「プレイヤーの中心が見えるか」
一番簡単な判定です。自分の座標から相手の座標までのベクトルが、どの壁にも遮られないのを確認するだけなので、外積で二発です(一発です!とはいきませんが…)。

②同行者用視野判定システム「プレイヤーが完全に見えるか」
同行者の場合プレイヤーを追うことができる精度が必要なので、①のような判定では細かい角で簡単にハマってしまって不十分です。これは、プレイヤーの姿が少しでも欠けたときにすぐ見失わせることで緩和できます。数学的には、自分から相手の左右への接線について①と同じことを行い、少なくとも一本が遮られた時に隠れたことにすればOKです。これて併せて見失った場合は最後に見えた地点に向かうようにすれば、十分実用性のある追跡AIができると思います。

③プレイヤー用視野判定システム「キャラがちょっとでも見えるか」
今回の視野判定で最難関です。ちょっとでも見えるっていうのは①や②と比べて遥かに考えにくくなります(私はね)。最初は②を応用して、「少なくとも一本」を「二本とも」に変えただけのアルゴリズムで試しました。しかしこれだと、スリット状の壁に左側と右側が隠されただけで成立してしまい、中央部は見えるはずなのに消えているという幽霊状態になります…(笑)ならば!と中央にも同じ処理を足し、三本遮られた時のみに消すようにしましたがこれもダメです。同じくスリットの両端それぞれに、左と中央あるいは右と中央を遮られた場合、やはりスリットの隙間から見えるはずの部分がハブられます。では五本に増やして…としていっても、これは本質的には解決しません。どうしてもこれでやるなら、無限本のベクトルが必要だからです。
ではどうするか?ちょっと違う切り口で攻めてみました。一回②に戻って、二本のベクトルで判定します。一枚の壁に二本とも遮られたら当然見えないので判定終了です。問題は一本だけ遮られた場合です!ここで、遮られたベクトルを、遮られない角度まで回転移動させます。回転の中心は自分です。すると自分から相手の左右へのベクトルが近づき、見かけの相手の大きさが小さくなります。そして次の壁からはこの小さな判定モデルで判定します。また一本だけ当たったら小さくします。こうしていって一度も「一枚の壁に二本とも遮られる」状態にならなければ可視と判定します。こうすれば!どんなにチラッとであっても、見えるものは見えると判定され、見えないものは見えないことになります!これを使ってキャラの描画を適宜省略すると…なんと!ネットブックで敵を7体出しても、敵が一人も映っていなければ、60FPSでました!!!!!(ちなみにこの省略を行わないと、敵が映っていようがいまいが常時15FPS程度になります…)敵1~2体映っていても30FPSくらいは出ます。これなら、普通の遊び方ならネットブックでも実用レベルじゃないでしょうか。

衝突判定と違ってこういう視野判定は直接の資料が少ないのが悩みどころです。衝突判定に近いものはありますが、「少しでも見えたら」の概念を数式(しかも計算が速くなくてはならない)にする方法は見つけられませんでしたし、自分ではなおさらなかなか思いつきませんでした。明日からほんとにほんとにAI作ります。ではおやすみなさい。

[2014/09/24 23:47] | 不死女 -Immortal girl-
|
こんにちは。やっと今までの機能全てを新システムに移植しました。今度こそ今度こそAIを作り始めます。といってもまずは視野判定システムを確立しなければならないんですがね…。恐らく夏休み中には完成しません。結局後期も帰宅後や土日を縫ってやらなければならないのか…。まあ、こうやって試行錯誤した苦労はきっと自分のためになってるよね!そういうことにしておきましょう。これからもゆっくり続けていきます。

[2014/09/23 09:36] | 不死女 -Immortal girl-
|
こんにちは。あんまり長くなるので同日更新です。同じ日にこの記事を更新しています。さて再び壁にぶち当たるShiroですが、今回は本当に壁です。そう、壁の当たり判定です。

今作の、ほぼ物理シミュレーションな当たり判定を実装するのは初めてということもあり、アルゴリズムやファイルフォーマットは随分粗雑な作りでした。そして壁に至っては凸になっている角に接触すると振動するという不具合まで抱えていました。ちょうどタスクシステムのせいで多くのコードを書き直すので、当たり判定も改良しようというのが今回の話です。紆余曲折ありましたが、最終的な仕組みをまとめたいと思います。

①判定用のデータをベクトルで保持
今までは壁データを線分の集まりとして処理していましたが、前述の振動はこれが原因です。そう、線分だと凸角の繋ぎ目での判定が2回行われ、「押し返しすぎ」により振動するのです。ではどうするか?繋ぎ目では片方の線分だけを処理すればよいのです。ただ片方といっても線分自体には向きはありませんので、方向を数値的に表現するために壁データはベクトルの集まりとして処理することにしました。

②直線として処理するか点として処理するか
壁とキャラなら直線と円として判定できそうです。ただし壁には長さがありますので、直線としては当たっていてもキャラが壁の真横にいなければ当たっているとは言い切れません。また壁の横にいない時も、繋ぎ目が凸角なら端点で当たる場合があります。このためまずは「キャラが壁の横にいるかいないか」を判定して「壁の面で判定するか端で判定するか」の場合分けをします。これは内積で計算できます(マルペケ様の受け売り)。

③(面)キャラは壁に接近しつつあるのか離れつつあるのか
今は壁に当たっているとしても、静止している場合や離れようとしている場合は処理する必要ありません。この判定は相対速度と法線の外積のy成分の符号を見ればわかります(言い忘れてましたがこの当たり判定は全てxz平面で行っています)。これは処理の高速化になるだけでなく、凹角で挟まるのを防止する効果があります。これがないと、一方の壁が与えた押し返しに反応してもう一方の壁が「吸い付け」をしてしまうことがあり、凹角で滑らかに壁ズリできません。ちなみにもちろん②で端を判定することになった場合、それは角だとしても凸角であり凹角なはずはありませんので、この判定は省略できます。

③(端)隣の辺の判定範囲に入っていないか
端を判定する場合は別の問題が発生します。隣の壁の面で判定できるのにこちらでも判定をしてしまうと、同じ接触点で2回判定されることになりやっぱり振動します。というわけで予め判定データには隣の壁のデータを入れておいて、②と同じように「キャラが隣の壁の横にいるかいないか」を判定して偽のときのみ処理を続けます。

④線分と点の距離の測定
ここに来てようやくキャラと壁の距離を計算します。面の場合はキャラの中心から直線への垂線の長さを、端点の場合はキャラの中心と端点の距離になります。やっぱりこれもマルペケ様を参考にさせて頂きました。ちなみにこんなものに平方根を使うのは無駄でしかありませんので、計算に2乗が入る場合は2乗距離のまま使いましょう。

⑤時間を巻き戻す必要があるかどうか
④での結果がキャラの半径より小さければキャラは壁に接触していることになります。ただし適当な閾値を設定しておいて、一定以上めり込んでいる場合は時間を戻す要求をし、接触とみなせる程度しかめり込んでいない場合は処理を続けます。時間を戻す要求を返り値で行うと、returnで無駄な処理を一気に飛ばせるので便利です。

⑥押し返しを実行する
遂に当たり判定の本編です。⑤をクリアしているということは今「ピッタリ接触している」はずですから、あとはキャラの移動速度の法線成分を打ち消すベクトルを求めるだけです。面の場合は相対速度と法線(正規化)の内積、端点の場合は相対速度と半径(正規化)の内積になります。凹角などで複数の打ち消しベクトルが同時に発生することがあるので、打ち消しベクトルは一旦変数に加算していって後で一気に打ち消すと、判定順による誤差がなくなります。

とまあプログラミングって数学っぽいところありますよね。でも数学と決定的に違うところがある。数学は理論的に同値ならそれらは全て「正しい」ですが、プログラミングでは適切な演算結果が得られても遅ければ「間違い」であり、逆に少々値がずれても要求仕様を満たせば「正しい」のです(そもそもデジタルコンピュータである以上、どんなに頑張っても値は離散的ですしね)。理学と工学の志向・思考の違いを感じます。では。

[2014/09/19 13:08] | 不死女 -Immortal girl-
|
こんにちは。AI作り始めたんですが、もうこのタスクシステムも厳しいと判断しました。キャラの視野判定を追加しようとしたときに思いました。やっぱり「兄弟タスク同士の連携」がやりにく過ぎます。

今回の設計理念は、「親は子を参照できないが、子は親を参照できる」というものです。このシステムでは親子間で連携する処理を子タスクにさせることでなんでもできました。ところがゲームのオブジェクトたちは同種のものなので兄弟関係であり、互いに参照のしようがありません。強引にやるなら、先日述べた「メッセージ」としてポインタを渡せないこともありませんが…。

また、もっと前から抱えていた問題もあります。コンストラクタが使えないのです。親子関係を前提としている以上、初期化処理の多くには親のポインタが必要です。しかし親ポインタは、タスク追加メソッドの引数としてnewした後にそのメソッド内で代入していたので、コンストラクタの段階では参照できないのです。このため本当の初期化は後から別の手段で行う必要がありましたが、どうもスマートになりません。

こんなに言語仕様と相性が悪いというのはやっぱり何かが違うんです。というわけでタスクシステムを見直しました(こんなんで夏休み中に完成するのかな…笑)。

まず、兄弟の問題。あるタスクに用があるということは、そいつの型は分かってるはずですよね。よってtypeidを使って目的の型の兄弟タスクを返すメソッドを追加しました。さらに、このように直に参照が得られればメソッド呼び出しができますので、もはやメッセージシステムすら要りません。ただし1フレームごとに溜めてから読み取るメッセージとは違い、同じフレームで同じ処理が複数回行われる可能性がありますので、その対策は必要ですが。

そしてコンストラクタ問題も解決しました。C++らしく、new演算子をオーバーロードして引数で親タスクへのポインタを受け取るようにしました。そしてmallocした直後にキャストして代入してしまうのです。こうすればコンストラクタ内で親ポインタが得られますので、初期化は全てコンストラクタで行えるようになりました。ついでにタスク追加処理もnewでやってしまいましたので、タスク追加メソッドも要らなくなりました!

というように仕様を策定するのも苦労するのですが、それを今までのコードに反映するというのもまた時間がかかります。そうしてこんなに遅れてしまいました。ようやく新システムに移植が完了しそうなので、今度こそ!AIを作りたいと思います。

[2014/09/19 11:35] | 不死女 -Immortal girl-
|
こんばんは。現在AI製作中ですが、新たな課題が露呈しました。本編の負荷を想定したテスト(人体モデル同時7体表示)をしたところ、私の環境でもCPU使用量が十数%、ネットブックではいきなり紙芝居と、非常に重いのです。不死女は60FPS設計ですが、1~2体のときはネットブックでも30前後は出ているのが常でした。しかしやっぱりポリゴン数が増えると一気に重くなるんですね。
せめてもの足掻きとして「インデックス・バッファ」「インデックス付き頂点ブレンディング」という2つの技術を導入しました。どちらも効率化によりメモリやCPUの使用量削減を図るものです。このうちインデックスブレンディングはGPUの対応にばらつきがあるようで、非対応ならCPUで代用せよ、とのこと。さて、とりあえず対応チェックのコードを書いて、と…んんんん!?

…私の環境ですら非対応でした。いや、ネットブックじゃないですよ、メインのそこそこ速いノートですよ。

これは不死女で採用する「固定機能パイプライン」という昔ながらのDirectXが時代遅れだからだそうです。今のDirectXは「プログラマブルシェーダ」という方式がメインのため、GPUメーカーも固定機能のサポートを削っているようです。とまあがっかりすることもありましたが、そう言われてもやってみなければ分からないので、昨日一日かけてインデックス付き頂点ブレンディング(とインデックスバッファもついでに)を利用するように改装して、同じテストをしました。確かに処理は簡単になりましたが、ブレンディングをするためだけに(泣)GPUの仕事を全部CPUにやらせているため、CPU使用量は最初とあまり変わりませんでした。ならば!もともとGPU頂点処理ができないネットブックではメリットしかないはずだろう!と思いましたが、やはりポリゴン数が同じなら速度もほぼ同じようです。

というわけで、本気でネットブックで動かすなら頂点数自体を減らすしかないようです。固定機能でできる高速化は全て行ったつもりです。一応もう一つの動作確認マシンのVista世代ノートでは(アンチエイリアスをかけなければ)XGA画質でも60FPS出るので、常軌を逸した重さというわけでもないですし、これを採用としたいと思います。


<本日のスクリーンショット>
上記のテスト中の画面です。グラフィックで皆さんの想像するような今どきのゲームに勝てる気はしないので、製品版もこの程度だと思っておいて下さい。のけぞっている敵は、可哀想ですがこの時撃ったものです。
不死女

[2014/09/12 23:00] | 不死女 -Immortal girl-
|


skall-o
ゲームはグラフィック至高主義の自分ですが、
なかなか良いじゃないですか!想像以上だ!

こんばんは!
Sva
(笑)に意味を求めてはいけません^^
なかなかグラフィックの完成度高いですね!コスチュームが…ですね;
期待しております。


Shiro
今ではフリゲでさえ見栄えのいいのが多いので恐縮です…。ですが私の技術では数ヶ月かかったシロモノですので、そう言っていただけると嬉しいです。

コメント:を閉じる▲
こんばんは。ドア出来ました。サイズや開き具合は統一していますが、モデル自体は必要とあらば入れ替えられる仕様になりました(まだ一種類しか作ってませんが)。操作性を考慮した結果全て自動の引き戸です。一つのゲームオブジェクトとして処理しているため、移動や当たり判定に特別な処理は必要ありませんでした。やっぱり基盤がしっかりしているといいですね!

次に何するか迷うところですが、あとAIがあるとゲームシステムはほぼ完成したことになるはずなので、それを優先的にやってしまいたいところです。それから、モデリングや脚本といった非プログラマー作業に入ろうと思います。

<不死女・作者のタスクリスト>
①AI
②ステージ
③キャラ
④ストーリー
⑤細部のデザイン

[2014/09/09 18:49] | 不死女 -Immortal girl-
|


skall-o
スクショの一枚でも・・見せてはくれませんか・・!


Shiro
わかりました。次の更新の時は適当なスクショを貼ります。

こんにちは
Sva
作業お疲れ様です。
お互い本業(?)以外の仕事もこなさないといけないので大忙しですね!

スクショ期待してますよ(笑)


Shiro
スクショは最新記事の方にありますよ。
ところで、その(笑)は一体どういう意味なんですか(笑)

コメント:を閉じる▲
こんにちは。今日は弾数の概念とリロードを実装しました。皆様に嬉しいお知らせをすると、一度弾薬を入手したら(その弾薬を使う銃の)リロードは無限にできます。その代わり敵も無限リロードなので覚悟して下さい(笑)
また、今までは大きい銃のモーションしか用意しておりませんでしたが、新しくモーションを追加して拳銃なども持てるようになりました。銃声は銃ごとに変えるつもりですが、忠実な音源は手に入らないので適当なフリー素材を使わせて頂きます。
他にも足音や操作説明表示など、細かい所をちまちま追加しました。ようやくステージ作りの続きができそうですね。ただ本格的に作る前にドアの処理は作っておきたいのでプログラミングもほぼ同時進行ですかね。


<不死女・作者のタスクリスト改>
①ステージ
②ドア
③AI
④キャラ
⑤ストーリー
⑥その他素材集め

[2014/09/07 15:46] | 不死女 -Immortal girl-
|
こんばんは。弾丸をほぼ新規に作りなおしました。どう変わったかというと、最大の違いは壁をすり抜けなくなったことです。この間のものは「変位ベクトルが接触していたら被弾」というだけの判定だったので、いとも簡単に、しかもかなりの誤差ですり抜けていました。今回はまじめに衝突時間を求めてから判定しているので、そのようなことは起こりません。前回のものは「すり抜けではありません、貫通です」と主張すれば必ずしもバグにはならないんですが(笑)やはりちょっとお粗末な気がしたのでなんとかしました。
ついでに、当たり判定全般の改良をしました。ピッタリ接触している2物体の衝突時間を求めようとするとどうしてもt=0に収束するまで時間がかかったのですが、tがある程度小さくなった時点で判定を中断することでちょっと軽くなりました。

他には、弾が当たったらちゃんとダメージを受けるようにして、倒した敵の銃と手持ちの銃を交換できるようにして(すべての銃を回収できてしまうとあまりに難易度が下がるので、あくまで「交換」です)、コードを少し見やすくしました。まだまだ完成には程遠いですが、基本機能が着々と整備されつつあります。


<不死女・作者のタスクリスト改>
①リロード処理
②ドア
③簡易AI
④キャラ
⑤ステージ
⑥アイテム
⑦ストーリー
⑧効果音・BGM

[2014/09/06 20:37] | 不死女 -Immortal girl-
|
こんにちは。前回述べた「どのコードがいつ実行されるのか」わからない状況は非常にまずいです。私のプログラムはたいてい、ここから崩れていくのです。こんなに堅牢()なタスクシステムなのに、なぜだ…。よく考えると、タスク分離がまずかったようです。
今作では調子に乗って、各モーションは切り換え時に補間するようにしています(エセ線形補間)。そのため、もしタスク遷移しようと思うと補間パターンも全て別々のモーションにしなければなりません。例えば停止・歩行・走行なら「停止」「歩行」「走行」「停→歩」「停→走」「歩→停」「歩→走」「走→停」「走→歩」ですが…あまり作りたくありませんね。更にモーションを増やそうと思ったらそりゃもうヘッダだけで大騒ぎになります。実はこれ、元祖状態遷移であるswitch文を使うと一気に解決します。停止も歩行も走行も一つのクラスで管理して、単純なフラグで合成すれば補間できます。モーションを追加する時は、caseを一つ増やすだけで全パターンに対応できます。
ところが、被弾を追加した際に問題が起こりました。被弾は強制的なものなので、別のモーションへの補間は許されません。しかし前述の通りこのタスクは全ての補間を自動サポートする設計なので、被弾時は全くの別処理に分岐することになります。でもよく考えたら被弾も前のめりと仰け反りがあって、さらに死亡モーションも…と考えていったらやっぱり無理があります(笑)強引に条件分岐で実装した結果が冒頭の状態というわけです。

この古典的な詰み方はどうせswitchが原因です。でも補間は自動でしたい。switchの改良に勤しんでいたとき、あることに気づきました。被弾や死亡を実行時に補間することはありません。動作が決まっているからです。しかもその動作は全然違います。つまり、「通常状態」「被弾」「死亡」というようなタスクを作って、switch補間はあくまで通常状態の中で行えばよかったのです。これにて原則どおりの分離ができました。

また、前回も書きましたが、当たり判定もタスクの分離を難しくする原因の一つです。これは、当たり判定モデルを統合した「ゲームオブジェクト」クラスを新たに作って、ゲームのオブジェクトは全てこれを継承することで解決しました。このクラスは、別のオブジェクトを引数に取る当たり判定用の仮想関数を持っているので、ステージが全てのオブジェクトに対してこの関数を呼び出すだけで、各オブジェクトは自分で好きなように当たり判定を行えます。これで当たり判定処理をタスク内に分離できました。

[2014/09/05 16:57] | 不死女 -Immortal girl-
|
どんなときも♪どんなときも♪
アルゴリズムに悩むー僕はー♪
無ー理ーなものは無ー理ーとー
仕様変更 迫られていた♪

こんにちは。キャラ移動及びモーション管理、銃を持たせる処理や弾丸クラスなど、個々の技術は完成しました。しかし、どうも全体の管理に問題があるように思えます。これは、当たり判定などが他のオブジェクトの情報を要し、処理を各タスク内で完結させられないことが原因の一つです。処理の分離がうまくいかず、結局どのコードがいつ実行されるのか予想できなくなっています。これではあまり柔軟なステージ制作はできません。また割り込み処理の概念を一切なくしたため、ポーズ画面時などにオブジェクトたちをフリーズさせることができません。これも、タスクシステムをまた改造して「子タスクの停止」機能でもつけないといけないでしょうね。

管理法に行き詰まっているだけで、表現技術なんかはほんとに進んでいます。新たにポイントスプライトを整備して、弾丸や火花を簡単に表示できるようになりましたし、DirectSoundを管理するクラスを作って効果音の再生も楽々です。では恒例のタスクリストといきましょう。

<不死女・作者のタスクリスト>
①ステージ作成(前と同じ)
②オブジェクト管理(統括的な設計が必要)
③キャラ作成(残る新規モデルはヒロインのみ!)
④アイテム作成(銃や小物など)
⑤AI作成(移動はしない。とにかく定位置に陣取り、プレイヤーやヒロインが見えたor接近したら発射。ただし、その定位置自体を動かすことによって変化をつける可能性はある)
⑥ストーリー作成(設定大幅変更。骨組みはできてきた)
⑦効果音・BGM集め(再生技術自体は余裕だぜ)

[2014/09/03 15:59] | 不死女 -Immortal girl-
|
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。