2024년 4월 12일 금요일

Unity 안드로이드 환경에서의 (adb 없이)효과적인 디버깅 방법

 

가끔씩 PC 환경과는 다르게 안드로이드에서 애플리케이션이 제대로 동작하지 않는 경우가 있습니다. 이때는 로그를 보면서 디버깅을 진행해야 합니다.

아래는 adb가 없을때 삼성 휴대폰에서 정리한 안드로이드 환경에서 Unity 애플리케이션을 디버깅하는 방법입니다.


  1. 문제가 의심되는 위치에 다음과 같은 형태로 로그를 추가합니다.

    Debug.Log("bgmEffect null");
    
  2. 휴대폰용으로 APK 빌드를 하여 문제를 재현합니다.

  3. ADB 프로그램이 있다면, 아래의 과정을 수행하지 않아도 됩니다. 그렇지 않은 경우에는 다음과 같은 절차를 따릅니다.

    *#9900#  화면 (두번째 메뉴 Run dumpstate/logcat, 다섯번째 Copy to sdcard 실행)

    • 휴대폰의 *#9900# 덤프 획득 모드로 진입
    • Run dumpstate/logcat 실행
    • Copy to sdcard 메뉴 실행
  4. 휴대폰을 PC와 연결한 후 log 폴더를 찾아서 dumpstate_모델명_날짜.log 파일을 PC로 복사합니다.

  5. 편집기 프로그램을 열고 "Unity :"로 검색합니다.

  6. 이렇게 로그를 확인하면 call stack이 나옵니다.

  7. 04-12 21:57:16.992 10610 31520 31564 I Unity   : bgmEffect null
    04-12 21:57:16.992 10610 31520 31564 I Unity   : GameManager:FadeOutToScene(String)
    04-12 21:57:16.992 10610 31520 31564 I Unity   : UnityEngine.Events.UnityEvent:Invoke()
    04-12 21:57:16.992 10610 31520 31564 I Unity   : UnityEngine.EventSystems.ExecuteEvents:Execute(GameObject, BaseEventData, EventFunction`1)
    04-12 21:57:16.992 10610 31520 31564 I Unity   : UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchPress(PointerEventData, Boolean, Boolean)
    04-12 21:57:16.992 10610 31520 31564 I Unity   : UnityEngine.EventSystems.StandaloneInputModule:ProcessTouchEvents()
    04-12 21:57:16.992 10610 31520 31564 I Unity   : UnityEngine.EventSystems.StandaloneInputModule:Process()
    


  8. 로그를 활용하여 필요한 경우 더 많은 곳에 로그를 추가하고 위의 절차를 반복하여 문제를 해결합니다.


이와 같은 방법을 통해 안드로이드 환경에서 Unity 애플리케이션의 문제를 신속하게 해결할 수 있습니다.





2024년 4월 10일 수요일

Unity에서 데이터 저장 최적화하기: 중복 저장 방지


Unity에서 데이터 저장 최적화하기: 중복 저장 방지

Unity 게임 개발 중, 플레이어의 진행 상황, 설정, 또는 게임 데이터를 저장하는 것은 필수적입니다. 하지만, 불필요하게 같은 데이터를 반복해서 저장하는 것은 효율적이지 못합니다. 이 글에서는 저장 전에 기존에 저장된 데이터와 내용이 같은지 비교하여, 같다면 저장하지 않는 방법을 소개합니다.


저장 데이터 비교 로직

먼저, 저장할 데이터와 기존에 로드된 데이터를 비교하는 함수 CompSaveData를 사용합니다. 이 함수는 SaveDataStruct 타입의 두 데이터 객체를 매개변수로 받아, 내부적으로 BinaryFormatter를 사용하여 객체를 바이트 배열로 직렬화한 후, 두 바이트 배열이 서로 같은지를 비교합니다.

csharp

private static bool CompSaveData(SaveDataSturct data1, SaveDataSturct data2)
{
    BinaryFormatter formatter1 = new BinaryFormatter();
    BinaryFormatter formatter2 = new BinaryFormatter();
    byte[] bytes1;
    byte[] bytes2;
    using (MemoryStream m = new MemoryStream())
    {
        formatter1.Serialize(m, data1);
        bytes1 = m.ToArray();
    }
    using (MemoryStream m = new MemoryStream())
    {
        formatter2.Serialize(m, data2);
        bytes2 = m.ToArray();
    }
    return bytes1.SequenceEqual(bytes2);
}


데이터 저장 최적화

저장할 데이터가 기존에 저장된 데이터와 다를 때만 데이터를 저장하는 로직을 구현합니다. 이를 위해, 먼저 기존의 데이터를 로드하고, 로드된 데이터가 없거나 (loaddata==null) 로드된 데이터가 있지만 현재 데이터와 다른 경우 (!CompSaveData(tempData, loaddata))에만 새로운 데이터를 저장합니다.

csharp

var loaddata = LoadData();
if ((loaddata==null) || (loaddata != null && !CompSaveData(tempData, loaddata)))
{
    WriteSaveDataToDisk(tempData);
    saveData = tempData;
}


데이터 저장 함수

데이터를 저장하기 위한 WriteSaveDataToDisk 함수는 BinaryFormatter를 사용하여 데이터를 직렬화하고, 지정된 경로에 파일로 저장합니다. 이 과정에서 데이터가 실제로 디스크에 기록되는 경로를 로그로 남겨, 디버깅에 도움을 줍니다.

csharp

private static void WriteSaveDataToDisk(SaveDataSturct data)
{
    BinaryFormatter formatter = new BinaryFormatter();
    string path = Application.persistentDataPath + "/game.savegame";
    FileStream stream = new FileStream(path, FileMode.Create);
    formatter.Serialize(stream, data);
    stream.Close();
    Debug.Log("Data saving path : " + path);
}


 




2024년 4월 7일 일요일

Unity 프로젝트 의존성 관리와 리소스 복사


Unity 프로젝트 의존성 관리와 리소스 복사


Unity 프로젝트를 진행하면서 외부 프로젝트를 가져오거나 Package Manager를 통해 다양한 리소스를 사용하게 되는 경우, 의존성(dependency) 관리에 어려움을 겪을 수 있습니다. 이를 해결하기 위한 몇 가지 방법을 소개합니다.


Select Dependencies 사용하기

  • 기능 설명: 프로젝트 내에서 특정 폴더를 선택한 후, 오른쪽 마우스 클릭 메뉴에서 'Select Dependencies'를 선택하면, 해당 폴더가 의존하고 있는 모든 파일들의 목록을 볼 수 있습니다. 이를 통해 어떤 파일들이 실제로 사용되고 있는지 파악할 수 있습니다.
  • 장점: 사용 중인 의존성 파일들을 한눈에 확인할 수 있으며, 파일을 클릭하면 상세 내용을 볼 수 있습니다.

Export Package 사용하기

  • 기능 설명: 'Export Package' 메뉴를 사용하면, 필요한 리소스만을 선택하여 Tree 형태로 확인할 수 있습니다. 이를 통해 불필요한 파일들을 쉽게 식별하고 삭제할 수 있습니다. 
  • 장점: 폴더 단위로 불필요한 파일들을 편리하게 삭제할 수 있으며, 프로젝트의 의존성을 보다 명확하게 관리할 수 있습니다.

Unity 프로젝트를 관리하면서 의존성 문제를 해결하는 것은 프로젝트의 효율성과 유지보수성을 높이는 데 중요합니다. 'Select Dependencies'와 'Export Package' 기능을 적절히 활용하여, 프로젝트의 의존성을 체계적으로 관리해 보세요. 🙂





2024년 3월 22일 금요일

python xml comment parsing

python xml comment parsing

python에서는 xml 파싱하는 방법은 여러가지 준비가 되어 있습니다.

그런데 간혹 comment 까지 파싱 해야 하는 경우가 있습니다. 

XML은 데이터를 저장하고 전송하는 데 사용되는 마크업 언어입니다. 다음은 XML의 간단한 예제입니다

python에서 아래와 같은 xml을 string에 담았습니다.

xml_str = \
"""<?xml version="1.0" encoding="UTF-8"?>
<!-- hello -->
<note>
<!-- dash1 -->
<to>kim</to><!-- dash2 -->
<from>lee</from>
<heading>alert</heading>
<body>i am a boy</body>
</note>
"""

여기에서 XML 파서는 lxml 을 사용합니다. 이 패키지는 comment까지 완벽하게 처리해냅니다.

기본 xml 처리는 아래와 같은 형태를 사용 가능합니다.

def print_xml(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

결과를 아래와 같이 실행시키면

print_xml(xml_str)

다음과 같은 결과를 얻습니다.

Root element: note
Child element: <cyfunction Comment at 0x00000234BF7132B0>
Child element: to
Child element: <cyfunction Comment at 0x00000234BF7132B0>
Child element: from
Child element: heading
Child element: body

cyfunction 이라고 나오는곳이 comment가 됩니다. 이 부분을 처리하려면 아래와 같은 코드가 필요합니다. tag가 etree.Comment 인지 비교해서 따로 처리하는 방법입니다.

if child.tag == etree.Comment:
print("comment:", child.text)

그러면 처음 예제와 합쳐서 구현해보도록 하겠습니다.

def print_xml_wc(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
if child.tag == etree.Comment:
print("comment:", child.text)
continue
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

이런 식으로 됩니다.

print_xml_wc(xml_str)

수행한 결과는 아래와 같습니다.

Root element: note
Child element: <cyfunction Comment at 0x00000234BF7132B0>
comment: dash1
Child element: to
Child element: <cyfunction Comment at 0x00000234BF7132B0>
comment: dash2
Child element: from
Child element: heading
Child element: body


xml 데이터의 root node보다 앞쪽에 있는 comment 처리 방법

xml을 자세히 보면 앞쪽 hello 라는 주석을 파싱하지 못하는 부분이 있습니다.

이건 어떻게 해야할까요?

처음에 문서도 보고 한참 헤맸는데요. 의외로 간단합니다.

element의 getprevious() 함수를 사용합니다.

변경된 코드는 아래와 같습니다.

def print_xml_wc_root(xml_str_):
# XML 파일을 불러옵니다.
root = etree.fromstring(xml_str.encode())

if root.getprevious() != None:
if root.getprevious().tag == etree.Comment:
print("comment:", root.getprevious().text)

# 루트 엘리먼트의 태그와 속성을 출력합니다.
print(f'Root element: {root.tag}')
for name, value in root.attrib.items():
print(f'Attribute - {name}: {value}')

# 모든 자식 엘리먼트를 순회하며 출력합니다.
for child in root:
print(f'Child element: {child.tag}')
if child.tag == etree.Comment:
print("comment:", child.text)
continue
for name, value in child.attrib.items():
print(f'Attribute - {name}: {value}')

root node의 앞쪽 tag 가 comment 라면 출력하는 코드를 넣었습니다.

실행한 결과는 아래와 같고

comment:  hello 
Root element: note
Child element: <cyfunction Comment at 0x0000026F045732B0>
comment: dash1
Child element: to
Child element: <cyfunction Comment at 0x0000026F045732B0>
comment: dash2
Child element: from
Child element: heading
Child element: body

전체 소스는 git hub에서 보시기 바랍니다.

sourcecode/python/example/_63_lxml/lxml_comment.py at main · donarts/sourcecode · GitHub



2024년 3월 21일 목요일

Unity Text scroll credits 만들기

 Unity로 Text가 위로 스크롤 되는 credits 입니다.

완성된 영상 입니다.


동작 원리

텍스트 스크롤 영역을 정하고 그곳에 텍스트를 주기적으로 갱신합니다.
스크롤은 Update에서 적절히 진행하며 특정 크기(텍스트 높이)만큼 스크롤이 되면 한줄을 추가하고 스크롤된 만큼 다시 스크롤 영역을 빼서 내려줍니다. 
모든 텍스트가 스크롤 되어 올라갈때까지 이 과정을 반복합니다.

소스 코드

https://drive.google.com/file/d/1XnTsYZyoe1uWSm1CW4SVWuKI_TBQg3KV/view?usp=sharing


조정 인자


MaxLinesOnScreen

화면에 Text가 몇 줄이 나타나는지 나타내는 숫자

Text 폰트나 화면 크기에 따라 화면에 표시되는 글자의 수가 다르므로 적절히 변경해야합니다.

줄 수를 알 수 있도록 Credits_Text 에 숫자를 넣어 놓았습니다. 여기에서는 25줄입니다만 더 작을 수도 늘어날 수도 있습니다.

CreditsFile

text 형태로 된 파일

LineHeight

텍스트의 높이

ScrollSpeed

스크롤 속도



2024년 3월 16일 토요일

OnScreen Controls not working correctly in Unity

 

Unity On Screen 사용시 컨트롤이 되지 않는 문제

증상은 

Player Input에 Auto-Switch off 상태에서 Debug 모드상 Keyboard&Mouse 가 고정되어 On screen game pad를 사용할 수 없는 증상


댓글에 유사 증상 언급이 있음

On Screen Controls not working correctly. - Unity Forum


해결 방법

Runtime에서 Player Input 컴포넌트를 disable/enable 했을때 정상적으로 동작되는지 확인 필요


정상 동작된다면 Player Input 컴포넌트를 off된 상태에서 시작해서 Start에서 enable 해줍니다.

다음과 같은 형태로 start 시 enable 시켰음
void Start()
{
PlayerInput playerInput = GetComponent<PlayerInput>();
playerInput.enabled = true;
}





2024년 3월 10일 일요일

Vector2 Serialize 할때 오류 발생하는 경우

Unity에서 Vector2 Serialize 를 사용하게 되면 아래와 같은 오류가 발생합니다.

SerializationException: Type 'UnityEngine.Vector2' in Assembly 'UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

아래와같은 Vector2S 와 같은 Class로 변경해주면 사용이 가능합니다.

Vector2 로 된 변수를 Vector2S 에 넣는 것이 가능하기 때문에 Serialize 하는 변수를 Vector2S 변경하고 Vector2 값을 Vector2S 로 복사 하게끔 구현해주면 됩니다.


[Serializable]
public struct Vector2S
{
public float x;
public float y;
public Vector2S(float x, float y)
{
this.x = x;
this.y = y;
}
public override bool Equals(object obj)
{
if (!(obj is Vector2S))
{
return false;
}
var s = (Vector2S)obj;
return x == s.x &&
y == s.y;
}
public Vector2 ToVector2()
{
return new Vector2(x, y);
}
public static bool operator ==(Vector2S a, Vector2S b)
{
return (a.x == b.x) && (a.y == b.y);
}
public static bool operator !=(Vector2S a, Vector2S b)
{
return (a.x != b.x) || (a.y != b.y);
}
public static implicit operator Vector2(Vector2S x)
{
return new Vector2(x.x, x.y);
}
public static implicit operator Vector2S(Vector2 x)
{
return new Vector2S(x.x, x.y);
}
}