토이 프로젝트/Spring&Java 갖고놀기

자바 리듬 게임 만들기(4)

TerianP 2022. 3. 23.
728x90

이번 글은 4번째 Note, KeyListener 클래스에 대한 부분입니다.

1. 구현 기능, 목표 및 Main, Dynamic 클래스

2. Game 클래스

3. Track, Music, Beat클래스

4. Note, KeyListener 클래스

 


1. Note

  • note 클래스는 곡의 Note 들에 대한 정보와 노트 판정 정보를 저장하는 클래스로 다른 클래스들과 마찬가지로 Thread 를 상속받는다.
  • 생성자 Note 메소드에서는 게임에서의 키보드 위치에따라 note의 x 좌표가 어디에 위치해야하는지를 알려준다.
  • screenDraw 는 많이 보아왔던 메소드로 그래픽적 요소를 그려내기 위한 메소드이다. drawImage 메소드를 사용해 어떤 그림을 어떤 위치에 그리고 싶은지 정해준다.
  • drop 메소드는 노트를 떨어뜨리는 메소드이다. 노트를 떨어뜨린다 라는 행위의 의미는 노트의 y 좌표가 일정한 양만큼 지속적으로 증가한다 라는 것을 의미한다. 
    • 이에따라서 drop 메소드가 실행될 때마다 y+=Main.Note_SPEED 를 해준 것이다.
    • 만약 y 좌표가 620 을 넘어간다면, 즉 노트 판정 위치를 넘어간다면 Close 를 통해 proceeded = false 로 바뀌고, 해당 노트 스레드를 지워주게 된다. 
  • run 메소드는 Thread 를 실행시키는 메소드로 drop 메소드를 지속적을 실행시킨다. 이때 proceeded 가 true 인 동안에만 노트를 떨어지고, false 인 경우 스레드가 종료된다.
    • 이때 노트가 미친듯이 떨어지지않게 하기 위해서 Thread.sleep 를 통해 노트가 정해진 Main.SLEEP_TIME 만큼 딜레이를 주면서 떨어지게 된다.
  • judge 메소드에서는 버튼을 눌렀을 때의 y 좌표에 따라서 해당 판정을 String 타입으로 리턴한다. 이때 판정된 노트의 스레드는 종료된다.
    • 여기서 y 좌표를 조절해주면 판정이 좀더 여유로워지거나 빡빡해지거나 하는듯 하다.
package DynamicMusic;

import javax.swing.*;
import java.awt.*;

// 각각의 떨어지는 노트 모두 Thread

    /*
        1. 노트 판정 과정
        노트판정은 Queue 형식으로 구현한다. 즉 first in first out 형식.
        이는 당연히도 먼저 출현 - 들어온 - 노트가 가장 먼저 판정이 되어야하기 때문이다.
        큐는 특별한 코드로 만들기보다는 ArrayList 로 구현하면 된다.
    */


public class Note extends Thread{
    private Image noteBasicImage = new ImageIcon(getClass().getResource("/menu_images/noteBasic_new.jpg")).getImage();

    // 현재 노트 위치 확인을 위한 x, y 좌표
    // 이 중 y 값의 시작위치를 특정값으로 고정으로 해두어서
    // 노트가 생성된 지 1초 뒤 노트 판정위치인 580에 도달 할 수 있도록 함
    private int x;
    private int y = 580 - 1000 / Main.SLEEP_TIME * Main.NOTE_SPEED;

    private String noteType;

    // 현재 노트의 진행 여부 -> 즉 노트가 판정이 필요한 범위를 넘어가는지 확인할 수 있또록
    private boolean proceeded = true;

    // 노트 판정 -> 현재 노트 타입 - 키보드 어떤 버튼인지 - 반환
    public String getNoteType(){
        return noteType;
    }

    public boolean isProceeded(){
        return proceeded;
    }

    // 노트를 더이상 사용할 필요가 없다면 false
   public void close(){
        proceeded = false;
    }


    public Note(String noteType){
        if(noteType.equals("A")){
            x = 228;
        }else if(noteType.equals("S")){
            x = 332;
        }else if(noteType.equals("D")){
            x = 436;
        }else if(noteType.equals("Space")){
            x = 540;
        }else if(noteType.equals("J")){
            x = 744;
        }else if(noteType.equals("K")){
            x = 848;
        }else if(noteType.equals("L")){
            x = 952;
        }
        this.noteType = noteType;
    }

    // 노트가 내려오는 그래픽을 그리기 위한 screenDraw
    public void screenDraw(Graphics2D g){

        // 짧은 노트라면 현재 x, y 좌표에 노트를 그려냄
        // 노트 타입 - 키 - 에 따라서 다른 그래픽 그림을 그려냄 출력함
        if(!noteType.equals("Space")){
            g.drawImage(noteBasicImage, x, y, null);

            // 긴 노트라면 노트 이미지 2개를 그려냄, 이때 겹치면 안되니까 한쪽은 x+100
        }else{
            g.drawImage(noteBasicImage, x, y, null);
            g.drawImage(noteBasicImage, x+100, y, null);
        }
    }

