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

マイページへ

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

投稿記事

無料プランの記事(76)

2Dで3D

2Dと3D…いや、ダイスの話じゃないですよ
(1D6とか3D6とかいう表記がありましたねえ。関係ないけどグーグルで 2D12 とかって打ち込むとダイスを振ってくれますよ)

今作っているゲームでは、これまで作っていたアクションゲームとは異なり奥行きの概念があります。
とは言っても基本的には横スクロールアクションなので、奥行き方向の移動はおまけ程度のものです。
よく勘違いされますが、たとえドット絵2Dのゲームであっても、奥行きの概念があるならばプログラム内部では3次元で計算しています。

そうでないと、

・ステージ奥方向に移動する(=キャラが画面の上方向に移動する)
・ステージ上空に向かって移動する(=キャラが画面の上方向に移動する)

この2つの移動を区別できないからです。
この動きを区別するためには、どうしても z という第3の座標を導入しないといけません。

縦横に加えて奥行きがあるということは、当然攻撃判定やダメージ判定も立体的で、地形も立体的なものとして扱わないといけないという事で、結局プログラム内部では3Dの計算をやることになります。

まあ本物の3Dゲームに比べればだいぶ楽だとは思いますけど。
キャラクターやオブジェクトは単なるビットマップなので四角いポリゴンにテクスチャをペタッと貼るだけで済みますし、基本的にはカメラは左右に動くだけで、奥行き方向にカメラが移動したりすることはありません。

内部の計算はそれで良いのですが、面倒なのはそれを表示する時です。
3Dモデルを作っているわけではありません。単なる平面の絵ですから、奥行き方向の情報はありません。薄っぺらです。

何が面倒かというと描画順序の決め方です。
どちらが手前に合ってどちらが奥にあるのか?これがすごく決めづらいんです。


ガチの3Dであれば、現実と同じように立体の形を持ったポリゴン(メッシュ)を作るので、その立体の情報をそのまま利用することができます。
Zバッファを使ってピクセルごとに手前か奥かの判断をすることもできますから、期待した通り綺麗に表示できます。

でも、背景も地形もキャラクターもすべて単なる平面の絵で作っている場合、奥行きに関する情報は全くありません。
奥行きがある物体があるように見えても、それは本当に奥行きがある3Dのモデルを表示しているわけではなく、板ポリゴンの薄っぺらい板に、いかにも「奥行きがあります」といった感じの絵を描いてみせているだけです。


というわけで、画面内に存在するキャラクターやオブジェクトをどのような順番で描画するかは、自分で決めなくてはなりません。
形に奥行き情報が無い以上、ビデオカードに任せることはできません。



こんな感じの箱があったとします。
ここにキャラクターを重ねたい。キャラクターは箱の上にも乗れるし、奥にも手前にも回り込めます。もしかしたら箱は高いところにあって、その下をキャラクターがくぐる事ができるかもしれません。

このとき、キャラクターと箱の描画順序はどうすれば良いでしょう?


とはいっても話は簡単で、キャラクターが【箱の上、奥、左側】にいるなら【キャラクター、箱】の順に、キャラクターが【箱の下、手前、右側】にいるなら【箱、キャラクター】の順で描画すればよさそうです。


このルールに従ってソートのための比較関数を作ってみましょう。
ソートするためには二つのオブジェクトを比較して、どちらが先になるかを判断する必要があります。

比較関数の内容は、こうです。
オブジェクトXとYを比較するとして、

・XがYよりも左にいるなら、Xを先に描画
・XがYよりも奥にいるなら、Xを先に描画
・XがYよりも下にいるなら、Xを先に描画
・それ以外の場合はYを先に描画

このルールで二つのオブジェクトを比較し、並び替えていけばよさそうです。

……ところがこれ、そう簡単にはいかないんです。
キャラクターも箱も、一つだけとは限らないからです。

図1


箱の左右にキャラAとBがいるとします。
ちなみにキャラBはAよりも奥側にいます(これは後で重要になってきます)

これ、単純に考えれば A<箱<Bの順で描画すればよいですね。


