Heliodorさんをフォローして、最新情報をチェックしよう!

マイページへ

Ci-enはクリエイターに対して、金銭的な支援を送ることができるサービスです。

投稿記事

2018年 12月の記事(5)

カラーパレット変更テスト


テストでカラーパレット変更サンプルを作ってみました。
文字通りのカラーパレットを変更して眺めるだけのものです。ゲーム部分は一切ありません。
実行形式ファイルで、フォロワー以上限定でダウンロード可能です。



ちょっとだけエロいポーズも入れました。
こちらは有料支援者限定配布で「シーン」という項目が変更可能です。

※このグラフィックは本編でも使用されますし、本当にちょっとだけエロいポーズをカラー変更して眺めるだけのものなので、これだけを目当てに課金するのはお勧めしません。

フォロワー以上限定無料

フォロワーが増えるとヘリオドールの製作速度が精神的に向上します。

無料
【 白銀ベリル 】プラン以上限定 月額:500円

先月以前に投稿された記事のため、この限定特典を閲覧するには[ バックナンバー購入 ]する必要があります。

月額:500円
購入する
\いいねで応援!/

謎のエラー

プログラム話です。

今回もすんごいへんてこなエラーに悩まされた話です。
結果から言うと、1日1回クリーンビルドしようね、ってことです。


以前お話しした通り、現在製作中のアクションゲームの開発ツールには Visual Studio を使っています。いつものように、ビルド(Ctrl+Shift+B)して実行、実行結果を確認、プログラムをいじって再ビルド、という風に作業していたところ、突然、見慣れないエラーが出てビルドできなくなりました。

しかも、全く触っていないはずのファイルでエラーが出ています。ためしに、今変更したファイルだけをピンポイントでコンパイル(Ctrl+F7)すると、そのファイルに関してのコンパイルは成功します。

たった今変更したファイルではコンパイルが通るのに、全く無関係な別のソースファイルでいきなりエラーが起きるようになったのです。

おかしい。さっきまで普通にビルドできてたのに。そもそも、そのファイルは1バイトたりとも変更してないぞと。
具体的には、名前空間 std が定義されていませんとか、cでは許されないキーワード使ってるとか、その他諸々のエラーが1000件ぐらい出てきます。見てみると、標準のc++ライブラリのヘッダでエラーが起きてるんですね。

まず疑ったのは、何かのミスで標準ライブラリのヘッダファイルに文字を書き込んでしまったのでは?という事です。試しに適当なc++ライブラリのヘッダにわざと無意味なコードを書き込んで保存しようとしましたが、当然ながら書き込みロックがかかっていて保存できません。これなら知らないうちに間違って書き込んで、さらに無意識にCtrl+Sして上書き保存してしまったなんて事は無さそうです。

次に疑ったのは、c++ではなくcでコンパイルするように設定が変わってしまったのでは?という事です。c++のソースをcコンパイラでコンパイルすれば、上記のようなエラーが山ほど出るのもうなずけます。

が、確認してみたところ普通の設定になってました。拡張子が.cppならc++コンパイラを、.cならcコンパイラを使うって設定ですね。まあ、普段いじるような設定ではないので、デフォルトのままになっています。当たり前です。

もしかしたら何かの具合で、それ以外のどこか細かい設定がおかしくなったのかもしれません。プロジェクトファイルなど、ソリューションファイル .slnを始め、キャッシュファイルなどを全て削除して、プロジェクトをクリーンな状態に戻してみます。

こういう時にcmakeの出番です。プロジェクトファイル一式を全て削除し、cmakeの設定ファイルであるCMakeLists.txtだけが残った状態にして、改めてcmakeでプロジェクトを再生成してみます。

CMakeLists.txtの内容が1バイトも変更されていないというのはTortoiseHgによる編集有無の検出機能で確認済みですから、最初にビルドができていた時と比べて、プロジェクト全体を通して1バイトも差分がないはずです。が、ビルド結果は変わらず、大量のエラーが出ます。

