안녕하세요

프로그램 과정에서 막혔던 문제들에 대한 해결책 정리


페이지 목록

2025년 12월 17일 수요일

LMAdSize.h 를 찾을 수 없다는 에러가 발생했을 때 해결 방법

유니티에서 iOS 빌드를 했을 때 .xcodeproj 로 열면 발생하는 이슈이다.

xcodeproj는 내부적인 코드만을 사용하기 때문에 외부 요소들을 배제하여 빌드하기 때문에 외부에 대한 코드를 가져오지 못해서 발생한다.

 그래서 .xcworkspace를 열어야 한다.


 그런데 보면 xcworkspace가 보이지 않을 수 있다. 이 때 해당 프로젝트에 Podfile 이 만들어졌는지 확인 해보자. 이게 없다면 나도 해결방법을 알지못하는데 이게 있다면 문제는 해결할 수 있다

podfile은 "애드몹이랑 광고를 돌리려면 A,B,C라는 파일들이 필요해" 라는 명단이 적혀 있다. 그런데 이걸 실제로 설치하려면 pod install을 돌려야 한다. 이걸 자동으로 돌리게 되있는데 iOS 환경이 복잡하여 이걸 실행 못하는 상황이 발생한다. 이럴 때 수동으로 pod install을 입력해서 수동 설치를 해줘야 한다.


 이렇게 pod install을 하면 xcodeproj에 이런 외부 부품들을 결합하여 xcworkspace를 만들어 주게 된다. 

 

결론: xcworkspace를 열어서 빌드하면 된다. 그런데 이게 없다면 podfile이 있는지 확인해 보고 있다면 해당 폴더에서 pod install을 하여 xcworkspace를 만들어 실행하면 된다.


 제미나이의 도움을 받아 작성하였습니다.

2025년 12월 15일 월요일

other.TryGetComponent(out var damageableTarget) 이란

충돌한 다른 게임 오브젝트에 특정 컴포넌트가 있는지 확인하고, 있다면 해당 컴포넌트의 참조를 안전하게 가져오는 역할을 함


 TryGetComponent<T>(out T component)

 <T> 에 대한 컴포넌트를 찾으라는 것 여기서 이번에는 T가 IDamagable임

Try를 붙여 시도한다는 것이다. 찾으면 true, 못찾으면 false를 반환하고 에러를 발생시키지 않음


out T Component(out var damageableTarget)

out 은 찾은 컴포넌트의 참조를 이 변수에 담아서 반환하라는 의미임

var damageableTarget : 컴포넌트를 찾으면 참조가 저장될 새 변수를 선언하고 이름이 damageableTarget이라는 뜻


즉, IDamagable을 구현한 컴포넌트가 other에 있는지 확인하고 있으면 damageableTarget 변수에 그 컴포넌트 정보를 담아주고 찾았는지 못 찾았는지 알려달라는 것이다.


제미나이의 도움을 받아 작성하였습니다.

 

2025년 12월 14일 일요일

[유니티][Unity] 볼과 보스가 맞았을 때 OntriggerEnter 처리하는 위치 결정

 결론적으로 볼에서 처리하는게 좋다.

 볼이 날라가서 부딪히면 데미지를 준다는 로직에 맞고

보스가 처리하게 되면 보스가 볼만 맞는게 아니고 레이저도 맞고 미사일도 맞는다면

보스가 일일이 이게 볼인지 레이전지 처리를 해줘야 되서 복잡성이 올라가게 된다.


구글 제미나이의 도움을 받아 작성하였습니다.

[유니티][Unity][C#] If ... is ... ... 과 (... as ...)?.Function() 에 대하여

 IMainCharAttackView Interface와

MainCharAttackView: ImainCharAttackView 로 인터페이스를 상속받는 class가 있을때


IMainCharAttackView _view;


MainCharAttackPresenter(IMainCharAttackView view)
{

  _view = view;

}


로 되어 있을 때 이런 식으로 사용하게 된다.


if( _view is MainCharAttackView concreteView)

   concreteView.function();

}

  이 뜻은 _view가 MainCharAttackView 라면 concreteView에 _view를 할당해서 쓰라는 것이다.

 그래서 MainCharAttackView의 function을 concreteView가 사용하게 된다.


사실은 _view가 이미 MainCharAttackView 타입이기에 그냥 써도 되지만 이런 if문을 씀으로 인해서 정확하게 MainCharAttackView 형임을 확인하고 명확하게 concreteView를 통해 안정적으로 실행하게 된다.


(_view as MainCharAttackView)?.Function() 은

 _view를 MainCharAttackView형으로 바꿨을 때 된다면 Function을 실행하고

형이 안 맞으면 null값을 반환하라는 뜻이다.


 Google AI의 도움을 받아 작성하였습니다.


2025년 12월 6일 토요일

[유니티][Unity] 보스HP와 주인공HP를 계산하는 방법이 같을 때 하나의 코드로 처리하는 방법

  보스HP가 애니메이션 효과로 줄어들게 구현하고 보니 

 (7가지 색의 SingleBar 를 만들고 curHpBar는 빨주노초파남보 라면 

 밑에 있는 BelowHpBar는 주노초파남보빨 로 나오게 하고

 curHpBar가 Singlebar의 끝까지 가면 주황이나오고 belowHpBar는 노랑이 나오도록 설계하고

이를 루프를 돌리고 마지막에 hp가 0이 될 때는 검은색으로 나오게 만들었다.)


 이 것을 보스에만 구현해놓고 이제 주인공 HP에 구현하려고 보니, 이 코드를 다시 만들어야 되는 것이다. 

 그래서 재사용할 수 있게 만들어 보니 몇가지 테크닉이 들어가게 되었다.


 1. static class를 지정한다.

  사용 이유를 정확하게는 알 수 없으나 공용으로 쓰이는 메소드를 static class에 넣어 쓰면 재사용하기에 좋다고 한다.

 이 경우에 GetColorByBarIndex로 7가지 색중 어느 index를 불러와야 할지 정하는 메소드를 static으로 만들어서 공용으로 컬러 값을 가져올 수 있게 하였다.

public static class HpColorHelper

{

    public static Color GetColorByBarIndex(int index, List<Color> barColors, Color depletedColor)

    {

        if (index < 0) return depletedColor;

        if (barColors == null || barColors.Count == 0) return Color.white;

        return barColors[index % barColors.Count];

    }

}


2. baseHpView를 만들어 거기에 색깔 계산과 애니메이션효과와 현재피/전체피 텍스트를 적게 했다.

 이렇게 하면 bassHpView를 상속받으면 bassHpView에서 로딩하는 UpdateHpBar(...) 클래스를 

가져오게 되니 한번 만든 코드를 재사용할 수 있게 되었다.


3. Interface를 사용하여 Presenter에서 view를 가져온다.

 아직 정확하게 감이 잡힌 건 아닌데 interface를 쓰면 Presenter에서 view가 바뀌어도 코드 바꿀 필요가  없어서 유연성이 증가된다고 한다. 

이런 식으로 interface를 선언 하고

public interface IHpBarView

{

    void UpdateHpBar(int currentHp, int maxHp);

}

Interface를 상속 받아 같은 구조로 된 메소드에 구현 한다.

using UnityEngine;

using UnityEngine.UI;


public class BaseHpBarView : MonoBehaviour, IHpBarView

{

    [SerializeField] private Image hpImage;


    // 인터페이스 메서드 구현 → 반드시 public

    public void UpdateHpBar(int currentHp, int maxHp)

    {

        float ratio = (float)currentHp / maxHp;

        hpImage.fillAmount = ratio; // 단순 예제

    }

}


그러면 하위 클래스는 코드 없이 사용가능하다.

public class BossHpBarView : BaseHpBarView

{

    // IHpBarView 다시 구현할 필요 없음

    // 필요 시 UpdateHpBar override 가능

}


public class MainCharHpBarView : BaseHpBarView

{

    // 특화 로직 override 가능

}


Presenter에서 호출 할 떄는 아래와 같이 쓴다.

public class HpBarPresenter : MonoBehaviour

{

    private IHpBarView view;

    private int currentHp = 100;

    private int maxHp = 100;


    // Init 메서드로 런타임에 View 주입

    public void Init(IHpBarView view)

    {

        this.view = view;

        view.UpdateHpBar(currentHp, maxHp); // 초기화

    }


