본문 바로가기
개발/Unity) 코드분석

코드분석) MegaMan-Unity-8Bit(메가멘 8비트)

by 테샤르 2021. 8. 16.

MegaMan-Unity-8Bit(메가멘 8비트)

8 비트 메가맨을 Unity로 개발한 오픈 프로젝트이다.

사운드부터 조작 및 옛날 레트로 감성이 물씬 나는 메가맨 게임이다.

선택(enter), 이동키, 점프(z), 공격(x)을 통한 조작이 가능하다.

 

 

프로젝트 링크 주소 : [링크]

 

<플레이 영상>

 

------------------------------------

분석 내용

 

<Input>

<Menu_Ctrl Cutscene>

을통해서 Cutscene에 대한 스토리에 대한 컨트롤을 따로 처리하고.

Animator를 통해서 Intro에 대한 모든 화면에 대한 애니메이션을 상태에 따라 처리가 되어서 매끄럽게 처리를 하고 있다.

 

게임 메뉴 화면은 다음과 같다.

Shop(아이템 구매) , 스테이지 선택 , 세이브/ 로드화 화면으로 구성되어있다.

코드 스타일

 

Tile map 으로 맵을 만들고 메인 카메라가 캐릭터를 기준으로 이동하고 특정 순간 (화면이동)에만 추가적으로 이동 처리가 되도록 CameraCtrl 이라는 스크립트에서 처리가 된다.

 

<특이한 기능>

특이하게 Componet 를 메뉴로 많은 스크립트가 있고 따로 컬러에 대한 스트라이트가 따로 존재한다.

 

<Item>을 로드하는 스크립트. 적 Enemy를 죽일경우에 드랍하는 아이템에 대한 생성 및 

  public static GameObject GetObjectFromItem(Items item)
    {
        // Returns the appropriate Item GameObject based on the Enum Item.
        switch (item)
        {
            default:
            case Items.empty:
                return null;
            case Items.smallHealth:
                return (GameObject)Resources.Load("Prefabs/Items/HealthSmall", typeof(GameObject));
            case Items.bigHealth:
                return (GameObject)Resources.Load("Prefabs/Items/HealthBig", typeof(GameObject));
            case Items.smallEnergy:
                return (GameObject)Resources.Load("Prefabs/Items/WeaponSmall", typeof(GameObject));
            case Items.bigEnergy:
                return (GameObject)Resources.Load("Prefabs/Items/WeaponBig", typeof(GameObject));
            case Items.smallGear:
                return (GameObject)Resources.Load("Prefabs/Items/GearSmall", typeof(GameObject));
            case Items.bigGear:
                return (GameObject)Resources.Load("Prefabs/Items/GearBig", typeof(GameObject));
            case Items.boltSmall:
                return (GameObject)Resources.Load("Prefabs/Items/BoltSmall", typeof(GameObject));
            case Items.boltBig:
                return (GameObject)Resources.Load("Prefabs/Items/BoltBig", typeof(GameObject));
            case Items.boltHuge:
                return (GameObject)Resources.Load("Prefabs/Items/BoltHuge", typeof(GameObject));
            case Items.OneUp:
                return (GameObject)Resources.Load("Prefabs/Items/OneUp", typeof(GameObject));
            case Items.ETank:
                return (GameObject)Resources.Load("Prefabs/Items/Tank-E", typeof(GameObject));
            case Items.WTank:
                return (GameObject)Resources.Load("Prefabs/Items/Tank-W", typeof(GameObject));
            case Items.MTank:
                return (GameObject)Resources.Load("Prefabs/Items/Tank-M", typeof(GameObject));
            case Items.LTank:
                return (GameObject)Resources.Load("Prefabs/Items/Tank-L", typeof(GameObject));
            case Items.DoubleGearChip:
                return (GameObject)Resources.Load("Prefabs/Items/Double Gear Chip", typeof(GameObject));
        }
    }

 

<Boss Pattern> 보스들의 패턴에 대해서는 해당 액션에 대한 동작과 공격을 수치로 다른 동작을 처리하도록 처리되어 있다.

