Java (2) - 슈팅 콘솔 게임 🔫

Jae Chan·2022년 11월 16일
1

JAVA

목록 보기
1/4

슈팅 콘솔 게임 🔫

Source Code (GitHub)

JAVA 를 이용하여 콘솔 슈팅 게임을 제작한다.
구현 요구사항은 총 4가지 이다.

  • 적 HP 표시
  • Player 는 총 2명이며 태그 기능이 필요하다.
    (이는, 철권 태그의 태그 기능 또는 포켓몬 게임에서의 교체 기능이라 판단했다 !)

  • 무기별 탄약 수 제한
  • 무기별 발사개수가 다르며 발사개수 만큼 적 HP가 감소한다.
  • 무기를 바꾸면 탄약은 다시 채워져야 한다.

Flow 🌊

슈팅 콘솔 게임 프로그램의 플로우이다. 총 두 가지로 표현했다.
Class메인 메소드 내에서의 플로우이다.

Class 플로우

먼저 클래스간의 플로우는 다음과 같다.

각 클래스의 기능을 간단하게 설명하자면,

  • Enemy 클래스는 콘솔 게임의 적(보스) 을 담당한다. 역할은 Player 에게 공격받는 역할이다.
  • Player 클래스는 2개 이다. 슈퍼 클래스인 Player 1과 서브 클래스 Player 2 로 총 두개의 클래스를 갖고 있다.
    Player 클래스는 콘솔 게임의 플레이어를 담당한다. 역할은 Weapon을 장착해 Enemy를 공격한다.
  • Weapon 클래스는 총 4개 이다. 슈퍼 클래스 1개와 서브 클래스 3개로 구성 돼있다.
    각각의 공통적인 역할은 Player의 공격을 도와주는 역할이다. 각 클래스마다 다른 점은
    탄약 수,적에게 입히는 데미지이다.
  • Menu 클래스는 콘솔 게임의 가독성을 높여주는 역할이다. 출력문으로 구성 되어있다.

메인 메소드 내부 플로우

다음은 메인 메소드에서의 프로그램 플로우다.

클래스 별 기능 및 소스코드

패키지 이름 : GunGame
클래스 : Enemy , Game , Menu , Player , Weapon
메소드 개수 : 8개

package GunGame;

public class Menu {
    void MainMenu() {
        System.out.println("======== 슈팅 게임 ========");
        System.out.println("::  [1] 게임 시작    ");
        System.out.println("::  [2] 게임 종료    ");
        System.out.println("===========================");
        System.out.print(" 숫자[1 - 2] 를 입력 해주세요.  >>> ");
    }

    void BattleMenu() {
        System.out.println("========== 전투 메뉴 ===========");
        System.out.println("::  [0]Shoot !               ");
        System.out.println("::  [1]권총        [2]리볼버    "); 
        System.out.println("::  [3]자동 소총    [4]핸드 캐넌   ");
        System.out.println("::  [5]플레이어 교체         ");            
        System.out.println("==============================");
        System.out.print("숫자[1 - 5] 를 입력 해주세요.  >>> ");
    }
}

Menu 클래스는 메인 메뉴에서의 전반적인 콘솔 출력문의 담당해주는 클래스이다.

  • MainMenu() 메소드는 프로그램 최초 컴파일 시 나타나는 메소드이다.
  • BattleMenu() 메소드는 플레이어가 전투 돌입 시 나타나는 메소드이다.

Player

package GunGame;

public class Player {
    Weapon myGun;
    public String pName = "Player 1";

    public String ShowUserName() {
        return pName;
    }

    public Player() {
        myGun = new Weapon();
    }

    int Attack() {
        return myGun.Shoot();
    }
}

class Player2 extends Player {
    public Player2() {
        super.pName = "Player 2";
    }
}

Player 클래스는 이 프로그램에서의 플레이어를 담당하는 클래스이다.

  • 내부에는 총 두 개의 클래스가 있다. Player 슈퍼 클래스Player2 서브 클래스가 있다.
  • 두 개의 클래스 모두 Weapon 클래스 객체가 존재한다.
  • ShowUserName() 메소드는 현재 플레이어 이름을 보여주는 메소드이다.
  • 메인 메소드에서 플레이어를 생성할 때마다 생성자에서는 무기를 새로 만들어준다.
  • Attack() 메소드는 플레이어가 장착한 무기로 적에게 공격을 한다. 여기서 내가 태그(교체)기능을 표현하고자 한 것은 Player2 서브 클래스에서 Player2를 생성 할 때 플레이어 이름을 부모 클래스로의 인스턴스 변수를 참조super()하여 Player 2로 변경 했다.