    public void TakeDamage(int dmg)

    {

        currentHp = Mathf.Max(0, currentHp - dmg);

        view.UpdateHpBar(currentHp, maxHp);

    }

}


 핵심은 IHpBarView를 넘겨받아 쓴다는 것이다. 그래서 baseView던지 boss던지 mainchar던지 상관없이 view를 넘겨 받을 수 있다는 게 핵심으로 보인다.


 chatGPT의 도움을 받아 작성하였습니다.

[유니티][Unity] Color 값을 메인케릭터와 보스가 같이 공유해서 사용하는 방법

 ScriptableObject를 통해 만들어진 컬러 값을 가져와서 사용하면 된다.

아래 코드 같이 만든 후 사용한다.


using NUnit.Framework;

using UnityEngine;

using System.Collections.Generic;


[CreateAssetMenu(menuName = "UI/HP Color Config")]

public class HpColorConfig : ScriptableObject

{

    public List<Color> hpBarColors;

    public Color animatedBarColor;

    public Color depletedHPColor = Color.black;    

}


CreateAssetMenu를 통해 UI  -> HP Color Config라는 메뉴가 생겼다.


Project의 Assets 폴더에서 Create -> UI -> HP Color Config를 누르면 생기는데 그 이름을 HP Color Config로 저장하자. 이것을 HP Color Config Asset이라 한다. 그걸 눌러보면 위에 선언한 Color 값들을 Inspector에서 조절할 수 있다. 이렇게 만들어 놓은 값들을 사용하기 위해 사용하려는 곳에 가서 

    [Header("Config")]

    [SerializeField] private HpColorConfig colorConfig;

로 선언하고 이 colorConfig 값에 만들어 놓은 HP Color Config Asset을 넣으면 연동이 된다.

 그 후 colorConfig.hpBarColors[index] 라는 식으로 사용하면 된다.


 chatGPT의 도움을 받아 작성하였습니다.

[유니티][Unity] Text 보다는 TextMeshPro를 써야 하는 이유와 TextMeshProugui와 tmp_text 비교

항목TextTextMeshPro
선명도❌ 낮음✅ 매우 선명
해상도 대응❌ 깨짐✅ 완벽
외곽선/그림자❌ 제한적✅ 매우 강력
성능❌ 상대적으로 나쁨✅ 더 좋음
숫자 전용 폰트❌ 불편✅ 최적화 가능


이렇게 차이가 많이 나기에 TextMeshPro를 택하는게 맞다.


TextMeshProUGUI와 TMP_Text는 조금 역할이 달라

UI는 TextMeshProUGUI를 쓰고 TMP_Text는 부모 클래스야


TextMeshProUGUI: 실제 UI에 붙는 컴포넌트로 HP바, 버튼, HUD, 데미지 숫자 전부 이걸로 써

TMP_Text는 TextMeshProUGIUI와 TextMeshPro의 공통 부모 클래스로 추상화용 타입이야. Inspector에서 붙는 컴포넌트는 아니고 스크립트에서 타입 통합용으로 사용하지.


TMP_Text text; 라고 하면 

UI용 TextMeshProUGUI 일 수도 있고 3D용 TextMeshPro 일수도 있어


그래서 언제 사용하는 건데?

UI 고정일때 (HP바,  HUD) TextMeshProUGUI를 쓰면

 가장 안전

 Inspector 연결 실수 없음

 의도 명확함


 UI/3D 텍스트 둘 다 받고자 하면 TMP_Text를 쓰지

 공용 처리 가능하고 재사용성이 올라가지만 실수로 3D TMP랑 연결될 수도 있어




  chatGPT를 참조하여 만들었습니다.

2025년 12월 5일 금요일

[유니티][Unity] Anchor 프리셋이 좌우 Stretch일 때 HP 깍이는 에니메이션 코드(feat.DOTween)

 애니메이션 효과를 구현하기 위해서는 세가지 Image가 필요하다.

Background, Animation, Cur Image 가 필요하다.

 여기서 백그라운드 이미지는 피 색을 7가지로 했을 경우에 curImage의 색상보다 index -1 값을 가지게 해서 마치 다음 피통을 미리 보여주는 효과를 가지게 한다. 


 그리고 Animation 은 피가 깍이는 효과를 애니메이션 효과를 넣어서 표현하는 것이다.

 여기서 DOTween을 쓰게 되면 애니메이션을 주기위해 lerp timer를 넣어서 어쩌고 저쩌고 해서 마치 선형적으로 내려가는 듯이 보이는 효과를 주는 것을 알아서 해준다. 그래서 코드가 단순해졌다. 

 그리고 그 위에 현재 피통을 보여주고 피가 깍이는 순간 깍인 곳까지 바로 이동한다.


 그렇게 하면 피가 깍이는 순간 바로 내려가고 내려간만큼 애니메이션 효과가 느리게 따라오면서 애니메이션 효과를 주면서 피가 깍이는 듯이 보이게 된다.


 이 과정을 다른 블로그를 참조로 해서 만들었더니 대략 100줄 정도의 코드가 필요했는데 이게 Stretch 모드를 아닌 상황에서 구현이 되어 있었는데, Stretch 모드를 적용하니 먹통이 되버린 것이다...


 그래서 난감했는데 제미나이에게 물어보니 DOTween을 추천해줬다. AI가 나보다 훨씬 코드를 잘 아는 이상황이 특이점이 이미 와버린듯 하다.

 그래서 DOTWEEN을 적용하니 10줄로 깔끔하게 떨어져 버렸다.


 아래 코드를 참조하면 된다.

public void AnimateBarWidth(float targetRatio, Color curBarColor, Color backBarColor)

{

    Vector2 targetAnchorMax = new Vector2(targetRatio, mainBarRectTransform.anchorMax.y);


    // -------------------------------------------------------------

    // 1. [즉시 동작] 빠른 바 (mainBarRectTransform) 처리: HP 표시 및 색상 갱신


    // A. 앵커를 목표 위치로 즉시 설정 (뒤쪽의 느린 바가 노출되도록)

    mainBarRectTransform.anchorMax = targetAnchorMax;


    // B. 빠른 바의 색상을 Presenter가 결정한 최종 색상으로 즉시 설정합니다.

    if (mainBarImage != null)

    {

        mainBarImage.color = curBarColor;

        backImage.color = backBarColor;

    }


    // -------------------------------------------------------------

    // 2. [천천히 동작] 느린 바 (damageMarkerRectTransform) 처리: 배경 애니메이션


    damageMarkerRectTransform.DOKill(true); // 이전 트윈 정리


    // A. 색상 고정: 느린 바의 색상을 인스펙터에 설정된 고정 배경 색상으로 즉시 설정

    // * 색상 깜빡임 로직이 모두 제거됨 *

    if (damageMarkerImage != null)

    {

        damageMarkerImage.color = slowBarOriginalColor;

    }


    // B. 앵커(크기) 애니메이션 (천천히 줄어들기)

    // 이 부분이 유일한 애니메이션입니다.

    damageMarkerRectTransform.DOAnchorMax(targetAnchorMax, duration)

        .SetEase(Ease.OutQuad);


제미나이의 도움을 받아 작성하였습니다.

[유니티][Unity] HP bar UI 크기가 폰 화면에 따라 바뀌는 방법

  HP Bar를 만들었다면 게임 할 때 원하는 위치에 원하는 크기로 보이고 싶을 것이다.

 그런데, 이럴수가 폰 마다 화면액정이 다 다른데, 이걸 어떻게 하나.


 어떻게 하면 HP Bar 크기를 폰 크기에 상관 없이 일정하게 보여줄 수 있을까?

 이렇게 하려면, 폰마다의 크기에 맞춰서 크기를 보여줘야 한다.

 말하자면, 동적으로 크기가 바뀌어야 하는 것이다.


 그렇게 하려면 아래와 같이 하면 된다.

 Canvas scalar를 scale with Screen size로 동적으로 변경되도록 설정해준다.


 체력바 Rect Transform으로 이동하여 화면 상단에 좌우로 꽉차고 위아래는 화면 크기의 5%인 HP Bar를 만든다고 치자.

 1. 앵커 프리셋 설정(가장 중요)

 체력바 오브젝트(예: Image 또는 Panel)의 Rect Transform으로 이동

 1. Anchor Presets 버튼 클릭

 2. Alt 키를 누른 채, 상단 우측에 있는 가로 스트레치(Stretch, Top) 프리셋을 선택한다.

    Alt 키를 누르면 앵커와 함께 피벗(Pivot)과 포지션(Position)이 동시에 이동하여 원하는 자리에 배치됨


2. 너비 IWidth) 설정: 좌우 꽉 채우기

 앵커를 가로 스트레치로 설정했기 때문에, 너비(Width)대신 Left와 Right 오프셋 값이 활성화 됨