謎なのは、TortoiseHgで数日前の状態にソースを戻してビルドしても、同じエラーが起きるという事です。いやいや、昨日とかも普通にビルドして実行していたぞと。

ところが、さらにもっと前の状態に戻すとビルドが成功するんですね。そして、一度でもビルドが成功した後にソースを現在のものに戻すと、普通にビルドが通るんです。で、その状態でまたプログラムを再開すると、何度かビルドに成功したあと、あるとき突然エラーが出てビルドが失敗します。

そこまできてようやく気づいたのですが、普通にビルドが通っていたはずの昨日や数日前のソースが、クリーンビルドすると通らないんですよ。

つまり昔のソースでビルドした後、あるファイルに変更が加わって本来ならばコンパイルが通らない状態になっていたが、差分ビルド(変更があったファイルだけコンパイルし、それ以外は以前のコンパイル結果を使う)のためにそのファイルについては再ビルドが行われず、正しくコンパイルできた時の結果が使われていた、と言うことになります。

ただ、ファイルに変更があったのに差分ビルドの対象にならないって事があるのでしょうか?ファイル自体は変わってなくても、そのファイルがincludeしているヘッダに変更があれば連鎖的に再ビルドされるはずですが...。

そこで、ソースを昔に戻しながら、どの時点でクリーンビルドが通らなくなったのか調べました。

TortoiseHgでソースを戻し、cmakeのキャッシュを消してクリーンなプロジェクトファイルを作り、フルビルドする。これを何回も繰り返して調べた結果、1週間ほど前のソースを境にエラーが出るようになっていたことが分かりました。その時行った変更は、ソースファイルの分割です。

とあるファイルが巨大になりすぎて扱いにくくなったので、その中からゲームキャラクターやシステム間の通信(様々なコマンドを送ったり、特定のイベントが発生したときに通知したりする)に関する部分だけを抜き出し、signal.h と signal.cpp というファイルを追加して、そこに移動しました。

この変更のうち、どの部分がまずかったのかをさらに調べます。まず、新規ファイルの追加だけをしてみます。すると、signal.h と signal.cpp をプロジェクトに追加した時点でビルドが通らなくなりました。最初は???だったのですが、少ししてピーンと来ました。

なんか signal.h ってすごく標準ライブラリにありそうな名前だぞ、と。

もしかして名前がかぶっているのでは?と。

見てみると、やはり singal.h というのは標準ライブラリに存在するヘッダファイルでした。普段使わないので全く気にしていませんでしたが…。

そこで signal.h ではなく event_signal.h という名前に変えてみたところ、あっさりとビルドできるようになったのです。ここまでくると原因追究は簡単でした。

このゲームのプログラムではスクリプトシステムとして lua を使っているのですが、lua.lib などのコンパイル済みファイルを使わずに lua をソースごとプロジェクトに取り込んでありました。

その中に、#include <signal.h> という、標準ライブラリを include している部分があったのですが、lua のソースは c で書かれているため、lua のファイルをコンパイルする時は自動的に c コンパイラが選択されます。#include <signal.h> の部分で、普通なら c コンパイラは標準ライブラリの signal.h を参照するはずですが、プロジェクト内に同名のファイルが見つかったためにそちらを優先し、 c コンパイラが c++ のヘッダファイルを処理しようとして大量のエラーが発生した、という事でした。

普通ならインクルード先のヘッダファイルが変わっていたら連鎖的に再コンパイルされるはずですが、ファイル名が同じで参照先が変わるだけ、というのは盲点だったのかもしれません。とにかく、再コンパイルが行われずに中途半端に過去の結果が使われてしたために変な挙動になっていました。

そういえば別々の cpp ファイルに同名のローカルクラスを作って挙動がおかしくなった、という事故が以前ありました。

例えば a.cpp 内で MyClass というクラスを作り、b.cpp 内でも MyClass というクラスを作ります。MyClass はヘッダには出さないので、a.cpp の MyClass は a.cpp 内からしか見えないし、b.cpp 内の MyClass は b.cpp 内からしか見えないはずです。