public Player2() {
        super.pName = "Player 2";
    }

Weapon

package GunGame;

public class Weapon {
    int ammo = 60;
    int damage = 10;
    String weaponName = "권총";

    public Weapon() {
        System.out.println("\"" + weaponName + "\" 을(를) 장착하셨습니다 !");
    }

    public Weapon(int ammo, int dmg) {
        this.ammo = ammo;
        this.damage = dmg;
    }

    public void ChangeName(String mine) {
        this.weaponName = mine;
        System.out.println("\"" + weaponName + "\" 을(를) 장착하셨습니다 !");
    }

    protected int Shoot() {
        System.out.println("빵 야 - ! !");
        System.out.println("현재 무기 : " + weaponName);
        ammo -= damage;
        if (ammo >= 0) {
            System.out.println("적에게 준 데미지 : \"" + damage +"\" Hit !");
            System.out.println("현재 남은 탄약은 \"" + ammo + "\" 이에요.");
            return damage;
        } else {
            System.out.println("탄약이 부족해요 ! !");
            System.out.println("무기를 바꿔 재장전 해주세요.");
        }
        return 0;
    }
}

class Revolver extends Weapon {
    String weaponName = "리볼버";

    public Revolver(int ammo, int damage) {
        super(ammo, damage);
        super.weaponName = weaponName;
        ChangeName(weaponName);
    }
}

class Rifle extends Weapon {
    String weaponName = "자동 소총";

    public Rifle(int ammo, int damage) {
        super(ammo, damage);
        super.weaponName = weaponName;
        ChangeName(weaponName);
    }
}

class Canon extends Weapon {
    String weaponName = "핸드 캐넌";

    public Canon(int ammo, int damage) {
        super(ammo, damage);
        super.weaponName = weaponName;
        ChangeName(weaponName);
    }
}

Weapon 클래스는 Player가 적에게 공격을 도와주는 역할을 하는 클래스이다.
클래스 내의 메소드는 총 2개이다.

  • 슈퍼 클래스의 생성자는 기본적으로 2개이다. 2개로 구분 지은 이유는 다음과 같다.
    첫번 째 생성자의 존재 이유는 프로그램 내에서 기본 무기(Weapon) 선택 시에는

     int ammo = 60;
      int damage = 10;
      String weaponName = "권총";
    
      public Weapon() {
          System.out.println("\"" + weaponName + "\" 을(를) 장착하셨습니다 !");
      }

    위의 코드들이 적용되어 총알은 60발, 데미지는 10발로 선택되게 하기 위함이고

    // 슈퍼 클래스 내 생성자
        public Weapon(int ammo, int dmg) {
          this.ammo = ammo;
          this.damage = dmg;
      }
      
      class Revolver extends Weapon {
      String weaponName = "리볼버";
    
      public Revolver(int ammo, int damage) {
          super(ammo, damage);
          super.weaponName = weaponName;
          ChangeName(weaponName);
      }
    }

    두 번째 생성자는 나머지 서브클래스(총 3개)들을 super() 를 이용하여 슈퍼 클래스에 생성자에 접근해 각각 서브 클래스들의 총알과 데미지를 메인 메소드내에서 서브 클래스 호출 시 쉽게 바꾸기 위해서였다.

public void ChangeName(String mine) {
        this.weaponName = mine;
        System.out.println("\"" + weaponName + "\" 을(를) 장착하셨습니다 !");
    }

해당 메소드는 기본 무기 클래스를 제외한 모든 무기 클래스에서 생성 될 때 마다 무기가 바뀌었다는 텍스트를 출력하는 메소드이다.

다음은 Weapon 클래스에서 중요한 적에게 데미지를 주는 Shoot() 메소드 표현방식이다.

    protected int Shoot() {
        System.out.println("빵 야 - ! !");
        System.out.println("현재 무기 : " + weaponName);
        ammo -= damage;
        if (ammo >= 0) {
            System.out.println("적에게 준 데미지 : \"" + damage +"\" Hit !");
            System.out.println("현재 남은 탄약은 \"" + ammo + "\" 이에요.");
            return damage;
        } else {
            System.out.println("탄약이 부족해요 ! !");
            System.out.println("무기를 바꿔 재장전 해주세요.");
        }
        return 0;
    }

해당 메소드의 로직은 다음과 같다.

  • Shoot() 메소드가 호출 될 때마다 클래스 내 데미지(damage) 값 만큼 총알(ammo)이 깎이도록 했다.
  • 총알이 0보다 클 경우에만 damage 값을 리턴하여 적에게 데미지를 줬다.
  • 총알이 0보다 작으면 0을 리턴하여 데미지를 못 주도록 설정했다.

Enemy

