2017-05-03 12 views
0

私はLibGDXで作っているビデオゲーム用の簡単なイベントドリブンGUIを作成しています。クリックされたときに呼び出される単一のact()関数を持つボタン(長方形)をサポートするだけです。これまで考えてきたソリューションが理想から遠いように思えるので、構造化に関するアドバイスをいただければ幸いです。簡単なイベントドリブンGUIの設計

私の現在の実装では、Buttonクラスを拡張するすべてのボタンが含まれています。各ボタンには、範囲がRectangleで、抽象度がact()のメソッドがあります。

各ゲーム画面(メインメニュー、文字選択、一時停止メニュー、ゲーム内画面)には、ボタンのHashMapがあります。クリックされると、ゲーム画面はHashMap内のすべてを繰り返し、クリックされたボタン上でact()を呼び出します。

私が抱えている問題は、ボタンがアクションを実行するためにスーパークラスからオーバーライドされたact()を持つ必要があり、ボタンがすべてのゲームコードを含むScreenクラスのメンバーでないことです。私はゲームの各ボタンのサブクラスButtonです。私のメインメニューには、ButtonPlay、ButtonMapDesigner、ButtonMute、ButtonQuitなどがあります。これは面倒な速さですが、ボタンごとに個別のact()メソッドを保持しながら、これを行うためのより良い方法は考えられません。

私のミュートボタンはメインメニュー画面の一部ではなく、ゲームロジックにアクセスできないため、act()mainMenuScreen.mute();です。ゲーム内のすべてのボタンについて、実質的にコードをゲーム画面のクラスに入れなければならないので、<currentGameScreen>.doThisAction();以上を実行するクラスクラスを作成する必要があります。

各クリックの座標を確認し、必要に応じて適切なアクションを呼び出すために大きなif/thenがあると考えました。たとえば、

if (clickWithinTheseCoords) 
    beginGame(); 
else if(clickWithinTheseOtherCoords) 
    muteGame(); 
... 

ただし、ボタンをオン/オフで追加/削除できるようにする必要があります。ゲーム画面からユニットをクリックすると、移動するボタンが表示され、実際にユニットを移動すると消えるボタンが表示されます。 HashMapを使用すると、ユニットをクリックまたは移動したときに呼び出されるコードでは、map.add("buttonMove", new ButtonMove())map.remove("buttonMove")とすることができます。 if/elseメソッドを使用すると、ボタンごとに別々のクラスは必要ありませんが、テストされた各クリック可能領域が、この時点でユーザーによって表示されクリック可能かどうかを追跡する必要があります。私が今持っているものよりも大きな頭痛。

+0

見て、それGUIのものを作るためのものです。それを使用するか、ゲームのコンセプトをコピーしてください。 –

答えて

0

Snehの応答は、すべてのボタンに対して別々のクラスを作成する代わりに、ボタンを作成したときに毎回座標とact()メソッドを指定するたびに匿名の内部クラスを使用することができました。私はこれを行うための可能な短い方法としてラムダ構文を調べましたが、それには限界がありました。私は柔軟な解決策に終わったが、私のニーズに合わせてそれをさらに減らした。両方の方法を以下に示します。

私のゲーム内の各ゲーム画面は、ボタンなどのHashMapを持つ、LibGDXのScreenを拡張しますが、サイズ変更のビューポートを更新するように普遍的な機能を追加しMyScreenクラスからサブクラス化され私はMyScreenクラスにbuttonPressed()メソッドを追加しました。このメソッドは、1つのパラメータとしてenumを取ります。私は可能なすべてのボタン(MAINMENU_PLAY、MAINMENU_MAPDESIGNERなど)を含むButtonValues enumを持っています。各ゲーム画面では、buttonPressed()がオーバーライドされ、スイッチが正しいアクションを実行するために使用される:

public void buttonPressed(ButtonValues b) { 
    switch(b) { 
     case MAINMENU_PLAY: 
      beginGame(); 
     case MAINMENU_MAPDESIGNER: 
      switchToMapDesigner(); 
    } 
} 

ではなくbuttonPressed()を必要とする、独自にアクションを実行できるように、他のソリューションは、ボタン・ストアにラムダ式を有しますどのボタンが押されたかに基づいて正しいアクションを実行する仲介者として働くことができます。

ボタンを追加するには、それはその座標とタイプ(列挙型)で作成され、ボタンのHashMapのに追加されます。

Button b = new Button(this, 
      new Rectangle(300 - t.getRegionWidth()/2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()), 
      tex, ButtonValues.MAINMENU_PLAY); 
    buttons.put("buttonPlay", b); 

、ちょうどbuttons.remove("buttonPlay").それを削除するには、それが画面から消えますゲームによって忘れられてしまいます。

引数はゲーム画面でbuttonPressed()を呼び出すことができるので、ボタンを所有するゲーム画面、座標を持つ矩形、テクスチャ(描画に使用する)、および列挙値です。そのポイントは、ボタンの中に含まれているかどうかを

public class Button { 

    public Rectangle r; 
    public TextureRegion image; 

    private MyScreen screen; 
    private ButtonValues b; 

