프로그래밍공부/C#기초

C# 프로그램 예제연습 - 텍스트게임만들기 - 06

Roslyn 2024. 1. 15. 15:54
반응형

오늘은 몹들을 등장시키기 위한 작업을 할 겁니다.

제 구상은 이렇습니다.

일전에 배운 배열에 몹들을 랜덤하게 담을 겁니다.

총 20마리의 몹을 배열에 담아서 배열에 있는 모든 몹을 물리치면 보스를 만나고, 보스를 이기면 끝나는 형식이 될 겁니다.

그런데 각기 다른 몹들을 배열안에 일관되게 담고, 동일한 행위를 호출하기 위해서는 이들에게 동일한 기능이 있다는 것을 프로그램에게 알려줘야 합니다.

우리는 그걸 인터페이스(interface)라고 부릅니다.

 

인터페이스는 규칙이에요!

예를 들어, 교실에서 학생들은 선생님이 정한 규칙을 따라야 해요.
그 규칙에는 일어서기, 문을 열고 닫기, 친구에게 인사하기 등이 있을 거에요. 이렇게 학생들이 지켜야 하는 규칙이 바로 '인터페이스'라고 생각할 수 있어요.
컴퓨터 프로그램에서도 클래스(학생)가 특정한 규칙(인터페이스)을 지켜야 해요.
예를 들어, '소리를 낼 수 있는 기능'이라는 규칙이 있다면, 여러 가지 클래스(강아지, 차, 비행기 등)가 이 규칙을 따라야 해요.

 

그럼 우리가 게임상에서 몹이 할 수 있는 행위를 생각해 봅시다.

크게 생각하면 두가지 입니다.

하나는 때리는 것이고, 다른 하나는 맞는 겁니다.

이 두가지 행위를 인터페이스로 구현해 보겠습니다.

 

먼저 프로젝트에 Entities폴더를 만들었듯이 이번에는 인터페이스 폴더를 하나 만들겁니다.

 

 

프로젝트에서 마우스 우클릭, 컨텍스트 메뉴에 [추가] 버튼 위로 마우스를 올리면 [새폴더] 메뉴가 있을 겁니다.

선택해서 "Abstracts" 라고 입력해 주세요.

Abstracts는 추상 클래스라는 건데, 지금은 알 필요 없고, 그냥 인터페이스 또는 그와 유사한 것들을 모아놓은 폴더라고 생각하시면 됩니다.

다음으로 해당 폴더에서 마우스 우클릭하여 [추가]버튼에서 클래스를 선택합니다.

 

파일명은 IMonsterAction.cs 라고 입력해 주세요.

그런데 파일명 앞에 I 가 뭘까요? 이런걸 프리픽스(prefix)라고 부른답니다.

 

"I"는 무엇을 하는지 알려주는 마법의 글자에요!

I와 같이 어떤 이름 앞에 짧은 약어를 붙여서, 이름만으로 그 파일이 어떤 역할을 하는지 알게 해주는 것을 프리픽스라고 해요.
I는 interface의 앞글자인 i를 붙임으로써, 이 파일이 인터페이스 임을 알게 해줘요.
그래서 굳이 파일을 열어보지 않아도, 탐색기에서 본 파일 이름만으로도 이 파일이 인터페이스 파일이란걸 알게 되는거죠.

 

이제 만든 파일에 두가지 기능이 있다는 것을 선언해 볼께요.

 

internal interface IMonsterAction
{
    Damage GetDamage();
    void SetDamage(Damage damage);
    string GetName();
    bool IsStatus();
    int GetHealth();
}

 

위에서 언급한 두가지 기능 외에, 세개의 기능이 더 있는데요.

이건 몬스터가 기본적으로 가지고 있는 속성을 반환하기 위한 함수입니다.

이름을 반환하는 GetName()함수와 상태(IsDead)를 반환하는 IsStatus() 함수, 피통을 반환하는 GetHealth() 함수가 작성되었습니다.

 

 

자, 이렇게 해서 IMonsterAction 에는 두개의 행위가 선언이 됐습니다.

이제 이 interface를 모든 몬스터에 적용해 보겠습니다.

interface는 상속과 마찬가지로 클래스 이름 옆에 콜론(:) 옆에 작성해 주면 되는데, 이미 상속되거나 구현된 인터페이스가 있을 경우, 콤마(,)로 구분해주면 되요.

 

컨트롤키(CTRL) + 마침표(.)를 눌렀을 때 화면

 

 

이렇게 먼저 상속된 클래스 옆에 콤마(,)를 넣고 IMonsterAction 인터페이스를 지정해주자, 노란색 글자로 표시됩니다.