ところが MyClass のメソッドを呼ぼうとしたとき、a.cpp の MyClass のメソッドを呼んだつもりが、b.cpp の MyClass のメソッドが呼ばれて、挙動がおかしくなったという事がありました。メンバ変数は a.cpp で定義した通りなのに、呼ばれたメソッドは b.cpp のものだった、というものです。

それ以来、たとえスコープ的に独立したものであっても同名のクラス名は付けないように気を付けていたのですが…。変な挙動をしたときは、同名のファイルやクラス、メソッドがないかどうか確認した方がよさそうです。

ところで、原因はともかく、今回の事故はこまめにクリーンビルドしていれば気づけたものでした。そうすれば、少なくとも何日も経過してからビルド出来ないことに気づくという事はなかったわけです。


結論:
「1日1回クリーンビルドしよう」

(あと、バージョン管理ツールは、こういう時にすごく役立つ)

以上です。

\いいねで応援!/

寒くなってきましたね

最近、身の回りがごたごたして忙しかったのですが、ようやく一息つきました。
現在製作中のアクションゲームは、何とか暖かくなる頃までには完成させたいと頑張っています(実際いつになるかはまだ何とも言えませんが、気持ち的に)。

今は先送りにしていたゲームルールの細かい部分をぼちぼちと考えています。
以前考えていた剣と蹴りを対霊・対人で分けるのは、やはり実際に試してみたら面倒なだけであまり面白くなかったので、操作は単純に攻撃とジャンプの2ボタンになりそうです。

探索ゲームみたいにじっくりやり込むゲームならボタン多かったり複雑なルールも面白いと思うのですが、本作はどちらかというとアーケードゲームのような方向性で作っているので、やはりこういう感じかなーと。



モ●ハンみたいにダッシュや溜め攻撃(必殺技)にスタミナゲージを消費するようにしようかなとか考えてみたんですが、これも果たして面白いかなぁ……でもスタミナ切れて動けないところを襲われちゃうのはエロいかも?

仮のゲージもそろそろちゃんと書き直したいんですが、やる事山積みです。
あ、暖かくなる頃……いや、暑くなる頃までには……。



話は変わりますがPCで作業するのは夜が多く、夜は足元が冷えて辛いので小さい電気カーペットを買ってみました(Amazonで1500円ぐらいのヤツ)。
足の裏の接触している部分は暖かいのですが、それ以外は全然暖かくない……。
膝から毛布とか被せると暖かそうだけど席立つとき面倒くさい……。

試行錯誤の結果こうなりました。

結構いいかも。(改良の余地あり)

\いいねで応援!/

Heliodor作品半額クーポン

クーポン機能が追加されたそうなので作ってみました。

しかし有料支援している人にしか配布できない割引券というのは果たしてお得なのか……。
そしてこの割引分は誰が負担するのか……。
(説明見ても書いてありませんでしたが、まあサークル負担でしょうw)

色々と疑問はありますが、まあとりあえずお試しということで。

【 白銀ベリル 】プラン以上限定 月額:500円

先月以前に投稿された記事のため、この限定特典を閲覧するには[ バックナンバー購入 ]する必要があります。

月額:500円
購入する
\いいねで応援!/

有料支援者特典のエロドットビュワー裏話

プログラム話です。

先日公開した支援者様向けのヴィータ大脱出エロドットビュワーですが、ただ敵をON/OFFするスイッチを付けただけのものなので、そんなに手間はかからないだろうと思ってました。
……しかしそんなことはありませんでした(泣)。

まず、素直に敵だけ非表示にできるように作ったのですが、そうすると白濁液とかヨダレとか白い息のエフェクトが何もない空間から出てくるんですよ。


文面では伝わりにくいかもしれませんが、実際に画面で見たときは衝撃的でした……。


ヴィータ大脱出ではキャラクターの表示・非表示を切り替えるといった動作が皆無だったので、エフェクトを生成するときに、生成元となるキャラクターが表示状態なのか、非表示状態なのかをチェックする機構というのはついていませんでした。