    // 노트가 떨어지도록 만드는 함수
    public void drop(){
        // 노트가 떨어진다 -> 노트가 아래로 움직인다 -> y 축으로 일정한 좌표만큼 움직인다
        y += Main.NOTE_SPEED;
        if(y>620){ // y 좌표가 620 이상이라면 Miss 판정
//            System.out.println("Miss");
            close();
        }
    }

    // 스레드 실행 함수
   @Override
    public void run(){
        try{
            // 노트 떨어지는것은 무한반복
            // 1초에 Main.NOTE_SPEED * 100 정도만큼 움직임
            while(true) {
                    drop();
                if (proceeded == true) {
                    // 떨어질때 Main.Sleep_Time 에 설정된 시간만큼 딜레이를 주면서 떨어짐
                    // 현재 노트가 계속해서 움직이고 있다면 반복적으로 내려옴
                    // 해당 노트에대한 작업처리가 끝나면 proceeded 가 false 로 변경
                    Thread.sleep(Main.SLEEP_TIME);
                }else{
                    // 노트에 대한 전반적 작업 - 판정, 입력 등- 끝나서
                    // 더이상 해당 노트가 필요없어지면 스레드 종료(interrupt)
                    // proceeded = false
                    interrupt();
                    break;
                }
            }
        }catch (Exception e){
            System.err.println(e.getMessage());
        }
    }

    public String judge() {
        if(y >= 613){
//            System.out.println("Late");
            close();
            return "Late";

        }else if(y >= 600){
//            System.out.println("Good");
            close();
            return "Good";

        }else if(y>= 587){
//            System.out.println("Great");
            close();
            return "Great";

        }else if(y>= 573){
//            System.out.println("Perfect");
            close();
            return "Perfect";

        }else if(y>= 565){
//            System.out.println("Great");
            close();
            return "Great";

        }else if(y >= 550){
//            System.out.println("Good");
            close();
            return "Good";

        }else if(y>= 535){
//            System.out.println("Early");
            close();
            return "Early";

        }else{
            // 범위 안에서 안누르고 막 눌렀을 경우 miss
            return "Miss";
        }

    }

    public int getY(){
        return y;
    }

}

2. KeyListener

  • 지금까지 보았던 모든 클래스와 메소드 중에서 가장 최고로 간단한 클래스로 말 그대로 내가 키보드를 눌렀을 때 실행할 이벤트를 정의하는 클래스이다.
  • KeyAdapter 을 상속받았으며, 각각 버튼을 누를때 -  keyPressed - 와 버튼을 뗄 때 - key released - 를 정의한다.
  • 만약 게임이 실행되고 있지 않은 상태인 game = null 인 경우라면 아래 if ~ else 문을 실행하지 않고 그냥 return 한다. 
package DynamicMusic;


import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

// 키보드 이벤트 감지
public class KeyListener extends KeyAdapter {

    @Override
    public void keyPressed(KeyEvent e) {
        // e.getKeyCode 는 사용자가 누르는 키
        // KeyEvent.VK_@ 컴퓨터가 갖는 키
        // static 으로 설정된 game 이 null 값을 갖는다면 => 어떤 게임도 실행되고 있지 않는다면
        // return 해서 아래 키보드 이벤트 작업 실행되지 않도록 함
        if(DynamicMusic.game == null){
            return;
        }
        if(e.getKeyCode() == KeyEvent.VK_A){
            DynamicMusic.game.pressA();
        }
        else if(e.getKeyCode() == KeyEvent.VK_S){
            DynamicMusic.game.pressS();
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            DynamicMusic.game.pressD();
        }
        else if(e.getKeyCode() == KeyEvent.VK_SPACE){
            DynamicMusic.game.pressSpace();

        }
        else if(e.getKeyCode() == KeyEvent.VK_J){
            DynamicMusic.game.pressJ();
        }
        else if(e.getKeyCode() == KeyEvent.VK_K){
            DynamicMusic.game.pressK();
        }
        else if(e.getKeyCode() == KeyEvent.VK_L){
            DynamicMusic.game.pressL();
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
        
        if(DynamicMusic.game == null){
            return;
        }
        
        // 여기서 사용되는 e.getKeyCode() 는 키보드의 아스키 코드값을 가져옴
        // KeyEvent.VK_% 도 출력해보면 해당 글자, 숫자의 아스키 코드값이 출력됨
        if(e.getKeyCode() == KeyEvent.VK_A){
            DynamicMusic.game.releaseA();
        }
        else if(e.getKeyCode() == KeyEvent.VK_S){
            DynamicMusic.game.releaseS();
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            DynamicMusic.game.releaseD();
        }
        else if(e.getKeyCode() == KeyEvent.VK_SPACE){
            DynamicMusic.game.releaseSpace();
        }
        else if(e.getKeyCode() == KeyEvent.VK_J){
            DynamicMusic.game.releaseJ();
        }
        else if(e.getKeyCode() == KeyEvent.VK_K){
            DynamicMusic.game.releaseK();
        }
        else if(e.getKeyCode() == KeyEvent.VK_L){
            DynamicMusic.game.releaseL();
        }
    }
}

댓글