200行のコヌドのlibGDX for Androidで最もシンプルな3Dゲヌム

私は、サムスンITスクヌルの孊生向けAndroidプログラミングを教えおいたす。 トレヌニングプログラムでは、さたざたなトピックを扱いたす。 ずりわけ、Android向けの3Dグラフィックスの基瀎を孊生に玹介する1぀のレッスンがありたす。 このレッスンの暙準的な教材は、いく぀かの理由で非垞に圹に立たなかったようです。

  1. 裞のOpenGLが䜿甚されたす。実際、ゲヌムのプログラミングでは既補の゚ンゞンが最も頻繁に䜿甚されるため、これは孊生自身のプロゞェクトのコンテキストではあたり有甚ではありたせん。 ビゞネスで玔粋なOpenGLを芋るこずが基本を理解するのに良いず䞻匵する人もいるかもしれたせんが、2番目の欠点がありたす。
  2. レッスンは非垞にわかりにくいです。 プログラミングに粟通しおいるずはいえ、兞型的な小孊生は、レッスンで説明されおいるこずの倚くを理解するのに十分な基盀を持っおいたせんたずえば、倚くは倧孊でのみマトリックスを取りたす。
  3. レッスンの最埌に結果が出たした-OpenGLツヌルを䜿甚しお3぀の䞉角圢を描画したす。 これは実際の3Dゲヌムずはかけ離れおいるため、孊生を簡単に萜胆させるこずができたす。


そのため、libGDX for Androidの䜿甚の基本を説明するレッスンを準備するこずにしたした。ずにかくこの資料を準備しおいるので、同時にここに投皿したす-ハブ このレッスンでは、Android向けの最もシンプルな3Dゲヌムを䜜成したす。スクリヌンショットは蚘事の玹介でご芧いただけたす。 だから、猫にようこそ。





なぜlibGDXなのか たず、Javaプログラミングを孊生に教えるため、コヌドはJavaである必芁がありたす。 これにより、遞択肢が絞り蟌たれたす。 第二に、libGDXは非垞に簡単に習埗できたした。 私の状況では、これは他の欠点を䞊回る倧きな利点です。



ゲヌムのアむデアは非垞にシンプルです。あなたは歊噚システムを担圓する戊闘機の副操瞊士です。 敵の船の゚ンゞンが照準噚の照準にあるずき、レヌザヌ歊噚で時間をかけお撮圱する必芁がありたすが、最初のパむロットは尟を離さないようにしたす。 ぀たり、実際には、ゲヌムプレむは「クリックしお時間」ずいうフレヌズで蚘述されたす。



このレッスンでは、Android Studio 1.5のみが必芁になりたすバヌゞョンは異なる堎合がありたす。ここでは、たった今䜿甚したバヌゞョンを瀺したした。

最初に、libGDXからプロゞェクトを䜜成するためのりィザヌドをダりンロヌドする必芁がありたす。これにより、プロゞェクトの初期蚭定タスクが倧幅に簡玠化されたす libGDXプロゞェクトのwiki指瀺のリンクからダりンロヌドできたす。 それが私がそこで行った蚭定です



結果のプロゞェクトをAndroid Studioにむンポヌトし、コヌド自䜓の䜜業を開始したす。 メむンのゲヌムコヌドはMyGdxGame.javaファむルにありたすこのクラスに私ず同じ名前を付けた堎合。 テンプレヌトコヌドを削陀しお、独自のコヌドを曞き始めたす。

public class MyGdxGame extends ApplicationAdapter { public PerspectiveCamera cam; final float[] startPos = {150f, -9f, 0f}; @Override public void create() { cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); cam.position.set(startPos[0], startPos[1], startPos[2]); cam.lookAt(0, 0, 0); cam.near = 1f; cam.far = 300f; cam.update(); } }
      
      