Aと箱の比較では、Aが箱の左にあるのでA<箱
箱とBの比較では、箱がBの左にあるので箱<B

となり、うまいぐあいにA<箱<Bとなりそうです。


図2

でも…この図の場合だとうまくいきません。

え?と思うでしょう
普通に考えれば、この図は B<箱<Aの順番で描画すればOKです。
先ほどのルールを思い出してみても、
Bは箱よりも奥なのでB<箱、
箱はAよりも奥なので箱<Aとなり、

B<箱<A

という順番が導かれそうです。


ところが


先ほど作った判定ルールをよく見てください。
左右かどうかの判定を、奥かどうかの判定よりも先にしていますね。

そうです。「AがBよりも左にあるからA<Bだ」という判断を、
「BがAよりも奥にあるからB<Aだ」とよりも優先してしまうんです。

上の図だと問題ありませんが、AとBの絵の横幅がもっと大きかったら?
この状態でAとBが重なった場合、BがAよりも手前に描画されてしまうんです。


じゃあ、決め方を少し変えてみます。
奥かどうかの判断を左右かどうかの判断よりも先にしてみましょう

・XがYよりも奥にいるなら、Xを先に描画 <--- ここの順番を変えた
・XがYよりも左にいるなら、Xを先に描画 <---
・XがYよりも下にいるなら、Xを先に描画
・それ以外の場合はYを先に描画

しかしこの場合ですと、図2ではうまくいっても図1ではうまくいきません。
新しい比較方法では、奥行き方向の比較を左右よりも優先しています。

図1ではBのほうがAよりも奥にいますから、比較結果はB<Aになります。
図1で見ると、AとBは重なっていませんからAとBのどちらを先に書いてもよさそうです。

でも忘れないでください。箱の存在を……。
この図を正しく描くためには、必ずA<箱<Bの順番で描かないといけないんです。
そのためには、AとBを比較したときにA<Bと判断されないといけません。

これ、箱の上下左右に10人ぐらいキャラクターがいた場合とか、どうします?
絶望的ですよね。たまたまうまくソートできる場合もあるとは思いますが、必ずどこかで矛盾が発生します。

まー

ややこしいといった理由がお分かりになったでしょうか?

一応、比較条件をもう少し細かくすると少しはマシになるのですが、矛盾をゼロにすることはできません。配置によっては必ず矛盾が発生するパターンがあります。

これを解決する一つの方法は、箱を細かく分割してしまうやり方です。
上での方法がややこしい理由は、単に箱がデカいからです(いや、本当に)。


昔よく見かけたクオータービューのゲームだと、ブロック化されていると言いますか、
例えば大きな建物などといったものは、いくつかのパーツに分かれてるんですね。
大きなものを、大きなままで扱おうとするから矛盾が発生するわけで。

でも今更背景や地形をブロック化するのもなァ……というわけで、結局3Dで作って2D視点固定にしちゃうのが早いんじゃない?となるわけです。なった。



おまけ
https://twitter.com/helio_dor/status/1182261982990225409

はい、この通り3Dなので、2Dではややこしい構造もやりたい放題です。


視点を変えて3Dっぽくするとこんな感じ。

最近はこういう3Dなのに2D(?)なゲーム、たまにありますよね。3D酔いの心配が無いので大好きです。

しかし元々立体化なんて考慮していない描き方の背景を無理矢理3Dっぽくしているので、よく見ると色々歪んでます。
というか、なんかスケール感がおかしいです。

まあこれは単なるテストで実際には使わない視点なので……。



おまけ2


本文中でチラッと触れた、キャラの横幅が長い場合。(思い返して画像作ってみた)

一体どうすりゃいいと思います……?(^ω^;)

\いいね・ツイートで応援!/

0.5ドットの罠

かなりマニアックな罠です。

DirectX 9 を使っていて、ドットバイドット(ディスプレイの1ドットが、ビットマップ画像の1ドットに完全に対応している状態)で表示をしようとしている方は、0.5 ドットのズレに気を付けてください。

