Libgdx 実用的な質問と回答

画像 こんにちはHabr!



VKontakteからのコンテストと正しい情報を見つけるためのインターネットでの2週間のマラソンは終了しました。 LibGDXグラフィックエンジンで少し経験を共有したいと思います。 インターネットには多くの例がありますが、ほとんどは実践からはほど遠い(描かれたスプライトはゲームからほど遠い)か、すでに時代遅れです。



正直なところ、Android Studioと簡単に統合でき、使いやすい言語で記述されており、グラフィックタスクを完全に実行するため、気に入っています。 このような問題を解決するためにアンドロイドグラフィックスを使用することを考えるのは恐ろしいことです。



私が解決しなければならなかった質問:



組み込みロガーを使用するオプション
便宜上、私はTimberをよく使います。 コアモジュールでは使用できないため、LibGDXで使用可能なロガーを使用して単純なヘルパークラスを作成し、リリースバージョンのアプリケーションはログの書き込みを停止します



import com.badlogic.gdx.Gdx; public class GdxLog { public static boolean DEBUG; @SuppressWarnings("all") public static void print(String tag, String message) { if (DEBUG) { Gdx.app.log(tag, message); } } @SuppressWarnings("all") public static void d(String tag, String message, Integer...values) { if (DEBUG) { Gdx.app.log(tag, String.format(message, values)); } } @SuppressWarnings("all") public static void f(String tag, String message, Float...values) { if (DEBUG) { Gdx.app.log(tag, String.format(message.replaceAll("%f", "%.0f"), values)); } } } //...  GdxLog.d(TAG, "worldWidth: %d", worldWidth);
      
      





さらに、便宜上、1.23456789のさまざまな浮動小数点値は丸められます