Left: 화면 왼쪽 경계(Anchor)로 부터의 거리를 0부터 설정가능

Right: 화면 오른쪽 경계(Anchor)로 부터의 거리를 0부터 설정가능


3. 높이 (Height) 설정: 화면 높이의 5%

 높이는 Canvas Scaler에 설정된 Reference Resolution을 기준으로 계산 된 값을 입력하면 됨

1. 기준 해상도 확인: Canvas Scaler에 설정된 Reference Resolution의 높이 확인 (예: 1080x1920)

2. 높이 계산: 기준 높이의 5% 계산

   1920* 0.05 = 96

3. 최종 Rect Transform 값 :

 Anchors: Left0, right 1(가로 스트레치)

Pivot: (0.5, 1)

 Left: 0

 Right: 0

 Height: 96 (사용자의 Reference Resolution 에 따라 달라짐)


 여기서 Height를 96으로 하드코딩해도 resolution에 따라 값이 변하는 이유는 canvas Scaler 컴포넌트의 Scale with screen size 설정을 했기 때문이다.


 이렇게 설정하면, 캔버스 스케일러의 기준 해상도(Reference Resolution)에 대한 상대적인 크기로 계산해 준다.

 캔버스 스케일러의 동적 스케일링 원리

 1. 기준 해상도 (Reference Reolution)의 역할

 96이라는 Height를 설정하면, Reference Resolution에서 96 크기를 가지는 것을 의미함

 2. 유니티가 런타임에 접속한 장시의 실제 해상도를 기준 해상도와 비교하여 계산된 스케일링 계수를 캔버스 전체에 적용함


[유니티][Unity] 피벗(Pivot)과 앵커(Anchor)와 포지션(Postion)에 대하여

 앵커, 피벗, 위치는 유니티 UI 시스템에서 요소의 크기, 위치, 그리고 반응성을 결정하는데 사용되는 Rect Transform 컴포넌트의 세 가지 핵심 개념이다.


 다양한 화면 크기(해상도)에 대응 하는 UI를 만들 때 매우 중요하다


 1. 앵커(Anchor)
 부모 요소(화면 또는 다른UI)의 어느 위치를 기준으로 삼을지를 결정하는 네 개의 작은 삼각형 모양의 마커이다.

  UI 요소의 위치와 크기를 결정하는 기준점  또는 기준 프레임으로 부모 요소의 크기가 변할 때, 이 기준점에 고정되거나 기준점을 따라 늘어나면서 UI의 반응성(Resilience)을 결정한다.


 앵커의 두 가지 주요 모드

 앵커 마커가 모여 있느냐, 떨어져 있느냐에 따라 요소의 위치와 크기 작동 방식이 달라진다.

 1. 고정 모드(Fixed/Grouped Anchor)

 마커 상태: 앵커 마커 네 개가 한점에 모여 있음( 예: 중앙, 좌상단 등)

 결과: 요소의 크기는 고정되고, 위치만 앵커를 따라 이동함. Position은 앵커를 기준으로 한 거리가 됨

 

 2. 스트레치 모드 (Stretch/Split Anchor)

 마커 상태: 앵커 마커가 서로 떨어져 있음 (예: 좌우로 떨어져 있음)
 결과: 요소의 크기가 부모 요소의 크기에 따라 늘어남. Left, Right, Top, Bottom 값이 활성화 되며, 이 값들은 앵커 경계선으로부터의 패딩(Padding) 거리를 지정함


2. 위치(포지션) 및 Offset (오프셋)

 위치 값은 UI 요소의 피벗(Pivot)이 앵커가 지정한 기준점 또는 기준 프레임에서 얼마나 떨어져 있는지 나타냄


 앵커를 기준으로 측정되는 UI 요소의 실제 중심점 위치 (픽셀 단위)

요소의 정확한 화면상 좌표를 결정함. 앵커가 어디에 있느냐에 따라 값의 의미가 달라진다.

 앵커가 고정 모드 일때는 Pos X, Pos Y로 표시되고, 앵커가 스트레치 모드일 때는 Left, Right, Top, Bottom(오프셋) 값으로 표시 된다.


 Position(0,0) 이 앵커가 화면 중앙이라면 중앙을 뜻하고 좌상단이면 좌상단 모서리를 의미함


 3. 피벗 (Pivot)

 피벗은 UI 요소 자체 내에서 변환(크기 조절, 회전 등)이 일어나는 기준점임


 요소 자체의 로컬 좌표계에서 (0,0)을 나타내는 회전 및 크기 조절의 중심점


 (0,0) 부터 (1,1) 사이의 정규화 된 값으로 표현 됨. (0.5,0.5)가 기본값으로, 요소의 정중앙을 의미함


 요소를 회전시키거나 스케일을 변경할 때, 이 피벗을 중심으로 변화가 일어남. 또한 Position 값이 피벗을 기준으로 측정됨.


 세 요소의 관계 요약

 세 가지 개념은 다음과 같은 순서로 상호작용함

 1. 앵커가 부모 화면/요소 내에서 UI의 기준점/기준 영역을 정함 (어디를 따라 움직일 것인가?)

 2. 위치 (Position/Offset)가 앵커 기준점에서 피벗(Pivot) 까지의 거리를 결정하여 요소의 최종 위치를 확정함 (기준으로부터 얼마나 떨어져 있을 것인가?)
 

 3. 피벗은 요소 내에서 움직임과 변형의 중심 역할을 함 (내 안에서 어디를 중심으로 돌고 늘어날 것인가?)


 따라서 화면 크기가 바뀌어 앵커의 위치가 변하면, Position 값은 유지된 채 요소의 피벗이 애어를 따라가게 되어, UI 요소가 의도한 반응성을 갖게 됨


 위치는 피벗 위치를 변경 (0,0) -> (1,1) 로 하면 위치(Position)도 변경하게 된다.


 위치(Position)가 피벗 위치에 따라 자동으로 변경되더라도 위치는 수동으로 지정할 필요가 있다.

 위치(Position)의 가장 중요한 역할은 UI 요소의 실제 위치를 결정하는 것이기 때문이다.

 1. 포지션의 핵심 역할: 배치 (Placement)

 UI 요소를 앵커(Anchor)를 기준으로 원하는 곳에 놓기 위해 포지션 값을 사용함

 예) 앵커를 화면 정중앙 (0.5,0.5)에 설정했다고 할 때

 버튼을 중앙에서 오른쪽으로 50픽셀 떨어진 곳에 배치하고 싶다면, Pos X를 50으로 직접 입력해야 함(Pos X:50, Pos Y: 0)

 이 경우에는 유니티가 Position을 자동으로 계산해 주지 않음. 개발자가 원하는 Offset(오프셋) 거리를 포지션 값으로 지정하는 것임


 2. 피벗 변경은 일회성 보정

 피벗을 변경하는 작업은 보통 UI 요소를 배치하는 작업 중 한 번만 이루어진다. 그 이후에는 앵커를 기준으로 포지션 값을 직접 변경하며 UI요소들을 정렬하고 배치하는 것이 주된 작업 방식임


 결론은, 피벗 변경 시의 자동 조정은 위치를 고정하는 기능이고, 포지션을 수동으로 입력하는 것은 위치를 이동시키는 기능이라고 구분하여 이해하면 됨


 제미나이의 도움을 받아 작성하였습니다. 

 

2025년 12월 3일 수요일

[유니티][Unity][MacOS][iOS][Windows] 유니티 Windows 개발 환경에서 아이폰에 앱 빌드하기

