티스토리 뷰


이번 포스팅에서는

방 만들기 기능을 구현해보도록 하겠습니다.

멀티쓰레드를 구현해서 여러 방을 동작할 때에는

생각보다 에러가 많이 발생하고 

코드 내부적으로 꼬일 가능성이 많기 때문에

주의를 기울이고 테스트해가며 구현해야합니다.


========================================================================


Rhythm Game : Create Room





1. Rhythm Game : Create Room


 먼저 방 만들기 기능을 구현하기 위해 사용자에게 어떠한 형식으로 인터페이스를 보여줄까 생각해야합니다.

저는 방만들기 버튼을 클릭하면 새로운 프레임을 띄워서 어떠한 방 형태로 만들 것인지 선택하도록 구현한 다음, 기존에 존재하는 ClientUI 프레임에 게임 대기방 패널을 생성하여 출력하도록 하겠습니다.






2. Rhythm Game : CreateRoomFrame.java


 일단 방만들기를 눌렀을 때 나타날 창은 별도의 Frame으로 설계합니다.


방만들 때 설정할 수 있는 기능은 3가지입니다.

 1) 1인용 or 2인용 방 설정 : 1인용일 경우에는 비밀번호, 방제목을 설정할 필요가 없기 때문에 리스너를 호출해 설정해줍니다.

 2) 방제목 : 기본적으로 정해진 4개의 방제목 중에서 한가지의 제목이 랜덤으로 출력되고 사용자가 변경할 수 있도록 합니다.

 3) 비밀번호 방 : 비밀번호 방을 만들기 위한 라디오 버튼을 체크했을 때만 비밀번호를 입력할 수 있도록 합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package clientPanel;
 
import javax.swing.JFrame;
 
 ...
 
public class CreateRoomFrame extends JFrame{
    public JTextField tfTitle;
    public JButton btCreate;
    public JPasswordField tfPw;
    public ClientUI ui;
    public JRadioButton rbSecret;
    public JRadioButton rbTwoUser;
    public JRadioButton rbOneUser;
 