E / libEGL:現在のコンテキストなしでOpenGL ES APIを呼び出します(スレッドごとに1回記録されます)
最初は、そのようなエラーが私を非常に動揺させ、何も理解できませんでした。 これは、GLSurfaceViewグラフィックが、外部からこのプロセスに干渉しようとすると、独自の個別のストリームでレンダリングされるために発生します。 修正方法:



 Gdx.app.postRunnable(new Runnable() { @Override public void run() { //      } });
      
      





原則として、view.postInvalidate()の場合のように



私は匿名クラスのファンではないので、コードを減らすための単純なメソッドを作成しました(そうしないと判読できなくなります)。 Java 8ではこれはもはや問題ではありませんが、追加の利点は、たとえばファイルが見つからない場合にInvocationTargetExceptionが処理されることです。このような小さなエラーが原因でアプリケーションがクラッシュすることはなくなります。



 // null may be only String params public void postRunnable(final String name, final Object...params) { Gdx.app.postRunnable(new Runnable() { @Override public void run() { Method method = null; Class[] classes = new Class[params.length]; for (int i = 0; i < params.length; i++) { classes[i] = params[i] == null ? String.class : params[i].getClass(); } try { method = World.class.getMethod(name, classes); } catch (SecurityException e) { GdxLog.print(TAG, e.toString()); } catch (NoSuchMethodException e) { GdxLog.print(TAG, e.toString()); } if (method == null) { return; } try { method.invoke(WorldAdapter.this, params); } catch (IllegalArgumentException e) { GdxLog.print(TAG, e.toString()); } catch (IllegalAccessException e) { GdxLog.print(TAG, e.toString()); } catch (InvocationTargetException e) { GdxLog.print(TAG, e.toString()); } } }); }
      
      





パラメータがプリミティブではなく、オブジェクトを継承することが重要です。 また、ここにnullパラメーターを使用した単純化があります(Stringクラスのみ)



LibGDXを他のウィジェットで使用する方法
フラグメントを通して。 wikiの詳細情報



例:



 public class ActivityMain extends AppCompatActivity implements AndroidFragmentApplication.Callbacks { protected FragmentWorld fragmentWorld; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... getSupportFragmentManager() .beginTransaction() .add(R.id.world, fragmentWorld, FragmentWorld.class.getSimpleName()) .commitAllowingStateLoss(); } @Override public void exit() {}
      
      





そしてフラグメント自体:



 public class FragmentWorld extends AndroidFragmentApplication { public World world; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { int worldWidth = getResources().getDimensionPixelSize(R.dimen.world_width); int worldHeight = getResources().getDimensionPixelSize(R.dimen.world_height); world = new World(BuildConfig.DEBUG, worldWidth, worldHeight); return initializeForView(world); } }
      
      







ビットマップで世界またはPixmapのレンダリングを描画する方法
pixmapをファイルに保存してから、Androidツールを使用してBitmapを引き出したくなかった

したがって、OutputStreamクラスを使用して、このようなライフハックを思いつきました。 正常に動作し、遅いr / w操作を必要としません



 final Pixmap pixmap = getScreenshot(); Observable.fromCallable(new Callable <Boolean> () { @Override public Boolean call() throws Exception { PixmapIO.PNG writer = new PixmapIO.PNG((int)(pixmap.getWidth() * pixmap.getHeight() * 1.5 f)); writer.setFlipY(false); ByteArrayOutputStream output = new ByteArrayOutputStream(); try { writer.write(output, pixmap); } finally { StreamUtils.closeQuietly(output); writer.dispose(); pixmap.dispose(); } byte[] bytes = output.toByteArray(); Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length); return true; } }).subscribeOn(Schedulers.io()).subscribe();
      
      







Androidと組み合わせて色を使用する方法
実際、おそらくintが可能です(LibGDXのColorクラスは、すべてのグラフィックスと同じように非常に具体的です。つまり、ビットレベルを理解する必要があります)。



したがって、パーサーが必要でした:



 protected Color parseColor(String hex) { String s1 = hex.substring(0, 2); int v1 = Integer.parseInt(s1, 16); float f1 = 1 f * v1 / 255 f; String s2 = hex.substring(2, 4); int v2 = Integer.parseInt(s2, 16); float f2 = 1 f * v2 / 255 f; String s3 = hex.substring(4, 6); int v3 = Integer.parseInt(s3, 16); float f3 = 1 f * v3 / 255 f; return new Color(f1, f2, f3, 1 f); }
      
      





パラメータ例「ffffff」



アクターのクリックを検出する方法
sticker.addListener()すべてがシンプルになりました。 シーンにヒットメソッドがあります



 Sticker sticker = (Sticker) stickersStage.hit(coordinates.x, coordinates.y, false);
      
      







ピンチイベントでの数学のスケールと回転(2本の指)
これは最善の解決策ではないかもしれませんが、うまく機能します。 鋭いジャンプなしで回転、スムーズなズーム



 @Override public boolean pinch(Vector2 initialPointer1, Vector2 initialPointer2, Vector2 pointer1, Vector2 pointer2) { // initialPointer doesn't change // all vectors contains device coordinates Sticker sticker = getCurrentSticker(); if (sticker == null) { return false; } Vector2 startVector = new Vector2(initialPointer1).sub(initialPointer2); Vector2 currentVector = new Vector2(pointer1).sub(pointer2); sticker.setScale(sticker.startScale * currentVector.len() / startVector.len()); float startAngle = (float) Math.toDegrees(Math.atan2(startVector.x, startVector.y)); float endAngle = (float) Math.toDegrees(Math.atan2(currentVector.x, currentVector.y)); sticker.setRotation(sticker.startRotation + endAngle - startAngle); return false; }
      
      





このイベントの前に現在のズームと回転を覚えておくだけで十分です。



 @Override public boolean touchDown(float x, float y, int pointer, int button) { if (pointer == FIRST_FINGER) { Vector2 coordinates = stickersStage.screenToStageCoordinates(new Vector2(x, y)); Sticker sticker = (Sticker) stickersStage.hit(coordinates.x, coordinates.y, false); if (sticker != null) { //  sticker.setPinchStarts(); currentSticker = sticker.index; } } return false; } @Override public void pinchStop() { Sticker sticker = getCurrentSticker(); if (sticker != null) { //  sticker.setPinchStarts(); } }
      
      





そして、ピンチイベントの間、俳優は動かない



アニメーションは発生しないが、アクションがアクターに追加される理由
シーン内のキーアクトメソッド。 知らないときの痛み)



 spriteBatch.begin(); stickersStage.act(); stickersStage.getRoot().draw(spriteBatch, 1); spriteBatch.end();
      
      







LibGDXの勾配
私が理解しているように、左上と右下の色のみを設定できます。 この場合、残り(透明)を設定する必要はなく、それらの間にスペースがあります。 すなわち 残りは、線形グラデーションになると、特定の距離におけるこれら2つの色の合計として定義されます。 その独特なことを言うこと、何も言わないこと



 gradientTopLeftColor = parseColor(topLeftColor); gradientBottomRightColor = parseColor(bottomRightColor); gradientBlendedColor = new Color(gradientTopLeftColor).add(gradientBottomRightColor);
      
      







アクターの移動処理のトリック(パンイベント)
ここにハンドラーがあります



 @Override public boolean pan(float x, float y, float deltaX, float deltaY) { if (currentSticker != Sticker.INDEX_NONE) { Sticker sticker = getCurrentSticker(); if (sticker != null) { sticker.moveBy(deltaX * worldDensity, -deltaY * worldDensity); } } return false; }
      
      





worldDensityは、画面座標で指を動かすこととゲーム内の俳優を動かすことの違いです。 このパラメーターがないと、アクターは指から離れます



 @Override public void resize(int width, int height) { if (height > width) { worldDensity = 1f * worldWidth / width; } else { worldDensity = 1f * worldHeight / height; } viewport.update(width, height, true); }
      
      





また、sticker.addListenerを介してタッチ入力バインディングを行う場合、入力座標は現在の俳優の指の位置に相対的です。 小さいサイズの俳優(ズーム)で、彼は痙攣してシーンから飛び出します(私がしたように)。



アクターアニメーションをより適切に操作する方法(アクション)
プールクラスを使用します 。 私のプロジェクトには、追加のより詳細な実装の例があります



 public void onAppear() { ScaleToAction scaleToAction = scaleToPool.obtain(); scaleToAction.setPool(scaleToPool); scaleToAction.setScale(startScale); scaleToAction.setDuration(ANIMATION_TIME_APPEAR); addAction(scaleToAction); }
      
      







おそらくすべてが興味深い。 私自身、ビューポートを増やすという問題の解決策を見つけませんでした。 ズームカメラはシーンのアプローチにのみ役立ち、シーンが必要以上に縮小することがわかります(スコープは変更されません)。



別の問題は、世界のレンダリングの保存です。 出力では、画面のサイズと一致しますが、特定のサイズが必要です。 フレームバッファで試しましたが、そこからピックスマップを取得できませんでした(テクスチャクラスの初期化にはいくつかのバグがあります)



エンジンの別の欠点。たとえば、キーボード入力を完全に無効にすることはできません。 彼は別のウィジェットからフォーカスをインターセプトしたことが判明しました(しかし、彼はこのために設計されていませんでしたが、それは素晴らしいことですが。



しかし、全体的に、すべてが非常に優れています。 LibGDXをさらに開発する)



プロジェクトへのリンク



All Articles