public IEnumerator Behavior()
    {
        // Pharaoh Man only uses two moves in specific patterns. He jumps and he shoots.
        yield return Jump(true, true, 1, 1);
        while (true)
        {
            int roll = Random.Range(0, 4);
            switch (roll)
            {
                case 0:
                    float startRightDir = (GameManager.playerPosition.x - transform.position.x);
                    yield return Jump(false, true, 2, 4);
                    yield return Jump((GameManager.playerPosition.x - transform.position.x) * startRightDir < 0, false, 2, 2);
                    yield return new WaitForSeconds(0.4f);
                    yield return Jump(false, true, 1, 4);
                    yield return Charge();
                    yield return Jump(true, false, 1, 1);
                    break;
                case 1:
                    yield return Charge();
                    yield return Jump(false, false, 2, 4);
                    yield return Charge();
                    yield return Jump(true, true, 2, 2);
                    yield return Charge();
                    break;
                case 2:
                    yield return Jump(false, true, 1, 1);
                    yield return Charge();
                    yield return Jump(false, false, 1, 1);
                    yield return Jump(false, true, 2, 2);
                    yield return Charge();
                    break;
                case 3:
                    yield return Jump(false, false, 3, 4);
                    yield return Charge();
                    yield return Jump(true, true, 1, 1);
                    break;
            }
            yield return Jump(true, false, 1, 1);

            yield return new WaitForSeconds(0.4f);
        }
    }

블루 프린트라고 하는 카메라 화면에 벗어났을때 다시 리스폰 처리되도록 특정위치를 맵에 기록해두고 Enemy 리스폰 처리를 진행한다.

 

Save / Load 에 대한 데이터도 처리가 되어있다.

특이하게 GUI.Label로 처리가 되어있다.

   private void GUIDataStageSelect()
    {
        if (!cameraInRoom)
            return;

        Vector2 cmrBase = new Vector2(Camera.main.rect.x * Screen.width, Camera.main.rect.y * Screen.height);
        float pixelSize = (Camera.main.pixelWidth / 272f);
        font.label.fontSize = (int)(pixelSize * 8);

        for (int i = 0; i < 10; i++)
        {
            string text = "Slot " + (i + 1).ToString();
            if (i == dataIndex.y)
                text = "> " + text;
            GUI.Label(new Rect(cmrBase.x + dataSelectorOrigin.x * pixelSize,
                               cmrBase.y + dataSelectorOrigin.y * pixelSize + i * 16f * pixelSize,
                               64f * pixelSize,
                               16 * pixelSize),
                      text,
                      font.label);
        }

        GUI.Label(new Rect(cmrBase.x + dataSelectorOrigin.x * pixelSize + 94f * pixelSize,
                   cmrBase.y + dataSelectorOrigin.y * pixelSize,
                   128f * pixelSize,
                   240f * pixelSize),
                   dataDesc,
                   font.label);
    }

 <Save / Load에 대한건 데이터를 1000101 이런형태로 string을 연결해서 처리되어 있다.(보스 클리어 여부)

결국 룰에 의거한 순서대로 데이터를 읽고 쓰는 처리라고 생각하면 된다.)

