Untiy 2D 팀 프로젝트 궁수의 전설 래퍼런스 작품 만들기

 

오늘로 팀프로젝트 2일차 시차 1일차때는 조원들과 프로젝트 계획과 약속을 정하고 끝이 나서 작품 제작은 2일차 부터 시작이 되었다.

 

이 프로젝트에서 내가 담당한 파트

  • 모든 투사체 관리
  • 스킬 담당

이 두가지를 메인으로 담당을 하게 되었다.

 

그래서 오늘 궁수의 전설에서 맵이 클리어 되었을 때 스킬을 선택하는 기능을 구연해 보았다.

 

먼저 스킬의 종류를 플레이어 스탯효과, 투사체 스탯효과, 액티브 스킬, 패시브 스킬로 나누어 작성하였다.

스킬 리스트를 만들기 위해 ScriptableObject를 사용하여 Skill Data를 만들어 주었다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public enum SkillCategory
{
    Player,
    Projectile,
    Active,
    Passive
}
[CreateAssetMenu(fileName = "Skill Data", menuName = "Scriptable Object/Skill Data", order = int.MaxValue)]
public class SkillInfo : ScriptableObject
{
    [Header("Skill Info")]
    [SerializeField] private string skillname;
    public string Name { get { return skillname; } }

    [SerializeField] private SkillCategory category;
    public SkillCategory Category { get { return category; } }

    [Header("Player State")]
    [SerializeField] private int health;
    public int Health { get { return health; } }

    [SerializeField] private float moveSpeed;
    public float MoveSpeed { get { return moveSpeed; } }

    [SerializeField] private float attackFreq;
    public float AttackFreq { get { return attackFreq; } }

    [SerializeField] private float skillFreq;
    public float SkillFreq { get { return skillFreq; } }

    [SerializeField] private float drainRatio;
    public float DrainRaio { get { return drainRatio; } }

    [SerializeField] protected float knockbackResistance;
    public float KnockbackResistance { get { return knockbackResistance; } }

    [SerializeField] private int projectileNumber;
    public int ProjectileNumber { get { return projectileNumber; } }

    [SerializeField] private float shootingRange;
    public float ShootingRange { get { return shootingRange; } }

    [Header("Projectile State")]

    [SerializeField] private float damage;
    public float Damage { get { return damage; } }

    [SerializeField] private float projectileSpeed;
    public float ProjectileSpeed { get { return projectileSpeed; } }

    [SerializeField] private float size;
    public float Size { get { return size; } }

    [SerializeField] private int penetration;
    public int Penetration { get {  return penetration; } }

    [SerializeField] private int reflection;
    public int Reflection { get { return reflection; } }

    [SerializeField] private float knockbackDistance;
    public float KnockbackDistance { get { return knockbackDistance; } }



    //<액티브>
    //피버타임 (일정 시간동안 화살 발사 속도 x배 증가)
    //대시


    //<패시브 스킬>
    //마늘 - 캐릭터 일정 범위 내 근접 시 데미지
    //책 - 캐릭터 돌면서 충돌 시 데미지
    //비둘기 - 추가 투사체 발사
    //오망성 - 맵 전체 몬스터에 일정 데미지
}

이렇게 작성하면 아래 이미지처럼 Create 목록에 Scriptable Object가 새롭게 생성된다

그렇게 만들어진 Skill Data들을 관리해줄 Skill Manager를 만들어주자

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SkillManager : SingleTon<SkillManager>
{
    [SerializeField] private SkillInfo[] skillInfo; // Skill Data 담을 리스트


    [SerializeField] private List<SkillInfo> SkillList; // Skill Data 담을 리스트 복제
    public List<SkillInfo> randomSkillList = new List<SkillInfo>(); // 랜덤으로 뽑은 스킬 담을 리스트

    public void GetRandomSkill(int count)
    {
        randomSkillList.Clear();
        
        if (skillInfo == null || skillInfo.Length == 0)
        {
            Debug.LogWarning("스킬 리스트가 비어있습니다!");
            return;
        }
        SkillList = new List<SkillInfo>(skillInfo);

        while (randomSkillList.Count < count && SkillList.Count > 0)
        {
            int random = Random.Range(0, SkillList.Count);
            randomSkillList.Add(SkillList[random]);
            SkillList.RemoveAt(random);
        }
    }
}

 

 

