자바의 GUI를 WindowBuilder라는 이클립스의 툴을 사용해서 디자인한 후에, GUI 요소를 스레드로 만들어 각각이 실행하면서 움직일 수 있도록하는 게임을 만들기
자바의 각 요소가 모두 스레드로써 동작한다는 점과, 만약 GUI의 JFrame 내 요소들을 각각 스레드로 생성하여 취급하지 않으면, 런타임(실행 중) 생각한 것처럼 각각 개별적으로 동작하지 않는다는 점을 이해하고, 의도한 프로그램을 만들기 위해 어떤 요소들을 스레드로 만들어야하는지 파악하고 게임 만들어보기

위의 그림과 같이 GUI 디자인은 제시된 게임의 디자인과 동일하게 만들었다. 이후에 코드로 이동하여 필요한 코드를 작성하였다.
과제로 제시된 게임의 화면과 동작을 보고 우선 가능한 부분은 직접 코드를 작성해보았고, 잘 모르겠는 부분은 예시로 제공된 코드를 참고하여 메웠다.
우선 각 요소를 클릭할 수 있는 JButton으로 만들어 MyView 클래스의 필드로 두었다.
public class MyView extends JFrame {
private static final long serialVersionUID = 1L;
private JPanel contentPane;
private JTextField textField;
private JTextField textField_1;
private Creature chickButton;
private Creature rabbitButton;
private Creature turtleButton;
private Creature targetButton;
...
MyView 클래스의 메인메소드에서 이 객체를 생성하고, setVisible(true)메소드를 사용해 창을 이곳에서 바로 띄운다.
public static void main(String[] args) {
new MyView().setVisible(true);
}
처음에는 토끼, 거북이, 병아리, 망나니 등의 버튼을 그냥 JButton으로 만들어뒀었는데, 각 버튼이 클릭했을 때의 동작이 유사해 JButton을 상속받은 이 프로그램 내 만의 클래스를 만드는 것이 좋을 것이라 생각해 Creature라는 클래스를 만들었다.
먼저 각 버튼은 JButton처럼 동작해야하기 때문에 JButton을 상속받는다. 그리고 각각의 버튼이 독립적으로 동작해야하기 때문에, 스레드로 만들어야한다. 하지만, 이미 extends JButton을 하였기에 이중상속은 불가하다. 즉, extends Thread의 형식으로 스레드를 만들 수는 없다.
이에 implements Runnable을 사용해 인터페이스를 구현하는 방향으로 스레드로 만들어주었다.
또 각 버튼을 클릭했을 때, 이벤트가 발생하고 이를 처리해야하기 때문에, ActionListener라는 인터페이스를 구현하도록한다. (이 부분은 헷갈려서 예시코드를 참고해서 작성했다.)
public class Creature extends JButton implements Runnable, ActionListener
각 버튼이 오른쪽으로 또는 아래쪽으로 이동하도록한다. 이건, 망나니 버튼에는 해당되지 않기에 망나니 버튼은 랜덤하게 움직이도록한다. 이 움직임을 구현하는 방법은 예시코드를 참고하여 열거형 enum을 사용해 정의했다.
enum MovementType {
RIGHT, DOWN, RANDOM
}
public void setMovementType(MovementType type) {
this.movementType = type;
}
enum은 서로 연관된 상수들의 집합을 의미한다. 이걸 사용해 버튼이 '오른쪽(RIGHT) 또는 아래(DOWN) 또는 랜덤(RANDOM)'하게 움직일지 정의한다. 그리고, 이 MovementType은 Creature의 필드로 두고, 이 필드에 따라 어떤 함수를 실행할 지 결정한다.
setMovementType메소드를 통해 이 필드를 설정한다.
public class Creature extends JButton implements Runnable, ActionListener {
boolean state = true;
MovementType movementType = MovementType.RIGHT;
int delayTime;
public Creature(String name) {
super(name);
this.addActionListener(this);
}
public void moveRight() {
while (state) {
int x = this.getX();
int y = this.getY();
this.setLocation(x + 10, y);
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
checkState();
}
this.setEnabled(false);
}
public void moveDown() {
while (state) {
int x = this.getX();
int y = this.getY();
this.setLocation(x, y + 10);
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
checkState();
}
this.setEnabled(false);
}
public void moveRandom() {
Random r = new Random();
while (state) {
int x = r.nextInt(750);
int y = r.nextInt(500);
this.setLocation(x, y);
try {
Thread.sleep(delayTime);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
checkState();
}
this.setEnabled(false);
}
...
위처럼 오른쪽, 아래쪽, 그리고 랜덤하게 움직이게하기위한 메소드를 정의하고, 필드로 MovementType을 둔다. 움직임을 관찰하기 위해서 Thread.sleep()을 넣어주었다.
state라는 필드를 사용해 객체(버튼)이 결승선에 닿았는지 여부를 확인하고 기록하는 변수를 둔다. state가 true일 동안은 계속 메소드를 실행하고, 결승선에 닿아 false가 되는 순간 동작이 중지되고 해당 객체의 동작이 멈춘다.
아래처럼 state를 확인하고 관리해주는 함수를 만들었다.
public void checkState() {
int x = this.getX();
int y = this.getY();
if (x >= 758) {
this.state = false;
}
if (y >= 500) {
this.state = false;
}
}
객체(버튼)의 위치가 결승선을 넘어가게되면 state를 수정한다.
또 각 버튼(망나니 제외)의 공통적인 클릭시 메소드 actionPerformed 부분과 각 버튼이 스레드로 동작할 것이기에 run() 메소드를 아래처럼 정의해준다.
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
this.delayTime /= 2;
Color tmp = this.getBackground();
this.setBackground(Color.pink);
this.repaint();
}
@Override
public void run() {
// TODO Auto-generated method stub
switch (movementType) {
case RIGHT:
moveRight();
break;
case DOWN:
moveDown();
break;
case RANDOM:
moveRandom();
break;
}
}
MyView.java에서 각 버튼들을 Creature 클래스로 생성하도록 변경해주었다. 그리고 아래처럼 움직임 형식을 세팅해주고, 대기값을 설정하였다.
chickButton = new Creature("귀여운 병아리");
chickButton.setMovementType(MovementType.RIGHT);
chickButton.setTime(400);
chickButton.setBackground(new Color(255, 255, 200));
chickButton.setBounds(31, 120, 192, 56);
contentPane.add(chickButton);
또 망나니 버튼의 경우 클릭 시 실행되는 메소드가 달라야하므로, MyView.java에서 관련 메소드를 다시 정의해준다.
targetButton = new Creature("정신없는 망나니");
targetButton.setMovementType(MovementType.RANDOM);
targetButton.setTime(500);
targetButton.setBackground(new Color(187, 221, 255));
targetButton.setBounds(31, 345, 192, 56);
targetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
targetButton.state = false;
chickButton.state = false;
rabbitButton.state = false;
turtleButton.state = false;
JOptionPane.showMessageDialog(null, "망나니를 잡았습니다.! \n망나니 쓰레드를 종료합니다.!");
}
});
그리고 StartGame 버튼을 클릭하면 게임이 실행되도록한다. 게임이 실행되면, 각 버튼, 즉 스레드가 실행되어야한다. 시작 메소드를 하나로 만들어 각 스레드가 알아서 실행되도록한다.
JButton btnNewButton = new JButton("START! 망나니 잡기");
btnNewButton.setBackground(new Color(189, 189, 223));
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
startGame();
btnNewButton.setBackground(new Color(189, 189, 223));
}
});
...
public void startGame() {
Thread chick = new Thread(chickButton);
Thread rabbit = new Thread(rabbitButton);
Thread turtle = new Thread(turtleButton);
Thread target = new Thread(targetButton);
chick.start();
rabbit.start();
turtle.start();
target.start();
}
뭔가 이제 무엇을 스레드로 만들어야하는지에 대해서는 감이오는 것 같은데..단순히 스레드만 생성하는게 아니라 이것저것 구현/상속시킨 뒤에 스레드를 만들어보니 여전히 조금 그 방법이 헷갈리기도 했다...(특히 Thread th = new Thread(this) 이 부분 적는 것을 까먹었었다.) GUI를 다루고 직접 만들어보면서 사용방법을 익힐 수 있어서 재미있었다... actionListener를 만들고, 정의하고 이런 작업을 하면서 생각한대로 돌아가는게 재미있었던 것 같다.