シンプルなメニューを実装するために、そのすべてを実行する必要はないと思います! それはすべて、さまざまなアイテムのオフセットを定義することです (特定の瞬間に 1 つのエントリのみが表示される (トランジションを除く)、Cut the Rope スタイルのメニューが必要だと仮定します)。次に、フリックが検出されるたびに、これらのオフセット間でトゥイーンします!
ジェスチャ システムはすべて接続されているようです。今は、メニューを表示する方法を理解する必要があります。簡単にするために、メニューをラップアラウンドさせたくないと仮定します。
このメニューがどのように見えるかを頭の中で想像することから始めます。それは、電話を通過して画面を通して見ることができるフィルムストリップのようなものです。
phone ("stuff" is currently selected)
========
|---------------| |-------|
| Start | About | Stuff | Quit |
|---------------| |-------|
| |
| |
| |
========
画面の幅は であると想定しw
、その結果、すべてのメニュー エントリは正確にその幅になります (Cut the Rope をもう一度考えてみてください!)。
ここで、"Start" を表示するときは、最初の要素 "Start" から始まるフリムストリップを画面にレンダリングする必要がありますが、残りは理論的には画面の右側にあります。これは、オフセット = 0でメニューをレンダリングする基本的なケースと見なされます。
はい、はい、このオフセットが小さなスライドメニューの鍵となります! ここで、about が選択されている場合、「フィルムストリップ」を 1 つの「フレーム」だけ左にオフセットする必要があることは明らかです。ここでは、offset = - 1 * frameWidthです。私の見事な ASCII アートで示した例では、3 番目のメニュー項目が選択されています。フレームは 0 から始まるインデックスが付けられているため、frameWidth の 2 倍を差し引いて、目的のオフセットを取得します。オフセット = -2 * フレーム幅から始まるメニューをレンダリングします。
(明らかに、API を使用して画面幅を取得し、メニュー要素のテキスト/グラフィックを中央に描画することで、事前に frameWidth を計算することができます)。
したがって、これは非常に簡単です。
しかし、スムーズなトゥイーンはどうですか?
ありがたいことに、Libgdx には素敵な小さなトゥイーン用にすべて設定された補間があります。足を撃たないように、いくつかのことを処理する必要があります。ここにそれらをリストします。
簡単なメモ:
Cut the Rope レベル セレクターは、ここで言っていることとは少し異なります。フリック (定義済みのジェスチャー) に反応するだけでなく、より敏感です。オフセットを操作して画面上の指の位置を追跡することで、おそらく同様の効果を得ることができます。(ユーザーがメニュー エントリを左/右にドラッグしすぎた場合、自動的に前/次に移行します) フレンドリーなアドバイス: シンプルで機能するメニューを設定し、このような詳細を最後に残してください。時間がかかる!:P
よし、軌道に戻ろう!
これで、オフセットをすばやく切り替える方法ができました。トゥイーンするだけです。いくつかの追加メンバーが登場しますが、それらは一目瞭然だと思います。2 つの要素間を遷移している間、「古い」オフセットとこれから向かうオフセット、および遷移からの残り時間を記憶し、これら 4 つの変数を使用してオフセットを計算します (現時点で libgdx 補間 (この場合は exp10) を使用すると、スムーズなアニメーションが得られます。
見てみましょう、簡単なモックアップを作成しました。できる限りコードにコメントしたので、次のスニペットがそれを物語っていることを願っています! :D
import java.util.ArrayList;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.Interpolation;
public class MenuManager {
// The list of the entries being iterated over
private ArrayList<MenuEntry> entries = new ArrayList<>();
// The current selected thingy
private int index;
// The menu offset
private float offset = 0.0f;
// Offset of the old menu position, before it started tweening to the new one
private float oldOffset = 0.0f;
// What we're tweening towards
private float targetOffset = 0.0f;
// Hardcoded, I know, you can set this in a smarter fashion to suit your
// needs - it's basically as wide as the screen in my case
private float entryWidth = 400.0f;
// Whether we've finished tweening
private boolean finished = true;
// How much time a single transition should take
private float transitionTimeTotal = 0.33f;
// How much time we have left from the current transition
private float transitionTimeLeft = 0.0f;
// libgdx helper to nicely interpolate between the current and the next
// positions
private Interpolation interpolation = Interpolation.exp10;
public void addEntry(MenuEntry entry) {
entries.add(entry);
}
// Called to initiate transition to the next element in the menu
public void selectNext() {
// Don't do anything if we're still animationg
if(!finished) return;
if(index < entries.size() - 1) {
index++;
// We need to head towards the next "frame" of the "filmstrip"
targetOffset = oldOffset + entryWidth;
finished = false;
transitionTimeLeft = transitionTimeTotal;
} else {
// (does nothing now, menu doesn't wrap around)
System.out.println("Cannot go to menu entry > entries.size()!");
}
}
// see selectNext()
public void selectPrevious() {
if(!finished) return;
if(index > 0) {
index --;
targetOffset = oldOffset - entryWidth;
finished = false;
transitionTimeLeft = transitionTimeTotal;
} else {
System.out.println("Cannot go to menu entry <0!");
}
}
// Called when the user selects someting (taps the menu, presses a button, whatever)
public void selectCurrent() {
if(!finished) {
System.out.println("Still moving, hold yer pants!");
} else {
entries.get(index).select();
}
}
public void update(float delta) {
if(transitionTimeLeft > 0.0f) {
// if we're still transitioning
transitionTimeLeft -= delta;
offset = interpolation.apply(oldOffset, targetOffset, 1 - transitionTimeLeft / transitionTimeTotal);
} else {
// Transition is over but we haven't handled it yet
if(!finished) {
transitionTimeLeft = 0.0f;
finished = true;
oldOffset = targetOffset;
}
}
}
// Todo make font belong to menu
public void draw(SpriteBatch spriteBatch, BitmapFont font) {
if(!finished) {
// We're animating, just iterate through everything and draw it,
// it's not like we're wasting *too* much CPU power
for(int i = 0; i < entries.size(); i++) {
entries.get(i).draw((int)(i * entryWidth - offset), 100, spriteBatch, font);
}
} else {
// We're not animating, just draw the active thingy
entries.get(index).draw(0, 100, spriteBatch, font);
}
}
}
そして、それ自体を描画できる単純なテキストベースのメニューエントリで十分だと思います! (汚れたハードコーディングされたテキスト ラップの幅に注意してください! )
public class MenuEntry {
private String label;
// private RenderNode2D graphic;
private Action action;
public MenuEntry(String label, Action action) {
this.label = label;
this.action = action;
}
public void select() {
this.action.execute();
}
public void draw(int x, int y, SpriteBatch spriteBatch, BitmapFont font) {
font.drawMultiLine(spriteBatch, label, x, y, 400, HAlignment.CENTER);
}
}
ああ、Actionは execute メソッドを持ち、アクションを表すものです。
public interface Action {
abstract void execute();
}
コメントで関連する質問をお気軽にお寄せください。何が必要かを明確にします。お役に立てれば!