이제 스킬들을 리스트에 담았으니 스킬을 뽑는 기능을 추가해 주자

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class SkillPicker : MonoBehaviour
{
    private SkillManager skillManager;

    private PlayerStat playerStat;
    //테스트용 UI
    public Text text1;
    public Text text2;
    public Text text3;
    public Button[] buttons;
    //테스트용 UI

    private void Awake()
    {
        playerStat = FindObjectOfType<PlayerStat>();
        skillManager = SkillManager.Instance;
    }
    private void Start()
    {

        // 버튼 배열의 길이가 3이라고 가정 (인덱스 0, 1, 2)
        for (int i = 0; i < buttons.Length; i++)
        {
            int value = i; // 현재 인덱스 값을 복사
            buttons[i].onClick.AddListener(() => SelectSkill(value));
        }

    }

    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.A))
        {
            SkillPickerList();
            

        }
    }

    public void SkillPickerList()
    {
        Debug.Log("Input A Key");
        skillManager.GetRandomSkill(3);
        //테스트용 UI
        text1.text = skillManager.randomSkillList[0].Name;
        text2.text = skillManager.randomSkillList[1].Name;
        text3.text = skillManager.randomSkillList[2].Name;
        //테스트용 UI
        for (int i = 0; i < skillManager.randomSkillList.Count; i++) // 디버그용
        {
            Debug.Log($"스킬 뽑기 : {skillManager.randomSkillList[i].Name}, {skillManager.randomSkillList[i].Category}");
        }
    }

    public void SelectSkill(int select)
    {
        Debug.Log($"뽑은 스킬 : {skillManager.randomSkillList[select].Name}, {skillManager.randomSkillList[select].Category}");
        //뽑은 스킬들 플레이어 스텟에 적용 시키기
        SkillInfo selectedSkill = skillManager.randomSkillList[select];

        switch (selectedSkill.Category)
        {
            case SkillCategory.Player:
                playerStat.Health += selectedSkill.Health;
                playerStat.MoveSpeed += selectedSkill.MoveSpeed;
                playerStat.AttackFreq += selectedSkill.AttackFreq;
                playerStat.ShootingRange += selectedSkill.ShootingRange;
                playerStat.SkillFreq += selectedSkill.SkillFreq;
                playerStat.DrainRatio += selectedSkill.DrainRaio;
                playerStat.KnockbackResistance += selectedSkill.KnockbackResistance;
                playerStat.ProjectileNumber += selectedSkill.ProjectileNumber;
                break;
            case SkillCategory.Projectile:
                //waponHandler에 적용 시키기
                break;
            case SkillCategory.Active:
                break;
            case SkillCategory.Passive:
                break;

        }
    }
}

 

 

이제 UI에 버튼을 추가해 버튼의 입력 값에 따라 리스트 인덱스 값을 부여하여 스킬을 선택해 준다

그렇게 선택된 스킬은 Skill Data에 따라 능력치를 적용해주면 궁수의 전설에서 맵을 클리어 했을때 스킬을 선택하는 기능이 완성된다.

'내일배움캠프 > Unity TeamProject' 카테고리의 다른 글

Unity 2D 팀 프로젝트 - 마지막 날  (0) 2025.02.28
Unity 2D 팀 프로젝트 - 4  (0) 2025.02.27
Unity 2D 팀 프로젝트 - 2  (0) 2025.02.25

세로읽기

 

문제 설명

문자열 my_string과 두 정수 m, c가 주어집니다. my_string을 한 줄에 m 글자씩 가로로 적었을 때 왼쪽부터 세로로 c번째 열에 적힌 글자들을 문자열로 return 하는 solution 함수를 작성해 주세요.

 