package GunGame;

public class Enemy {
    int enemyHP;

    public Enemy() {
        // 적 체력 500-1000 난수 생성
        enemyHP = (int)(Math.random()*(1000-500)) + 500;
    }

    void EnemyStatus() {
        System.out.println("보스의 현재 체력은 \"" + enemyHP + "\" 이에요!");
    }
    
    void EnemyClearText() {
        System.out.println("★★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        System.out.println("★★    보스 퇴치 성공 !    ★★");
        System.out.println("☆★★★★★★★★★★★★★★★★★★★★★★★★★★★");
        System.out.println("메인 메뉴로 돌아 갑니다..");
    }
}

Enemy 클래스는 이 프로그램에서 적(보스)를 담당하는 클래스이다.

  • 메인 메소드에서 Enemy 클래스가 호출 될 때마다 생성자 내부에서 적의 체력을 500-1000 사이로 랜덤하게 나오도록 했다.
  • EnemyStatus() 메소드는 현재 적의 체력상태를 텍스트로 출력해주는 메소드이다.
  • EnemyClearText() 는 적을 정상적으로 처치했을 때 텍스트를 출력해주는 메소드이다.

Game(Main Method)

package GunGame;

import java.util.InputMismatchException;
import java.util.Scanner;

public class Game {

    // 메인 메소드
    public static void main(String args[]) {
        Menu menu = new Menu();
        Enemy boss = new Enemy();
        Scanner sc = new Scanner(System.in);
        int inputNum = 0;

        try {
            while (true) {
                menu.MainMenu();
                inputNum = sc.nextInt();
                sc.nextLine();
                if (inputNum == 1) {
                    System.out.println("*         - 게 임 스 타 트 -          *");
                    Player p1 = new Player();
                    System.out.println("\"" + p1.ShowUserName() + "\" : 연결 완료 !");                    
                    boss.EnemyStatus();
                    while (true) {
                        if (boss.enemyHP > 0) {
                            int selNum;
                            menu.BattleMenu();
                            selNum = sc.nextInt();
                            sc.nextLine();
                            switch (selNum) {
                                case 0:                            
                                    boss.enemyHP -= p1.Attack();
                                    boss.EnemyStatus();                                
                                    break;
                                case 1: // 기본 무기 장착 및 재장전
                                    p1.myGun = new Weapon();
                                    break;
                                case 2: // 리볼버 장착 및 재장전(탄약 : 120 , 데미지 30) 총 4 번 슈팅 가능
                                    p1.myGun = new Revolver(120, 30);
                                    break;
                                case 3: // 라이플 장착 및 재장전(탄약 : 200 , 데미지 50) 총 4 번 슈팅 가능
                                    p1.myGun = new Rifle(200, 50);
                                    break;
                                case 4: // 핸드캐넌 장착 및 재장전(탄약 : 450 , 데미지 150) 총 3 번 슈팅 가능
                                    p1.myGun = new Canon(450, 150);
                                    break;
                                case 5:
                                    if (p1 instanceof Player2) {
                                        p1 = new Player();
                                        System.out.println("\"" + p1.ShowUserName() + "\" 로 변경!");
                                        break;
                                    }
                                    if (p1 instanceof Player) {
                                        p1 = new Player2();
                                        System.out.println("\"" + p1.ShowUserName() + "\" 로 변경!");
                                        break;
                                    }
                                default:
                                    System.out.println("숫자 [1-5] 만 입력해주세요.");
                                    break;
                            }
                        }
                        
                        else if (boss.enemyHP <= 0) { // 보스 처치 시 실행되는 부분.
                            boss.EnemyClearText();
                            boss = new Enemy(); // 보스 새로 생성
                            break; // 메인 메뉴로 복귀
                        }
                    }
                }

                else if (inputNum == 2) {
                    System.out.println("게임을 종료 합니다.");
                    break;
                }

                else
                    System.out.println("숫자 [1 - 2] 만 입력해주세요.");

            }
        } catch (InputMismatchException e) {
            System.out.println("입력값 오류 ! 숫자만 입력 해주세요.");
            System.out.println("프로그램을 종료합니다. .");
        }
    }
}

Game 클래스는 프로그램의 메인이자 실행을 담당하는 메소드이다.

