2018년 12월 13일 목요일

Unity에서 string.Format 으로 숫자에 comma 찍기

재화 표시에 Comma 를 추가하기 위해
string text = string.Format("{0:0.#,###}", Gold)
를 사용중이었는데, Gold가 0일 경우 string이 "0" 이 아니라 비어있었다.

애용하는 c# web ide 페이지에서는 같은코드를 사용해도 0으로 출력이 잘되는걸로 보아 유니티 문제인듯.

string.Format("{0:n0}", Gold)
를 사용하면 0도 정상적으로 표시 된다.

Gold.ToString("#,##0") 도 된다.

2018년 11월 28일 수요일

iOS는 파일명 대소문자를 구분한다

제목 그대로.

 번들 다운로드 시스템을 변경한 후 테스트 도중 아이폰에서만 파일을 찾지 못하는 현상이 발생.
 윈도우 유니티 에디터, 맥 유니티 에디터, 안드로이드 기기에서는 문제가 없었음

 매니페스트 파일명을 대문자로 넣었더니 아이폰에서만 파일을 찾지 못해서 소문자로 수정했더니 문자 해결.

 약 3/4일 걸림

2018년 10월 18일 목요일

WorldToScreenPos in UGUI

개발 중인 게임의 UGUI 캔버스 모드가 Screen Space - Overlay 에서 Screen Space - Camera 로 변경되게 되었다.

별 일 없을줄 알았는데, 월드 좌표를 UI 좌표로 바꿨을때 이생해져서 열심히 구글링을 한 결과, Render Mode에 따라 변환하는 방식이 다르다는것을 알게되었다.

Overlay 모드에서는 단순히 

rectTransform.position = RectTransformUtility.WorldToScreenPoint (Camera.main, target.position);

로 해결되었지만 Camera모드에서는 좀더 복잡한 계산이 필요하다

  var pos = Vector2.zero;
  var uiCamera = Camera.main;
  var worldCamera = Camera.main;
  var canvasRect = canvas.GetComponent<RectTransform> ();

  var screenPos = RectTransformUtility.WorldToScreenPoint (worldCamera, target.position);
  RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, screenPos, uiCamera, out pos);
  rectTransform.localPosition = pos;


참고 링크

일본 개발자들이 정리를 참 깔끔하게 하는것 같다. 번역도 편하고.

2018년 10월 15일 월요일

Sprite 를 Texture2D로 변환하던 중 발생한 문제

Atlas 의 Sprite 를 Texture2D로 변환하여 잘 활용하던 중, 2017.1에서 2018.3 b5로 유니티 버전업을 하게 되었다.

발생하던 모든 Error 를 다 처리하고 테스트 하는 도중 Sprite 에서 변환한 Texture2D가 나
오지 않는 걸 확인하고 여러가지 방법(텍스쳐 포멧 바꿔 보기 등)으로 수정하려고 노력해 보았지만 소용이 없었다.

원인은 Sprite.textureRect 의 모든 값이 0으로 반환되는 것이었는데 2018에 해당 내용이 Obsolated 된 것도 아니었다.

마지막이라는 심정으로 해당 아틀라스의 옵션을 확인하니, 2017.1 에서 설정했던 내용이 바뀌어 있었다. Allow Rotation 과 Tight Packing 옵션이 켜져 있었다. 그 중 Tight Packing 옵션을 끄니 Sprite.textureRect 값이 정상적으로 반환되었다.

2018년 9월 4일 화요일

GetAllScenePathsFromAssetBundle crash


  1. 백그라운드로 번들을 다운받고 캐싱하는 코드를 만들고 있는 중
  2. 번들 파일을 받고  UnityWebRequest.GetAssetBundle 을 사용하여 캐싱을 하면 에디터에서 100% 크래쉬가 발생(2017.2.1p1)
  3. Editor 로그를 확인하니까 마지막 콜스텍이 GetAllScenePathsFromAssetBundle
  4. 구글링 해 보니 나같은 현상이 발생한다는 애들이 있었음
    issuetracker
  5. We noticed that this bug was related to using WebRequest to cache bundles in background and not accessing the DownloadHandler.assetBundle property before the UnityWebRequest dispose.
  6. UnityWebRequest 를 쓸 때는 꼭 using 을 사용하자 유니티 메뉴얼

2018년 7월 3일 화요일

소소한 최적화


생각날 때마다 업데이트.
  • transform.position = p; transform.rotation = r; 대신 transform.SetPositionAndRotation(p,r)
  • null 조건 연산자를 사용하라