제한사항

  • my_string은 영소문자로 이루어져 있습니다.
  • 1 ≤ m ≤ my_string의 길이 ≤ 1,000
  • m은 my_string 길이의 약수로만 주어집니다.
  • 1 ≤ c ≤ m

입출력 예

my_string m c result
"ihrhbakrfpndopljhygc 4 2 "happy"
"programmers" 1 1 "programmers"

.2열에 적힌 글자를 세로로 읽으면 happy이므로 "happy"를 return 합니다.

 

using System;

public class Solution {
    public string solution(string my_string, int m, int c) {
        int rows = my_string.Length / m; // 행 개수
        char[] result = new char[rows]; // 결과를 저장할 배열
        
        for (int i = 0; i < rows; i++) {
            result[i] = my_string[i * m + (c - 1)]; // c번째 열의 문자 선택
        }

        return new string(result); // 배열을 문자열로 변환하여 반환
    }
}

my_string의 길이를 m으로 나누어 행의 개수를 구하고 구한 행의 갯수만큼 배열의 길이를 설정 해줍니다

 

반복문을 통해 c번째 열의 문자열을 선택해 result 배열에 저장합니다.

 

new string(result)를 통해 문자 배열을 문자열로 변환하여 반환합니다.

 

우리가 유니티로 개발을 하다보면 버그가 빈번히 발생하는데 그 버그를 직접 손으로 찾기는 쉽지 않다.

그래서 디버깅을 사용하면 보다 쉽게 버그를 찾을 수 있다.

 

사용방법으로 사용하고 있는 유니티 프로젝트에서 비주얼스튜디오를 이용해 스크립트를 열어줍니다 

 

 

F9를 눌러 오류가 난 부분을 Breakpoint로 설정 해줍니다. 만약 설정을 잘 못 하셨으면 다시 F9를 누르시면 해제가 됩니다.

 

Breakpoint로 멈출 부분을 설정해주 셨으면 [Unity에 연결]을 눌러 디버깅을 실행 해주시고 유니티에서 Play 버튼을 눌러 프로젝트를 실행 해주시면 유니티 프로젝트가 디버깅이 실행이 된 것입니다.

 

다시 비주얼 스튜디오로 돌아오시면 Breakpoint인해 코드 작동이 잠시 멈춰있고 밑에 코드에 어떤 데이터 값들이 들어오는지 참조하는 확인 가능합니다.

 

 

 

이제 여기서 Breakpoint를 한 기준으로 디버깅 단축키를 활용하여 버그를 찾으시면 됩니다.

 

디버깅 단축키

  • F9 : Breakpoint 설정/해제
  • F10 : Step Over :현재 줄 실행 후 다음 줄로 이동
  • F11 : Step Into : 함수나 메서드 내부로 진입
  • Shift + F11 : Step Out : 함수 내부에서 빠져나오기
  • F5 : 디버깅 시작 도는 중단된 코드 실행 재개

 

 

 

 

 

게임에서 빠질 수  없는 NPC와 대화 오늘은 이걸 간단하게 구현을 해보자


NpcTalkDate.cs 제작

 

우선 각 종족별 NPC들의 대화말을 정리해 줄 리스트들을 만들어주고

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;

public class NpcTalkData : MonoBehaviour
{
    public List<string> elflist;
    public List<string> dwarflist;
    public List<string> humanlist;
    public List<string> guidelist;


