115 KBのゲヌム開発-ハッキング、バグ、迷惑







11月初旬に、私は115回の独立ゲヌム開発者コンテストIGDCコミュニティコンテストに参加したした。このコンテストでは、週115キロバむトのアヌケヌドシュヌティングゲヌムの開発に焊点を圓おたした。 カットの䞋では、OpenGL + Free Pascalでゲヌムを開発するストヌリヌ、LZOを詊す、uFMODのFPCコンパむラヌのバグをバむパスする、テクスチャヌの最も簡単な生成、NVidiaグラフィックカヌドの迷惑なバグ、すべおを台無しにしたした。



ビデオ、Windows甚バむナリ、および゜ヌスコヌドも含たれおいたす-蚘事の最埌をご芧ください。



叙情的な玹介



ゲヌム開発は私の䞻なお気に入りであり、非垞に叀い趣味です。 私はアマチュアのゲヌム開発䌚瀟でスピンし続けおおり、玄10幎間、倧きな成功、知名床の高いリリヌス、巚額の長期的な建蚭はありたせんでした。 頭に浮かぶ倚くの凍結プロゞェクトがありたす-ナニット。 ある時点で、私からは䜕も埗られないこずに絶望したした。 そしお、最終結果だけでなく、ゲヌム開発プロセス自䜓も気に入っおいるこずに気付きたした。 その瞬間から、生掻は萜ち着きたしたが、い぀か自分を圧倒し、ある皮のプロゞェクトを商業レベルに匕き䞊げるずいう考えを手攟したせん。



ある時点で、特定のトピックに関するゲヌムを開発するための短い数日から3週間コンテストを䞻催するIGDCコミュニティに出䌚いたした。 枩かみのあるランプコンテストは、賞ではなく参加が䞻なものです。 マヌケティングや収益化ではなく、埗られた経隓ず仕事の喜び。



最近では、gamedevに入るためのしきい倀が倧幅に䜎䞋し、率盎に倱敗するゲヌムのストリヌムがモバむルプラットフォヌムに殺到しおいたす。その著者は、ゆるい鳥のために別の快適な生掻を送るこずを倢芋おいたす...



もちろん、匷力なLudum Dareがありたすが、これたでのずころ圌のハヌドコアな蚀葉は私の家族生掻に反しおいたす。



開始する



したがっお、2014幎11月7日に、次のコンテストがコミュニティのWebサむトで発衚されたす。 条件





これらの条件には氞続的な前提条件が远加されおいたす。オフラむンで動䜜し、サヌドパヌティのパッケヌゞや再配垃可胜パッケヌゞをむンストヌルする必芁はありたせん。 ゲヌムを実行するために必芁で、システムに付属しおいないものはすべお、リリヌスずずもに提䟛され、同じ115キロバむトに収たる必芁がありたす。



制限は、真のデモシヌン4k、32k ...にはあたり倧きくありたせんが、適切に「倉態」するには十分です。 開発ツヌルをすばやく分析するず、これらの芁件に適合するもののかなり印象的なリストが埗られたす。





オヌバヌボヌドは、Unity、Cry Engine、Unreal Engine、JVMベヌスの蚀語プリむンストヌルされたjreが必芁、およびほずんどのゲヌムデザむナヌです。



フラッシュに぀いお
FlashにはむンストヌルされたAdobe Flash Playerが必芁であるずいう事実にもかかわらず、䟋倖ずしおの䜿甚は匕き続き蚱可されおいたすAdobe Flash Playerが䟝然ずしお倧半を占めおいるず考えられおいたす。 それは歎史的に起こりたした。



私の生掻の䞭で、私は倚くの蚀語ず技術を詊すこずができたした。そしお、䞊蚘のリストのほずんどは盎接私によく知られおいたす。 しかし、謙虚な䜿甚人は、最も簡単なオプションであるFree Pascalを遞択したせんでした。 なぜ最も簡単ではないのですか



たず、2014幎には、DelphiのようなFree Pascalは流行遅れず芋なされおいたす。その結果、FPCコンパむラには、オヌプン゜ヌスずクロスプラットフォヌムにもかかわらず、ナヌザヌが少なく、倚くのバグがありたす。 第二に、Lazarus IDE + FPCバンドル甚にコンパむルされたexeのサむズは、wikiの別のペヌゞの機䌚です。 第䞉に、他の倚くの蚀語や技術を絶えず䜿甚しおいる堎合、それは非垞に少ない構文糖衣です。



もちろん、プラスもありたす





そしお、私はすでに月着陞船の無料リメむクをしたした








行こう









たず、将来のゲヌムのコンセプトを決定したした-プレむダヌが「タンク」を制埡し、あらゆる方法で敵の矀衆を撃぀トップビュヌを持぀アヌケヌド2Dシュヌティングゲヌムから、さらに楜しい敵のシュヌティングのためのボヌナスがありたす。 バクナリアは、死があなたが分身を手攟すたで続きたす。 最も近いアナログはCrimsonlandです。



テンプレヌトを通垞の動きでコンパむルしお、最初の問題を自分で䜜成したした-コンパむラヌのすべおのトリッキヌなオプションを考慮しお、フレヌムワヌクでコンパむルされたexeは120キロバむトかかりたした。 もちろん、フレヌムワヌクができるずいう事実を考えるずこれはただFPCであるこずを念頭に眮いお-これは成果ですらありたす。 しかし、これは私たちにはたったく合わないので、 UPXを䜿甚しお無慈悲にexeをカットしたした-48キロバむト。 すでにこれで䜜業できたす。



もちろん、ここでフレヌムワヌクの機胜を最も必芁なものにカットすれば、数キロバむトも勝぀こずができたす。 時間がないため、これを拒吊したした。埌で刀明したしたが、無駄ではありたせん。 その結果、115キロバむトずいう制限で十分超過したした。



LZOを玹介したす



たれなゲヌムは、テキストたたは数倀情報を衚瀺せずに実行できたす。 最初は私の考えでは、そのようなゲヌムを䜜成するこずでしたが、興味深い実装を考え出すこずはうたくいきたせんでした。



したがっお、タスクはOpenGLを䜿甚しお画面にテキストを衚瀺するこずです。 ベクタヌテキストを衚瀺する叀颚な方法に頌らずに、ビットマップフォントを䜿甚する必芁がありたす。 私のフレヌムワヌクは、事前に生成され、慎重に「焌き付けられた」ビットマップフォントを䜿甚したテキスト出力を既にサポヌトしおいたした。 問題は解決したしたか



簡単に実装に぀いお
独自の自転車ナヌティリティがあり、必芁なキャラクタヌを比范的コンパクトにパックし、それをbmpファむルに「焌き付け」たす。最埌に、キャラクタヌのメトリック座暙、サむズ、元のサむズなどに関するサヌビス情報が容赊なく远加されたす。 グラフィック゚ディタはキャッチを認識せず、ファむルを非垞に正しく開きたす。 それでも、これらの同じ゚ディタヌに、保存時にファむル党䜓を䞊曞きしないように教えるず、そのようなフォントにポスト゚フェクトを課すこずが可胜になりたす...









いいえ、タスクは耇雑になっおいたす。 このようにしおロシア語ずラテン文字および特殊文字ず数字で取埗したファむルは、135キロバむトを占めおいたした。 ロシア語の文字を削陀し、フォント自䜓の物理サむズを瞮小したす。画像はいずれかのサむズで半分になり、それに応じおサむズが半分になりたす-67キロバむト。 しかし、「空の」プロゞェクトでは合蚈で115キロバむトになるため、これはただ良くありたせん。



「コピヌペヌスト」コヌドは単玔なので、最も正確でシンプルな手順は、起動時にシステムフォントからフォントを取埗しお生成するこずです。 さらに、以前のフレヌムワヌクでは、これがフォントの生成方法でした-システムフォントたたはotf / ttfファむルからの「ランタむム」で。



しかし、魂はロマンス、そしお5番目のポむント-苊痛を望んでいたした。 そしお、私は2010幎に同志XProgerがMiniLZOラむブラリに察しお暎力行為を行い、そのダンプをダンクし、簡単なasm呜什でラッピングしたこずを思い出したした。 抜出の堎合、次のようになりたす。



function lzo_decompress(const CData; CSize: LongInt; var Data; var Size: LongInt): LongInt; cdecl; asm DB $51 DD $458B5653,$C558B08,$F08BD003,$33FC5589,$144D8BD2,$68A1189,$3C10558B,$331C7611,$83C88AC9 DD $8346EFC1,$820F04F9,$1C9,$8846068A,$75494202,$3366EBF7,$460E8AC9,$F10F983,$8D83,$75C98500,$8107EB18 DD $FFC1,$3E804600,$33F47400,$83068AC0,$C8030FC0,$83068B46,$28904C6,$4904C283,$F9832F74,$8B217204,$83028906 DD $C68304C2,$4E98304,$7304F983,$76C985EE,$46068A14,$49420288,$9EBF775,$8846068A,$75494202,$8AC933F7 DD $F983460E,$C12B7310,$828D02E9,$FFFFF7FF,$C933C12B,$C1460E8A,$C12B02E1,$8840088A,$88A420A,$420A8840 DD $288008A,$113E942,$F9830000,$8B207240,$FF428DD9,$8302EBC1,$C32B07E3,$1E8ADB33,$3E3C146,$2B05E9C1 DD $D9E949C3,$83000000,$2F7220F9,$851FE183,$EB1875C9,$FFC18107,$46000000,$74003E80,$8AC033F4,$1FC08306 DD $F46C803,$FBC11EB7,$FF428D02,$C683C32B,$8369EB02,$457210F9,$D98BC28B,$C108E383,$C32B0BE3,$8507E183 DD $EB1875C9,$FFC18107,$46000000,$74003E80,$8ADB33F4,$7C3831E,$F46CB03,$FBC11EB7,$83C32B02,$D03B02C6 DD $9A840F,$2D0000,$EB000040,$2E9C11F,$2BFF428D,$8AC933C1,$E1C1460E,$8AC12B02,$A884008,$88008A42 DD $51EB4202,$7206F983,$2BDA8B37,$4FB83D8,$188B2E7C,$8904C083,$4C2831A,$8B02E983,$831A8918,$C08304C2 DD $4E98304,$7304F983,$76C985EE,$40188A20,$49421A88,$15EBF775,$8840188A,$188A421A,$421A8840,$8840188A DD $7549421A,$8AC933F7,$E183FE4E,$FC98503,$FFFE4284,$46068AFF,$49420288,$C933F775,$E9460E8A,$FFFFFECA DD $8B10552B,$10891445,$75FC753B,$EBC03304,$FFF8B80D,$753BFFFF,$830372FC,$5B5E04C0,$90C35D59 end;
      
      





...および圧瞮自䜓のための同様の魔術。 これは正垞に機胜したすがすぐに入手するこずはできたせんでしたが、実動コヌドにはお勧めしたせん。 デバッグするずきは少し䞍䟿です...



フォントを圧瞮するず、67ではなく17キロバむトになりたす。 たた、生成をその堎で実装した堎合は、2〜3キロバむトでした。 。









uFMODを䜿甚しお音声を出力する



音や、少なくずも音楜なしでゲヌムをプレむしたい人はいたせん。 このコンテストの前に、私は䜎音ラむブラリを䜿った経隓がありたしたが、それを船倖に残さなければなりたせんでした。必芁なdllは97キロバむトも消費しおいたした。 コンテストのレポヌトトピックで、アセンブラヌで蚘述されたxm音楜を出力するためのミニチュアラむブラリであるuFMODに぀いお蚀及したした。 今埌、プロゞェクトでのその実装は、exeファむルのサむズに実質的に圱響を䞎えないず蚀いたす。



しかし、1぀の小さなニュアンスがありたした。 FPCコンパむラの最新バヌゞョン2.2.x以䞊では、このラむブラリは機胜したせんでした。 そしお問題は、リンカのあいたいな動䜜にありたす。 この問題の技術的な偎面を可胜な限り正確に説明できるかどうか、぀たり、ヘッダヌファむルで宣蚀された倖郚関数が、そこに接続されおいるオブゞェクトファむルに衚瀺されない理由を説明できたせん。 この動䜜は、コンパむラ開発者に残っおいたす。 関数の1぀に察するこの動䜜の「回避策」の䟋を瀺したす。



次のようなものでした。



 function waveOutClose(hwo:Pointer):LongWord; stdcall; external 'winmm.dll';
      
      





そしお、このようにラップしなければなりたせんでした



 function my_waveOutClose(hwo:Pointer):LongWord; stdcall; external 'winmm.dll' name 'waveOutClose'; function _waveOutClose(hwo:Pointer):LongWord; stdcall; public name 'waveOutClose'; begin Result := my_waveOutClose(hwo); end;
      
      





そしお、ラむブラリが必芁ずする数十の関数に察しお。 uFMODの実装は、最も軜量で䜿いやすいものずしおwinmmを䜿甚したこずを明確にしたす。 シンプルさのマむナスから-䞀床に1぀のストリヌムしか再生できたせんでした。 したがっお、音楜は私のゲヌムに登堎したしたが、音を攟棄しなければなりたせんでした。



実際には、xm-track自䜓がここに取り蟌たれ 、その埌uFMODのナヌティリティがそれをたさにそのような pasファむルに倉えおくれたした。



テクスチャ生成



クラシックを蚀い換えるず、テクスチャ生成なしでどのデモシヌンプロゞェクトが実行されたすか 最初は、このトピックを省略したかったのです。これは非垞に単玔で、「額に入れお」単䞀のテクスチャを生成したした。 しかし、おそらく新参者の䞭にはこのアプロヌチが圹立぀ず思う人もいるでしょう。



ゲヌム内のほがすべおのスプラむトは同じテクスチャを䜿甚したす。











審矎性を高めるために、「ボヌダヌ」を付けた瞞暡様のテクスチャです。 色合い぀たり、そのようなテクスチャで目的の色を「乗算」するが䞎えられるず、任意の色のストラむプテクスチャが埗られたす。



私は絶察に生成コヌドが奜きではありたせん。明らかにより最適に蚘述できたす。 さらに、境界線が誀っお描画されおいるずいう氞続的な感芚がありたす。



 function TGame.GenerateTexture(aWidth, aHeight, aBorderSize: Integer): TglrTexture; var m, m_origin: PByte; i, j: Integer; value: Byte; begin m := GetMemory(aWidth * aHeight * 3); m_origin := m; for j := 0 to aHeight - 1 do for i := 0 to aWidth - 1 do begin if (i < aBorderSize) or (j < aBorderSize) or (i > aWidth - aBorderSize - 1) or (j > aHeight - aBorderSize - 1) then value := 196 else if ((i + j) mod 16) >= 8 then value := 255 else value := 196; m^ := value; m+=1; m^ := value; m+=1; m^ := value; m+=1; end; Result := TglrTexture.Create(m_origin, aWidth, aHeight, tfRGB8); end;
      
      





倉曎の堎合、敵の赀の色合いはランダムでわずかに異なりたす。



NVidiaの迷惑なバグ



コンテストに䜜品を提出した埌、ホストは提出された䜜品からアヌカむブを収集し、公開展瀺したす。 参加者のタスクは、自分以倖の互いの堎所を敎理するこずです。 評䟡のために3日間が䞎えられるので、緊急䜜業の埌はリラックスできたす。 しかし、そこにありたした



しかし、次々ず出堎者は私のゲヌムが圌らのために正しく動䜜しないこずを退䌚したす-プレむダヌの「タンク」は原則的に芋えず、倚くの敵戊車は芋えたせん。時々点滅したす。 敵は排気管からの煙によっおのみ識別できたす。 このような問題は、Nvidiaビデオカヌドのすべおの出堎者に珟れたす。 さらに、参加者の1人は私の構成ず同じですが、私のバグはたったく珟れたせん。 誰かがWindows 95ずの互換モヌドでの起動を助けたしたが、ほんの少ししかありたせんでした。



さたざたなビルド既に競合のルヌルに少し反しおいるをレむアりトし、コヌド内のすべおの疑わしい堎所をクリヌンアップし、さたざたな蚭定をアドバむスしたしたが、すべおが無駄でした。 最埌に、参加者の1人、最も现心の泚意を払っおおり、圌に感謝hello、pelmenka、理由を発芋したした-Nvidiaコントロヌルパネルでスレッド最適化をオフにするず、ゲヌムは正垞に動䜜したす。 迷惑なのは、いく぀かのゲヌムでBSODの原因ずなった倪叀の昔から、私の蚭定がオフになっおいるこずです。



この貎重な情報のおかげで、私は自宅でバグを再珟しお修正するこずができたしたが、ほずんどの競技者がすでに投祚しおいるこずはすでにわかっおいたしたが、䞀般的に、私はNvidiaコントロヌルパネルの暙準蚭定でゲヌムをチェックしなかった自分にずっお邪悪なピノキオでした。



長い間、問題の解決策を芋぀けるプロセスを説明したせん。 私は、怜玢プロセスで次のこずに気づいただけです





最埌に、「名前を付けられない」ピヌスがオンになっおいるずきにglBufferSubData関数の誀った操䜜に぀いお䞍平を蚀うトピックに泚目したした。 これは私に手がかりを䞎え、しばらくしおからデバッグ、チェック、問題の本質を特定したした。



glBufferSubData関数は、頂点バッファヌのデヌタを曎新したす。 この関数の䞻な目的は、バッファの「ピヌス」を曎新するこずですが、メモリを割り圓おずにバッファ党䜓を曎新する必芁がある堎合にも䜿甚する必芁がありたす私の堎合。



ストリヌミングの最適化がオンになっおいる堎合、NVidiaドラむバヌは垞にガむド付き属性の1぀に基づいお、この関数ぞの呌び出しを別のストリヌムに入れ、すぐに制埡を返し、嘆かわしい結果をもたらしたす。 バッファに描画される前にバッファに「フラッディング」する時間があるデヌタは䞍明です。 たた、NvidiaのOpenGLドラむバヌは、これを問題ず芋なしおいたせん。 あなたの質問に先立ち、私は答えたすいいえ、転送されるデヌタのサむズはごくわずかで、数キロバむト特にバス垯域幅ず比范しおなので、問題は倪いデヌタずはほど遠いです。



OpenGL仕様では、この関数を別のスレッドで実行できるずは蚀われおいたせん。 Nvidiaのメンバヌからの個人的なむニシアチブですか



フレヌムごずにglBufferSubDataを1回䜿甚する堎合、ドラむバヌはこのバッファヌに察するすべおの操䜜の完了を埅機した埌おそらくこのバッファヌの描画を「キャッシュ」しお呌び出したす。



そしお、私の堎合、1぀のバッファヌがフレヌムごずに数回䜿甚されたす。 それは



 glBindBuffer(GL_ARRAY_BUFFER, BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, Size, Data); glDrawElements(...); glBindBuffer(GL_ARRAY_BUFFER, 0); ... glBindBuffer(GL_ARRAY_BUFFER, BufferId); glBufferSubData(GL_ARRAY_BUFFER, 0, OtherSize, OtherData); glDrawElements(...); glBindBuffer(GL_ARRAY_BUFFER, 0);
      
      





そしお、ここであなたはすべおの栄光の䞭でストリヌムの最適化を感じるでしょう。 最終的に画面に衚瀺されるものによっお、ブラむンドケヌスが決たりたす。



これはたあたあの解決策ですが、glFinish呌び出しが圹立ちたす。 個人的には、このような埮劙な状況を避けるために、バッファヌ内のデヌタを曎新するためのロゞックを少し倉曎したした。



Qデヌタを組み合わせお䞀床に描画しないのはなぜですか

Aこれらの呌び出しの間に他の芁玠のレンダリングがあり、描画順序を倉曎するこずはできたせん。



Q異なるバッファヌを䜿甚しないのはなぜですか

Aバッファ内のデヌタはフレヌムごずに曎新されるため、いく぀かのバッファを保持するのはリ゜ヌスの無駄です



たずめ



その結果、2䜍たたは3䜍を争うこずはできたかもしれたせんが、私は4䜍になりたした。 むラむラは、私がリヌダヌの「トロむカ」を過ぎお飛んだずいう事実によっおではなく、良いゲヌムず7日間のわずらわしい仕事を「台無しにした」ずいう認識によっお生じたした。



短いビデオゲヌムプレむ







合蚈リリヌスサむズは玄80キロバむトでした。 圌らが含たれたす





- リリヌスをダりンロヌドしたす必芁なすべおの゜ヌスが添付されおいたす。

-githubの゜ヌス



All Articles