Project Review/Unity

[Unity] 2D Shooting

김조성준 2025. 5. 14. 14:39

1. Link

깃허브 주소 : https://github.com/kimchosungjun/Toy_2D_Shooting.git

 

플레이 영상 : https://youtube.com/shorts/aCYT8Qj98GM?feature=share

 

- YouTube

 

www.youtube.com

 

 

 

2. Issue

- Limiter

- Ranking

- Boss Pattern

- Infinite Scrolling

 

3. 설명

1) Limiter

 

플레이어와 적 그리고 아이템이 화면 밖으로 나가지 않게 경계선을 제한하는 기능을 구현했습니다.

초기에 Viewport의 최대, 최소 지점(경계선)을 지정하고 이를 World 좌표로 변환하였습니다.   

 //ViewPort의 경계선을 World 좌표로 변환했습니다.
 
  [SerializeField] Vector2 viewPortLimitMin;
  [SerializeField] Vector2 viewPortLimitMax;

    private void initWorldPos()
    {
        worldPosLimitMin = cam.ViewportToWorldPoint(viewPortLimitMin);
        worldPosLimitMax = cam.ViewportToWorldPoint(viewPortLimitMax);
    }

 

 

캐릭터가 화면 밖으로 나가지 못하도록 현재 위치가 최대, 최소 Viewport 지점을 넘어서지 못하게 하였습니다.

public Vector3 checkMovePosition(Vector3 _pos, bool _isBoss = false)
    {
        Vector3 viewPortPos = cam.WorldToViewportPoint(_pos);


        if (viewPortPos.x < (_isBoss == false ? viewPortLimitMin.x : viewPortLimitMinBoss.x))//0~1
        {
            viewPortPos.x = (_isBoss == false ? viewPortLimitMin.x : viewPortLimitMinBoss.x);
        }
        else if (viewPortPos.x > (_isBoss == false ? viewPortLimitMax.x : viewPortLimitMaxBoss.x))
        {
            viewPortPos.x = (_isBoss == false ? viewPortLimitMax.x : viewPortLimitMaxBoss.x);
        }

        if (viewPortPos.y < viewPortLimitMin.y)
        {
            viewPortPos.y = viewPortLimitMin.y;
        }
        else if (viewPortPos.y > viewPortLimitMax.y)
        {
            viewPortPos.y = viewPortLimitMax.y;
        }

        return cam.ViewportToWorldPoint(viewPortPos);
    }

 

 

 

아이템의 경우, x와 y좌표 중 어느 방향이 경계에 닿았는지를 Tuple로 반환하였습니다.

    public (bool _x, bool _y) IsReflectItem(Vector3 _pos, Vector3 _dir)
    {
        bool rX = false;
        bool rY = false;

        if ((_pos.x < worldPosLimitMin.x && _dir.x < 0) || (_pos.x > worldPosLimitMax.x && _dir.x > 0))
        {
            rX = true;
        }

        if ((_pos.y < worldPosLimitMin.y && _dir.y < 0) || (_pos.y > worldPosLimitMax.y && _dir.y > 0))
        {
            rY = true;
        }

        return (rX, rY);
    }

 

 

 

2) Ranking

 

PlayerPrefers를 활용해 레지스터에 플레이어의 이름과 점수를 담은 클래스를 저장하고, 이를 불러오는 랭킹 시스템을 만들었습니다. 아래는 랭킹 키를 통해 저장된 랭킹 정보를 불러오는 스크립트 입니다.

private void initRankView()
    {
        List<cUserData> listUserData = null;
        clearRankView();
        if (PlayerPrefs.HasKey(Tool.rankKey) == true)
        {
            listUserData = JsonConvert.DeserializeObject<List<cUserData>>(PlayerPrefs.GetString(Tool.rankKey));
        }
        else
        {
            listUserData = new List<cUserData>();
            int rankCount = Tool.rankCount;
            for (int iNum = 0; iNum < rankCount; ++iNum)
            {
                listUserData.Add(new cUserData() { Name = "None", Score = 0 });
            }

            string value = JsonConvert.SerializeObject(listUserData);
            PlayerPrefs.SetString(Tool.rankKey, value);
        }

        int count = listUserData.Count;
        for (int iNum = 0; iNum < count; ++iNum)
        {
            cUserData data = listUserData[iNum];

            GameObject go = Instantiate(fabRank, contents);
            FabRanking goSc = go.GetComponent<FabRanking>();
            goSc.SetData((iNum + 1).ToString(), data.Name, data.Score);
        }
    }

 

 

 

3) Boss Pattern

 

 

보스는 3가지의 패턴을 가지도록 구상하였고, 한 패턴에 돌입했다면 중복해서 실행되지 않게 구현했습니다.

    protected override void shooting()
    {
     	// 중복 방지
        if (isMovingTrsBossPosition == false)
        {
            return;
        }

        timer += Time.deltaTime;

        if (patternChange == true)
        {
            if (timer >= 1.0f)
            {
                timer = 0.0f;
                patternChange = false;
            }
            return;
        }
        
        // 패턴 실행
        if (curPattern == 1)// First Pattern
        {
            if (timer >= pattern1Reload)
            {
                timer = 0.0f;
                shootStraight();
                if (curPatternShootCount >= pattern1Count)
                {
                    curPattern++;
                    patternChange = true;
                    curPatternShootCount = 0;
                }
            }
        }
        else if{ .. }
        else { .. }
	}

 

 

 

 

4) Infinite Scrolling

 

무한 스크롤링을 만드는 방법으로 두 스프라이트를 붙이고 이동시키는 방법을 주로 사용했었는데, Unlit/Transparent Shader를 가진 Material 사용해서 구현할 수 있다는 것을 알고 이를 이용하여 구현했습니다.

Material Offset의 값을 반복시켜 무한스크롤링을 구현했습니다.

    Material matTop;
    Material matMid;
    Material matBot;
    
    public void InfiniteMap()
    {
        Vector2 vecTop = matTop.mainTextureOffset;
        Vector2 vecMid = matMid.mainTextureOffset;
        Vector2 vecBot = matBot.mainTextureOffset;

        vecTop += new Vector2(0, speedTop * Time.deltaTime);
        vecMid += new Vector2(0, speedMid * Time.deltaTime);
        vecBot += new Vector2(0, speedBot * Time.deltaTime);

        vecTop.y = Mathf.Repeat(vecTop.y, 1.0f);
        vecMid.y = Mathf.Repeat(vecMid.y, 1.0f);
        vecBot.y = Mathf.Repeat(vecBot.y, 1.0f);

        matTop.mainTextureOffset = vecTop;
        matMid.mainTextureOffset = vecMid;
        matBot.mainTextureOffset = vecBot;
    }
댓글수0