    private void Awake()
    {
        elflist = new List<string> {
    "바람은 모든 것을 알고 있지... 네가 여기에 온 이유도 말해줄 수 있을까?",
    "숲의 귓가에 속삭여 보게. 네 목소리가 나뭇잎을 흔들지 않는다면 거짓이야.",
    "시간은 우리에게 친절하지만, 인간에게는 그렇지 않지. 하지만... 넌 예외일까?",
    "이 땅의 마법이 흐려지고 있어. 그대가 손을 내밀어 주겠나?",
    "신성한 숲을 지나려는가? 네 발걸음이 가벼운지 시험해 보겠어."};

        dwarflist = new List<string> {
    "우린 바위처럼 단단하지! 하지만 술 한잔 앞에선 무너지는 법이지, 크하하하!",
    "내가 만든 도끼라면 바위도 두 동강 내지! 자, 이거 하나 사지 않겠나?",
    "엘프 녀석들은 너무 가벼워. 어휴, 그들의 맥주 맛이야말로 최악이지!",
    "일할 시간인가? 아니면 한 잔 할 시간인가? 어차피 정답은 후자지!",
    "내 조상 대대로 전해 내려오는 전설의 망치가 있다고! 근데 어디 뒀더라..." };

        humanlist = new List<string> {
    "이곳에서 여행자는 환영받지. 하지만 돈이 없다면 이야기가 좀 다를걸?",
    "왕국은 겉으로는 평온해 보이지만, 그 속은 썩어가고 있어.",
    "너 같은 모험가가 또 나타날 줄이야. 지난번 녀석은 늑대한테 잡아먹혔지.",
    "정의? 명예? 다 좋지만, 빈 주머니엔 아무 소용 없지 않나?",
    "조심하게나, 밤이 되면 거리는 더 위험해지니까."};
        guidelist = new List<string> {
    "오! 새로운 얼굴이네? 처음 왔다면 내가 좀 알려줄게!\n\n[스페이스바를 눌러 다음 대화로 넘어갈 수 있다.]",
    "저기 오른쪽에 보이는 포탈 보이지? 스페이스바를 눌러 상호작용하면 미니게임에 들어갈 수 있어! 실력을 시험해보는 거지. 꽤 재미있으니까 한 번 도전해보는 것도 나쁘지 않을 걸?  [계속]",
    "그리고 바로 옆에 있는 커다란 동상, 눈에 띄지? 스페이스바를 눌러 상호작용하면 랭킹을 확인할 수 있어. 네가 얼마나 잘했는지, 다른 사람들은 얼마나 대단한지 확인할 수 있다고!  [계속]",
    "자, 뭐부터 해볼래? 그냥 멍하니 서 있지 말고 한 번 움직여보라고! 하하!"};
    }
        


}

 


NpcInfo.cs

이제 NPC들에게 부여해줄 Type을 제작하여 각 종족에 알맞는 대화말을 NpcTalkData.cs에서 호출

using TMPro;
using UnityEngine;

public enum NpcType //NPC 종족
{
    elf,
    dwarf,
    human,
    guide
}

public class NpcInfo : MonoBehaviour
{
    [SerializeField] private NpcType type;

    NpcTalkData npcTalkData;
    UIManager uimanager;

    public TextMeshProUGUI NpcName;
    public TextMeshProUGUI NpcTalk;

    public string Name; // NPC이름은 직접 입력

    bool guideTalking = false;

    int i = 0;

    private void Start()
    {
        npcTalkData = FindAnyObjectByType<NpcTalkData>();
        uimanager = FindAnyObjectByType<UIManager>();
    }

    public void printText()
    {
        int selet = Random.Range(0, 5); // 랜덤 값을 이용해 NPC 말을 리스트에서 랜덤으로 호출
        switch (type)
        {
            case NpcType.elf:
                NpcTalk.text = npcTalkData.elflist[selet];
                break;
            case NpcType.dwarf:
                NpcTalk.text = npcTalkData.dwarflist[selet];
                break;
            case NpcType.human:
                NpcTalk.text = npcTalkData.humanlist[selet];
                break;
            case NpcType.guide:
                guideTalking = true;
                i = 0;
                break;
        }
    }