    public CreateRoomFrame(ClientUI c) {
        setTitle("\uBC29 \uB9CC\uB4E4\uAE30");
        ui = c;
        setSize(400,300);
        setLocation(400,400);
        getContentPane().setLayout(null);
        c.setLocationRelativeTo(null);
 
        
        // 방제목 필드 ================================================
        tfTitle = new JTextField();
        tfTitle.setBounds(1827911621);
        getContentPane().add(tfTitle);
        tfTitle.setColumns(10);
        // ===========================================================        
        
        // 비밀번호 입력 필드 =========================================
        tfPw = new JPasswordField();
        tfPw.setEnabled(false);
        tfPw.setEditable(false);
        tfPw.setBounds(18311811521);
        getContentPane().add(tfPw);
        // ===========================================================
        
        // 방만들기 완료 버튼 =========================================
        btCreate = new JButton("\uBC29 \uC0DD\uC131");
        btCreate.setBounds(1401979723);
        getContentPane().add(btCreate);
        // ===========================================================
        
        
        
        // 비밀번호 방 설정 라디오 버튼 ================================
        rbSecret = new JRadioButton("\uBE44\uBC00\uBC29");
        rbSecret.setBounds(15915712123);
        getContentPane().add(rbSecret);
        rbSecret.setFocusable(false);
        // ===========================================================
        
        // 1인용 or 2인용 설정 라디오 버튼 =============================
        rbOneUser = new JRadioButton("1\uC778\uC6A9");
        rbOneUser.setBounds(182406223);
        rbOneUser.setFocusable(false);
        getContentPane().add(rbOneUser);
        
        rbTwoUser = new JRadioButton("2\uC778\uC6A9");
        rbTwoUser.setBounds(248406723);
        rbTwoUser.setSelected(true);
        rbTwoUser.setFocusable(false);
        getContentPane().add(rbTwoUser);
        
        ButtonGroup b = new ButtonGroup();
        b.add(rbOneUser);
        b.add(rbTwoUser);
        // ===========================================================
 
        // 기타 라벨 ======================================================
        JLabel label = new JLabel("\uCC38\uC5EC \uC778\uC6D0 : ");
        label.setHorizontalAlignment(SwingConstants.RIGHT);
        label.setBounds(92446715);
        getContentPane().add(label);
        
        JLabel lblNewLabel = new JLabel("\uBC29 \uC774\uB984 : ");
        lblNewLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        lblNewLabel.setBounds(102825715);
        getContentPane().add(lblNewLabel);
        
        JLabel lblNewLabel_1 = new JLabel("\uBE44\uBC00\uBC88\uD638 : ");
        lblNewLabel_1.setHorizontalAlignment(SwingConstants.RIGHT);
        lblNewLabel_1.setBounds(921216715);
        getContentPane().add(lblNewLabel_1);
        // ================================================================
        
        // 1인용 or 2인용 / 비밀번호 방 라디오 버튼이 눌렸을 때 동작 ==========
        rbTwoUser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                tfTitle.setEditable(true);
                tfPw.setEditable(false);
                rbSecret.setEnabled(true);
                String [] titles = new String[] { "페어플레이합시다.""매너게임","일단 들어오세요.","스피드 한게임"};
                tfTitle.setText(titles[(int)(Math.random()*titles.length)]);
            }
        });
        rbOneUser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                tfTitle.setText("1인용 방입니다.");
                tfTitle.setEditable(false);
                rbSecret.setEnabled(false);
            }
        });
        rbSecret.addActionListener(new ActionListener() {
            
            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                if(rbSecret.isSelected()) {
                    tfPw.setEditable(true);
                    tfPw.setEnabled(true);
                } else {
                    tfPw.setEditable(false);
                    tfPw.setEnabled(false);
                }
            }
        });
        // =================================================================
        
        // 방만들기 완료 버튼 누를 시 호출할 리스너 ==================================
        ActionListener bcrfh = new BtCreateRoomFinishHandler(this);
        btCreate.addActionListener(bcrfh);
        // ========================================================================
        
        // 비밀번호 필드 or 제목 입력 필드에서 엔터 누를 시 방만들기 버튼 자동 클릭 ====
        tfTitle.addKeyListener(new KeyListener() {
            
            @Override
            public void keyReleased(KeyEvent e) {
                // TODO Auto-generated method stub
                if(e.getKeyCode() == 10) {
                    btCreate.doClick();
                }
            }
        });
        
        tfPw.addKeyListener(new KeyListener() {
            
            @Override
            public void keyReleased(KeyEvent e) {
                // TODO Auto-generated method stub
                if(e.getKeyCode() == 10) {
                    btCreate.doClick();
                }
            }
            
        });
        // ==========================================================================
    }
}
 
cs






3. Rhythm Game : BtCreateRoomHandler.java


 위에서 생성한 CreateRoomFrame은 RoomPanel의 방만들기 버튼을 눌렀을 때 호출되는 것입니다.

때문에 일단 CreateRoomFrame을 불러오기 위해 리스너를 설계하고 해당 리스너를 버튼에 설정해줍니다.