그런데 밑에 빨간 줄이 그어지며 오류가 뜨는데요, 이럴 때 컨트롤키(CTRL) + 마침표(.)를 눌러주면, 인텔리센스 컨텍스트 메뉴가 뜹니다.

여기서 "인터페이스 구현" 버튼을 클릭해 주세요.

그럼 다음과 같이 코드가 구현됐을 겁니다.

 

internal class Mummy : Monster, IMonsterAction
{
    public Mummy()
    {
        this.Name = "미이라";
        this.IsDead = false;
        this.Health = 120;
        this.Attack = 10;
        this.Guard = 2;
    }

    public Damage GetDamage()
    {
        throw new NotImplementedException();
    }

    public void SetDamage(Damage damage)
    {
        throw new NotImplementedException();
    }
    
    public string GetName()
    {
        return this.Name;
    }

    public bool IsStatus()
    {
        return this.IsDead;
    }
    
    public int GetHealth()
    {
        return this.Health;
    }
}

 

 

모든 몬스터는 하나의 스킬을 가지고 있기로 했죠.

그렇기 때문에 몬스터는 항상 스킬공격 또는 일반공격 두가지 중 하나를 선택하게 됩니다.

저는 스킬 공격과 일반 공격을 다음과 같이 구현했습니다.

 

public Damage SkillAtack()
{
    Damage damage = new Damage();
    damage.Attack = this.Attack * 2;
    damage.Chance = 10;
    return damage;
}

public Damage NormalAttack()
{
    Damage damage = new Damage();
    damage.Attack = this.Attack;
    damage.Chance = 100;
    return damage;
}

 

미이라의 스킬 공격은 공격력 2배에 확률 10%의 공격입니다.

이제 공격 스킬함수인 GetDamage에서는 이 두개의 스킬중 하나를 랜덤(난수)하게 부르면 됩니다.

 

public Damage GetDamage()
{
    Random random = new Random();
    int randomNumber = random.Next(2);
    if (randomNumber == 0)
    {
        return SkillAtack();
    }
    else
    {
        return NormalAttack();
    }
}

 

이것이 공격함수 입니다.

Random 클래스는 닷넷이 기본적으로 제공하는 함수입니다.

사용법은 다음과 같습니다.

Random random = new Random();
int randomNumber = random.Next(2);

 

랜덤, 즉 난수란 정해진 범위안에서 어떤 수가 나올지 예측할 수 없는, 임의의 수를 뽑아오는 걸 말합니다.

 

이렇게 하면 randomNumber에는 1과 2라는 숫자 사이에 하나가 랜덤하게 들어가게 됩니다.

Random 함수는 이번 텍스트 게임에서 가장 많이 사용하게 될 함수입니다.

 

이번에는 공격을 받았을 때 상황을 구현해 보겠습니다.

public void SetDamage(Damage damage)
{
    Random random = new Random();
    int randomNumber = random.Next(100);
    if (randomNumber <= damage.Chance)
    {
        this.Health = this.Health - (damage.Attack - this.Guard);
        if (this.Health < 0)
        {
            this.IsDead = true;
            this.Health = 0;
        }
    }
    else
    {
        Console.WriteLine("공격이 빗나갔습니다.");
    }
}

 

마찬가지로 Random 클래스를 이용해서 숫자 1부터 100까지중 랜덤한 수를 뽑아 옵니다.

이 수가 damage 인스턴스에 담겨져 있는 확률(Chance)보다 작거나 같다면 공격이 적중한 겁니다.

그럼 방어력을 뺀 만큼의 피해를 피통(Health)에서 감소시키고, 피통이 0 이하가 되면 사망(IsDead) 처리를 합니다.

 

이렇게 해서 미이라 클래스는 다음과 같이 완성되었습니다.

** 재미를 위해 상황상황마다 메시지를 출력하도록 임의의 메시지를 넣었어요.  각자 취향대로 메시지를 넣어보세요. **

 

using ConsoleExample.Game01.Abstracts;

namespace ConsoleExample.Game01.Entities
{
    internal class Mummy : Monster, IMonsterAction
    {
        public Mummy()
        {
            this.Name = "미이라";
            this.IsDead = false;
            this.Health = 120;
            this.Attack = 10;
            this.Guard = 2;
        }

        public string GetName()
        {
            return this.Name;
        }

        public bool IsStatus()
        {
            return this.IsDead;
        }

        public int GetHealth()
        {
            return this.Health;
        }

        public Damage SkillAtack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack * 2;
            damage.Chance = 10;
            return damage;
        }

