본문 바로가기
C# 콘솔 & 윈도우폼

[C#] 콘솔로 슈팅게임 구현하기 2 : 갤러그

by 17번 일개미 2022. 7. 1.
728x90

의의

 

콘솔로만 구현하는 C#기반 슈팅게임 구현


개선점

 

1. 1편의 문제점은 키 입력이 존재하지 않으면, 화면이 갱신되지 않는다는 것이었다.

이 부분은 키의 입력이 존재하지 않더라도 실행이 가능하도록 Console.KeyAvailable 을 활용하여 해결하였다.

 

if (Console.KeyAvailable == true) // 키입력이 존재한다면

 

2. 키 입력 문제가 해결되었으니, 적의 움직임, 총알의 움직임, 플레이어의 움직임을

매 프레임마다 계산하여 동시에 표현할 수 있게 되었다.

프레임을 구하는 원리는 다음을 사용하였다.

 

const int waitTick = 1000 / 10;
int currentTick; int lastTick = 0;

while (true)// 반복
            {
                // 현재 시간
                currentTick = System.Environment.TickCount;
                // 경과 시간이 1 / 10 초 보다 작다면
                if (currentTick - lastTick < waitTick)
                {
                    continue; // 실행 건너뜀
                }
                else // 조건 만족 시
                {
                    lastTick = currentTick;
                    // 이곳에 실행할 함수
                }
             }

currentTick에 매 Tick을 대입하고 마지막 Tick인 lastTick에서 뺀 값이,

미리 상수로 정한 waitTick, 즉 프레임률을 넘어가면 else이하 문을 실행하게 된다.

 


결과

 

 

10프레임의 주기로 슈팅게임이 실행된다.

하지만, Console.Clear()의 호출로 화면을 지우면서 갱신하기에

프레임이 높아지면 거의 백지가 보이는 현상이 발생하고,

프레임이 더 낮아지면 플레이 속도가 너무 느리다는 단점이 존재한다.

 

전체 코드

using System;
using System.Runtime.InteropServices;
using System.Windows.Input;
using System.Threading.Tasks;
using System.Threading;


/*  - 프로그램 구조 
    
    1. 전체적인 콘솔 창을 15행 20열의 2차원 행렬로 본다.

    2. 각 원소는 char[] 형의 pixel 이라는 이름으로 정의하고, 각 원소는 기본적으로 ' ' 공백을 담고 있다.

    3. 플레이어, 적, 총알은 각 pixel의 위치에 char 형으로 삽입된다.

    4. 10프레임의 주기를 가지고 연산이 자동 발생!

    5. 적 플레이어도 같은 원리로 총알을 발사함 >> 피격 당하면 점수가 -100점

    6. 적 플레이어 총알과 내 총알이 부딪히면 둘다 사라짐

    7. 적 플레이어가 너무 한쪽 벽에 쏠리는 현상을 방지함 >> 한쪽 끝에 붙으면 반대방향으로 전환함

    8. 두 플레이어 모두 총알은 한발이 어딘가에 맞기 전까지, 한발 씩만 쏠 수 있음

 */

namespace ConsoleApp2
{
    class Shooting
    {
        static int row = 15; static int column = 20; // 표시할 행과 열의 수
        static int score = 0; // 점수 저장 변수
        static char[,] pixel = new char[row, column]; // 표시할 픽셀 2차원 배열
        public static int[] playerPos = { 13, 9 }; // 플레이어 현 위치
        public static int[] bulletPos = { playerPos[0] - 1, playerPos[1] }; // 총알의 현 위치
        public static int[] enemyPos = { 3, 10 }; // 적의 현 위치
        public static int[] enemyBulletPos = { enemyPos[0] + 1, enemyPos[1] }; // 적 총알 위치
        public static bool isAttack;
        static void Main(string[] args)
        {
            int width = 30; int height = 20; // 윈도우 창 사이즈 설정

            const int waitTick = 1000 / 10; // 10 프레임(10FPS)

            int lastTick = 0; // 마지막 틱
            int currentTick; // 현재 틱

            isAttack = false;
            InitializeWindow(width, height); // 윈도우 창 초기화
            //DrawFrame(isAttack);
            while (true)// 반복
            {
                // 현재 시간
                currentTick = System.Environment.TickCount;
                // 경과 시간이 1 / 10 초 보다 작다면
                if (currentTick - lastTick < waitTick)
                {
                    continue; // 실행 건너뜀
                }
                else // 조건 만족 시
                {
                    lastTick = currentTick;

                    if (Console.KeyAvailable == true) // 키입력이 존재한다면
                    {
                        var key = Console.ReadKey();
                        if (key.Key == ConsoleKey.RightArrow) // 우측 이동
                        {
                            playerPos[1] += 1;
                        }
                        if (key.Key == ConsoleKey.LeftArrow) // 좌측 이동
                        {
                            playerPos[1] -= 1;
                        }
                        if (key.Key == ConsoleKey.Spacebar) // 공격
                        {
                            if (isAttack == false) // 발사 중이 아니라면
                            {
                                isAttack = true; // 발사 가능
                            }
                        }
                    }
                    DrawFrame(isAttack); // 프레임 연산

                    CheckBullet(); // 적과 나의 총알 충돌 체크
                }
            }
        }
        // 윈도우 세팅
        static void InitializeWindow(int width, int height)
        {
            Console.Title = "Console Shooting"; // 제목 설정
            Console.SetWindowSize(width, height); // 창 크기 설정
            Console.BackgroundColor = ConsoleColor.White; // 배경 색 설정(흰색)
            Console.Clear(); // 클리어
            Console.CursorVisible = false;
        }
        // 화면 그리기

        public static void DrawFrame(bool isAttack) // 프레임 갱신
        {
            Console.Clear(); // 일단 전 프레임을 전부 지운다.

            MoveEnemy(); // 적 이동

            // 만약 적 또는 플레이어가 화면 사이즈 밖으로 벗어나게 된다면, 가장 자리에 위치를 Fix 시킴.
            if (enemyPos[1] <= 0) enemyPos[1] = 0; if (enemyPos[1] >= column - 1) enemyPos[1] = column - 1;
            if (playerPos[1] <= 0) playerPos[1] = 0; if (playerPos[1] >= column - 1) playerPos[1] = column - 1;

            enemyBulletPos[0] += 1; // 적 총알 이동
            if (isAttack == true) bulletPos[0] -= 1; // 플레이어 총알 이동
            else
            {
                bulletPos[0] = playerPos[0] - 1; // 총알을 쏘게되면 플레이의 위치보다 1만큼 앞라인에서 출발한다.
                bulletPos[1] = playerPos[1];    // 열의 위치는 일정함.
            }

            // 2차원 배열 순회
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < column; j++)
                {
                    if (i == 0 && j >= 0 && j <= 19)
                    {
                        pixel[i, j] = '■';
                    }
                    else if (i == 14 && j >= 0 && j <= 19)
                    {
                        pixel[i, j] = '■';
                    }
                    else if (i == bulletPos[0] && j == bulletPos[1] && isAttack == true) // 총알 위치라면
                    {
                        pixel[i, j] = '■'; // 총알 그림 대입
                    }
                    else if(i == enemyBulletPos[0] && j == enemyBulletPos[1])
                    {
                        pixel[i, j] = '■'; // 적 총알
                    }
                    // 적의 포지션이라면
                    else if (i == enemyPos[0] && j == enemyPos[1])
                    {
                        pixel[i, j] = '▼'; // 적 캐릭터 저장
                    }
                    else if (i == playerPos[0] && j == playerPos[1]) // 플레이어 라면
                    {
                        pixel[i, j] = '▲'; // 플레이어 저장
                    }
                    else // 아무것도 아니면
                    {
                        pixel[i, j] = ' '; // 공백
                    }
                }
            }
            // DarkBlue컬러로 점수판 생성
            Console.ForegroundColor = ConsoleColor.DarkBlue;
            if (score == 0)
            {
                Console.WriteLine("Score " + "000");
            }
            else
            {
                Console.WriteLine("Score " + score);
            }
            Console.ResetColor(); // 색상 초기화
            Console.BackgroundColor = ConsoleColor.White; // 배경색 흰색
            // 다시 배열 순회
            for (int i = 0; i < row; i++)
            {
                for (int j = 0; j < column; j++)
                {
                    if (i == bulletPos[0] && j == bulletPos[1] && isAttack == true) // 총알 위치라면
                    {
                        Console.ForegroundColor = ConsoleColor.Blue; // 파란색으로
                        Console.Write(pixel[i, j]);
                        Console.ResetColor();
                        Console.BackgroundColor = ConsoleColor.White;
                    }
                    else if (i == enemyBulletPos[0] && j == enemyBulletPos[1])
                    {
                        Console.ForegroundColor = ConsoleColor.DarkRed;
                        Console.Write(pixel[i, j]);
                        Console.ResetColor(); // 색상 초기화
                        Console.BackgroundColor = ConsoleColor.White; // 배경 흰색 초기화
                    }
                    else if (i == playerPos[0] && j == playerPos[1]) // 배열의 원소가 플레이어 라면
                    {
                        Console.ForegroundColor = ConsoleColor.DarkGreen; // 청록색으로
                        Console.Write(pixel[i, j]); // 플레이어 원소를 그린다.
                        Console.ResetColor(); // 색상 초기화
                        Console.BackgroundColor = ConsoleColor.White; // 배경 흰색 초기화
                    }
                    else if (i == enemyPos[0] && j == enemyPos[1]) // 배열의 원소가 적이라면
                    {
                        Console.ForegroundColor = ConsoleColor.Red; // 빨간색
                        Console.Write(pixel[i, j]);
                        Console.ResetColor();
                        Console.BackgroundColor = ConsoleColor.White;
                    }
                    else
                    {
                        Console.Write(pixel[i, j]); // 아무것도 아니면 공백 있던거 출력
                    }
                }
                Console.WriteLine(); // 한 행 끝날때마다 개행
            }

            Console.SetCursorPosition(0, 0);
        }

        static void MoveEnemy() // 적 이동
        {
            Random rand = new Random();
            int number = rand.Next(-1, 2); // -1, 0, 1 중 난수를 생성하고 적 위치에 합산한다.
            enemyPos[1] += number;  // 프레임 호출될 때마다 적은 랜덤하게 좌우로 한칸씩 이동

            if (enemyPos[1] == 19) // 너무 오른쪽이나 왼쪽으로 붙지 않게 조정
            {
                enemyPos[1] -= 1;
            }
            else if (enemyPos[0] == 0)
            {
                enemyPos[1] += 1;
            }
        }
        static void CheckBullet()
        {
            if (bulletPos[0] == enemyPos[0] && bulletPos[1] == enemyPos[1])  // 만약 총이 적에게 맞는다면
            {
                pixel[bulletPos[0], bulletPos[1]] = ' '; // 총알 사라짐
                score += 100;                           // 점수는 100점 증가
                Console.Beep();
                bulletPos[0] = playerPos[0] - 1;    // 앞으로 생성 될 총알 위치 초기화
                bulletPos[1] = playerPos[1];
                //pixel[enemyPos[0], enemyPos[1]] = '□'; // 적 사라짐
                isAttack = false;
            }
            else if (bulletPos[0] <= 1) // 만약 시야를 벗어난다면
            {
                pixel[bulletPos[0], bulletPos[1]] = ' '; // 총알 사라짐
                bulletPos[0] = playerPos[0] - 1; // 초기화
                bulletPos[1] = playerPos[1];
                isAttack = false;
            }
            else if (enemyBulletPos[0] == bulletPos[0] && enemyBulletPos[1] == bulletPos[1]) // 적 총알 == 플레이어 총알 일 때
            {
                pixel[enemyBulletPos[0], enemyBulletPos[1]] = ' ';
                enemyBulletPos[0] = enemyPos[0] + 1;
                enemyBulletPos[1] = enemyPos[1];

                pixel[bulletPos[0], bulletPos[1]] = ' '; // 총알 사라짐
                bulletPos[0] = playerPos[0] - 1; // 초기화
                bulletPos[1] = playerPos[1];
                isAttack = false;
            }
            if (enemyBulletPos[0] == playerPos[0] && enemyBulletPos[0] == playerPos[1]) // 적 총알 == 플레이어 일때
            {
                pixel[enemyBulletPos[0], enemyBulletPos[1]] = ' ';
                enemyBulletPos[0] = enemyPos[0] + 1;
                enemyBulletPos[1] = enemyPos[1];
                if(score > 0) score -= 100; // 점수 차감

            }
            else if (enemyBulletPos[0] >= 14) // 적 총알 시야 이탈
            {
                pixel[enemyBulletPos[0], enemyBulletPos[1]] = ' ';
                enemyBulletPos[0] = enemyPos[0] + 1;
                enemyBulletPos[1] = enemyPos[1];
            }
        }
    }

}
728x90