テクスチャ画像を載せたポリゴンとかをそのまま素直に整数座標で表示しようとすると、ぼやけてしまったり画像がゆがんだりします。
詳しくはリンク先に書いてあります:

DirectX9のスクリーンドット化(ラスタライゼーション)ルール
http://marupeke296.com/DXG_No63_RasterizationRule.html

Directly Mapping Texels to Pixels (Direct3D 9)
https://docs.microsoft.com/en-us/windows/desktop/direct3d9/directly-mapping-texels-to-pixels

テクセルとピクセル間の直接マッピング
https://docs.microsoft.com/ja-jp/previous-versions/direct-x/cc373047(v=msdn.10)

要するに、テクスチャのサンプリング位置がテクセル(テクスチャ上のピクセル)のド真ん中ではなくテクセル境界上になってしまうことが原因で、ドットバイドットで表示しようとしたときに変に補完されてぼやけてしまう、というものです。
(ちなみに DirectX 10 以降ではサンプリング位置がテクセル中心になっているので、この問題は起こりません)

でもよく考えてみれば、ドットバイドット前提で表示するなら補間する必要がないんですよね。

SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR)
SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR)

ではなくて

SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT);
SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT);

にしとけば問題なくね?ってことです。補完されちゃうからダメなのであって、補間しなければ問題ね―じゃないかと。

……ところがこれ、結構微妙なことになります。


次の画像は開発中のあやかし紅白戦のものですが、何も考えずに補間を切って(D3DTEXF_POINT) 、ポリゴンを表示させた状態です。
いろいろ微妙に歪んでいるのが分かるでしょうか?


分かりやすいようにキャラだけ拡大してありますが、他にも色々おかしいです。
これらの歪みは全てポリゴン接合部で発生しています。


補完は切ったまま、オブジェクトを描画する時の座標を 0.5 ドット左上にずらすと、こうなります。


歪みがキレイに取れていますね。

こんな感じで、変な風に歪んでいたらまず 0.5 ドットのずらし忘れを疑ってみてください。私が初めてこの問題に当たったときは、その原因に気づくまでかなりの時間を費やしました……。

特にドット絵を売りにしているゲームは同人やインディーズでは多いので、引っ掛かる方も多いのではないでしょうか。
……と思ったけど今更DirectX 9 を使う人はもうあまり居ないのかな?

\いいね・ツイートで応援!/

お手軽袋文字とか

袋文字(塗りつぶしあり)を作る方法はいくつかあるともいますが、自分がいつも使っているお手軽な方法を紹介します。
例えば太さ1の輪郭で輪郭部分が白、中身が赤色のテキストを書く場合には

for (int i=-1; i<=1; i++) for (int j=-1; j<=1; j++) {
 DrawText(x+i, y+j, text, WHITE);
}
DrawText(x, y, text, RED);

のようにします。少しずつずらしながら同一画像を描画することで元のテキストを上下左右に1ドット太らせて、最後に真ん中にテキストを描画して塗りつぶし部分を表現します。

けっこう無駄のある方法なのですが、とにかく手軽に袋文字(塗りつぶしあり)を描画したいというのであれば上記の方法で完全に達成できます。

ちなみに塗りつぶしなし袋文字は直接描画することができまんが、別の画像バッファを用意し、

for (int i=-1; i<=1; i++) for (int j=-1; j<=1; j++) {
 DrawText(x+i, y+j, text, WHITE);
}
SetBlendMode(BLEND_SUBTRACT); // <-- 減算合成にする!!
DrawText(x, y, text, WHITE);

みたいにして文字の真ん中部分を引いてやれば文字の内側にぽっかりと透明な穴が開くので、そうしてできた画像を改めてテキストとして描画します。




袋文字(塗りつぶしあり)の使いどころとしては、例えばこんな感じの文字表示。


この背景では大丈夫ですが、白っぽい場所では白い文字は見えにくくなってしまいます。

そこでお手軽袋文字!


こうやって黒い縁取りがあると安心ですね。
……いやこれ文字ちょっと細すぎるかな。まあいいか。




おまけの小ネタ