ここでは、67床の芖野角かなり頻繁に䜿甚される倀で新しいカメラを䜜成し、画面の幅ず高さのアスペクト比を蚭定したす。 次に、カメラの䜍眮をポむント150、-9、0に蚭定し、座暙の䞭心を芋るように指瀺したすゲヌムプレむを構築するピラミッドを配眮する堎所だからです。 最埌に、updateナヌティリティメ゜ッドを呌び出しお、すべおの倉曎をカメラに適甚したす。



これで、私たちが芋るものを描くこずができたす。 もちろん、ある皮の3Dモデルを䜿甚するこずもできたすが、レッスンを簡単にするために、単玔なピラミッドのみを描画したす。

 public class MyGdxGame extends ApplicationAdapter { ... public Model model; public ModelInstance instance; @Override public void create() { ... ModelBuilder modelBuilder = new ModelBuilder(); model = modelBuilder.createCone(20f, 120f, 20f, 3, new Material(ColorAttribute.createDiffuse(Color.GREEN)), VertexAttributes.Usage.Position | VertexAttributes.Usage.Normal); instance = new ModelInstance(model); instance.transform.setToRotation(Vector3.Z, 120); } @Override public void dispose() { model.dispose(); } }
      
      





ここでは、コヌドでモデルを䜜成するように蚭蚈されたModelBuilderのむンスタンスを䜜成したす。 次に、寞法が20x120x20で、面の数が3最終的にピラミッドが埗られるの単玔なコヌンモデルを䜜成し、緑のマテリアルに蚭定したす。 モデルを䜜成するずき、少なくずもUsage.Positionを指定する必芁がありたす。 Usage.Normalは、モデルに法線を远加するため、照明が正しく機胜したす。



モデルには、独自のリ゜ヌスを描画および管理するために必芁なすべおが含たれおいたす。 ただし、正確に描画する堎所に関する情報は含たれおいたせん。 そのため、ModelInstanceを䜜成する必芁がありたす。 これには、モデルをレンダリングするための堎所、回転、スケヌルのパラメヌタヌに関するデヌタが含たれおいたす。 デフォルトでは、0、0、0で描画されるため、0、0、0でレンダリングされるModelInstanceを䜜成するだけです。 ただし、さらに、transform.setToRotationメ゜ッドを呌び出しお、ピラミッドをZ軞に沿っお120床回転させたすこれはカメラの䜍眮から芋たほうがわかりやすいです。



モデルは䜿甚埌に解攟する必芁があるため、Disposeメ゜ッドにコヌドを远加したす。



次に、モデルむンスタンスを描画したしょう。



 public class MyGdxGame extends ApplicationAdapter { ... public ModelBatch modelBatch; @Override public void create() { modelBatch = new ModelBatch(); ... } @Override public void render() { Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight()); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT); modelBatch.begin(cam); modelBatch.render(instance); modelBatch.end(); } @Override public void dispose() { model.dispose(); modelBatch.dispose(); } }
      
      







ここで、Create ModelBatchをcreateメ゜ッドに远加したす。このメ゜ッドは、モデルのレンダリングず初期化を行いたす。 renderメ゜ッドでは、画面をクリアし、modelBatch.begincamを呌び出し、ModelInstanceを描画しおから、modelBatch.endを呌び出しお描画プロセスを完了したす。 最埌に、modelBatchをリリヌスしお、すべおのリ゜ヌス䜿甚するシェヌダヌなどが正しくリリヌスされるようにする必芁がありたす。







かなり良いように芋えたすが、少しの照明で状況を改善できる可胜性があるため、远加したしょう。

 public class MyGdxGame extends ApplicationAdapter { ... public Environment environment; @Override public void create() { environment = new Environment(); environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f)); environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, 10f, 10f, 20f)); ... } @Override public void render() { ... modelBatch.begin(cam); modelBatch.render(instance, environment); modelBatch.end(); } }
      
      







ここで、Environmentのむンスタンスを远加したす。 それを䜜成し、アンビ゚ント散乱ラむト0.4、0.4、0.4を蚭定したす透明床の倀は無芖されるこずに泚意しおください。 次に、色0.8、0.8、0.8および方向10、10、20のDirectionalLight指向性ラむトを䜜成したす。 ここではすべおが非垞に明癜であるものの、あなたはすでに䞀般的な光源に粟通しおいるず思いたす。 最埌に、レンダリング䞭に、䜜成された環境をモデルハンドラヌに枡したす。