public class EventSource
{
    private EventHandler<int> Update;
    private int counter;    

    public void RaisUpdates()
    {
        counter++;
        if (Update != null)
            Update(this, counter);
    }
}
대신

    public void RaisUpdates()
    {
        counter++;
        Update?.Invoke(this, counter);
    }
Effective c# 8장 참고. Atomic 하다고 함.


  • Enum 을 문자열로 바꿀 때 nameof(MyEnum.EnumValue);
  • c# 7.2 에서 추가된 in 키워드를 최대한 활용하자. 링크 구조체를 파라매터로 넘길 때 속도가 10.1배 정도 차이가 난다
  • 만들 갯수가 예측되는 프리팹은 동적으로 만들지 말고 미리 만들어 놓자.

2018년 6월 28일 목요일

Unity Assert on Device


using System.Collections.Generic;

private readonly Dictionary<TYPE, IValue> _values = new Dictionary<TYPE, IValue>();

private void SetTable(TYPE type, IValue origin)
{
    Assert.IsFalse(_values.ContainsKey(type), type.ToString());
    _values.Add(type, origin);
}

public ITable GetTable(TYPE type)
{
    IValue value = null;
    Assert.IsTrue(_values.TryGetValue(type, out value));
    return value;
}



유니티에서 위의 코드를 사용하고 있었는데 지속적으로 핸드폰에서만 문제가 발생했다.

에디터에서는 재현이 되지 않아 로그를 추가하여 APK를 빌드하고 기기에 설치하여 Logcat 으로 로그를 확인해 보았다.

_values 에는 분명 값이 있고 _values.TryGetValue 를 사용하면 정상적인 값을 얻을 수 있었지만,  GetTable 함수를 사용하면 null 만 반환되는 문제였다.

몇 줄 되지 않는 코드라 대체 어떤것이 문제인지 도통 감을 잡을수가 없었다.

마지막이라는 심정으로 GetTable 함수의 Assert 를 제거하고 다시 APK를 빌드하여 핸드폰에서 실행하자 거짓말처럼 GetTable 함수가 정상적인 값을 반환했다.

대체 이해가 불가하여 옆자리 동료에게 물어보니 이유를 들을 수 있었다.

Assert 함수들의 소스 코드를 보면

[Conditional("UNITY_ASSERTIONS")]
public static void IsTrue(bool condition)
{
    Assert.IsTrue(condition, (string) null);
}

[Conditional("UNITY_ASSERTIONS")] 가 붙어있음을 확인 할 수 있다.

에디터에서 Assert 함수들이 잘 동작하는 이유는 유니티가 C# 프로젝트 파일을 만들때

속성에 위 스크린샷 처럼 UNITY_ASSERTIONS 속성을 강제로 넣어주기 때문이다.

APK에서는 UNITY_ASSERTIONS 속성이 정의되어 있지 않고, Assert 함수들은 동작하지 않게 된다.

게다가 GetTable 함수의 Assert 코드 위에 IValue value = null; 로 초기화까지 해 버렸다.(C++ 쓰던 습관으로)
아마 IValue value; 로 했었으면 APK 빌드 도중 컴파일 에러가 나서 더 빨리 문제를 찾을수 있었을 지도.

APK에서도 Assert 함수들을 사용하고 싶으면 UNITY_ASSERTIONS 디파인을
Player Settings -> Other Settings -> Scripting Define Symbols 에 추가하면 된다.

2018년 6월 26일 화요일

Android Studio Logcat 은 어디 있을까

안드로이드 스튜디오가 버전 업 되면서 최신 버전을 깔면 Logcat 이 나오질 않는다.
구글링을 해봐도 온통 Alt + 6 을 누르라는 글 뿐.

최신 스튜디오에서는 로그캣+Android Device Monitor 가 빠지고 Sdk 의 tool 로 옮겨 갔다 .
위치는

C:\Users\사용자이름\AppData\Local\Android\Sdk\tools\monitor.bat

위의 배치 파일을 실행하면 Logcat 이 포함되어 있는 Android Device Monitor 가 실행된다.

2018년 6월 22일 금요일