iPhone 에 빌드하려면 macOS를 이용해야만 한다.
 
 이 제약사항으로 인해 Windows -> iPhone으로 앱 빌드하는 과정이 꽤 먼길을 가게 된다.

 Git 을 이용해 코드를 받아 오게 되는데 해 보니 Unity Version Control이라는 방법도 있었다. 
 
 편한 방법을 쓰면 되겠다. 용량도 체크 해 보고

 여기서 gitlab을 사용하게 됐는데 알고보니 .gitignore로 서버에 안 올릴파일과 올릴 파일을 선별하는데

 이 전에 프로젝트가 2.5Gb라 큰 용량이 필요하다 했는데 .gitignore를 공식 사이트에서 unity 개발용으

로 받아온 후 서버에 올려 보니 10메가 정도 밖에 안되었다. 그래서 굳이 gitlab을 안 써도 됐을 거 같다.

 이렇게 gitlab으로 윈도우즈의 코드를 올리면 그 코드를 맥에서 받을 수 있다. git 을 쓰기 때문에 이렇게 안 하면 매번 빌드 새로 할 때마다 전체코드 혹은 변경된 코드들을 일일이 usb로 복사해서 넘겨야 되는데 사실상 어렵다.

 이렇게 해서 gitlab을 통해 맥북에 코드를 옮기게 되었다.
 
 옮기다 보니 알게 된 것이 ssh key 를 통해서 git을 사용하는 게 편했다. 

 https로 할려고 하니 token 을 발행해야 되는데 이게 발행 시점에만 비밀번호?를 준다. 그리고 닫는 순간 이 비번을 알 수가 없다. 그리고 엄청 길어서 이걸 제대로 쳐 넣기가 어렵다. 물론 복사 붙여 넣기 하면 되는데 윈도우즈에서 복사 붙여 넣기 해서 해결 했더니 맥에서 다시 비번을 물어보는데 이걸 가져와서 쓰기가 어렵고 번거롭다. ssh key는 각 각 windows 나 맥에서 받아 쓰면 되고 한번만 입력하면 되니 편리했다.

 이렇게 맥북에서 project 코드를 다 받고 유니티로 실행해 보니 아니 hierarchy에 Main Camera만 덩그러니 있는 거 아닌가.. 여기서 뭐가 잘못 됐지 하고 많이 헤맸는데 찾다찾다 보니 그냥 scene을 로딩 안해서 그랬던 것이었다. 매우 간단한데 처음 써보면 헤깔릴만 하지 않나 쉽다.

 이렇게 macOS에 코드를 옮기면 이제 iOS (iPhone)에 빌드를 해 넣으면 된다.

 물론 또 첨부터 이런 저런 이슈들로 한번에 안되는데 구글 AI한테 물어 보니 해결이 된다. 

 그렇게 iOS에서 처음으로 게임을 빌드 해 봤는데 UI가 너무 작게 나오고 터치 패드도 말을 안듣고 할 일이 참 많아졌다.

구글 AI의 도움을 받아 작성하였습니다.

2025년 12월 2일 화요일

[Windows] dcctrldrv.sys 라는 호환되지 않는 드라이버 때문에 메모리 무결성을 켤 수 없을 때

 dcctrldrv.sys를 삭제 하고 재부팅하니 동작한다.


dcctrldrv.sys는 c:\Windows\System32\ 에 있다.

삭제 후 재부팅하고 

데이터 무결성은 검색에 윈도우즈 보안을 치고 들어가서 장치 보안에 가면 나타난다.

키고 재부팅하면 된다.


구글 AI 도움을 받아 작성하였습니다.

[유니티][Unity] 윈도우에서 개발 중인 iOS 게임(모바일)을 맥북에서 받아서 빌드하는 편한 방법은(gitHub,GitLab,BitBucket)

 웹 기반 코드 호스팅 및 협업 플랫폼을 쓰면 좋다.

GitHub,GitLab, Bitbucket은 모두 Git을 기반으로 한다.

 조금씩 차이점이 있다. 이 부분은 직접 검색해 보면 좋겠다.

 유니티가 용량이 커서 용량을 많이 주는 GitLab을 선택하게 될 거 같다.


 이러한 플랫폼을 쓰면 굳이 usb로 윈도우 -> 맥 으로 코드를 옮기는 불편함을 없앨 수 있다.

 거기다가 새로 만든 코드를 올리면 그걸 맥에서 받아서 쓰면 된다.

 USB로 옮긴다면 계속해서 덮어쓰기를 해줘야 하는데 그런 작업을 안해도 된다.


구글 AI를 참조하여 만들었습니다.

[유니티][Unity][iOS] 유니티 소스 iOS 폰에서 앱 테스트 할 때 Apple Developer Program(애플 개발자 프로그램)을 사야만 되는가

  꼭 Apple Developer Program(애플 개발자 프로그램)을 써야만 할 필요는 없다.

  아래 제약사항이 있음에도 무료로 쓰고자 한다면 쓸 수 있다.

  • 설치 유효 기간 (가장 큰 차이): 기기에 설치된 앱은 7일 동안만 실행 가능합니다. 7일이 지나면 Xcode를 통해 다시 빌드하고 설치해야 합니다. 유료 계정은 앱이 만료되지 않습니다.
  • 서비스 제한: 푸시 알림, Game Center, CloudKit 등 특정 유료 개발자 서비스는 사용할 수 없습니다. 
  • 배포 불가: App Store에 앱을 제출할 수는 없습니다.

 

 구글 AI의 도움을 받아 작성하였습니다.

2025년 12월 1일 월요일

[유니티][Unity] 구글 애드몹 광고 개발 시에 애드몹 계정을 만들거나 활성화 해야 하나?

  개발 과정에서는 테스트 ID를 사용하면 되기 때문에 애드몹을 활성화할 필요가 없다.

 실제로 6개월 동안 광고 게재를 하지 않으면 다시 비활성화 되기 때문에 개발 기간이 6개월 이상 걸릴 것으로 보인다면 테스트 ID로 개발 하다가 출시 직전에 애드몹을 활성화하고 애드몹 연동 테스트 및 실제 배포를 하면 된다.


샘플 ios 앱 ID

Sample AdMob app ID: ca-app-pub-3940256099942544~1458002511

출처: https://developers.google.com/admob/ios/quick-start?hl=ko


 구글 AI 도움을 받아 작성하였습니다.

2025년 11월 30일 일요일

[유니티][Unity] Windows 로 개발 중이던 게임 IOS로 변경하기

 프로젝트 오픈

File -> Build Profiles 오픈

iOS 선택 -> 우측의 Switch Platform 선택

 이 후 작업이 진행되고 종료 후

iOS 옆에 Active 가 뜨는지 확인

Active가 뜨면 아래 설정을 해줘야한다.

Player Settings 설정

 Edit -> Project Settings -> Player -> iOS 탭 선택 -> Other Settings 열기

하위의 Identification 하위의 Bundle Identifer 입력란 확인

com.yourcompanyname.yourappname 으로 작성해야 되고 유니크해야 한다.

영문 소문자,숫자,마침표만 사용가능함

마침표로 시작하거나 끝낼 수 없고, 숫자나 마침표로 시작은 권장하지 않음


한번 정하면 변경이 어렵기에 쉰중하게 결정 필요함


구글 AI의 도움을 받아 작성하였습니다.

[유니티][Unity] 현재 개발 중인 환경이 Windows 인지 IOS인지 Android 인지 확인하는 방법

 프로젝트를 연다.

File -> Build Profiles 확인 

하면 다양한 OS 가 나타나는데 그 중 오른쪽에 Active 라고 적혀있는 OS가 개발 중인 OS 이다.

2025년 11월 28일 금요일

[유니티][Unity] Instantiate(uiPrefab) 이 화면에 안 나오는 이유

 uiPrefab을 연결 시켜주고 Instantiate(uiPrefab)을 해도 UI가 보이지 않았다.

 이유는 연결하려는 부모 캔버스를 연결해줘야 되기 때문이다.

using UnityEngine;
using UnityEngine.UI; // UI 관련 기능을 사용하려면 이 네임스페이스가 필요합니다.

public class UIManager : MonoBehaviour
{
    // 유니티 인스펙터에서 캔버스 오브젝트를 드래그하여 할당해주세요.
    [SerializeField] private Transform parentCanvasTransform;

    // 인스턴스화할 UI 프리팹 (예: 버튼, 패널 등)
    [SerializeField] private GameObject uiPrefab;

    void Start()
    {
        // Start 시점에 UI 생성 함수 호출 예시
        SpawnUIElement();
    }