少し前にツイートした触手研究の成果
https://twitter.com/helio_dor/status/1169622936732307457
自分で作っておいてなんですが……最初に動いているのを見たとき、子供の頃に釣りの餌箱を見てしまったときのトラウマが蘇りました。
まあそれは置いといて、後はチンアナゴみたいに下から生えてユラユラしてるのも欲しいですよね。


https://twitter.com/helio_dor/status/1175691528431267840
実際はこの暗いステージには触手生やす予定無いので、この問題は取り合えず放置ですが。





あと、たまに主人公(の体操着)の色が変わっているのはパワーアップで変わるからです。

何気にこだわりのオーラ。(実はヴィータ大脱出で作ったやつ)

\いいね・ツイートで応援!/

XLSX ファイル

以前 ZIP の読み込みプログラムについて説明しましたが、ついでなのでその延長線上にあるプログラム、「.xlsx ファイルのロード」も載せておきます。

Excel ファイルをゲームのデータ管理に使えたら便利だなー、とは誰もが一度は考えることだと思います。
レベルごとの経験値、能力値、アイテム効果など、表形式で管理できればこれほど楽なことはありません。

しかも、Excel ファイルをわざわざ .csv や .xml にエクスポートして使うのではなく、ゲーム側で直接ファイルをロードできるようにしておけばすごく便利ですよね?

それを実現しようとすると以前は結構面倒だったのですが、ナイスなことに Excel 2007 からは独自のバイナリ形式 .xls ではなく ZIP と XML を組み合わせてわかりやすい形式で保存してくれるようになりました。それが .xlsx ファイルです。


しかしバイナリ形式に比べれば格段に楽になったとはいうものの、ZIP を自力で展開し、中にある XML ファイルを解析し、Excel のシート内のセルを復元しないといけない事に変わりはありません。

ですので、そのあたりのプログラムもサンプルとして置いておこうと思います。



ファイル形式

.xlsx ファイルは実際のところ単なる .zip ファイルなので、とりあえず zip の解凍プログラムに .xlsx ファイルをそのまま放り込んでみます。

すると中には

 docProps
 xl
 _rels
 [Content_Types].xml

といったファイルが入っています。



シート概要

シートが何枚あるとか、それぞれのシートの名前といった情報は

 xl/workbook.xml

に入っています。この中身はだいたい次のような感じになっています。
(本当はもっといろいろなタグが付いていますけど)

 <workbook>
  <sheets>
   <sheet sheetId="1" name="シート1"/>
   <sheet sheetId="2" name="シート2"/>
   <sheet sheetId="3" name="シート3"/>
  </sheets>
 </workbook>

これはそのまま見たとおりの内容で、シートの識別番号と名前を表しています。



シート詳細

肝心のシートの中身ですが、その情報は

 xl/worksheets/

にあって、フォルダの中には sheet[シート識別番号].xml というファイルが
シート枚数と同じ数だけ入っています

 sheet1.xml
 sheet2.xml
 sheet3.xml

これも見たとおりの内容で、それぞれのファイルが 1 枚のシートを表しています。



セル

いよいよセルの中身についてです。シート内にあるセルにどんなテキストが書いてあるのかは、
xl/worksheets/sheet[シート識別番号].xml の中身を見ます。
必要な部分だけ抜き出すと、だいたい次のような感じになっています。

 <worksheet>
  <dimension ref="A1:E4">
  <sheetData>
   <row r="1">
    <c r="A1" t="s" s="3"><v>0</v></c>
    <c r="B1" t="s" s="3"><v>1</v></c>
    <c r="C1" t="s" s="3"><v>2</v></c>
   </row>
   <row r="2">
    <c r="A2" s="2"><v>1000</v></c>
    <c r="B2" s="2"><v>2000</v></c>
    <c r="C2" s="2"><v>3000</v></c>
   </row>
  </sheetData>
 </worksheet>

<dimension> の ref 属性には有効セルの範囲情報があります。
この例でいうと、セル A1 を左上、セル E4 を右下とする範囲内のセルにだけ何かの文字が入っていて、それ以外のセルはすべて空っぽだという事です。

<row> は行を表していて、 r 属性には 1 起算の行番号が入っています。