    public Button(MyScreen screen, Rectangle r, TextureRegion image, ButtonValues b) { 
     this.screen = screen; 
     this.r = r; 
     this.image = image; 
     this.b = b; 
    } 

    public void act() { 
     screen.buttonPressed(b); 
    } 

    public boolean isClicked(float x, float y) { 
     return x > r.x && y > r.y && x < r.x + r.width && y < r.y + r.height; 
    } 
} 

isClicked()だけの(x、y)の中に取り込み、チェック:

そしてここでは、Buttonクラスです。マウスをクリックすると、すべてのボタンを繰り返して、isClickedの場合はact()に電話します。

私が行った2番目の方法は、ButtonValues列挙型の代わりにラムダ式を使用して同様でした。 Buttonクラス似ていますが、これらの変化と(それが聞こえるよりもずっと簡単です):

フィールドButtonValues bRunnable rに置き換えられ、これは、コンストラクタから削除されます。追加されたsetAction()メソッドは、Runnableを受け取り、rを渡されたRunnableに設定します。 act()の方法はちょうどr.run()です。例:

public class Button { 

    [Rectangle, Texture, Screen] 
    Runnable r; 

    public Button(screen, rectangle, texture) {...} 

    public void setAction(Runnable r) { this.r = r; } 

    public void act() { r.run(); } 
} 

ボタンを作成するには、私は次のようにします。

Button b = new Button(this, 
      new Rectangle(300 - t.getRegionWidth()/2, 1.9f * 60, t.getRegionWidth(), t.getRegionHeight()), 
      tex); 
    b.setAction(() -> b.screen.doSomething()); 
    buttons.put("buttonPlay", b); 

まず、ボタンはそれを含むゲーム画面クラス、そのバウンディングボックス、およびそのテクスチャを使用して作成されます。次に、2番目のコマンドで、私はそのアクションを設定します - この場合、b.screen.doSomething();.この時点でbとb.screenは存在しないため、コンストラクタに渡すことはできません。 setAction()Runnableで、act()が呼び出されたときに呼び出されるButtonのRunnableに設定されます。しかし、Runnableはラムダ構文で作成できるので、匿名のRunnableクラスを作成する必要はなく、実行する関数を渡すことができます。

この方法では、柔軟性は大幅に向上しますが、1つの注意点があります。 Buttonscreenフィールドには、すべてのゲーム画面が拡張されるベース画面クラスであるMyScreenが格納されています。ボタンの機能は、MyScreenクラスの一部であるメソッドを使用することができます(buttonPressed()MyScreenにして、ラムダ式を完全に取り除くことができたことがわかりました)。明らかな解決策は、screenフィールドをキャストすることですが、私にとってはbuttonPressed()メソッドを使用するだけでは余分なコードを書く価値はありませんでした。

私は(MyScreenを拡張する)私のMainMenuScreenクラスでbeginGame()メソッドを持っていた場合、ボタンに渡されたラムダ式は、MainMenuScreenにキャストを関与させる必要があります。残念ながら

b.setAction(() -> ((MainMenuScreen) b.screen).beginGame()); 

、でもワイルドカード構文は」doesnのここで助けてください。

そして最後に、完全性のために、ボタンを操作するゲームループ内のコード:ヘルパークラスに位置してそれらを描画する

public abstract class MyScreen implements Screen { 

    protected HashMap<String, Button> buttons; // initialize this in the constructor 

    // this is called in every game screen's game loop 
    protected void handleInput() { 
     if (Gdx.input.justTouched()) { 
      Vector2 touchCoords = new Vector2(Gdx.input.getX(), Gdx.input.getY()); 
      g.viewport.unproject(touchCoords); 
      for (HashMap.Entry<String, Button> b : buttons.entrySet()) { 
       if (b.getValue().isClicked(touchCoords.x, touchCoords.y)) 
        b.getValue().act(); 
      } 
     } 
    } 
} 

そして、:Scene2Dで

public void drawButtons(HashMap<String, Button> buttons) { 
    for (HashMap.Entry<String, Button> b : buttons.entrySet()) { 
     sb.draw(b.getValue().image, b.getValue().r.x, b.getValue().r.y); 
    } 
} 
1

私はactメソッドで実行するすべてのボタンにrunnableを提供します。あなたに簡単な例を与える。

private final Map<String, Button> buttons = new HashMap<>(); 

public void initialiseSomeExampleButtons() { 
    buttons.put("changeScreenBytton", new Button(new Runnable() { 
     @Override 
     public void run() { 
      //Put a change screen action here. 
     } 
    })); 

    buttons.put("muteButton", new Button(new Runnable() { 
     @Override 
     public void run() { 
      //Do a mute Action here 
     } 
    })); 
} 

public class Button { 

    //Your other stuff like rectangle 

    private final Runnable runnable; 

    protected Button(Runnable runnable) { 
     this.runnable = runnable; 
    } 

    public void act() { 
     runnable.run(); 
    } 
} 

あなたはマップからボタンを追跡し、コンストラクタ内のすべてのボタンに実行可能アクションを渡すだけで済みます。私は意図的にいくつかのコードをスキップして、自分で試すことができます。ご不明な点がございましたら、お知らせください。

関連する問題