    private void Update()
    {
        if (guideTalking)
        {
            NpcTalk.text = npcTalkData.guidelist[i];
            if (Input.GetKeyDown(KeyCode.Space) && i < 4)
            {
                NpcTalk.text = npcTalkData.guidelist[i];
                if (i < 3) i += 1;
            }
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {
        if (collision.gameObject.tag == "Player") // 충돌 이벤트를 통해 플레이어와 닿으면 작동
        {
            printText();
            if (npcTalkData == null)
            NpcName.text = Name;
            uimanager.SetOnNPCTalkUI(); // 대화 UI 켜기
        }
    }

    private void OnCollisionExit2D(Collision2D collision) // 충돌에서 벗어나면 UI종료
    {
        uimanager.SetOffNPCTalkUI(); // 대화 UI 끄기
        if (guideTalking == true) guideTalking = false;
    }
}

 

 

 

호출한 대화말은 충돌 이벤트를 통해 대화를 출력해주면 이런 식으로 대화가 표시 된다

 

 

uimanager.SetOnNPCTalkUI();부분과 uimanager.SetOffNPCTalkUI();부분은 NPC들의 대화창 UI를 다른 UI들과 같이 관리 하려고 만든 스크립트 안에 있는 것이라서 저 부분을 지우고 NPC들의 대화창 UI를 담을 GameObject 코드를 추가하여  SetActive()를 이용하여 켯다가 끄면 된다.

 

 

트러블 슈팅

배경

Unity 2022.3.17f1 LTS 버전을 사용하여 플레이어 캐릭터가 특정 오브젝트에 닿았을때 OnTriggerStay2D()를 사용하여 상호작용을 개발 중이였습니다.

 

발단

플레이어에게 Rigidbody 2D와 Box Collider 2D를 넣고 특정 오브젝트에는 Box Collider 2D를 넣고 Is Trigger를 True를 적용한 상태였지만 충돌 이벤트가 작동을 하지 않는 현상이 발생했습니다.

 

전개

문제를 해결하기위해

1. Rigidbody 2D 컴포넌트 확인 : 플레이어에 Rigidbody 2D가 제대로 추가되어 있는지 점검

2. Box Collider 2D  컴포넌트 확인 : 플레이어와 특정 오브젝트에 Box Collider 2D가 제대로 추가되어 있는지 점검

3. Is Trigger 확인 : 특정 오브젝트에 Box Collider 2D의 Is Trigger가 True인지 점검

4. 충돌 이벤트 확인 : OnTriggerStay2D() 코드안에 Debug.Log를 추가해 충돌 이벤트가 제대로 작동하는지 점검

 

이 과정을 통해 충돌 이벤트가 작동하지 않는 원인으로 플레이어가 움직이지 않으면 충돌 이벤트가 작동을 하지 않는 다는 것을 파악했습니다.

 

위기

위 문제를 해결하기 위해 플레이어와 특정 오브젝트를 새롭게 만드는 방식도 해보고 컴포넌트를 새로 추가도 해보았지만 실패 했습니다 최후의 수단으로 튜터님에게 찾아가 질문을 한 결과 Rigidbody의 설정에서 Sleeping Mode가 Start Awake로 설정 되어 있었습니다.

 

절정

문제를 해결하기 위해 

1. Rigidbody의 Sleepping Mode를  Naver Sleep로 변경

2. Rigidbody의 Collision Detection를  Continuous로 변경

을 하였습니다.

 

결말

Rigidbody의 Sleepping Mode를 수정한 후 충돌 이벤트가 게속 작동을 하였습니다.

플레이어는 특정 오브젝트와 닿았을때 상호작용을 할 수 있었습니다.

이와 함께 충돌이벤트가 계속 작동이 필요할때 Rigidbody의 설정을 점검하여 추후 유사한 문제가 발생하지 않도록 설정을 하였습니다. 여기서 유의사항으로 Sleeping Mode를 never Sleep으로 하면 충돌 여부를 게속 확인 하여 메모리 사용량이 증가 하기 때문에 필요한 상황에서만 사용하는 것을 유의해야한다고 튜터님이 말씀하셨다.

 

따라오는 카메라 구현

카메라가 플레이어를 따라오게 하려면

public Transform target; //카메라가 따라갈 오브젝트
float offsetX; // 카메라 x 좌표
float offsetY; // 카메라 y 좌표

카메라가 따라갈 오브젝트의 위치와

 

스크립트가 시작하면 offsetX와 offsetY의 초기 값을 설정해주고

void Start()
{
    if (target == null)
        return;

    offsetX = transform.position.x - target.position.x;
    offsetY = transform.position.y - target.position.y;
}

 

 

실시간으로 카메라의 위치값을 바꾸어주면 

void Update()
{
    if (target == null)
        return;

    Vector3 pos = transform.position;
    pos.x = target.position.x + offsetX;
    pos.y = target.position.y + offsetY;
    transform.position = pos;
}

 

플레이어를 따라다니는 카메라가 완성이 된다.

오늘은 C# 프로그래밍 학습 과정이 어느 정도 끝이나고 Unity 학습 과정에 진입하였다.

첫 강의에서는 우리에게 친숙한 플래피 버드 게임을 만들었는데 여기서 맵 생성에 대해 내가 생각했던 알고리즘 구현 방식과 차이가 있어 작성하게 되었다

 

내가 생각했던 알고리즘 구현 방식은 플래피버드의 일정 거리 뒤에 투명 오브젝트를 만들어 플래피버드를 따라오게 하여 투명 오브젝트에 닿은 배경과 장해물들은 제거하고 새롭게 배경끝부분에 생성하는 방식으로 구현했었는데 강의는 투명 오브젝트에 닿은 배경과 장해물들의 위치를 현재 배경 끝부분에 다시 이어 주면 되는 알고리즘 이였다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BgLooper : MonoBehaviour
{
    public int numBgCount = 5;

    public int obstacleCount = 0;
    public Vector3 obstacleLastPostion = Vector3.zero;

    private void Start()
    {
        //씬에 존재하는 모든 Obstacle 객체를 찾아 배열에 저장
        Obstacle[] obstacles = GameObject.FindObjectsOfType<Obstacle>();

        // 첫 번째 장애물의 위치를 obstacleLastPostion에 저장
        obstacleLastPostion = obstacles[0].transform.position;

        // 장애물 개수를 저장
        obstacleCount = obstacles.Length;

        // 모든 장애물의 위치를 랜덤하게 설정
        for (int i = 0; i < obstacleCount; i++)
        {
            obstacleLastPostion = obstacles[i].SetRandomPlace(obstacleLastPostion, obstacleCount);
        }
    }

    private void OnTriggerEnter2D(Collider2D collision)
    {
        Debug.Log("Triggerd:" + collision.name);

        // 충돌한 오브젝트가 "BackGround" 태그를 가지고 있는지 확인
        if (collision.CompareTag("BackGround"))
        {
            // 배경 오브젝트의 가로 길이를 가져옴
            float widthOfBgObject = ((BoxCollider2D)collision).size.x;
            // 배경 오브젝트의 현재 위치를 저장
            Vector3 pos = collision.transform.position;

            // 배경을 오른쪽으로 이동시켜 배경을 반복되도록 설정
            pos.x += widthOfBgObject * numBgCount;
            collision.transform.position = pos; 
            return;
        }


        // 충돌한 오브젝트가 Obstacle 컴포넌트를 가지고 있는지 확인
        Obstacle obstacle = collision.GetComponent<Obstacle>();
        if (obstacle)
        {
            // 장애물의 위치를 랜덤하게 재배치
            obstacleLastPostion = obstacle.SetRandomPlace(obstacleLastPostion, obstacleCount);
        }
    }
}

주사위 게임 2

1부터 6까지 숫자가 적힌 주사위가 세 개 있습니다. 세 주사위를 굴렸을 때 나온 숫자를 각각 a, b, c라고 했을 때 얻는 점수는 다음과 같습니다.

세 숫자가 모두 다르다면 a + b + c 점을 얻습니다.
세 숫자 중 어느 두 숫자는 같고 나머지 다른 숫자는 다르다면 (a + b + c) × (a2 + b2 + c2 )점을 얻습니다.
세 숫자가 모두 같다면 (a + b + c) × (a2 + b2 + c2 ) × (a3 + b3 + c3 )점을 얻습니다.
세 정수 a, b, c가 매개변수로 주어질 때, 얻는 점수를 return 하는 solution 함수를 작성해 주세요.

 

제한사항

a, b, c는 1이상 6이하의 정수입니다.

 

입출력 예

a b c result
2 6 1 9
5 3 3 473
4 4 4 110592

 

풀이

using System;

public class Solution
{
    public int solution(int a, int b, int c)
    {
        int answer = 0;

        if (a != b && b != c && c != a)
        {
            answer = a + b + c;  
        }
        else if (a == b && b == c && a == c)
        {
            answer = (a + b + c) * (a * a + b * b + c * c) * (a * a * a + b * b * b + c * c * c);
        }
        else
        {
            answer = (a + b + c) * (a * a + b * b + c * c);
        }


        return answer;
    }
}

간단한게 if문을 활용하여  조건을 넣어 계산한 값을 출력하면 끝나는 쉬운문제

 


원소들의 곱과 합

 

정수가 담긴 리스트 num_list가 주어질 때, 모든 원소들의 곱이 모든 원소들의 합의 제곱보다 작으면 1을 크면 0을 return하도록 solution 함수를 완성해주세요.

 

제한사

2 ≤ num_list의 길이 ≤ 10
1 ≤ num_list의 원소 ≤ 9

 

입출력 예

num_list result
[3, 4, 5, 3, 2, 1] 1
[5, 7, 8, 3] 0

 

풀이

using System;

public class Solution {
    public int solution(int[] num_list) {
        int answer = 0;
        int x = 1;
        int y = 0;
        
        for(int i = 0; i < num_list.Length; i++){
            x *= num_list[i];
            y += num_list[i];
        }
        
        
        return answer = (x < y * y) ? 1 : 0;
    }
}

반복문을 활용하여 num_list의 길이만큼 반복을 하여 x에는 원소들의 곱을 y에는 원소들의 합의 제곱을 저장하여

조건문을 통해 참이면 1 거짓이면 0을 출력한다

싱글톤 패턴 (Singleton Patton)

싱글톤 패턴이란 클래스의 인스턴스를 오직 하나만 생성하도록 보장하는 디자인 패턴이다.

이를 통해 전역적으로 접근할 수 있는 유일한 객체를 제공하며 보통 설정, 데이터 관리, 게임 메니저 등에서 활용된다.


싱글톤 구현

public class Singleton
{
    private static Singleton instance;  // 인스턴스 저장 변수
    private static readonly object lockObject = new object(); // 동기화를 위한 객체

    // 외부에서 객체 생성 금지
    private Singleton() { }

    public static Singleton Instance
    {
        if (instance == null)
            {
                if (instance == null)
                {
                	instance = new Singleton();
                }
            }
            return instance;
    }

    public void SingletonCalling()
    {
        Console.WriteLine("싱글톤 인스턴스의 메서드 호출!");
    }
}

싱글톤 패턴 장/단점

장점

  1. 전역 접근 가능 - 어디서든 동일한 인스턴스를 쉽게 사용할 수 있다
  2. 메모리 절약 - 하나의 인스턴스만 유지하여 불필요한 객체 생성을 방지한다.
  3. 데이터 일관성 유지 - 전역적으로 동일한 데이터를 유지할 수 있어 설정값이나 상태 관리가 편리하다.

단점

  1. 테스트가 어려움 - 의존성 주입이 어렵기 때문에 단위 테스트 작성이 어려워질 수 있다.
  2. 의존성이 높아짐 - 특정 클래스에 강하게 결합되어 유지보수가 어려워질 수 있다.
  3. 멀티스레드 환경에서 동기화 문제 발생 가능 - 다중 스레드 환경에서 예상하지 않은 버그가 발생할 수 있다.

 

싱글톤 패턴 사용 시 주의할 점

1. 불필요한 싱글톤 남용 금지

     - 모든 클래스를 싱글톤으로 만들면 코드가 복잡해지고 유지보수가 어려워진다.

     - 전역 상태를 너무 많이 공유하면 예상치 못한 버그가 발생할 가능성이 높다.

2. 멀티스레드 환경 고려

     - 싱글톤이 여러 스레드에서 동시에 접근될 경우, lock을 사용하여 안전하게 동작하도록 해야 한다.

3. 유닛 테스트 어려움 해결

     - 인터페이스 또는 의존성 주입을 사용하여 테스트 가능하도록 설계하는 것이 좋다.


결론

싱글톤 패턴을 잘 사용하면 메모리 절약을 하면서 전역에서 접근이 가능하지만, 무분별하게 사용하면 유지보수가 어려워질 수 있다.

'내일배움캠프 > C# 이론' 카테고리의 다른 글

C# 입문 (변수와 자료형)  (0) 2025.01.24

문제 : 사용자로부터 배열을 입력받아 내림차순으로 정렬하는 함수를 작성하시오

 

내가 쓴 풀이 : 이중 for문을 이용하여 직접 하나씩 하니씩 비교해가며 정렬 하는 방식으로 작성하였다.

using System;

class Program
{
    static void Main(string[] args)
    {
        Console.Write("배열의 숫자를 공백으로 구분하여 입력하세요: ");
        string input = Console.ReadLine(); // 사용자 입력 받기
        string[] strArr = input.Split(' '); // 공백 기준으로 나누기
        int[] intArr = Array.ConvertAll(strArr, int.Parse); // 문자열 배열을 정수 배열로 변환

        // 정렬 (버블 정렬 방식)
        for (int i = 0; i < intArr.Length - 1; i++)
        {
            for (int j = 0; j < intArr.Length - 1 - i; j++)
            {
                if (intArr[j] < intArr[j + 1]) // 내림차순 정렬
                {
                    int temp = intArr[j];
                    intArr[j] = intArr[j + 1];
                    intArr[j + 1] = temp;
                }
            }
        }

        Console.Write("정렬된 배열: ");
        foreach (int num in intArr)
        {
            Console.Write(num + " ");
        }
    }
}

 

 


 

다른 방법 : 기능함수인 Array.Sort()를 사용하여 배열을 먼저 오름차순으로 정렬을 해주고 Array.Reverse()를 이용하여 오름차순으로 정렬된 배열을 뒤집어 내림차순으로 변경해준다

class Program
{
    static void Main(string[] args)
    {
        Console.Write("배열의 숫자를 공백으로 구분하여 입력하세요: ");
        string input = Console.ReadLine();
        int[] intArr = Array.ConvertAll(input.Split(' '), int.Parse);

        Array.Sort(intArr); // 배열 정렬 (오름차순)
        Array.Reverse(intArr);

        Console.WriteLine("정렬된 배열: " + string.Join(" ", intArr));
    }
}

 

 

내가 쓴 풀이와 다른 풀이를 비교 : 내가 풀이는 이중 for을 사용하여 버블 정렬로 배열을 정리하고 다른 풀이는 제공 메서드를 이용하여 퀵 정렬로 배열을 정리한다. 그래서 속도 성능을 생각했을때 내가 쓴 풀이는 이중 for문으로 인해 불필요한 계산이 발생해 속도가 느려지고 다른풀이는 C# 기본 제공 메서드를 사용하여 내부적으로 빠르게 연산하여 속도가 빠르다.

그래서 배열이 크고 빠른 연산이 필요할때는 다른 풀이 처럼 Array.Sort() 메서드를 사용하고 정렬 알고리즘을 직접 구현해야 하거나 커스텀이 필요할 경우 이중 for문을 이용한 버블 정렬이 용이하다고 한다.

 

+ Recent posts