    public void SpawnUIElement()
    {
        if (parentCanvasTransform != null && uiPrefab != null)
        {
            // 1. Instantiate() 메서드를 사용하여 프리팹 생성과 동시에 캔버스의 자식으로 설정합니다.
            // 두 번째 매개변수로 부모 Transform을 전달합니다.
            GameObject newUIElement = Instantiate(uiPrefab, parentCanvasTransform);

            // 추가적으로 생성된 UI 요소의 이름이나 위치를 조정할 수 있습니다.
            newUIElement.name = "DynamicUI_Instance";
            // newUIElement.GetComponent<RectTransform>().anchoredPosition = new Vector2(0, 0);
        }
        else
        {
            Debug.LogError("Canvas Transform 또는 UI Prefab이 할당되지 않았습니다. 인스펙터를 확인해주세요.");
        }
    }
}

이렇게 parentCanvasTransform을 할당해줘야 한다.

그래서 GameObject newUIElement = Instantiate(uiPrefab, parentCanvvasTransform);

으로 부모 Transform을 전달해주면 화면에 나타나게 된다.


 구글 AI의 도움을 받아 작성하였습니다.

[유니티][Unity] FindFirstObjectByType()을 해도 null값이 반환되는 이유

  여러 이유가 있겠지만 내가 찾은 이유는 생각 보다 단순했다.

 해당 Object를 setActive(false)를 해서 숨겼기 때문이다.

 처음에 v 표시를 해놔도 setActive로 false를 하면 참조할 수 없게 된다.


 아니면 script를 만들어 놓고는 GameObject에 부착하지 않았을 수 있다.


 아니면 스크립트 실행 순서의 문제로 간헐적으로 발생하게 된다.


구글 AI의 도움을 받아 작성하였습니다.

[유니티][Unity][C#] delegate란? (유니티의 Action,Func)

 C#에서 메소드(함수) 자체를 변수처럼 취급할 수 있게 해주는 타입이다.
 다른 말로, 메소드를 가리키는 포인터(Pointer) 또는 메소드에 대한 참조라고 보면 된다.

 델리게이트의 기본 개념
 일반적으로 변수는 숫자, 문자열, 객체 등을 저장하는데 델리게이트는 "어떤 작업을 수행하는 코드 블록(메소드)"의 위치를 저장함
 메소드를 다른 메소드의 매개변수로 전달하거나, 나중에 실행하기 위해 저장해 둘 수 있게 된다.

 비유하자면 델리게이트는 TV 리모컨의 버튼과 같다. 버튼(델리게이트)을 누르면 실제로 채널을 바꾸는 동작(메소드)이 실행된다. 리모컨 버튼이 채널 변경 동작을 "가리키고" 있다.

 델리게이트 사용 예시
 
 델리게이트를 사용하면 코드를 더 유연하게 만들 수 있다.

public delegate void ProcessCompletedHandler(bool success); // 1. 델리게이트 선언

public class TaskProcessor
{
    // 2. 델리게이트 타입의 변수(이벤트) 생성
    public event ProcessCompletedHandler OnProcessCompleted;

    public void StartTask()
    {
        // 작업을 수행...
        bool result = true; // 성공했다고 가정

        // 3. 작업 완료 후, 델리게이트가 가리키는 메소드 호출
        // 이 시점에서 실제로 어떤 메소드가 실행될지는 모름.
        OnProcessCompleted?.Invoke(result); 
    }
}
델리게이트의 핵심 용도
 1. 이벤트 핸들링(Event Handling): Unity의 UI 버튼 클릭이나 충돌 감지와 같은 이벤트는 델리게이트를 사용하여 구현 됨 (가장 흔한 용도)
 2. 콜백 함수(Callback Functions): 비동기 작업(파일 다운로드 등)이 완료 되었을 때 실행할 함수를 지정할 때 사용
 3. 플러그인 아키텍처: 실행 시점에 어떤 함수를 사용할 지 결정해야 할 때 사용

 Action과 Func (축약형 델리게이트)
 C#에서는 대부분의 경우 delegate 키워드를 직접 선언하는 대신, 내장된 제네릭 델리게이트인 Action과 Func를 사용한다.
Action: 반환 값이 void (없음)인 메소드를 지칭함
Action<int> action = MyMethod; 
action(10); // MyMethod(10) 호출
Func: 반환 값이 있는 메소드를 가리킴 (마지막 제네릭 타입이 반환 타입)
Func<int, string> func = MyMethodWithReturn;
string result = func(10); // MyMethodWithReturn(10) 호출

구글 AI의 도움을 받아 작성하였습니다.

[유니티][Unity] view.OnHit += (collider2D, fireballView) => OnFireballHit(collider2D, model, view); 코드 분석

 view.OnHit +=  : view 객체의 OnHit 이벤트에 새로운 동작 추가

(collider2D,fireballView) : 이벤트가 발생할 때 전달되는 매개변수(파라미터) 목록

=> : 람다 연산자 왼쪽의 입력을 받아서 오른쪽 코드를 실행하라는 의미

OnFireballHit(...) : 이벤트가 발생했을 때 실제로 실행될 Presenter의 메소드

(collider2D, model, view) : OnFireballHit 메소드로 전달되는 인수(Arguments)


이 코드가 하는 일

 1. View에서 이벤트 발생: View에서 OnHit 이벤트가 발생하면 Presenter로 알림

2. Presenter의 반응(람다 실행) : OnFireballHit 메소드를 실행한다.

3. 데이터 전달: collider2D와 model view 객체를 OnFireballHit 메소드에 함께 전달한다


여기서 (collider2D,fireballView)는 파라미터이기에 사실 (a,b) 와 같이 적어도 된다. 넘어 오는 파라미터 값의 의미를 알 수 없기 때문에 알 수 있는 표현을 쓰는 것이다.


근데 왜 매개변수가 2개인데 3개의 인수가 생기지?? 이것은 풀어보면 알 수 있다.

// 람다 표현식:
// view.OnHit += (collider2D, fireballView) => OnFireballHit(collider2D, model, view);

// 위 코드를 풀어서 쓰면:

view.OnHit += delegate(Collider2D collider2D, FireballView fireballView)
{
    // 이벤트가 발생했을 때 이 내부 코드가 실행됩니다.
    // 여기서 Presenter의 메소드를 호출합니다.
    OnFireballHit(collider2D, model, view);
};

이렇게 2개의 파라미터가 넘어오지만 실제 메소드는 3개의 인수를 가진다고 보면 된다.

그러면 의문이 생기는 것이 fireballview는 쓰이지도 않는데 왜 선언하는 것인가?

 그것은 처음에 만들 때는 collider2D 와 fireballView가 둘다 쓰일것으로 예상했지만 실제 사용하는 데에서는 collider2D만 사용하게 되어서 생긴 것이다.

 그럼 왜 fireballview를 적어서 헤깔리게 하느냐, 이것이 C#9.0 이상에서는 변수 이름 대신 밑줄(_)을 써서 사용하지 않는 매개변수를 표현할 수 있게 해줬다. 

 (collider2D, _ ) => ... 이렇게 사용하면 된다.

 그럼 delegate는 뭐냐? 다음에 설명하겠다.


구글 AI를 참조하여 작성하였습니다.

2025년 11월 27일 목요일

[유니티][Unity] [RequireComponent(typeof(Collider))] 이란?

 [RequireComponent(typeof(Collider))] ??

 Unity의 어트리뷰트(Attribute, 속성) 중 하나로, 특정 스크립트가 작동하려면 반드시 동일한 게임 오브젝트에 Collider 컴포넌트가 있어야만 한다는 것을 나타내는 코드이다.

 이는 개발 과정에서 발생하는 실수를 방지하는 안전장치 역할을 한다.

 주요 기능 및 의미

1. 자동 추가: 이 스크립트를 게임 오브젝트에 추가하려고 할 때, 만약 해당 오브젝트에 Collider 컴포넌트가 없으면, Unity 에디터가 자동으로 Collider를 추가해 줌

2. 삭제 방지: Collider 컴포넌트가 이 스크립트의 필수 요소로 지정되었기에, 사용자가 실수로 Collider 컴포넌트를 삭제하려고 하면 Unity에서 경고 메시지를 표시하며 삭제를 막음

3. 설정 오류 방지: 코드가 충돌 감지 (OnCollisionEnter)와 같은 Collider의 기능에 의존할 때, 이 어트리뷰트를 사용하면 필수 컴포넌트가 누락되어 스크립트가 정상 동작하지 않는 상황을 사전에 방지할 수 있음. 코드는 GetComponent<Collider>()를 호출할 때 해당 컴포넌트가 항상 존재한다고 확신할 수 있음


 요약하면, "이 스크립트는 콜라이더가 반드시 필요하니, 오브젝트에 콜라이더를 꼭 넣어줘" 라고 Unity 에디터에게 알려주는 역할을 함


구글 AI의 도움을 받아 작성하였습니다.


2025년 11월 26일 수요일

[유니티][Unity] MVP를 이용해 주인공 움직임 애니메이션을 사용하기

 Model 

// Assets/Scripts/Model/CharacterStateModel.cs
using System;

public enum CharacterState
{
    Idle,
    Walking,
    Attacking,
    Skilling,
    Dying
}

public class CharacterStateModel
{
    public event Action<CharacterState> OnStateChanged;
    private CharacterState currentState;

    public void ChangeState(CharacterState newState)
    {
        if (currentState != newState)
        {
            currentState = newState;
            OnStateChanged?.Invoke(currentState); // 상태 변경 시 이벤트 발생
        }
    }
}

CharacterState 를 enum으로 저장한다.

OnStateChanged 액션으로 액션 변경을 알리도록 하고

CharacterState currentState로 현재 상태를 저장한다.


View

// Assets/Scripts/View/ICharacterStateView.cs
public interface ICharacterStateView
{
    void PlayIdleAnimation();
    void PlayWalkAnimation();
    void PlayAttackAnimation();
    void PlaySkillAnimation();
    void PlayDieAnimation();
}

CharacterStateView에서 재생해야 하는 Animation을 Interface로 만들어 놓는다.

Idle,Walk,Attack,Skill,Die에 대한 애니메이션이다.


// Assets/Scripts/View/CharacterStateView.cs
using UnityEngine;

public class CharacterStateView : MonoBehaviour, ICharacterStateView
{
    [SerializeField] private Animator animator;
    // 코드와 Animator 파라미터 이름을 일치시킵니다.
    private const string IdleTrigger = "Idle";
    private const string WalkTrigger = "Walk";
    private const string AttackTrigger = "Attack";
    private const string SkillTrigger = "Skill";
    private const string DieTrigger = "Die";

    void Awake()
    {
        if (animator == null)
        {
            animator = GetComponent<Animator>();
        }
    }

    // Presenter의 지시에 따라 Animator의 트리거를 설정합니다.
    public void PlayIdleAnimation() => animator.SetTrigger(IdleTrigger);
    public void PlayWalkAnimation() => animator.SetTrigger(WalkTrigger);
    public void PlayAttackAnimation() => animator.SetTrigger(AttackTrigger);
    public void PlaySkillAnimation() => animator.SetTrigger(SkillTrigger);
    public void PlayDieAnimation() => animator.SetTrigger(DieTrigger);
}

실행시킬 코드이다. animator.SetTrigger를 이용해서 Trigger를 활성화시켜 애니메이션을 재생한다.


Presenter

// Assets/Scripts/Presenter/CharacterPresenter.cs
using UnityEngine;

public class CharacterPresenter : MonoBehaviour
{
    [SerializeField] private CharacterStateView characterStateView;

    private CharacterStateModel model;
    private ICharacterStateView characterStateViewInterface;

    void Start()
    {
        model = new CharacterStateModel();
        characterStateViewInterface = characterStateView;
model.OnStateChanged += OnStateChanged;
// Model 이벤트 구독 model.ChangeState(CharacterState.Idle); // 초기 상태 설정 } private void OnStateChanged(CharacterState newState) { // Model의 상태에 따라 View에게 재생할 애니메이션 지시 switch (newState) { case CharacterState.Idle: viewInterface.PlayIdleAnimation(); break; case CharacterState.Walking: viewInterface.PlayWalkAnimation(); break; case CharacterState.Attacking: viewInterface.PlayAttackAnimation(); break; case CharacterState.Skilling: viewInterface.PlaySkillAnimation(); break; case CharacterState.Dying: viewInterface.PlayDieAnimation(); break; } } // 테스트용 사용자 입력 처리 void Update() { if (Input.GetKeyDown(KeyCode.Alpha1)) model.ChangeState(CharacterState.Idle); if (Input.GetKeyDown(KeyCode.Alpha2)) model.ChangeState(CharacterState.Walking); if (Input.GetKeyDown(KeyCode.Alpha3)) model.ChangeState(CharacterState.Attacking); if (Input.GetKeyDown(KeyCode.Alpha4)) model.ChangeState(CharacterState.Skilling); if (Input.GetKeyDown(KeyCode.Alpha5)) model.ChangeState(CharacterState.Dying); } }

OnStateChanged 때 Statte가 변경되었으면 해당 Animation을 재생하게 된다.

애니메이션을 재생할 애니메이터도 설정해야 한다.

 1. Animator Controller 생성 : 프로젝트 창에서 마우스 우클릭 -> Create -> Animator Controller 선택 이름은 characterAnimController로 지정

2. 캐릭터에 할당: 씬의 캐릭터 모델을 선택하고, Animator 컴포넌트의 Controller 필드에 생성한 characterAnimController를 드래그해서 할당한다.  

View의 SerializeField의 Animator에도 할당한다.

3. 애니메이션 클립 추가: Animator 창을 열어 프로젝트에 있는 Idle,Walk,Attack,Skill, Die 애니메이션 클립들을 드래그하여 상태(State)로 추가한다.

4. 기본 상태 설정: Idle 상태를 마우스 우클릭 -> Set as Layer Default State를 선택

5. 파라미터 추가: Animator 창의 Parameters 탭에서 + 버튼을 눌러, 코드와 동일한 이름의 Trigger 파라미터 5개를 추가함 (대소문자 일치 할 것)

 Idle, Walk, Attack, Skill, Die

6. 트랜지션 설정: Any State에서 각 애니메이션 상태로 트랜지션을 만든다.

 Any State에서 Idle로 드래그하여 트랜지션 생성

해당 트랜지션을 선택하여 인스펙터의 Conditions에 Idle 트리거를 추가하고 Has Exit Time 체크해제 

다른 상태(State)도 동일하게 Any State에서 연결하고 해당 트리거 조건에 맞게 설정한다.

테스트는 숫자 1-5를 눌러서 할 수 있다.

구글 AI의 도움을 받아 작성하였습니다.

[유니티][Unity][C#] => (람다 연산자 또는 표현식 본문) 활용

 C#에서 => 는 람다 연산자(Lambda Operator) 나 표현식 본문(Expression Body) 정의에 사용 됨


 1. 표현식 본문(Expression Body Definitions)

 메서드나 속성(Property)의 내용이 단일 표현식(값을 반환하거나 실행하는 하나의 줄)일 때, {}(중괄호) 와 return 대신에 => 만 사용해서 짧고 가독성이 좋게 한다.

public int GetHP()

{

    return HP;

}

=>을 사용해서

public int GetHP() => HP; 로 한 줄로 사용가능하다.


2. 람다식(Lambda Expression)

 입력 매개변수와 실제 실행될 코드 블록을 구분하는 역할을 함

 주로 이벤트 핸들러나 LINQ 쿼리 등에서 익명 함수를 정의할  때 사용함

  

 //( 입력 매개변수) => { 실행 코드}

button.onClick.AddListener(() => {Debug.Log("버튼 클릭 시 할 내용들"); });


구글 AI를 참조하여 작성하였습니다.

[유니티][Unity] MVC와 MVP 소프트웨어 디자인 패턴 및 MVP가 유니티개발에 유리한 이유

 사용자 인터페이스(UI)를 구성할 때 사용되는 아키텍쳐 패턴으로 소프트웨어 디자인패턴이다.
 세가지 핵심 역할로 분리해서 애플리케이션의 로직을 만드는데 이렇게 함으로써 코드의 유지 보수성과 확장성을 높이게 된다.
 
 UI 패턴 또는 애플리케이션 설계 패턴으로 불린다.

 1. MVC ( Model-View-Controller) 모델
  
    Model (모델): 비즈니스 로직과 데이터를 관리함
    View(뷰) : UI(화면)을 담당
    Controller(컨트롤러): Model이나 View를 업데이트하는 사용자의 입력(마우스 클릭,키보드 입력)을 받음

 2. MVP (Model-View-Presenter) 모델
   MVC에서 파생된 패턴으로 Unity와 같은 환경에서 더 선호하는 경향이 있다.
 
  Model(모델) : 비즈니스 로직과 데이터 관리 (1번 모델과 동일)
  View (뷰) : 화면(UI)를 담당하여 사용자에게 보여지는 부분이다, Presenter에 사용자 입력을 전달, 자체 로직은 거의 없음
  Presenter (프리젠터): 중개자 역할을 함 (View와 Model 사이), View에게 받은 입력으로 Model을 업데이트하고, Model의 변경사항을 감지하여 View를 업데이트 하도록 지시함, View와 Model을 강력하게 연결함

 MVP를 선호하는 이유
 유니티의 컴포넌트 기반 아키텍쳐(Component-based Architecture)와의 테스트 용이성 및 호환성이 좋기 때문이다.

 MVP는 MVC에서 파생되었지만 유니티 환경에서 몇 가지 핵심적인 차이가 있고 이 부분에서 MVP가 장점을 가진다.

 1.View와 Presenter 간의 명확한 분리 (느슨한 결합)

    MVC의 Cotroller: Controller와 View가 강하게 결합되는 경향이 있음 Cotroller가 입력을 처리하고 View를 직접 업데이트 할 수 있음

    MVP의 Presenter: Presenter는 중개자 역할을 함 (View와 Model 사이), View는 Presenter의 지시를 따르기만 함. View가 수동적인 역할을 하게 되고 자체 로직이 최소화 됨

    유니티에서의 장점: 유니티의 MonoBehaviour 스크립트는 필연적으로 Transform,Animator, GameObject 등 유니티 엔진 요소에 종속됨. 
    MVP에서는 이런 종속적인 코드를 View에 집중시키고, 제어 흐름과 핵심 게임로직을 담당하는 Presenter는 유니티 의존성을 줄일 수 있게 됨

2. 테스트 용이성 (Testability)
 테스트는 유니티 개발에서 중요한 이슈임
  
 MVP의 Presenter및 Model: 순수 C# 클래스(Plain C# Objects)로 MonoBehaviour를 상속받지 않을 수 있음. 이렇게 되면 유니티 엔진 없이 단위 테스트(Unit Test) 프레임 워크를 사용하여 쉽게 테스트가 가능함
 
 MVC의 Controller/View: 유니티 컴포넌트에 강하게 의존하는 경우, 전체 유니티 씬과 엔진 환경을 구동해야 테스트가 되어 테스트가 복잡하고 느려짐

 유니티에서의 장점: 유니티와 독립적으로 빠르게 테스트 가능 (핵심 게임로직(체력 계산, 사망 판정 등))

 3. 유니티 컴포넌트 시스템과의 조화
 
 MVP 구조: Presenter는 메인 Gameobject(예:보스)에 부착하고, View는 UI Canvas에 부착하는 등 유니티의 컴포넌트 기반 아키텍처에 자연스러운 매핑이 가능함

 MVC 구조: 전통적인 MVC 구조는 UI 프레임워크(예: 웹, 모바일)에 더 적합하게 설계되어서 유니티의 GameObject 계층 구조에 완벽하게 맞지 않을 수가 있음
 

 구글 AI의 도움을 받아 작성되었습니다.

2025년 11월 25일 화요일

[유니티][Unity][C#] Invoke() 사용

  Invoke()는 C#에서 "호출하다" 또는 "실행하다" 라는 뜻을 가진 메서드 또는 키워드이다.

 MVP로 코드를 만들 때 Invoke()는 이벤트(Event) 트리거로 사용된다.


1. 이벤트 트리거로서의 Invoke()

 C#에서 event Action OnDied; 와 같이 선언된 이벤트 핸들러를 실행하기 위해 내부적으로 invoke()를 사용한다.

 public event Action OnDied;

 if(_playerHealth == 0)

{

  OnDied?.Invoke();

}

 ?. (널 조건부 연산자): null인지 아닌지 판단해서 Null이 아니면 OnDied 이벤트에 구독된 하나 이상의 메서드가 있다면

Invoke(): 연결된 모든 메서드를 순서대로 호출(실행) 할 것

 플레이어 체력이 0이 되면 OnDied에 이벤트를 발생시켜 "체력이 0이 되었다"고 알려서 모두 실행시키게 한다.


2. Unity의 Invoke()메서드

  Unity에 있는 MonoBehaviour 클래스는 Invoke()라는 이름의 내장 메서드를 제공한다. 이 메서드는 특정 시간 지연 후에 함수를 실행하도록 한다.

void Start()

{

    Invoke("GameStart", 5f);

}


void GameStart()

{

    Debug.Log("게임 시작");

}


5초 후에 게임을 시작하도록 하는 Invoke 함수의 사용


 구글 AI의 도움을 받아 작성하였습니다.

2025년 11월 22일 토요일

[유니티][Unity] floatingJoystick 크기 조절

 https://dreamchallenger.blogspot.com/2025/11/unity_22.html

에서 가져온 floatingJoystick이 생각보다 커서 크기를 조절하고 싶었다.

코드를 살펴 보니 Floating Joystick prefab 하부의 자식들로

Background의 차일드 Handle이 있다.

이 백그라운드와 핸들의 크기를 조절해주면 된다.

초기 크기가 백그라운드는 posx posy width height가 256으로 되어 있다.

이걸 배수로 줄여 주지 않으면 중심 축이 무너질 거 같아서 1/4로 조절하였다.

그래서 64로 주고

핸들의 크기를 보면 width height 가 128이다. 그래서 1/4로 줄여서 32로 세팅하니 잘 동작하였다.


[유니티][Unity] floatingJoystick값 가져와서 케릭터 움직이기

 https://www.blogger.com/blog/post/edit/4269806382658593631/6192122025302748439

에서 가져와서 Canvas아래에 넣은 floating Joystick 이 움직이는대로 케릭터가 움직이게 해보자.


움직이고자 하는 케릭터의 controller.cs 에 아래와같이 선언해준다.

public FloatingJoystick floatingjoystick;


여기서 중요한 점이 이렇게 선언한 floatingjoystick에 주인공 Object에 연결된 controller Inspector 창으로 이동한다.

 그러면 public이기에 Floatingjoystick none 이라고 적혀 있을 것이다. 여기에 Floating Joystick prefab을 넣어주면 연동이 된다.

 이 후 사용하려면 움직이는 값에 넣어주면 된다. 

 아래와 같이 

float horizontalValue = floatingjoystick.Horizontal;

float verticalValue = floatingjoystick.Vertical;

이렇게 하면 floatingjoystick에 prefab이 연동되었으니 그 조이스틱이 움직이는 Horizontal, Vertical 값을 가져오게 된다.

그리고 속도 조절을 위해 아래와 같이 조치한다.

Vector2 pos2 = new Vector2(horizontalValue, verticalValue);

pos2 = pos2 * 0.1f * Time.fixedDeltaTime;

여기서 fixedDeltaTime은 프레임이 변해도 동일한 속도를 가지기 위해 넣어준다.

this.rigid2D.MovePosition(this.rigid2D.position + pos2);

this.transform.localScale = new Vector3(scaleX, 0.5f, 1);

이렇게 하면 원하는 데로 동작하는 것을 볼 수 있다.


참조: https://blog.naver.com/shookit/223258675307

[유니티][Unity] 클릭한 곳에 생성되는 조이스틱 만들기

 https://assetstore.unity.com/packages/tools/input-management/joystick-pack-107631#asset_quality

위 에셋을 다운로드 후 임포트한다.


다운로드 후 prefab 안의 Floating JoystickCanvas 아래에 넣으면 된다.


Canvas에 그려지는 요소라 Canvas 아래 child로 넣어야 되는 걸로 보인다.


[유니티][Unity][MVC] 보스가 날린 파이어볼을 플레이어에게 날리기

  보스가 날리는 파이어볼이 플레이어에게 날아가고 플레이어에 닿으면 없어지면서 데미지를 넣어 보자.


FireballModel.cs

using UnityEngine;


public class FireballModel

{

    public float Speed { get; private set; }

    public float Damage { get; private set; }

    public float Lifetime { get; private set; }


    public FireballModel(float speed, float damage, float lifeTime)

    {

        Speed = speed;

        Damage = damage;

        Lifetime = lifeTime;

    }

}


Fireview.cs
using UnityEngine;
using System;

public class FireballView : MonoBehaviour
{
    // Start is called once before the first execution of Update after the MonoBehaviour is created
    private Rigidbody2D rb;
    public event Action<Collider2D, FireballView> OnHit;


    void Start()
    {
        
    }

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        if( rb == null)
        {
            Debug.LogError("FireballView requires a Rigidbody component!");
        }
    }

    public void Launch(Vector2 direction, float speed)
    {
        if(rb != null)
        {
            rb.linearVelocity = direction * speed;
            //rb.useGravity = false;
        }

    }
   

    void OnTriggerEnter2D(Collider2D other)
    {
        Debug.Log("fireballview oncollisionenter");
        OnHit?.Invoke(other, this);
      
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

BossController.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UIElements;
using static UnityEngine.GraphicsBuffer;

public GameObject fireballPrefab;
//public Transform firePoint;
public float shootInterval = 2f;
public float fireballSpeed = 4f;
public float fireballDamage = 10f;
public float fireballLifeTime = 5f;


void Start()
{
    StartSkill();
}

public void StartSkill()
{
    //if (isCooldown) return;
    //isCooldown = true;

    // 주인공 위치 기준으로 스킬 시작
    if (player == null)
    {
        Debug.Log("player is null");
    }
    Vector2 targetPosition = player.position;
    

    // 일정 시간 후 스킬 발동
    StartCoroutine(ActivateSkillAfterDelay(targetPosition, bossSkillModel.WarningDuration));
    //Invoke(nameof(() => ActivateSkill(targetPosition)), bossSkillModel.WarningDuration);
    //Invoke(nameof(ResetCooldown), cooldownDuration);
}

IEnumerator ActivateSkillAfterDelay(Vector2 targetPosition, float delay)
{
    while (true)
    {
        yield return new WaitForSeconds(delay);
        if(Random.value > 0.5f)
        {
            Vector2 playerPosition = player.position;
            bossSkillView.ShowWarning(playerPosition, bossSkillModel.Radius);

            yield return new WaitForSeconds(skillWaitTime);

            ActivateSkill(targetPosition);
        }
        else
        {
            Shoot();
        }
        
    }
}

 void Shoot()
 {
     FireballModel model = new FireballModel(fireballSpeed, fireballDamage, fireballLifeTime);
     //Debug.Log("fire Point : " + firePoint.position);
     Vector2 firePosition = new Vector3(0.5f,3.5f);
     GameObject fireballGO = Instantiate(fireballPrefab, firePosition, Quaternion.identity);
     fireballGO.SetActive(true);
     FireballView view = fireballGO.GetComponent<FireballView>();

     if( view != null)
     {
         view.OnHit += (collider2D, fireballView) => OnFireballHit(collider2D, model, view);

         if (player == null)
         {
             Debug.Log("player is null");
         }
         Vector2 playerPosition = player.position;
         Vector2 shootDirection = (playerPosition - firePosition).normalized;
         Debug.Log("Shoot Direction: " + shootDirection);
         view.Launch(shootDirection, model.Speed);

         Destroy(fireballGO, model.Lifetime);
     }
 }

 void OnFireballHit(Collider2D other , FireballModel model, FireballView fireballView)
 {
     Debug.Log("player attack fireball");
     if (other.gameObject.CompareTag("Player"))
     {
         int minAttackPower = Mathf.RoundToInt(bossSkillModel.Damage - 20);
         int maxAttackPower = Mathf.RoundToInt(bossSkillModel.Damage);
         Debug.Log("player attack 2");
         other.gameObject.GetComponent<MainCharController>()?.TakeDamage(minAttackPower, maxAttackPower);

         if (fireballView != null)
         {
             Debug.Log("destroy fireball");
             Destroy(fireballView.gameObject);
         }
     }

     
     //Destroy(other.gameObject);
 }

본문은 구글 AI의 도움을 받아 작성하였습니다.

2025년 11월 19일 수요일

[유니티][Unity] 물음표(?)의 역할

 ? = null일 수 있음을 뜻한다.

int? number => number가 null 값을 가질 수 있음을 나타냄


?. = null이 아닐경우 실행하고 null 이면 null을 반환 한다.

user?.transform.position => user가 null이 아니면 transform을 가져오고 null이면 null을 반환


 본 내용은 구글 AI 답변을 참조하였습니다.

2025년 11월 16일 일요일

[유니티][Unity] 최신 버전의 에디터 다운받기

 Unity Hub 실행 -> Installs 선택 -> Install Editor 선택 

 Unity Supported 버전과 LTS 버전이 나타난다.

 Supported 버전은 관리 주기가 짧지만 최신 기능들을 사용해 볼 수 있고

 안정적인 버전을 선택하려면 LTS 버전을 선택한다.

Install을 선택하면 설치한다.

2025년 1월 3일 금요일

[GIMP][Copilot] GIMP(이미지편집앱)로 흰색을 투명색으로 바꾸기(배경색 지우기)

 GIMP를 이용해서 그림판의 기본 배경색인 흰색을 지워서 배경색을 투명하게 바꿔보겠습니다.


 GIMP에 그림판에서 그린 그림을 가져옵니다.

 

 레이어 -> 투명도 -> 알파 채널 추가를 선택합니다. (저는 기본적으로 알파 채널이 추가 된 상태였습니다.)

 

 색 -> 색상을 알파로 를 선택하면, 기본적으로 흰색을 투명색으로 바꿔주게 됩니다.


 원하는 색상을 택하면 해당 색을 투명하게 바꿔주는 것으로 보이는데, 그건 해보진 못했고 그림판 기본 배경색이 흰색이였던 관계로 흰색이 투명하게 바뀌게 되었습니다.

 이를 적용하기 위해서는 export as로 png 파일로 바꿔주면 됩니다. 다른 이름으로 저장하면 기본 확장자명이 xcf 이기에 png로 바꾸기 위해서는 export as를 해야 합니다. 

 export as 는 파일 -> export as 에 있습니다.


 이를 통해 흰색을 투명색으로 바꿔서 게임에 적용하면 불필요한 흰색 배경이 나타나지 않도록 하였습니다.


** 본문은 Microsoft Copilot의 도움을 받아 제작하였습니다.

[이미지편집앱][Copilot] Gimp 와 같은 무료 이미지 편집앱 추천

 GIMP와 유사한 기능을 제공하는 몇 가지 무료 이미지 편집 프로그램을 다시 추천해드릴게요:

  1. Photopea: 웹 기반으로 제공되며, 포토샵과 유사한 환경을 제공합니다. 설치 없이 바로 사용할 수 있어 편리합니다.

  2. Pixlr: 사용자 친화적인 인터페이스와 다양한 필터 및 효과를 제공하는 웹 기반 프로그램입니다.

  3. Fotor: 사진 편집 외에도 콜라주 제작 및 디자인 기능을 제공하는 멀티 기능 프로그램입니다.

  4. Krita: 주로 디지털 페인팅에 특화되어 있지만, 사진 편집도 가능합니다. GIMP처럼 오픈소스입니다.

  5. Paint.NET: 사용하기 쉬운 인터페이스와 다양한 기본 편집 도구를 제공하는 프로그램으로, 윈도우 사용자에게 추천합니다.

  6. Inkscape: 벡터 그래픽 디자인에 특화된 프로그램으로, 로고나 일러스트를 제작할 때 유용합니다.

이 프로그램들은 모두 무료로 제공되며, 각 프로그램의 특징과 장단점을 고려하여 자신에게 맞는 것을 선택해보세요. 어떤 프로그램이 마음에 드시나요?


 이 중에서 쉽게 사용 할 수 있다는 Paint.NET을 사용해 보기로 하였다. 기본적인 편집 기능을 통해서 스킬관련 이펙트나 범위 표시들을 직접 그려봐야겠다.

 근데 스토어에 유료로 올라와 있다. 비영리 개인 사용자는 무료로 사용 할 수 있지만, 상업적 용도로 사용할 때에는 라이선스를 구매해야 한다고 하니 다른 걸로 알아 보자.

 그냥 최초로 추천 받았던 GIMP를 설치해 봐야 겠다.

** 본 문은 Microsoft Copilot의  도움을 받아 작성되었습니다.