  • 먼저, 사용자로부터 모든 입력값은 숫자만 입력받기 때문에 예외처리로 숫자를 제외한 모든 입력값을 받았을 때 오류 출력문을 출력하고 프로그램이 꺼지도록 했다.
catch (InputMismatchException e) {
            System.out.println("입력값 오류 ! 숫자만 입력 해주세요.");
            System.out.println("프로그램을 종료합니다. .");
        }
  • 메인 메소드 내에서 중요하게 생각하는 부분은 switch문 내부이다.
			switch (selNum) {
                                case 0:                            
                                    boss.enemyHP -= p1.Attack();
                                    boss.EnemyStatus();                                
                                    break;
                                case 1: // 기본 무기 장착 및 재장전
                                    p1.myGun = new Weapon();
                                    break;
                                case 2: // 리볼버 장착 및 재장전(탄약 : 120 , 데미지 30) 총 4 번 슈팅 가능
                                    p1.myGun = new Revolver(120, 30);
                                    break;
                                case 3: // 라이플 장착 및 재장전(탄약 : 200 , 데미지 50) 총 4 번 슈팅 가능
                                    p1.myGun = new Rifle(200, 50);
                                    break;
                                case 4: // 핸드캐넌 장착 및 재장전(탄약 : 450 , 데미지 150) 총 3 번 슈팅 가능
                                    p1.myGun = new Canon(450, 150);
                                    break;
                                case 5: // 플레이어 변경 기능
                                    if (p1 instanceof Player2) { // Player1가 Player2에 속할 경우 플레이어1 생성
                                        p1 = new Player();
                                        System.out.println("\"" + p1.ShowUserName() + "\" 로 변경!");
                                        break;
                                    }
                                    if (p1 instanceof Player) { // Player1가 그대로 Player에 속할 경우 플레이어2 생성
                                        p1 = new Player2();
                                        System.out.println("\"" + p1.ShowUserName() + "\" 로 변경!");
                                        break;
                                    }
  • 기본 무기 선택 시(1번 입력시)에는 슈퍼 클래스의 생성자가 생성되고, 그 외에 모든 무기는 서브 클래스이기 때문에 상속 받은 슈퍼 클래스를 참조하여 생성자에 접근 한 후 총알과 데미지를 설정 했음을 알 수 있다.

  • 5번 선택시는 instanceof를 이용하여 클래스를 확인한 후 각각의 플레이어가 특정 클래스에 속한지 체크 후 형변환하여 서로 태그(교체)해서 새로운 플레이어로 만들어줬다.

결과

프로그램 작동 시 출력 화면

아래 이미지를 클릭하여 출력 영상(Youtube)을 볼 수 있다.

Java

아래는 실행 결과다.

최초 시작 메뉴시

  • 1번 입력시 게임이 시작된다.
  • 2번 입력시 프로그램이 종료된다.

게임 시작 시

  • 사용자로부터 1번 입력을 받고 게임 시작과 동시에 Player보스(Enemy) 가 생성된다.
    Player는 기본적으로 Weapon(기본 권총)을 들고 있다.

  • 보스의 체력은 500-1000 으로 랜덤 생성된다.

  • 입력 시 행동

    • [0] 번 입력 시 보스에게 총을 쏴 데미지를 입힌다.
    • [1] 번 입력 시 기본 권총을 선택한다.
    • [2] 번 입력 시 리볼버를 선택한다.
    • [3] 번 입력 시 자동 소총을 선택한다.
    • [4] 번 입력 시 핸드 캐넌을 선택한다.
    • [5] 번 입력 시 Player 1 또는 Player 2 로 교체 된다.

여기서 총을 선택[1-4 번 입력] 할 때 마다 무기의 탄약이 자동으로 충전된다.

보스 처치 시

보스 처치 시 시작시 최초 메뉴로 돌아간다.

코드를 완성하며 느낀 점 💭

이번 콘솔 게임을 설계하고 작성하며 객체 지향에 점점 익숙해져 갔다.
슈퍼클래스 및 서브클래스의 상호작용 개념super() 의 사용법, 각 클래스의 메소드를 어디서 호출해야 코드를 간결하게 표시할 수 있을지 고민하고 또 고민했다.

메인 메소드를 더욱 간결하게 표현하고 싶었는데, 현재 내 지식으로 최대한 간결하게 짜봤다.
이 코드에서 개선해야할 부분은 상속 할 때 오버라이딩을 활용 못한 점, instanceof를 적절히 사용했는 지 피드백을 듣고 싶다. 또한 이 코드에서의 문제점이 무엇인지 다른 분들께 피드백을 받고 싶다.

1개의 댓글

comment-user-thumbnail
2024년 9월 15일

Emily had always been fascinated by games of chance, but she never truly engaged with them until a friend introduced her to a new online https://stellarspinscasino1.com/ blackjack site. With its fresh interface and promises of exciting gameplay, Emily decided to delve in. She was meticulous in her approach, studying the site’s rules and bonuses thoroughly before placing her first bet. Her aim was to balance enjoyment with strategy, a challenge she was eager to embrace.

답글 달기