다운받은 AssetBundle 검증하기


  1. 아카마이에 올린 어셋 번들을 다운 받았을 때 cr and fl 문제가 발생해서 용량도 달라지고, 번들을 로딩하면 멈추거나 크래쉬가 발생함.
  2. 패치 서버로 부터 각 어셋번들 파일의 Hash와 CRC 정보를 제공받고 있으므로, 다운로드 후 .Net 코드를 이용하여 MD5해쉬와 CRC32 를 추출하여 패치서버의 AssetBundleManifest의 Hash, CRC와 비교했는데 같은 파일이라도 동일하지 않았음.
  3. 단순 용량 비교는 가능하지만 파일 변조를 검증할 수 없어서 검사하지 않음.
  4. 번들 파일만으로는 검증할 방법이 없어 Unity 의 AssetBundle.LoadFromFile 의 파라메터로 들어가는 Crc를 이용하기로 결정
  5. 패치 서버로 부터 받은 Crc 를 파라메터로 추가해서 LoadFromFile 함수를 호출하고, 바로 Unload(true) 를 호출 하도록 작업
  6. 변조된 번들 파일을 LoadFromFile 로 호출 해 보니(Crc 를 추가하여) 정확하게 에러로그와 함께 null을 뱉어 줌(Exception 이 아님).
  7. 번들파일 경로가 담긴 구조체를 List 에 넣어 Linq.Each 를 사용해서 LoadFromFile 을 호출 했을 때, 에러가 발생하면 Each 가 더 이상 동작하지 않아 for( 로 수정함

2018년 6월 21일 목요일

서버에서 DateTime을 문자열로 받는 경우


  1. 점검 시간 표시를 위해 서버에서 UTC 시간을 string 으로 받아오고 있었음.
  2. 서버에서 받은 string을 Local 로 변환하기 위해서 DateTime.Parse(문자열).ToLocalTime(); 을 사용하고 있었음.
  3. 서버 머신의 OS가 영문에서 한글로 바뀜
  4. 서버에서 주는 string의 내용이 6/21/2018 6:59:22 AM 에서 6/21/2018 오전 6:59:22 으로 변경됨
  5. DateTime.Parse 예외 발생


해결
  • 서버에서 ToString("yyyy-MM-dd HH:mm:ss") 으로 변경함.

폴더만 생성해도 IOException : Disk full 이 발생할 수 있다.


  1. 파이어베이스에서 IOException이 발생함
  2. 코드는 Directory.CreateDirectory인데 해당 기기에 40메가 정도 용량이 남은 상태
  3. try catch 로 Disk_Full 처리 함.
  4. https://stackoverflow.com/questions/9293227/how-to-check-if-ioexception-is-not-enough-disk-space-exception-type

TortoiseSVN - “is locked in another working copy” error

On Windows, I fixed the problem by the following steps:
1. Launch Repository Browser by clicking "Repo-browser" in the menu.
2. Locate the locked file.
3. Right click your mouse on the locked file.
4. Click "break lock".

https://stackoverflow.com/questions/21862134/svn-frequently-says-file-is-locked-by-me-in-another-working-copy

ADB로 메모리 확인

adb shell pidof com.xxx.xxx Pid 확인

adb shell dumpsys meminfo com.xxx.xxx 메모리 확인



PC에 기기가 두대 이상이면 안됨.

Unity Certificate 문제


  • 내부 번들 서버가 주소는 https 지만 인증서 기간이 만료되어 있던 상태에서 UnityWebRequest 를 사용하여 번들 패치 시스템을 만들어야 했음
  • www.google.co.kr은 접속이 되도 내부 번들 주소에 접근이 되지 않아서 삽질 중에 ServicePointManager.ServerCertificateValidationCallback = delegate { return true; }; 를 발견함
  • 비슷한 형식을 UnityWebRequest 에서도 발견함. public CertificateHandler certificateHandler
  • 유니티의 CertificateHandler 을 상속받은 클래스를 UnityWebRequest 의 certificateHandler에 넣어주면 끝
  • 에디터에서는 동작하나 APK에서는 동작하지 않음
  • 결국 주소를 https에서 http 로 변경함

Nox 디버깅


  1. Root 켜기
  2. 개발자 옵션, USB 디버깅 켜기
  3. 녹스가 켜진 상태에서 
C:\Program Files (x86)\Nox\bin>nox_adb.exe connect 127.0.0.1:62001 already connected to 127.0.0.1:62001 C:\Program Files (x86)\Nox\bin>nox_adb.exe devices List of devices attached 127.0.0.1:62001 device

NGUI Particle Clipping

중국 애들이 만든걸 주워옴

Shader "ParticlesAdditiveAreaClip"
{
Properties
    {
        _TintColor ("Tint Color", Color) = (0.5,0.5,0.5,0.5)
        _MainTex ("Particle Texture", 2D) = "white" {}
        _Area ("Area", Vector) = (0,0,1,1)
    }

    Category
    {
        Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" }
        Blend SrcAlpha One
        AlphaTest Greater .01
        ColorMask RGB
        Cull Off
        Lighting Off
        ZWrite Off
        Fog { Color (0,0,0,0) }

        SubShader
        {
            Pass
            {
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma multi_compile_particles

                #include "UnityCG.cginc"

                sampler2D _MainTex;
                fixed4 _TintColor;
                float4 _Area;

                struct appdata_t
                {
                    float4 vertex : POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                };

                struct v2f
                {
                    float4 vertex : SV_POSITION;
                    fixed4 color : COLOR;
                    float2 texcoord : TEXCOORD0;
                    float2 worldPos : TEXCOORD1;
                };

                float4 _MainTex_ST;

                v2f vert (appdata_t v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.texcoord = TRANSFORM_TEX(v.texcoord,_MainTex);
                    o.color = v.color;
                    o.worldPos = mul(unity_ObjectToWorld, v.vertex).xy;
                    return o;
                }

                fixed4 frag (v2f i) : SV_Target
                {
                    bool inArea = i.worldPos.x >= _Area.x && i.worldPos.x <= _Area.z && i.worldPos.y >= _Area.y && i.worldPos.y <= _Area.w;
                    return inArea? 2.0f * i.color * _TintColor * tex2D(_MainTex, i.texcoord) : fixed4(0,0,0,0);
                }
                ENDCG
            }
        }
    }

}

using System;
using UnityEngine;

[RequireComponent(typeof(UIPanel))]
public class CustomUIClipper : MonoBehaviour
{
    private const string ShaderName = "ParticlesAdditiveAreaClip";
    private const float ClipInterval = 0.5f;

    private UIPanel m_targetPanel;
    private Shader m_shader;

    private void Start()
    {
        // find panel
        m_targetPanel = GetComponent<uipanel>();

        if (m_targetPanel == null)
            throw new ArgumentNullException("Cann't find the right UIPanel");
        if (m_targetPanel.clipping != UIDrawCall.Clipping.SoftClip)
            throw new InvalidOperationException("Don't need to clip");

        m_shader = Shader.Find(ShaderName);

        //if (!IsInvoking("Clip"))
        //    InvokeRepeating("Clip", 0, ClipInterval);

        Clip();
    }

    private Vector4 CalcClipArea()
    {
        var clipRegion = m_targetPanel.finalClipRegion;
        var nguiArea = new Vector4()
        {
            x = clipRegion.x - clipRegion.z / 2,
            y = clipRegion.y - clipRegion.w / 2,
            z = clipRegion.x + clipRegion.z / 2,
            w = clipRegion.y + clipRegion.w / 2
        };

        var uiRoot = m_targetPanel.root;
        var pos = m_targetPanel.transform.position;
        var rate1 = (float)Screen.width / (float)Screen.height;
        var rate2 = (float)uiRoot.manualWidth / (float)uiRoot.manualHeight;
        const float h = 2f;
        var w = h * rate1;
        var tempH = h / uiRoot.manualHeight;
        var tempW = w / uiRoot.manualWidth;
        var tempRate = Mathf.Max(tempW, tempH);
        if (rate1 < rate2)
        {
            tempRate = Mathf.Min(tempW, tempH);
        }

        var result =  new Vector4()
        {
            x = pos.x + nguiArea.x * tempRate,
            y = pos.y + nguiArea.y * tempRate,
            z = pos.x + nguiArea.z * tempRate,
            w = pos.y + nguiArea.w * tempRate
        };

        return result;
    }

    private void Clip()
    {
        var clipArea = CalcClipArea();
        var renderers = GetComponentsInChildren<renderer>();
        for (var i = 0; i < renderers.Length; ++i)
        {
            var mat = renderers[i].material;

            if (mat.shader.name != ShaderName)
                mat.shader = m_shader;

            mat.SetVector("_Area", clipArea);
        }
    }
}
네이버 블로그에서 좋은걸 발견함 http://naver.me/5ink6yfx

msb3482 에러

VS 2015와 VS 2017이 동시에 설치되어 있어서 2015를 삭제하자 발생한 문제.

체크가 꺼져 있었음

Visual Studio 업데이트가 느릴 때

제어판 -> 네트워크 연결 -> 우클릭 -> 속성 -> IPV6 Off -> 재부팅

https://stackoverflow.com/questions/44563536/visual-studio-2017-installer-extremely-slow?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa