
約1年前、私は自転車の1つについて「
グラフィックプログラミングが大好き 」というストーリーを書きました。 そのストーリーでは、グラフィックをプログラムするときにすべてがとても面白くて面白いと言って、開発プロセスを「ロマンチックな側で」見せようとしました。 私は話を「わあ! Stripy ...」、そして今、ほぼ1年後、私はそれがどのように機能し、どのように終わったかについての話をあなたと共有することにしました。 これはまだ自転車についての話であることを警告します。 これは、革新的なテクノロジーやスーパーメガスマートソリューションに関する話ではありません。 これは、私の喜びのために、意図的に自転車を書いた方法についての物語です。
ストーリーはまたやや面倒で、Android、C ++、ライブ壁紙、Minecraft、自転車、トピックにゆるく結び付けられた意識のストリームなどが好きではないすべての人にとって、この投稿の内容がそれらを混乱させる可能性があることをすぐに警告したいので、読み続けてくださいあなた自身の危険とリスクで。
過去の話?
知識がなく、前の記事を読みたくない人のために、議論されたことについて簡単に説明します。 MinecraftのスタイルでAndroid用ライブ壁紙を作成しました。 実際、これはMinecraftスタイルの単なる風景画像であり、指でスクロールでき、昼と夜が変化します。
バイクについて少し
私の最後の話では、懐かしい気持ちのために何年も前に書いたゲームについて話をするために余談をしました。この話では、あまり独創的ではなく同じものから始めることにしました。
2006年、私はPDAの所有者として誇りを持っていました。 VGA画面とこのデバイスのその他すべての利点は、非常に幅広い可能性を提供し、その時点でWindows Mobile用のアプリケーションをいくつか作成しました。
私が書いたアプリケーションの1つは、私の子供時代に「Eggplant」と呼ばれ、多くの子供時代と子供の神経を費やしたゲームでした。 詳細を説明することなく、エミュレータから一連の画像を取得し、それらに基づいてゲームを作成しました。

しかし、私はあなたに、CCPを宣伝しないで、ノスタルジックな感情からだけではないことを伝えています。 これは自転車についての物語です-意図的に作成された自転車について。
当時、私はソフトウェア開発で十分な経験があり、そのような簡単なゲームを1日もかからずに書くことができました。 既に述べたように、グラフィックスとサウンドはエミュレーターから取得されたものであり、ロジックは非常に単純であり、10個のスプライトの表示は問題になりませんでした。 おそらく、実装が単純すぎるという事実が私にとっての主な問題でした。 結局のところ、私はこのゲームを自分用に書くことにしました-売り物ではなく、誰かのためではなく、自分のためだけに。 このプロセスは楽しいものでした。
ここで自転車が助けになりました...画面に(背景画像の上に)表示されるスプライト用の独自の画像形式。 これらのスプライトをホワイトノイズの効果で出力する独自のプロセス。 そして、すべてがFrameBuffer、つまり ピクセルの配列を通して。 そして今、このタスクはすでに約4日かかり、さらに興味深いものになっています。
前の記事から引用するには:
書いた、それは私が覚えていた通りに機能し、一度プレイして、やめた。 デバッグの過程ですでに「十分にプレイ」されています。
しかし、私にとってそれは一種の伝統になりました...
毎年12月31日、私の友人と私は浴場に行きます。 これが私たちの伝統です。
私は時々、彼らが言うように、「脳を伸ばす」ために物事を実現し始めました。 そして、このプロセスは私に多くの喜びを与えてくれるので、すぐにコメントで叫ぶのが好きな人に答えを与えたいと思います。「ふふふ、どうして?! はい、パーリンノイズとフラクタル領域の生成がすぐに必要です!」 -はい、このストーリーで説明するすべてのことについて、私自身の方法とツールがあります-これはよく知っています。 私の実装は、既成のソリューションの存在について知らないという事実とは何の関係もありません。 本質は彼らの決定の実施にあります。
トピックに戻る
画面上に特定の色で簡単にペイントされるシンプルなライブ壁紙アプリケーションが作成されました。 私はこれを行う方法を教えません これは、検索エンジンで簡単に見つけることができる部分です。 アプリケーションでは、drawメソッドが描画を担当し、次のような処理を行いました。

ここで生産性に関する最初のトラブルが始まりました...
SDKの機能
最初は、JNIから画像を取得する方法が問題でした。最も論理的な解決策は、32ビットピクセル表現(32bpp)でビットマップを表すintから配列を取得するようでした。 このアプローチは、Javaがそのような配列をキャンバスに出力するメソッドを提供するという事実によって促されました。

私はこのロジックから始めました-ピクセルの配列もBitmapオブジェクトから取得できますが、配列を渡して、そのために特別に作成されたメソッドを使用して配列をレンダリングするよりも明らかに長くなります。
Androidからの驚きが私を待っていました:drawBitmapメソッドは約21ミリ秒(720x1280)の画像を描いたので、1秒あたり30フレームを描画したい場合(遅延がなくても、すべてのプロセッサ時間を使用)、実装は12ミリ秒に収まらなければなりません(そしてはい、ここでは、描画自体が時間を要する唯一のものではないという事実も考慮していません。 この事態の取り決めは間違いなく私には合わなかったので、他の選択肢を実験して探す必要がありました。 解決策は、ビットマップオブジェクトをJNIに転送することでした。これは、描画メソッドで次のように処理されました。

したがって、ビットマップタイプのオブジェクトを転送し、そのオブジェクトに関する情報(AndroidBitmap_getInfo)を取得し、ピクセルの配列(AndroidBitmap_lockPixels / AndroidBitmap_unlockPixels)を取得し、実際にJNIを呼び出す(レンダリングなし)のにかかる時間は1ミリ秒以下になりました。 この時点で、JNIとの間で画像を転送する問題は解決されました。 ドロービットマップメソッドをピクセルの配列で使用するのにこれほど時間がかかっている理由については、ドキュメントには何も見つかりませんでしたが、この場合、描画の直前にビットマップオブジェクトが作成されると仮定できます。
なんらかの理由でオーバーヘッドコストが残った、なぜなら 描画ごとにCanvasオブジェクトを取得および解放するには、約1〜2ミリ秒かかります。 また、drawBitmapメソッドを使用したビットマップ出力自体は、さらに3〜4mかかります。

合計で、追加の操作に約5〜6ミリ秒を指定する必要がありますが、ここでは何もできず、条件に合わせる必要がありました。
これはおそらくJavaが遭遇した唯一の興味深い技術的側面なので、残りの話はJNIとランドスケープ生成アルゴリズムの実装に進みます。
スカイラインとサイクリング
ランドスケープ自体は周期的に表示されました。 画像は、任意の方向(水平)に無限にスクロールできる閉じたテープです。 アルゴリズムの観点から、任意の長さのランドスケープを生成することは可能ですが、長さが制限されているため、メモリを多く消費しすぎません。
実装の観点から見ると、すべてが非常に単純です。
ポイントがマップの外側(水平方向)に伸びる領域を生成する必要がある場合、これらのポイントはマップの反対側に単純に転送されます(x <0、x + = widthおよびx> = width、x-= widthの場合)。 もう少し興味深いのは、地平線レベルの実装に関する問題です。地平線はランダムでなければなりませんが、開始点と終了点は収束する必要があるため、このような効果はありません。

この問題を解決するために、次のアルゴリズムが作成されました。
- 開始点の座標をランダムに選択します。
- 次のポイントは、前のポイントに対するオフセットのいずれかを持つことができます:{-2、-1、0、1、2}。各オフセットについて、残りの遷移数で開始点に戻ることができるかどうかがチェックされます。 選択すると開始点に移動できないオフセットがリストから削除されます。
- リストからランダムなオフセット値が選択されます。
- 最初に来るまで、2番目のポイントから繰り返します。
実際には、このアプローチにより、平坦な地形と山の両方を生成できます。 実際には、アルゴリズムが最も最適であり、それは直線の歪みに起因するはずであることを知っていますが、その時点では解決策は十分であるように見えました。
洞窟とその他の要素
主なタスクの1つは、洞窟とさまざまなブロック(石炭、砂など)の生成でした。 この問題を解決するためのアルゴリズムは非常に多くありますが、私はすべてを自分で書くことにしました。
実装は、ランダム性の一部の要素のみを使用して、フラッドフィルアルゴリズムの実装に似ていました。
- ランダムなポイントを選択し、このポイントの「重み」を設定します(乱数)。
- 隣接するポイントの可用性を確認します(隣接する各ポイントを処理し、「重み」をランダムに減らします)-画面外にポイントがあり、そのポイントは以前に処理されています。
隣接ポイントの「重み」は、(親ポイントの「重み」/ 2)から親ポイントの「重み」までの範囲の乱数によって決定されます。 さらに、娘ポイントが20%の確率で考慮されない「追加のランダム性」の条件を追加することにより、よりランダムな領域を作成する機能が生成アルゴリズムに追加されました。
ポイントを移動するプロセスは、アニメーションを使用して非常に明確に表示できます。

中心からの「重量」を減らすプロセスを示すわずかに異なるアニメーション:

コアでは、アルゴリズムは再帰的ですが、実装にはキューが使用されています。 開始ポイントが処理キューに追加され、キューにポイントがある限りループが実行されます。 重みが0より大きい使用可能な隣接ポイントがキューに追加されます。 一般に、呼び出しスタックの過度の深さの問題を解決するという点では、すべてが非常に標準的です。
このアルゴリズムは複数のパスで実行されます-子ポイントが処理される開始ポイントをいくつか作成します。 その結果、リージョンはランダムに相互接続され、より複雑なリージョンが作成されます。 5つの領域を作成する例を次に示します(各領域のポイントには1から5までの番号が付けられ、領域の開始点には赤枠があります)。

このアルゴリズムは、ほとんどのランドスケープの基礎であり、開始点(個別の領域が作成される点)の数、開始点の初期「重み」、および開始点の位置(特定の領域が特定の領域内でのみ作成されるように変更されます)深さ、たとえば、底部付近のみ)。
領域の開始点は、照明要素(トーチ)の配置にも使用されました。 以前のバージョンでは、トーチはリージョンの開始点に正確に配置されていましたが、「地面に」下げることを決定し、リージョンの開始点が「床」から2ブロックより高い場合、トーチは床からちょうど2ブロックに配置されました。 このアプローチにより、照明がより面白くなりました(領域の大部分は照明が不十分なままになる可能性があります)。
再びパフォーマンス
開発プロセス全体で、生産性に苦労しました。 テスト段階で描画に時間がかかりすぎることが明らかになったとき、私はかなりやり直さなければなりませんでした。 特にオリジナルなものは何も思いつきませんでした。変更された領域を特定するプロセスのみを実装したため、実際に再描画する必要がある領域のみが描画に関与しました。 残りは過去の画像から取られました。 したがって、たとえば、裏返した場合、大半は変更されませんでしたが、追加する必要があるセグメントのみが描画されました(画像では、描画する必要のあるブロックは赤でマークされています)。