<c> はその行に含まれているセルを表していて、 r 属性はセル番号です。
<v> はそのセルの値なのですが、どんな種類の値が入っているのかについては <c> の t 属性を見ます。

t = "n" だと、<v> には数値が入っています。例: <v>3.1415</v>
t = "s" だと、<v> には文字列IDが入っています。例: <v>2</v>
t = "str" だと、<v> には文字列がそのまま入っています。例: <v>Hello World!</v>
t が省略されている場合は t = "n" と同じで、数値が入っているようです。



文字列テーブル

セルの値として文字列IDが指定されている場合、そのIDを元にして実際の文字列を探してこないといけません。
文字列のテーブルは xl/sharedStrings.xml に入っています。
例によって必要な部分だけ抜き出すと、だいたい次のような感じになっています。

 <si><t>January</t></si>
 <si><t>February</t></si>
 <si><t>March</t></si>

上から順番に、文字列ID=0, 1, 2, ... に対応しています。
ちなみにこれとは別のパターンもあって、

 <si>
  <rPr>スタイル情報いろいろ</rPr>
  <r><t>This is</t></r>
  <r><t> a pen</t></r>
  ....
 </si>

のようになっている場合もあります。この場合、単に <t> の中身を全て連結したものがセルの文字列になります。
セルの途中でテキストの色を変えたりしているとこうなるみたいです。



ソースコード

というわけで、これらの情報を元にしていけば、自力で Excel ファイルの内容を復元することができます。

フォロワー以上の方の特典として、サンプルプログラムのソースコードを置いておきます。
ビルドに必要なファイルは全て一緒に入っているので、ただ .sln ファイルを開いてビルドするだけで普通に実行できます。
(Visual Studio 2017 で確認しています)

例によってこのプログラムを使用したことによる責任は一切負いません。
その代わり、組み込み、改造、再配布など全て自由です。

フォロワー以上限定無料

まずは無料プランで様子見を。 お気軽にフォローしてみて下さい。

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

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

月額:500円
購入する
\いいね・ツイートで応援!/

雑談

たまには雑談でも。

前回の記事のアンケート、ご回答ありがとうございます。
適当に決めましたけど、回答期間1週間はちょっと長かったですかね?

今のところこんな感じで、あと二日程ありますが大勢は変わらないかなと思います。
なんだか自分に都合のいい結果過ぎて逆に不安になりましたw

しかし回答数137で77%ということは100人以上かな? 描く方としては楽なんですが、一見手抜きっぽいブルーマンでも許容される方が結構多いんですね。もちろん作品内容によっては話が違うのでしょうけど。(例えば百合系作品とか……片方半透明シルエットだったら怒りますよね)

そして不透明派も決してゼロではないということも心に留めておきたいと思います。




さてもうひとつ、ゲーム製作と全然関係無い話です。


これ! なんだかご存知ですか?

モデリングブラシ(静電気防止)というものです。
単に埃を払うためのモノですが、これがすごく便利……というか「こうかばつぐん」なのです。

飾ってあるフィギュアやプラモデルに積もった埃って、掃除機のノズルを近付けた程度ではなかなか取れません。
特にポロリしやすい武装とか部品とかが多いガンプラは無理して掃除機で掃除しようとするといやぁぁ私のガンダムがぁぁぁぁ大惨事になることもありますが、このブラシはとても柔らかく、軽く撫でるだけで綺麗に埃を落とせるので安心です。
今まで埃は見えているのに手が出せなかった細かい隙間なんかもバッチリ綺麗にできました。

私はあまりやりませんが、改造とかでパテ盛ったり削ったりする方には当たり前の道具だったりするのでしょうか? 今までこんな便利なものを知らずに生きてきたなんて……。
(多分、お店で視界には入っていたけど完全スルーしていました)



エロ同人で触手プログラムとか作りながら作業の合間に美少女フィギュアをブラシで優しく撫でて悦に浸る自分は控えめに言ってもヤベー人なのでは……という気もしましたが深く考えないことにします!

\いいね・ツイートで応援!/

記事を検索