⭐Swing을 활용해 간단한 게임을 만들어봤다.
요청사항
----- ----- -----
캐릭터가 방향키에 맞게 움직이도록 만들것
움직이는 장애물을 만들것
캐릭터와 장애물이 충돌시 캐릭터가 삭제되도록 할 것
🤔기억해야 할 내용
쓰레드, While문을 활용해
사용자가 조작하지 않아도 스스로 움직이는 장애물을 설계할 수 있다.
1. 클래스 생성
JFrame 상속
KeyListener 구현
package _game;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class GameFrame extends JFrame implements KeyListener {
2. 변수 선언
이미지를 움직이기 위해
BufferImage 클래스를 불러왔다.
자동으로 움직이는 장애물을 구현하기 위해
내부클래스도 변수로 선언한다.
또한 컴포넌트들의 좌표값도 변수로 설정했다.
조건문 적용을 위해 플래그 값도 선언해둔다.
//변수
private BufferedImage backgroundImage;
private BufferedImage player1;
private BufferedImage player2;
private ImagePanel imagePanel;
private int playerX = 200;
private int playerY = 300;
private int player2X = 100;
private int player2Y = 300;
private boolean flag = true;
3. 생성자
생성자는 가장 먼저 실행되는 구문이다.
start() 구문으로 내부클래스 안에 만들어둔 run() 구문이 수행되도록 한다.
//생성자
public GameFrame() {
initDate();
setInitLayout();
addEventListener();
//메인작업자가 서브작업자를 생성
Thread thread1 = new Thread(imagePanel);
thread1.start();
//imagePanel 안에 구현한 run() 메서드가 시작된다.
}
4. 메서드 - initData
생성자 호출시 실행
각 요소들을 생성한다.
ImageIO.read(new File(주소값))
은 주소값이 틀리면 런타임 오류를 발생시킨다.
try-catch 구문으로 예외처리한다.
//메서드
private void initDate() {
setSize(1000, 600);
setDefaultCloseOperation(3);
setResizable(false);
try {
backgroundImage = ImageIO.read(new File("images/background.png"));
player1 = ImageIO.read(new File("images/player1.png"));
player2 = ImageIO.read(new File("images/player2.png"));
//TODO player 이미지도 올려야 함
} catch (IOException e) {
e.printStackTrace();
}
imagePanel = new ImagePanel();
}//initDate
4-1. 메서드 setInitLayout
역시 생성자 호출시 실행
디자인을 정의한다.
setLayout을 지정하지 않았으므로
기본배치관리자인 BorderLayout이 적용됐다.
setLayout(new BorderLayout());
이런식으로 직접 넣어줘도 된다.
private void setInitLayout() {
add(imagePanel);
setVisible(true);
}//setInitLayout
4-2. 메서드 addEventListener
역시 생성자 호출시 실행
프로그램이 키 값을 인지하도록 만든다.
private void addEventListener() {
/*
keyListener는 인터페이스다.
자바 문법에서는
인터페이스나 추상클래스를
구현클래스, 즉 객체로 사용하는 문법을 제공한다.
new KeyListener() {
추상 메서드를 구현 메서드로 오버라이드}
*/
addKeyListener(this);
/*
JFrame 자체에
KeyEventListener를 등록한다.
*/
}//addEventListener
5. 인터페이스의 추상메서드 오버라이드
키가 눌려질 때
KeyPressed
메서드에서
정해진 키값(방향키)을 인식하면
캐릭터의 좌표값이 변화하도록 설계했다.
KeyEvent.VK_UP
은 위쪽방향키의 주소값을 가리킨다.
변한 값이 화면에 나타나도록
키를 입력할때마다
repaint() 가 호출되도록 한다.
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}//keyReleased
@Override
public void keyPressed(KeyEvent e) {
System.out.println("키 코드: " + e.getKeyCode());
//TODO 화살표만 추출해 낼 예정
int code = e.getKeyCode();
if (code == KeyEvent.VK_UP) {
playerY -= 10;
} else if (code == KeyEvent.VK_DOWN) {
playerY += 10;
} else if (code == KeyEvent.VK_LEFT) {
playerX -= 10;
} else if (code == KeyEvent.VK_RIGHT) {
playerX += 10;
}
//리페인트
repaint();
}//keyPressed
6.내부 클래스
JPanel 상속
Runnable 구현
내부클래스를 만들고
여기에 서브작업자가 할 일을 명시했다.
//내부클래스
private class ImagePanel extends JPanel implements Runnable {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backgroundImage, 0, 0, 1000, 600, null);
g.drawImage(player1, playerX, playerY, 50, 50, null);
g.drawImage(player2, player2X, player2Y, 50, 50, null);
}
@Override
public void run() {
//서브작업자가 해야하는 일을 명시하도록 약속돼 있다.
boolean direction = false;
//direction true 오른쪽, false 왼쪽
while (flag) {
if (direction == true) {
player2X += 5;
} else {
player2X -= 5;
}
if (player2X >= 800) {
direction = false;
}
if (player2X <= 100) {
direction = true;
}
if (Math.abs(playerX - player2X) < 25 && Math.abs(playerY - player2Y) < 25) {
player1 = null;
System.out.println(playerX+ "," + playerY);
System.out.println(player2X + "," + player2Y);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
repaint();
}//end of while
}//run
}//end of inner class
7. 메인함수
//메인
public static void main(String[] args) {
new GameFrame();
}//end of main
}//end of outer class
예제2)
부활버튼을 달아서 캐릭터가 죽어도 계속 살아날수 있게 해봤다.
바꿀 점
JButton을 추가
구조
===== ===== ===== ===== ===== =====
JFrame 상속/ KeyListener 구현
//변수
BufferImage (3), 내부클래스, 좌표변수, 플래그
//생성자 (3 + Thread)
//메서드 (3)
//오버라이드
KeyPressed
//내부클래스
JPanel 상속/ Runnable 구현
//내부클래스 오버라이드
paintComponent
run (While)
//메인
===== ===== ===== ===== ===== =====
JButton을 추가했을 뿐인데 KeyListener가 동작하지 않는 문제가 발생했다.
해결방법.
initData() 메서드에
setFocusable(true);
requestFocusInWindow();
구문을 넣어 포커스를 되돌렸다.
부활 뒤에도
requestFocusInWindow();
구문을 넣어 포커스를 되돌렸다.
챗지피티에 따르면
KeyListener가 아닌 KeyBinding을 사용하면
포커스 문제를 원천차단할 수 있다.
package _my;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
/**
* 구조
* =====
* JFrame 상속/ KeyListener 구현
* <p>
* //변수
* BufferImage (3), 내부클래스, 좌표변수, 플래그
* <p>
* //생성자 (3 + Thread)
* <p>
* //메서드 (3)
* <p>
* //오버라이드
* KeyPressed
* <p>
* //내부클래스
* JPanel 상속/ Runnable 구현
* <p>
* //내부클래스 오버라이드
* paintComponent
* run (While)
* <p>
* //메인
*/
public class Image_Game extends JFrame implements KeyListener, ActionListener {
//멤버변수
private JButton button;
private BufferedImage backImg;
private BufferedImage player1Img;
private BufferedImage player2Img;
private ImagePanel imagePanel;
private int player1X = 400;
private int player1Y = 50;
private int player2X = 300;
private int player2Y = 400;
private boolean flag = true;
//생성자
public Image_Game() {
initData();
setInitLayout();
addEventListener();
addActionListener();
Thread thread1 = new Thread(imagePanel);
thread1.start();
}
//메서드
private void initData() {
setSize(1000, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
button = new JButton("부활버튼");
try {
backImg = ImageIO.read(new File("images/background.png"));
player1Img = ImageIO.read(new File("images/player1.png"));
player2Img = ImageIO.read(new File("images/player2.png"));
} catch (IOException e) {
e.printStackTrace();
}
imagePanel = new ImagePanel();
}//initData
private void setInitLayout() {
add(button, BorderLayout.NORTH);
add(imagePanel);
setVisible(true);
setFocusable(true);
requestFocusInWindow();
}//setInitLayout
private void addEventListener() {
addKeyListener(this);
}//addEventListener
private void addActionListener() {
button.addActionListener(this);
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("키코드:" + e.getKeyCode());
int keyNo = e.getKeyCode();
if (keyNo == KeyEvent.VK_UP) {
player1Y -= 10;
} else if (keyNo == KeyEvent.VK_DOWN) {
player1Y += 10;
} else if (keyNo == KeyEvent.VK_LEFT) {
player1X -= 10;
} else if (keyNo == KeyEvent.VK_RIGHT) {
player1X += 10;
}
repaint();
}//keyPressed
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void actionPerformed(ActionEvent e) {
player1X = 250; player1Y = 250;
try {
player1Img = ImageIO.read(new File("images/player1.png"));
requestFocusInWindow();
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
//내부클래스
private class ImagePanel extends JPanel implements Runnable {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backImg, 0, 0, 1000, 600, null);
g.drawImage(player1Img, player1X, player1Y, 100, 100, null);
g.drawImage(player2Img, player2X, player2Y, 100, 100, null);
}
@Override
public void run() {
boolean direct = true;
while (flag) {
if (direct == true) {
player2X += 5;
} else {
player2X -= 5;
}
if (player2X >= 800) {
direct = false;
}
if (player2X <= 100) {
direct = true;
}
if ((Math.abs(player1X - player2X) <= 25) && (Math.abs(player1Y - player2Y) <= 25)) {
System.out.println("사망지점:"+player1X +","+player1Y);
player1Img = null;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
repaint();
}//while
}//run
}//inner
//메인
public static void main(String[] args) {
new Image_Game();
}//end of main
}//end of GF
예제2-2) 챗지티피가 제안한 KeyBinding 방식의 코드
package _my;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
public class Image_Game2 extends JFrame implements ActionListener {
//멤버변수
private JButton button;
private BufferedImage backImg;
private BufferedImage player1Img;
private BufferedImage player2Img;
private ImagePanel imagePanel;
private int player1X = 400;
private int player1Y = 50;
private int player2X = 300;
private int player2Y = 400;
private boolean flag = true;
//생성자
public Image_Game2() {
initData();
setInitLayout();
addActionListener();
initKeyBindings();
Thread thread1 = new Thread(imagePanel);
thread1.start();
}
//메서드
private void initData() {
setSize(1000, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
button = new JButton("부활버튼");
try {
backImg = ImageIO.read(new File("images/background.png"));
player1Img = ImageIO.read(new File("images/player1.png"));
player2Img = ImageIO.read(new File("images/player2.png"));
} catch (IOException e) {
e.printStackTrace();
}
imagePanel = new ImagePanel();
}//initData
private void setInitLayout() {
add(button, BorderLayout.NORTH);
add(imagePanel);
setVisible(true);
}//setInitLayout
private void addActionListener() {
button.addActionListener(this);
}//addActionListener
private void initKeyBindings() {
InputMap inputMap = imagePanel.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
ActionMap actionMap = imagePanel.getActionMap();
inputMap.put(KeyStroke.getKeyStroke("UP"), "moveUp");
inputMap.put(KeyStroke.getKeyStroke("DOWN"), "moveDown");
inputMap.put(KeyStroke.getKeyStroke("LEFT"), "moveLeft");
inputMap.put(KeyStroke.getKeyStroke("RIGHT"), "moveRight");
actionMap.put("moveUp", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player1Y -= 10;
repaint();
}
});
actionMap.put("moveDown", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player1Y += 10;
repaint();
}
});
actionMap.put("moveLeft", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player1X -= 10;
repaint();
}
});
actionMap.put("moveRight", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
player1X += 10;
repaint();
}
});
}//initKeyBindings
@Override
public void actionPerformed(ActionEvent e) {
player1X = 250; player1Y = 250;
try {
player1Img = ImageIO.read(new File("images/player1.png"));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}//actionPerformed
//내부클래스
private class ImagePanel extends JPanel implements Runnable {
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(backImg, 0, 0, 1000, 600, null);
g.drawImage(player1Img, player1X, player1Y, 100, 100, null);
g.drawImage(player2Img, player2X, player2Y, 100, 100, null);
}//paintComponent
@Override
public void run() {
boolean direct = true;
while (flag) {
if (direct == true) {
player2X += 5;
} else {
player2X -= 5;
}
if (player2X >= 800) {
direct = false;
}
if (player2X <= 100) {
direct = true;
}
if ((Math.abs(player1X - player2X) <= 25) && (Math.abs(player1Y - player2Y) <= 25)) {
System.out.println("사망지점:"+player1X +","+player1Y);
player1Img = null;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
repaint();
}//while
}//run
}//inner
//메인
public static void main(String[] args) {
new Image_Game2();
}//end of main
}//end of GF
'Java' 카테고리의 다른 글
컬렉션 프레임워크. 배열보다 나은 데이터 정리법 1. List (0) | 2025.05.02 |
---|---|
자바 Swing 예제. 플래포머 게임 만들기 1. 설계 (0) | 2025.05.01 |
Swing 예제, 로또번호 만들기 (0) | 2025.04.30 |
Swing예제, KeyListener로 특정 키 값 인식 (0) | 2025.04.29 |
Swing예제, ActionListener로 버튼 상호작용 (0) | 2025.04.29 |