アルゴリズムは、オフセットだけでなく、時間とともにカバレッジが変化するエリアにも関与しました。

擬似3D
「擬似スコープ」も非常に簡単に作成されました-ブロックの位置は常に固定されています-前面に加えて、側面(左)と上面を見ることができます。 ブロックのタイプごとに、テクスチャから5つのイメージが生成され、ボリュームの錯覚を作成しました。

合計で、ブロックを配置するための5つの異なるオプションを区別できます(描画するブロックは黒でマークされ、隣接するブロックはグレーで表示され、隣接するブロックがない場所は白でマークされます)。

各オプションで、特定の画像セットを描画する必要があります。
1.a、b、c、d1、d2
2.a、c、d2
3. a、c
...
ブロックセグメントが異なる色でマークされている図面の例:

ブロックの可視エッジの値は、ランドスケープ生成の段階で計算されました。つまり、描画するときに必要なものを描画するだけで済みました。
乱数について少し
「ランダムな」数値を生成するために、指定された文字列と追加のパラメーターに基づいて一連の数値を生成するクラスが作成されました。 たとえば、洞窟を生成するとき、種と「空洞」の組み合わせが、木に使用されました-種と「木」。 このアプローチにより、特定の領域の生成をオフにすることができました(たとえば、石炭や鉄のブロックを生成しない)が、乱数に基づく他の要素の外観には影響しません。
crc32チェックサムアルゴリズムにアルゴリズムを構築しました。 同時に、任意のデータから数値を取得することができ、その実装が手元にありました。
結果について簡単に
年間のアプリケーションインストール:16553(+ 81(有料)):

アプリケーションからの総収入は
2794.91ルーブルに達しました。 一般に、自転車を書くことで生計を立てていれば、すでにstar死していました。
開発プロセスについて言えば、私たちの仕事では通常遭遇しない問題を解決することは非常に興味深いことでした。 もちろん、既製のエンジンのいずれかを使用して、それに基づいて正直な3Dで説明された倒錯なしに同様のことを行うことができましたが、これは再び誰かが自分の手で何かを作成するようにプッシュすることを願っています既製のフレームワークとライブラリを使用します。
ご清聴ありがとうございました!