        public Damage NormalAttack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack;
            damage.Chance = 100;
            return damage;
        }

        public Damage GetDamage()
        {
            Random random = new Random();
            int randomNumber = random.Next(2);
            if (randomNumber == 0)
            {
                Console.WriteLine("미이라의 다중 붕대공격이다!");
                return SkillAtack();
            }
            else
            {
                Console.WriteLine("미이라가 공격합니다!");
                return NormalAttack();
            }
        }

        public void SetDamage(Damage damage)
        {
            Random random = new Random();
            int randomNumber = random.Next(100);
            if (randomNumber <= damage.Chance)
            {
                Console.WriteLine("적중했습니다!");
                this.Health = this.Health - (damage.Attack - this.Guard);
                if (this.Health <= 0)
                {
                    Console.WriteLine("몬스터가 소멸됩니다.");
                    this.IsDead = true;
                    this.Health = 0;
                }
            }
            else
            {
                Console.WriteLine("공격이 빗나갔습니다.");
            }
        }
    }
}

 

이제 마찬가지로 스켈레톤과 좀비 클래스도 완성해 봅시다.

 

using ConsoleExample.Game01.Abstracts;

namespace ConsoleExample.Game01.Entities
{
    internal class Skeleton : Monster, IMonsterAction
    {
        public Skeleton() 
        {
            this.Name = "스켈레톤";
            this.IsDead = false;
            this.Health = 80;
            this.Attack = 16;
            this.Guard = 0;
        }

        public string GetName()
        {
            return this.Name;
        }


        public bool IsStatus()
        {
            return this.IsDead;
        }

        public int GetHealth()
        {
            return this.Health;
        }

        public Damage SkillAtack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack * 3;
            damage.Chance = 5;
            return damage;
        }

        public Damage NormalAttack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack;
            damage.Chance = 100;
            return damage;
        }

        public Damage GetDamage()
        {
            Random random = new Random();
            int randomNumber = random.Next(2);
            if (randomNumber == 0)
            {
                Console.WriteLine("스켈레톤의 죽음의 일격이다!");
                return SkillAtack();
            }
            else
            {
                Console.WriteLine("스켈레톤이 공격합니다!");
                return NormalAttack();
            }
        }

        public void SetDamage(Damage damage)
        {
            Random random = new Random();
            int randomNumber = random.Next(100);
            if (randomNumber <= damage.Chance)
            {
                Console.WriteLine("적중했습니다!");
                this.Health = this.Health - (damage.Attack - this.Guard);
                if (this.Health <= 0)
                {
                    Console.WriteLine("몬스터가 소멸됩니다.");
                    this.IsDead = true;
                    this.Health = 0;
                }
            }
            else
            {
                Console.WriteLine("공격이 빗나갔습니다.");
            }
        }
    }
}

 

using ConsoleExample.Game01.Abstracts;

namespace ConsoleExample.Game01.Entities
{
    internal class Zombie : Monster, IMonsterAction
    {
        public Zombie()
        {
            this.Name = "좀비";
            this.IsDead = false;
            this.Health = 50;
            this.Attack = 8;
            this.Guard = 1;
        }

        public string GetName()
        {
            return this.Name;
        }


        public bool IsStatus()
        {
            return this.IsDead;
        }

        public int GetHealth()
        {
            return this.Health;
        }

        public Damage SkillAtack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack + 5;
            damage.Chance = 20;
            return damage;
        }

        public Damage NormalAttack()
        {
            Damage damage = new Damage();
            damage.Attack = this.Attack;
            damage.Chance = 100;
            return damage;
        }

        public Damage GetDamage()
        {
            Random random = new Random();
            int randomNumber = random.Next(2);
            if (randomNumber == 0)
            {
                Console.WriteLine("좀비의 물어뜯기 공격이다!");
                return SkillAtack();
            }
            else
            {
                Console.WriteLine("좀비가 공격합니다!");
                return NormalAttack();
            }
        }

        public void SetDamage(Damage damage)
        {
            Random random = new Random();
            int randomNumber = random.Next(100);
            if (randomNumber <= damage.Chance)
            {
                Console.WriteLine("적중했습니다!");
                this.Health = this.Health - (damage.Attack - this.Guard);
                if (this.Health <= 0)
                {
                    Console.WriteLine("몬스터가 소멸됩니다.");
                    this.IsDead = true;
                    this.Health = 0;
                }
            }
            else
            {
                Console.WriteLine("공격이 빗나갔습니다.");
            }
        }
    }
}

 

 

 

반응형