/// <summary>
    /// This saves the current game data. Simplified to be more intuitive.
    /// Writing an external file and reading from it is really common when it comes to coding,
    /// so you can find plenty of info online if you've got questions.
    /// They can also be edited by the player with a text editor as they are right now.
    /// </summary>
    /// <returns></returns>
    public static string SaveData(int position)
    {
        //  Writes the data as a string.
        string data = "";
        data += bossDead_BombMan ? "1" : "0";
        data += bossDead_MetalMan ? "1" : "0";
        data += bossDead_GeminiMan ? "1" : "0";
        data += bossDead_PharaohMan ? "1" : "0";
        data += bossDead_StarMan ? "1" : "0";
        data += bossDead_WindMan ? "1" : "0";
        data += bossDead_GalaxyMan ? "1" : "0";
        data += bossDead_CommandoMan ? "1" : "0";

        data += maxFortressStage.ToString("00");

        data += recItemsOwned[(int)RecoveryItems.ETank].ToString("00");
        data += recItemsOwned[(int)RecoveryItems.WTank].ToString("00");
        data += recItemsOwned[(int)RecoveryItems.MTank].ToString("00");
        data += recItemsOwned[(int)RecoveryItems.LTank].ToString("00");
        data += recItemsOwned[(int)RecoveryItems.RedBullTank].ToString("00");
        data += recItemsOwned[(int)RecoveryItems.Yashichi].ToString("00");

        data += bolts.ToString("0000");

        // Creates a new file.
        string path = Application.dataPath + "/GameSaveData/";
        string fileName = "SaveData_" + position.ToString("00");
        Debug.Log(path);

        // Creates a folder, if one doesn't already exist.
        System.IO.Directory.CreateDirectory(path);

        System.IO.StreamWriter sw = System.IO.File.CreateText(path + fileName);
        using (sw)
        {
            sw.Write(data);
        }
        sw.Close();

        return data;
    }
    public static string LoadData(int position,  bool load)
    {
        string path = Application.dataPath + "/GameSaveData/";
        string fileName = "SaveData_" + position.ToString("00");
        Debug.Log(path);

        if (!System.IO.File.Exists(path + fileName))
            return null;

        System.IO.StreamReader sr = System.IO.File.OpenText(path + fileName);

        string s = "";
        using (sr)
        {
            if ((s = sr.ReadLine()) == null)
            {
                sr.Close();
                return null;
            }

            if (!load)
            {
                sr.Close();
                return s;
            }

            int output = 0;
            int.TryParse(s[0].ToString(), out output);
            bossDead_BombMan = output == 1;
            int.TryParse(s[1].ToString(), out output);
            bossDead_MetalMan = output == 1;
            int.TryParse(s[2].ToString(), out output);
            bossDead_GeminiMan = output == 1;
            int.TryParse(s[3].ToString(), out output);
            bossDead_PharaohMan = output == 1;
            int.TryParse(s[4].ToString(), out output);
            bossDead_StarMan = output == 1;
            int.TryParse(s[5].ToString(), out output);
            bossDead_WindMan = output == 1;
            int.TryParse(s[6].ToString(), out output);
            bossDead_GalaxyMan = output == 1;
            int.TryParse(s[7].ToString(), out output);
            bossDead_CommandoMan = output == 1;
            int.TryParse(s.Substring(8, 2), out output);
            maxFortressStage = output;
            int.TryParse(s.Substring(10, 2), out output);
            recItemsOwned[(int)RecoveryItems.ETank]= output;
            int.TryParse(s.Substring(12, 2), out output);
            recItemsOwned[(int)RecoveryItems.WTank] = output;
            int.TryParse(s.Substring(14, 2), out output);
            recItemsOwned[(int)RecoveryItems.MTank] = output;
            int.TryParse(s.Substring(16, 2), out output);
            recItemsOwned[(int)RecoveryItems.LTank] = output;
            int.TryParse(s.Substring(18, 2), out output);
            recItemsOwned[(int)RecoveryItems.RedBullTank] = output;
            int.TryParse(s.Substring(20, 2), out output);
            recItemsOwned[(int)RecoveryItems.Yashichi] = output;

            int.TryParse(s.Substring(22, 4), out output);
            bolts = output;

        }

        sr.Close();
        return s;
    }

 

-----------------------------------

총평 

 

8비트의 사운드와 메가멘의 향수를 느낄수 있는 프로젝트여서 신선하고 분석할만 했다.

그리고 몬스터들의 AI 로직은 특정 리스폰 영역과 패턴으로 구성되어있다.

맵은 Tile Map으로 구성되어있고 기본기에 탄탄하게 메뉴구성도 레트로 감성적이어서 너무 좋았다.

 

[Unity -Top Paid Package]

[Unity -Top Free Package]

[Unity -New Asset Package]

 

 

 

★★

 

반응형

댓글