2017-05-03 5 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

봐 : 헬퍼 클래스에있는

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(); } } } } 

그리고 그들을 그릴, GUI 물건 만들기위한 것입니다. 그것을 사용하거나 게임의 개념을 복사하십시오. –

답변

0

Sneh의 반응은 모든 버튼에 대해 별도의 클래스를 만드는 대신 버튼을 만들 때마다 매번 좌표 및 act() 메소드를 지정하여 익명의 내부 클래스를 사용할 수있었습니다. 나는 이것을 할 수있는 가능한 더 짧은 방법으로 람다 문법을 탐구했지만 한계를 보았다. 필자는 유연한 솔루션으로 끝났지 만 결국 내 필요에 맞게 조금 줄였습니다. 두 가지 방법이 아래에 나와 있습니다.

내 게임의 각 게임 화면은 LibGDX의 Screen을 확장하는 MyScreen 클래스의 하위 클래스이지만 크기 조정시 뷰포트 업데이트, 버튼의 HashMap 포함 등의 범용 기능이 추가되었습니다.MyScreen 클래스에 buttonPressed() 메서드를 추가했습니다.이 메서드는 하나의 매개 변수로 열거 형을 사용합니다. MAINMENU_PLAY, MAINMENU_MAPDESIGNER 등과 같은 가능한 모든 버튼을 포함하는 ButtonValues enum을 가지고 있습니다. 각 게임 화면에서 buttonPressed()는 오버라이드 (override)과 스위치가 올바른 작업을 수행하는 데 사용됩니다 :

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

을 다른 솔루션 대신 buttonPressed()을 요구하는 자체에 대한 작업을 수행 할 수 있도록 버튼이 람다 식을 저장할 수있다 어떤 버튼을 눌렀는지에 따라 올바른 행동을 수행하는 중개자 역할을한다.

버튼을 추가하기 위해서는 그 좌표 입력 (ENUM)로 생성되고, 버튼의 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)를 입력 받아 확인 :

그리고 여기 버튼 클래스이다. 마우스 클릭시 모든 버튼을 반복하고 버튼 isClicked 인 경우 act()으로 전화하십시오.

내가 한 두 번째 방법은 비슷했지만 ButtonValues ​​열거 형 대신 람다 식을 사용했습니다. Button 클래스도 비슷하지만 다음과 같은 변경 사항이 있습니다 (소리보다 훨씬 간단합니다).

필드가 생성자에서 제거되었습니다. 추가 된 setAction() 메서드는 Runnable을 사용하고 전달 된 Runnable에 r을 설정합니다. 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); 

는 첫째, 버튼의 포함 된 게임 화면 클래스의 경계 상자, 그 질감 생성됩니다. 그런 다음 두 번째 명령에서 해당 동작을 설정합니다 (이 경우 b.screen.doSomething();). 이 시점에서 b와 b.screen이 존재하지 않기 때문에 생성자에 전달할 수 없습니다. setAction()Runnable을 취해 act()이 호출 될 때 호출되는 Button의 Runnable로 설정합니다. 그러나 Runnable은 람다 구문을 사용하여 만들 수 있으므로 익명의 Runnable 클래스를 만들 필요가 없으며 클래스가 수행하는 함수를 전달할 수 있습니다.

이 방법을 사용하면 유연성이 훨씬 향상되지만주의해야합니다. screen 필드는 Button이며 모든 게임 화면이 확장되는 기본 화면 클래스 인 MyScreen을 보유합니다. 버튼의 기능은 MyScreen 클래스의 일부인 메소드 만 사용할 수 있습니다. 따라서 buttonPressed()MyScreen에 작성한 다음 람다 식을 완전히 스크랩 할 수 있음을 깨달았습니다.분명한 해결책은 screen 필드를 캐스팅하는 것이지만, buttonPressed() 메서드를 사용할 수있을 때 추가 코드를 쓸 가치가 없었습니다.

내가 (MyScreen를 확장) 내 MainMenuScreen 클래스의 beginGame() 방법이 있다면, 버튼에 전달 된 람다 식을 MainMenuScreen에 캐스트 포함해야합니다 : 불행하게도

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

을, 심지어 와일드 카드 구문은 '아무튼 여기에서 도와주세요.

그리고 마지막으로, 완성도, 게임 루프의 코드가 작동 할 수있는 버튼 : 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(); 
    } 
} 

지도를 통해 버튼을 추적하고 생성자의 모든 버튼에 실행 가능한 액션을 전달하면됩니다. 일부 코드는 의도적으로 건너 뛰었으므로 직접 시도 할 수 있습니다. 질문이 있으시면 알려주세요.