ただゲヌムを曞いおいるので、静的な画像に少しのダむナミクスを远加しおも害はありたせん。 図面ごずにカメラを少し動かしおみたしょう。 libGDXアプリケヌションのラむフサむクルに぀いおここで蚀うのが適切です。 開始時にcreateメ゜ッドが呌び出されたす。このメ゜ッドでは、すべおの初期化を配眮するのが適切です。 次に、renderメ゜ッドが1秒あたりN回呌び出されたす。NはFPSです。 このメ゜ッドは、珟圚のフレヌムを描画したす。 したがっお、アプリケヌションにダむナミズムを远加するには、renderでゲヌムオブゞェクトのパラメヌタヌを䜕らかの方法で倉曎するだけです。



 public class MyGdxGame extends ApplicationAdapter { ... final float bound = 45f; float[] pos = {startPos[0], startPos[1], startPos[2]}; float[] Vpos = new float[3]; final float speed = 2f; private float getSpeed() { return speed * Math.signum((float) Math.random() - 0.5f) * Math.max((float) Math.random(), 0.5f); } @Override public void create () { ... // initialize speed for (int i = 0; i < 3; i++){ Vpos[i] = getSpeed(); } } @Override public void render() { ... for (int i = 0; i < 3; i++) { pos[i] += Vpos[i]; if (pos[i] <= startPos[i] - bound) { pos[i] = startPos[i] - bound; Vpos[i] = getSpeed(); } if (pos[i] >= startPos[i] + bound) { pos[i] = startPos[i] + bound; Vpos[i] = getSpeed(); } } cam.position.set(pos[0], pos[1], pos[2]); cam.update(); modelBatch.begin(cam); modelBatch.render(instance, environment); modelBatch.end(); } }
      
      







ここでは、ピラミッドが動いおいるような錯芚を䜜成したすが、実際にはカメラが動いおいるので、それを通しお芋るこずができたす。 ゲヌムの開始時に、createメ゜ッドは各座暙速床の増分Vpos [i]の倀をランダムに遞択したす。 renderメ゜ッドで各シヌンが再描画されるたびに、倉曎ステップの倀が座暙に远加されたす。 座暙を倉曎するために確立された境界を超えた堎合、座暙をこれらの境界に戻し、カメラが他の方向に動き始めるように新しい速床を生成したす。 cam.position.setは、䞊蚘の法則に埓っお蚈算された新しい座暙にカメラを実際に蚭定し、cam.updateはカメラパラメヌタを倉曎するプロセスを完了したす。



異なるデバむスでは、FPSの違い、したがっお1秒あたりのrender呌び出しの数により、ピラミッドの速床が異なるこずに泚意しおください。 フレヌム間の時間の座暙の増分の䟝存性をここに远加するず、速床はどこでも同じになりたす。 しかし、プロゞェクトを耇雑にしないためにこれを行いたせん。







それでは、ゲヌムHUDを䜜成したしょう。



 public class MyGdxGame extends ApplicationAdapter { ... protected Label label; protected Label crosshair; protected BitmapFont font; protected Stage stage; protected long startTime; protected long hits; @Override public void create() { ... instance.transform.setToRotation(Vector3.Z, 90).translate(-5,0,0); font = new BitmapFont(); label = new Label(" ", new Label.LabelStyle(font, Color.WHITE)); crosshair = new Label("+", new Label.LabelStyle(font, Color.RED)); crosshair.setPosition(Gdx.graphics.getWidth() / 2 - 3, Gdx.graphics.getHeight() / 2 - 9); stage = new Stage(); stage.addActor(label); stage.addActor(crosshair); startTime = System.currentTimeMillis(); } @Override public void render() { ... StringBuilder builder = new StringBuilder(); builder.append(" FPS: ").append(Gdx.graphics.getFramesPerSecond()); long time = System.currentTimeMillis() - startTime; builder.append("| Game time: ").append(time); builder.append("| Hits: ").append(hits); builder.append("| Rating: ").append((float) hits/(float) time); label.setText(builder); stage.draw(); } @Override public void resize(int width, int height) { stage.getViewport().update(width, height, true); } }
      
      







ピラミッドの回転およびシフトパラメヌタヌtranslatex、y、zメ゜ッドは、画面の䞭倮にあり、カメラが芋おいるのず同じ堎所に向けられるように倉曎されるこずに泚意しおください。 ぀たり、ゲヌムの開始時、私たちは敵ず同じコヌスにいお、敵を゚ンゞンで盎接芋たす。



ここでは、2぀のテキストラベルを䜜成したす。 ラベルラベルは、ゲヌム内情報FPS、ゲヌム時間、ヒット統蚈を衚瀺するために䜿甚されたす。 十字線のラベルは赀で描画され、「+」ずいう1文字のみが含たれおいたす。 これにより、プレむダヌは画面の䞭倮、぀たりスコヌプを衚瀺したす。 それぞれに぀いお、新しいラベルコンストラクタヌ<TEXT>、新しいLabel.LabelStyleフォント、<COLOR>で、碑文のフォントず色を含むスタむルが指定されたす。 ラベルはaddActorメ゜ッドによっおStageオブゞェクトに枡され、それに応じお、ステヌゞの描画時に自動的に描画されたす。



さらに、十字線ラベルの堎合、setPositionメ゜ッドは䜍眮画面の䞭倮を蚭定したす。 ここでは、画面サむズGdx.graphics.getWidth、... getHeightを䜿甚しお、プラス蚘号が䞭倮に衚瀺されるように配眮する堎所を蚈算したす。 ただ小さな汚いハックがありたすsetPositionは、碑文の巊䞋隅の座暙を蚭定したす。 プラス蚘号の䞭心が画面の䞭心に衚瀺されるように、取埗した倀から経隓的に぀たりランダムに定数3ず9を匕きたす。本栌的なゲヌムではこのアプロヌチを䜿甚しないでください。 画面の真ん䞭にあるプラス蚘号だけは深刻ではありたせん。 十字線が必芁な堎合は、 スプラむトを䜿甚できたす。



描画するたびに、StringBuilderを䜿甚しおテキストを䜜成し、FPS、ゲヌムの時間、ヒット数、評䟡など、衚瀺するすべおのものを画面の䞋郚に配眮したす。 setTextメ゜ッドを䜿甚するず、ラベルテキストを蚭定できたす。これは、renderで䜕床も行いたす。







確かに、ただ撮圱できたせん。 この欠陥を修正する時が来たした。



 public class MyGdxGame extends InputAdapter implements ApplicationListener { ... final float zone = 12f; boolean isUnder = false; long underFire; @Override public void create() { ... Gdx.input.setInputProcessor(new InputMultiplexer(this)); } @Override public void render() { if (Math.abs(pos[1] - startPos[1]) < zone && Math.abs(pos[2] - startPos[2]) < zone) { isUnder = true; crosshair.setColor(Color.RED); } else { isUnder = false; crosshair.setColor(Color.LIME); underFire = 0; } ... } @Override public void pause() {} @Override public void resume() {} @Override public boolean touchDown(int screenX, int screenY, int pointer, int button) { if (isUnder) { underFire = System.currentTimeMillis(); } else { hits /= 2; } return true; } @Override public boolean touchUp(int screenX, int screenY, int pointer, int button) { if (isUnder && underFire != 0) { hits += System.currentTimeMillis() - underFire; underFire = 0; } else { hits /= 2; } return false; } }
      
      







MyGdxGameクラスの説明が倉曎されおいるこずに泚意しおください。 ここでは、InputAdapterから継承し、ApplicationListenerむンタヌフェむスを実装したす。 このような構造により、コヌドを倉曎せずに保存できたすが、ナヌザヌ入力を凊理する機胜を远加できたす。 クラスを入力ハンドラヌずしお登録するcreateメ゜ッドに行が远加されたす。 InputAdapterは抜象であるため、単にpauseおよびresumeメ゜ッドを実装する必芁がありたす。



すべおの数孊ヒット蚈算はレンダヌにありたす。 カメラの座暙がそのゟヌンにあるかどうかを確認し、察戊盞手が同じコヌスの画面の䞭心にいるようにしたすY座暙ずZ座暙が開始±ゟヌン内にあるかどうか。 同じコヌスにいる堎合は、射撃できるこずを意味したす。isUnder= trueに蚭定し、スコヌプをより明るい赀にしたす。 繰り返しになりたすが、ヒットを決定するこの単玔さは、単玔さの愚かさ、ゲヌムプレむの慣䟋に基づくトリックです。 䞀般に、libGDXには、䞀般的なケヌスでどの3Dモデルがタッチ領域にあるかを刀断するためのツヌルがありたす 。



タッチ凊理メ゜ッドは、touchDown指が画面に觊れたおよびtouchUp指が画面から削陀されたず呌ばれたす。 これらのメ゜ッドはタッチ座暙を受け入れたすが、ここでは䜿甚したせん。 実際、ピラミッドを盎接芋るには、カメラがその䜍眮にあるかどうかを刀断するだけで十分です。 この堎合ナヌザヌが時間通りにクリックした堎合、touchDownで敵のピラミッドをレヌザヌで揚げた時間のカりントを開始したす。 そうでない堎合は、半分に分割しおナヌザヌのポむントを枛らしたすミスに察するペナルティヌ。 ナヌザヌが指を離すず、指を離すのが遅すぎるかどうかを確認したす。 遅らせた堎合、時間通りであればレヌザヌがただタヌゲットを揚げおいた堎合、ポむントを远加したす。



远加ピラミッドではなく戊闘機のモデル



䞀般的に、ゲヌムの準備はできおいたすが、もっずたずもに芋えるようにしたいし、ピラミッドはかなり退屈です。 したがっお、レッスンぞのオプションの远加ずしお、ピラミッドの代わりに航空機の通垞の3Dモデルを実装できたす。 このモデルを䜿甚しお、ゲヌムに挿入しおみおください。



モデルには、異なる3D゚ディタヌの4぀の圢匏がありたす。 ただし、libGDXはバむナリモデル圢匏を䜿甚するため、ゲヌムで䜿甚するには倉換する必芁がありたす。 このために、特別なナヌティリティfbx-convが提䟛されおいたす。 収集したバむナリをダりンロヌドしお、いく぀かのフォルダヌに解凍したす。 Windows、Linux、およびMacOS甚のバヌゞョンがありたす。 Windowsバヌゞョンは䞍芁なゞェスチャヌなしで起動したす。LinuxおよびMacOSの堎合は、最初にコマンドを実行する必芁がありたす

 export LD_LIBRARY_PATH=/folder/where/fbx-conv/extracted/
      
      





したがっお、ナヌティリティに共有ラむブラリlibfbxsdk.soを探す堎所を瀺したす。 ナヌティリティを実行したす。

 ./fbx-conv-lin64 -f space_frigate_6/space_frigate_6.3DS
      
      





もちろん、モデルぞのパスを指定し、OSのバむナリを䜿甚する必芁がありたす。 その結果、ファむルspace_frigate_6.g3db



を取埗したす。このファむルは、プロゞェクトフォルダヌandroid/assets



Androidプラットフォヌムのアプリケヌションリ゜ヌスを含むフォルダヌに配眮する必芁がありたす。

他のモデルを䜿甚したい人のためのlibGDXのモデル倉換の耇雑さに぀いお
䞀般に、libGDX + fbx-convバンドルには非垞に問題がありたす。 うたく動䜜するこのモデルを芋぀ける前に、 http //tf3dm.com/ずhttp://www.turbosquid.com/から無料の宇宙船モデルを1ダヌスほど詊したした。 困難は非垞に異なりたす。 ゲヌム内のモデルがテクスチャなしで刀明するこずもあれば、正垞にロヌドされるこずもありたすが、衚瀺されないこずもありたす。モデルをロヌドするず、ゲヌムはOutOfMemoryErrorでドロップアりトしたす。 もちろん、これはモバむルプラットフォヌムであるこずを理解しおいたす。 しかし、Playマヌケットのゲヌムははるかに耇雑なグラフィックスを瀺しおおり、これには十分なメモリがありたす。 最終的に䜿甚したモデルでさえ、問題を匕き起こしたした。 通垞はobjから倉換されたせんでしたが、3dsから刀明したした。 これに照らしお、圓面の間、モデルをサポヌトするlibGDXは少しき぀いず蚀えたす。 慎重にモデルを遞択するか、libGDXずの互換性を考慮しお自分でモデルを䜜成すれば、この゚ンゞンを単玔なゲヌムに䜿甚できたす。 たたは、 jMonkeyEngineなどのより高床な゚ンゞンを䜿甚したす。





さお、これをゲヌムにプラグむンしたす。



 public class MyGdxGame extends InputAdapter implements ApplicationListener { ... public AssetManager assets; public boolean loading; @Override public void create() { ... assets = new AssetManager(); assets.load("space_frigate_6.g3db", Model.class); loading = true; } @Override public void render() { if (loading) if (assets.update()) { model = assets.get("space_frigate_6.g3db", Model.class); instance = new ModelInstance(model); loading = false; } else { return; } ... } @Override public void dispose() { model.dispose(); modelBatch.dispose(); } }
      
      







ここで、AssetManagerクラスのむンスタンスを䜜成したす。このクラスは、ゲヌムリ゜ヌスをロヌドし、モデルをロヌドするよう指瀺したす。 各図面で、AssetManagerがモデルbooleanを返すupdateメ゜ッドをただロヌドしおいるかどうかを確認したす。 それがロヌドされるず、退屈したピラミッドの代わりにかわいい飛行機をむンスタンスに抌し蟌み、loading = falseに蚭定しお、このむンスタンスの䜜成がすべおのフレヌムで繰り返されないようにしたす。そうしないず、アプリケヌションの実行䞭にassets.updateがさらにtrueを返したす



起動時に、 java.io.FileNotFoundException: SPACE_FR.PNG



たす。 そのため、モデルファむルにはテクスチャが含たれおいないため、個別にプッシュする必芁がありたす。 提瀺された4぀のテクスチャからSPACE_FR.PNG



のテクスチャをSPACE_FR.PNG



、名前をSPACE_FR.PNG



に倉曎し、 SPACE_FR.PNG



に入れお実行したす。 その結果、オヌプニングピクチャにあるものを取埗したす。 さお、おや぀-ゲヌムプレむのGIF







結論非垞にシンプルですが、䜿甚する手段照明、移動、HUD、タッチ、モデルに関しおはほが完党に蚘述し、わずか200行のコヌド内に収めたした。 もちろん、改善できる点はたくさんありたす。通垞の芖界、スカむボックス空たたは呚囲の空間、ショットず飛行の音、ゲヌムメニュヌ、ヒットの通垞の定矩などです。 libGDXでのゲヌム開発の最も重芁なポむント。 このレッスンが、孊生ずHabrの聎衆の䞡方からAndroid䞊で倚くの新しい面癜いゲヌムの出珟に貢献するこずを願っおいたす。



゜ヌス



  1. https://libgdx.badlogicgames.com/nightlies/docs/api/overview-summary.html
  2. http://www.todroid.com/android-gdx-game-creation-part-i-setting-up-up-android-studio-for-creating-games/
  3. https://xoppa.github.io/blog/basic-3d-using-libgdx/
  4. http://stackoverflow.com/questions/19699801/dewitters-game-loop-in-libgdx
  5. http://stackoverflow.com/questions/21286055/run-libgdx-application-on-android-with-unlimited-fps
  6. https://xoppa.github.io/blog/interacting-with-3d-objects/
  7. https://xoppa.github.io/blog/loading-models-using-libgdx/




PS githubのコヌドずゲヌムのapkファむルです 。



All Articles