[BtCreateRoomHandler.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package handler;
 
import java.awt.Color;
 
 ...
 
public class BtCreateRoomHandler implements ActionListener {
    ClientUI ui;
    public BtCreateRoomHandler(ClientUI c) {
        ui = c;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
 
        CreateRoomFrame r = new CreateRoomFrame(ui);
        String [] titles = new String[] { "페어플레이합시다.""매너게임","일단 들어오세요.","스피드 한게임"};
        r.tfTitle.setText(titles[(int)(Math.random()*titles.length)]);
        r.setVisible(true);
        r.setLocationRelativeTo(null);
    }
}
 
cs


[ClientUI.java]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package client;
 
import java.awt.event.ActionListener;
 
    ...
 
public class ClientUI extends JFrame{
    
    ...
 
    private void addListeners() {
        
        ...
 
        ActionListener bcrh = new BtCreateRoomHandler(this);
        pnRoom.btCreateRoom.addActionListener(bcrh);
        
        ...        
    
    }
 
    ...
}
 
cs







4. Rhythm Game : BtCreateRoomFinishHandler.java


 이제 본격적으로 CreateRoomFrame에서 방생성 완료 버튼을 눌렀을 때 동작할 기능을 설계해봅시다.

 

 클라이언트에서 서버로 방만들기를 요청할 때 비밀번호 방일 경우와 아닐 경우를 나누어 처리하는 것은 불필요한 작업이라 생각하여 동일한 메소드에 처리하도록 설계했습니다. 이때 1인용이나 2인용인데 비밀번호방이 아닐 경우에는 "" 인자값을 넘깁니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package handler;
 
import java.awt.event.ActionEvent;
 
    ...
 
public class BtCreateRoomFinishHandler implements ActionListener {
    CreateRoomFrame target;
    public BtCreateRoomFinishHandler(CreateRoomFrame c) {
        target = c;
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        
        String title = target.tfTitle.getText().trim();
        String pw = "";
            
        // 1인용 방 만들기
        if(target.rbOneUser.isSelected()) {
            target.ui.net.sendCreateRoomRequest(title, pw, false);
            target.setVisible(false);
        } else {     // 2인용 방 만들기
            // 2인용인데 비밀번호 방일 
            if(target.tfPw.isEditable() ) {
                if(String.valueOf(target.tfPw.getPassword()).equals("")) {
                    JOptionPane.showMessageDialog(target, "비밀번호를 입력해주세요.");
                    return;
                } else {
                    pw = String.valueOf(target.tfPw.getPassword()).trim();
                }
            }
            target.ui.net.sendCreateRoomRequest(title, pw, true);
            target.setVisible(false);
        }
    }
    

}
 
cs







5. Rhythm Game : ClientNetwork.java


 방 만들기를 성공했을 때 띄울 프레임은 추후에 알아보도록 하고 일단 클라이언트에서 서버로 데이터를 전송하고 처리하는 과정만 보겠습니다.

서버로 방만들기 요청을 보낼 때에는 createroom이라는 키워드를 이용해 보내게 되고 3개의 값을 더 넘겨 제목, 1인용 / 2인용, 비밀번호의 정보를 넘깁니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package client;
 
import java.io.IOException;
 
    ...
 
public class ClientNetwork extends Thread {
    
    ...
    
    // 방 만들기 
    public void sendCreateRoomRequest(String title, String pw, boolean twouser) {
        Room resp = null;
        synchronized (oos) {
            try {
                oos.writeObject("createroom#" + title + "#" + twouser + "#" + pw);
                resp = (Room) ois.readObject();;
                System.out.println(resp);
 
                if (resp == null) {
                    JOptionPane.showMessageDialog(ui, "이미 모든 방이 생성되어있습니다.");
                } else if(!twouser){
                    // 1인용 방 만들기 성공했을 때 게임 대기방에서 띄울 정보 셋팅
                } else {
                    // 2인용 방 만들기 성공했을 때 게임 대기방에서 띄울 정보 셋팅
                }
            } catch (ClassNotFoundException | IOException e) {
                System.out.println(e.toString());
            }
        }
    }
    
    ...
 
}
cs






6. Rhythm Game : PersonalServer.java


  기본적으로 방의 갯수를 최대 8개만 생성할 수 있도록 설계했습니다. 

때문에 방을 설계할 수 없는 조건은 현재 생성되어있는 방의 갯수가 8개일 때입니다. 이경우를 제외하고는 방을 생성할 수 있다는 것이죠.


한가지 더 주의할 것은 비밀번호 방일 경우입니다.

클라이언트에서 데이터를 전송할 때 비밀번호에 해당하는 정보는 맨 뒤에 보내도록 했습니다.

하지만 비밀번호가 없다면(""이라면) 서버에서 #으로 split했을 때 command[3] 값이 존재하지 않게 됩니다.

때문에 command의 길이에 따라 비밀번호 방인지 아닌지를 구분하여 방을 생성할 수 있습니다.


방이 생성되었다면 사용자가 생성한 방은 list에서 마지막에 저장된 방이기 때문에 list의 마지막 방을 클라이언트로 전송합니다.

또한 방이 만들어졌다는 것은 다른 유저들의 방 목록 창의 정보가 바뀌었다는 것이기 때문에 갱신시켜주기 위해 UDP로 갱신 요청을 전송합니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package server;
 
import java.io.IOException;
 
    ...
 
public class PersonalServer extends Thread {
    
    
    @Override
    public void run() {
        String[] command = null;
        while(socket.isConnected()) {
            
            ...
 
            switch(command[0]) {
                
                ...
 
                case "createroom":    // 방 생성
                    if(rooms.size() >=8) {
                        sendToClient(null);
                        sendToClient(0);
                    } else {
                        user.setJoinRoomIndex(rooms.size());
                        // command의 길이가 3이라면 비번방이 아니고 1인용이나 2인용
                        // command의 길이가 4라면 비번방이기 때문에 무조건 2인용
                        if(command.length == 3) {
                            if(command[2].equals("true")) {
                                rooms.add(new Room(user,command[1],true,""));
                            } else {
                                rooms.add(new Room(user,command[1],false,""));
                            }
                        } else {
                            rooms.add(new Room(user,command[1],true,command[3]));
                        }
                        resp = rooms.get(rooms.size()-1);
                        sendToClient(resp);
                        sendAlramToAll("changerooms");
                    }
                    break;
                    
                ...
 
            }
        }
    }    
}
cs






7. Rhythm Game : ClientNetwork.java


 이제 방만들기 기능까진 다 완성되었고 방만들었을 때 다른 유저에게 방의 목록을 갱신하도록 처리만 해주면 됩니다.

 덤으로 로그인 성공했을 때 방 목록 불러오기 기능까지 같이 구현한다면 로그인했을 때 방 목록을 출력할 수 있고, 실시간으로 방의 정보를 알 수 있습니다.


 방의 정보를 서버로부터 요청하여 전달받아 앞서 만든 버튼에 정보를 입력합니다. 

2인용일 경우에는 방의 인원이 가득찼을 경우 방에 입장 못하도록 해야하고, 1인용일 경우에는 아예 입장하지 못하도록 해야하기 때문에 경우에 따라 버튼을 비활성화 처리해줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package client;
 
import java.io.IOException;
 
    ...
 
public class ClientNetwork extends Thread {
    
    ...
 
    @Override
    public void run() {
        while (!ds.isClosed()) {
            DatagramPacket dp = new DatagramPacket(new byte[2048], 2048);
            try {
                
                ...
 
                switch (str[0]) {
                
                ...
 
                case "changerooms":        // 방 목록 최신화
                    sendStateRoomRequest();
                    break;
                
            } catch (IOException e) {
                System.out.println("dp failed .. " + e.toString());
                ds.close();
                break;
            }
 
        }
    }
    
    // 로그인 요청
    public void sendLoginRequest(String nick, String pass) {
        this.nick = nick;
        String resp = null;
        System.out.println("[client] request : ");
        if (nick.trim().equals(""|| pass.trim().equals("")) {
            JOptionPane.showMessageDialog(ui, "아이디와 비밀번호를 입력하세요.");
            return;
        }
        synchronized (oos) {
            try {
                oos.writeObject("join#" + nick + "#" + pass);
 
                resp = (String) ois.readObject();
                System.out.println("[client] response : " + resp);
 
                // 여기서 ui 제어.
                String[] data = resp.split("#");
                if (data[0].equals("true")) {
                    System.out.println("come");
                    ui.pnLogin.tfid.setText("");
                    ui.pnLogin.tfpw.setText("");
                    ui.setSize(800800);
                    ui.setTitle("Drop the beat!! - Wating Room");
                    ui.setLocationRelativeTo(null);
                    ui.setContentPane(ui.pnRoom);
                    ui.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
                    sendStateRoomRequest();            // 로그인했을 때 방 정보 불러오기(추가)
                } else {
                    ui.pnLogin.tfid.setText("");
                    ui.pnLogin.tfpw.setText("");
                    JOptionPane.showMessageDialog(ui, data[1]);
                }
 
            } catch (ClassNotFoundException | IOException e) {
                System.out.println("[client] network error " + e.toString());
            }
        }
    }
    
    // 방 목록 불러오기 or 갱신
    public void sendStateRoomRequest() {
        List<Room> resp = null;
        synchronized (oos) {
            try {
                oos.writeObject("roomlist");
                resp = (List<Room>) ois.readObject();
                int i = 0;
                System.out.println(resp);
                if (!resp.isEmpty()) {
                    do {
                        System.out.println("룸 !null");
                        ui.pnRoom.btList.get(i).setText("");
                        ui.pnRoom.btList.get(i).setEnabled(true);
                        ui.pnRoom.btList.get(i).setText("<html>제목 : " +resp.get(i).getTitle() + "<br/>" + "방장 : " + resp.get(i).getJoiner().get(0).getNick() + "<br/>" + "인원 : " + resp.get(i).getJoiner().size() + " / 2" + "<br/>" + "암호방 : " + (resp.get(i).getPass().equals("") ? "NO" : "YES"+ "<br/>" + "방 상태 : " + (resp.get(i).isGameStart() ? "게임 중.." : "대기 중..")+  "</html>");               
                        if(resp.get(i).getJoiner().size() == 2) {
                            ui.pnRoom.btList.get(i).setEnabled(false);
                        }
                        if(!resp.get(i).isTwoUserRoom()) {
                            ui.pnRoom.btList.get(i).setText("<html>제목 : " +resp.get(i).getTitle() + "<br/>" + "방장 : " + resp.get(i).getJoiner().get(0).getNick() + "<br/>" + "인원 : " + resp.get(i).getJoiner().size() + " / 1" + "<br/>" + "암호방 : " + (resp.get(i).getPass().equals("") ? "NO" : "YES"+ "<br/>" + "방 상태 : " + (resp.get(i).isGameStart() ? "게임 중.." : "대기 중..")+  "</html>");               
                            ui.pnRoom.btList.get(i).setEnabled(false);
                        }
                        i++;
                    } while (i < resp.size());
                }
                while (i < 8) {
                    System.out.println("룸 null");
                    ui.pnRoom.btList.get(i).setEnabled(false);
                    ui.pnRoom.btList.get(i).setText("");
                    i++;
                }
 
            } catch (ClassNotFoundException | IOException e) {
            }
        }
    }
 
    ...
 
}
cs






8. Rhythm Game : PersonalServer.java


 이제 마지막으로 서버에서 클라이언트로 방의 정보를 전송해주면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package server;
 
import java.io.IOException;
 
    ...
 
public class PersonalServer extends Thread {
    
    ...
    
    @Override
    public void run() {
        String[] command = null;
        while(socket.isConnected()) {
            
            ... 
 
            switch(command[0]) {
                
                ...
                case "roomlist":    // 방 목록 불러오기
                    sendToClient(rooms);
                    break;    
            }
        }
    }
}
cs






## 방만들기 기능에 대한 클래스 동작 흐름.

방만들기 버튼 클릭시 리스너(BtCreateRoomHandler) 호출하여 방만들기 프레임(CreateRoomFrame) 생성

 -> 방 설정 정보를 결정하고 완료 버튼 클릭 -> 리스너(BtCreateRoomFinishHandler) 호출하여 ClientNetwork를 통해 서버로 해당 정보 전송

 -> (Server) 방의 정보를 받아 방을 생성 -> (Server) UDP를 통해 방 정보 갱신 요청을 보내고 TCP로 만들어진 방 정보를 해당 클라이언트에게 전송

 -> (Client) TCP를 통해 방의 정보를 받은 클라이언트는 받은 데이터에 따라 방의 패널을 출력하고, 

    UDP를 통해 요청 받은 클라이언트들은 다시 서버에서 방 목록 요청

 -> (Server) 현재 생성되어있는 방 목록을 클라이언트에게 전송

 -> (Client) 방 목록 정보를 받아 자신의 방 목록을 갱신


'Project > Java를 이용한 게임 프로그램 구현' 카테고리의 다른 글

10. Rhythm Game : Quick Enter Room  (4) 2018.12.05
9. Rhythm Game : Enter Room  (0) 2018.12.04
7. Rhythm Game : LoginUserList  (0) 2018.12.03
6. Rhythm Game : Login  (0) 2018.11.30
5. Rhythm Game : Signup  (0) 2018.11.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함