そんなわけで、「エフェクトを生成するときにその生成元のキャラクターの状態をチェックし、非表示状態であればエフェクトを生成しない」というコードを追加しました。

ただ、それを一律に適用してしまうと、パーティクルをまき散らすための隠しキャラクター(ジェネレーターとかエミッターとか呼ばれる)がパーティクルを一切生成しなくなってしまいますので、アルバムモードかつCi-en限定版でのみ、このような条件でエフェクト生成するようにしたわけです。

本当ならば敵をOFFにしたときに、敵から生成されたヨダレ、地面にヨダレが落ちた跡、跳ね返りの液体もすべて一括でOFFにできればスマートでよかったのですが、意外と大変なのでボツにしました。

それをやるためには、何が何を生成したのかという関係を追跡して管理する仕組みが必要ですが、そういった仕組みはヴィータ大脱出では不要だったので作っていなかったのです。そもそも、そういう仕組みを作ること自体が意外と大変です。

例えば、敵がヨダレをたらし、そのヨダレが地面に衝突して涎の跡(以下水たまり)を生成すると同時に、付近に飛び散る液体(以下水しぶき)を生成したとします。生成関係の追跡を素直に実装すると、各オブジェクトがそれぞれ自分が誰から生まれたのかという情報を持つようにして、ヨダレは敵から、水たまりはヨダレから、水しぶきは水たまりから、ということが後から調べられるように作ると思います。

これを逆に辿っていくと、水しぶきは元々敵から由来したものだから、敵の表示をOFFにしたら水しぶきも消えるべきだとわかるわけです。

これで問題なさそうですが、実際にはそうはいきません。水しぶきが上がったときにはヨダレはすでに消えているため、水しぶき→水たまり→ヨダレまでは辿ることができますが、この時点でヨダレはすでに存在しないため、ヨダレがどこから来たのかという情報が消えているからです。

となれば、こういった情報は各オブジェクトに持たせるのではなく、外部の管理者に任せるようにすればよいという事になります。しかし、それはこれまでに作成したオブジェクトの履歴をずっと持っているという意味になるので、実行時間が長くなるほど管理するべき履歴の量が増え、負担が大きくなってしまいます。

だったらどこか適当なところで古い履歴を削除して……などとやっていると、こういった仕組みをまじめに作るのは結構大変だという事に気づきます。そんな面倒なことしなくても、空中からヨダレが出ないようにするだけで十分ですしね。

それよりも大変だったのは、アルバムモードでの非同期ロードが原因と思われるエラーです。

今回のアルバムモードでは起動時にすべてのアルバムが強制的に解放されるので、アルバムのロードが40個ぐらい同時進行で進んでいます。これは初めから意図していた動作ですので、当然スレッドセーフになるように作っていたのですが、どうやら穴があったようで、起動直後にアルバムを高速スクロールしていると落ちる時があるんです。

さらにデバッグモードでは落ちずにリリースモードの場合だけ落ちるといういやらしさです。しかも検証用のコードを入れたとたんに落ちなくなるという……。プログラムあるある的な話ですね。

直接の不正落ちの原因は、配列の範囲外のインデックスを参照してNULLが返った事によるNULLポインタ参照エラー(ぬるぽ、懐かしいですね)なのですが、そこに配置してあるインデックス確認用の assert には一度もひっかったことがないんですよね。デバッグモードで assert が有効な時は不正なインデックスが入って来ず、リリースモードで assert が消えた瞬間に不正なインデックスが入ってくるという……。

メモリ関係のエラーはちょっとコードを書き換えただけで挙動が変化してしまうので、本当にやりにくいです。結局今回は NULL チェックを追加して参照を回避しただけで、そもそもなぜ範囲外のインデックスが来たのかという根本的解決にはならず、でした。

https://twitter.com/helio_dor/status/1064442433004482560
https://twitter.com/helio_dor/status/1064442686541721600

この辺のツイートは大体そんな感じです。ヤケクソ感漂いますね。

\いいねで応援!/

記事を検索