2019년 2월 22일 금요일

Unity3D Oculus Go Spec & video quality

유니티를 활용한 오큘러스 고 빌드와 오큘러스 고 내부에서 360 비디오를 출력 하는 내용에서의 시행착오 및 정보들을 정리 하고자 한다.


Oculus Spec



주관적인 의견으로는 오큘러스 고의 화질은 그렇게 좋진 않다. 안경쓴 사람은 늘 그렇듯 착용하기 힘들고, 같이 주는 안경을 쓸 수 있개 고무패드? 같은것들을 주는데 마땅히 쓸일 없을것 같다.. 100%의 완전한 충전으로 2시간~3시간가량의 영상을 볼 수 있다는데... 아직 배터리의 부족함은 느끼지 못한것 같다.

개발함에서 어려운 점은, 명령프롬프트 창을 열고 'adb Install apk의 주소'를 입력 하거나
'adb Uninstall 프로젝트명(com.Company.Name)'는 등등 명령어로 처리 해야하는 부분이 있다. 처음 초기 개발자 셋팅이 어렵진 않지만 귀찮을 수 있으며, 개발자에게 약간 불친절? 한 느낌을 받았다.

오큘러스 고 해상도 변경

출처: https://www.clien.net/service/board/lecture/12193042

안녕하세요, 오큘러스 고 사용중에 클리앙에 올리신 사용기 글을 보고 
우연히 해상도 향상법을 알게 됐는데... 적용해보니 효과가 너무 드라마틱하게 좋아서 좀 더 자세히 알아보고 글 써서 공유해봅니다.
먼저 사용기에 글 써서 해당 정보 알려주신 까샨님 정말 감사합니다.

오큘러스 고는 2560 x 1440 해상도를 지원하는 기기입니다. 

그런데 오큘러스 고에서 어플을 구동시키게 되면 이 해상도를 모두 활용하지 않습니다. sky box와 같은 동영상 앱을 작동시켰을 때만 해상도를 모두 활용하고, 대다수의 앱에서는 1024 해상도로만 작동하게 되어있습니다. 이는 기어 대다수 앱이 기어 vr에 최적화되어있기 때문으로 보입니다. 배터리, 발열 문제도 있을테고요.

현재 오큘러스 스토어에 있는 어플은 대부분 갤럭시 s7 정도 스펙에 최적화 되어 있습니다. 오큘러스 고는 갤럭시 s7과 동일한 스냅드래곤 821 칩셋을 탑재하고 있지만, 주변부 저해상도 렌더링 및 기타 트윅, 그리고 따로 휴대폰 관련 서비스가 없어 리소스를 낭비하지 않는다는 점, 발열 제어가 훨씬 나은 점 등의 여러 요인으로 인해 갤럭시 s7보다 훨씬 나은 성능을 보여줍니다. 이건 제 뇌피셜이 아니라 https://developer.oculus.com/blog/optimizing-oculus-go-for-performance/ 이곳에 자세한 설명이 나와있슴다.


이런 성능상 이점 덕분에, 해상도를 1.5배에서 2배 정도 높여도 대다수의 앱 구동에 별 문제가 없으며 
텍스쳐 1.5배 향상의 경우 프레임 드랍이 있는 어플은 거의 없었습니다. 
추후 오큘러스 go에 맞게 해상도 설정 및 그래픽 옵션 설정이 다 된다면 아래에서 소개할 트윅은 별 필요가 없어질 것이라고 합니다. 그러나 아직은 거의 모든 앱이 기어 vr에 최적화 되어 있기 때문에 매우 유용한 정보입니다.

아래에 설정법을 공유합니다.

1. 먼저, 오큘러스 go가 페어링된 휴대폰에서 Oculus 어플에 들어갑니다.
2. 어플에 들어간 후 설정에 들어가면, 현재 연결된 오큘러스 Go 헤드셋이 뜹니다. 아래와 같습니다.


3. <설정 더보기> 탭을 클릭하세요.  그 다음 <개발자 모드>를 눌러주세요.

개발자 모드를 켜게 되면 바로 활성화 되지 않고 인터넷 창이 하나 뜰겁니다.
여기서 양식 입력하기 누르시고 원하는 이름 입력하시면 됩니다. 그런 후 다시 돌아와 개발자모드를 켜주세요.

아이폰은 이 메뉴가 바로 뜨지 않는다고 합니다!!!
오큘러스 홈페이지에서 먼저 오큘러스 개발자 등록을 해야 해당 메뉴가 보이는 듯합니다.


압축 풀어주세요! 경로는 c드라이브, adb 폴더 만들어서 압축 풀면 됩니다.



경로가 위와 같이 설정되어있는지 확인해주세요. 맞다면 다음 단계로 넘어가시면 됩니다.

5. 거의 다 왔습니다. 이제 오큘러스 고 헤드셋을 컴퓨터에 연결해주세요.
    연결하시고, 오큘고 화면 보시면 데이터 전송 권한 수락해달라는 창이 뜹니다. 수락 눌러주세요. 

6. 명령 프롬프트를 실행해주세요. 시작 누르시고 명령 프롬프트라고 검색하시면 바로 뜹니다.

실행되면, cd/adb/platform-tools 라고 입력 후 엔터.


이렇게 뜨면 정상입니다. 혹시 안되시는 분들은 다른 경로에 adb 설치하신 분들일텐데, 경로 알맞게 입력하시면 됩니다.

7. 다시 명령 프롬프트에   adb devices     라고 입력해주세요.

오큘러스 고 해드셋이 검색될겁니다. 시리얼 넘버가 뜰거예요.

1KWP어쩌구저저구8171  device

이런식으로요. 저렇게 안뜨면 오큘러스 헤드셋 한 번 써보세요! 권한 요구하는 창이 떠있으면 확인 눌러주시면 됩니다.

8. 이제 해상도를 바꿔봅시다.

*  트윅은 오큘러스 고 전원이 껐다가 켜지면 초기화 된다는 것을 알아두셔야 합니다.
    만약 전원 껐다가 켰다면 다시 pc 연결해서 cmd로 다시 설정해주셔야 합니다ㅜ
     슬립모드 들어갔다 나오는걸로는 초기화되지 않습니다. 

1536 해상도 (1.5배)

adb shell setprop debug.oculus.textureWidth 1536
adb shell setprop debug.oculus.textureHeight 1536

입력하시면 됩니다.
한번에 한 줄 치고 엔터, 두 줄 모두 입력하세요. 자세히 보시면 둘이 다릅니다!
2048 해상도(2배 텍스쳐 해상도)로 올리고 싶으시면,
위 명령어의 1536 부분을 2048으로 바꾸시면 됩니다.

그러나 2048 설정은 권장하는 설정이 아니라고 하네요.
기본적으로 오큘러스 고는 렌즈 중앙 부분의 화면에 대해 1536 해상도를 지원하기 때문에 이 이상 해상도를 높이는 것은
효과가 적을 뿐더러 다운 스케일링으로 인한 앨리어싱을 유발할 수 있다고 합니다.
@ 추가로,

해외 포럼에서는 색수차 보정 및 FFR 옵션을 포함해서 아래와 같이 설정을 조정하는 것이 좋다고 말하고 있습니다.

베스트 세팅값 아래에 적어드립니다!

글 수정합니다. 적힌 옵션 중 마지막 줄은 약간의 문제가 있습니다. 일단 해상도와 FFR만 적용해주세요! (세번째 줄 까지)

 조만간 글 업데이트하겠습니다 

adb shell setprop debug.oculus.textureWidth 1536
adb shell setprop debug.oculus.textureHeight 1536
adb shell setprop debug.oculus.foveation.level 3
adb shell setprop debug.oculus.forceChroma 1


세번째 줄이 FFR 설정입니다.  옵션 적용시  화면 가장자리를 흐릿하게 렌더링해서 gpu 부하를 줄여줍니다.
Ultrawings 같은 최신 게임들에는 이미 적용되어서 나오지만, 대부분의 기어 vr용 게임에는 적용되어있지 않습니다.
강제로 옵션을 켜주세요. 일부 고퀄 게임들에서 프레임 향상 확인했습ㄴ디ㅏ.

마지막줄 CA 옵션은 일단 보류... 오큘러스 홈에서 약간 오류가 있습니다. 색수차 보정 옵션인데 크게 중요한 옵션은 아닙니다.


해상도 부분은 직접 써보시고 원하는 대로 조정하시면 되겠습니다. 2048에서는 약간 프레임 떨어지는 앱들이 몇 있긴 합니다

네.. 간단하죠? 끝입니다. 정상 적용되었습니다. 기본 메뉴, 오큘홈, 동영상 소스(sky box나 pigasus 어플에서는 극장 퀄리티만 증가하고 영상 재생 퀄리티는 원래 최대해상도라 그대로입니다)에는 적용되지 않습니다. 기타 게임, 체험 어플 등에서 적용됩니다.


!!! 주의하실것은, 해상도 트윅 적용 시 배터리 타임이 대략 30프로정도 줄어든다고 합니다. 저는 보조배터리를 연결해서 사용하고 있어서 문제가 없지만, 아닌 분들께는 문제가 될 수 있을 것 같아요.
위에서 말씀드렸듯이, 

이 트윅은 오큘러스 고 전원이 껐다 켜지면 초기화됩니다. 다시 명령 프롬프트 열어서 위 명령어 입력해주셔야 합니다.

(슬립모드 들어갔다 나와도 초기화되지 않습니다)
저는 적용하고 ultrawings를 비롯해서 몇 개 어플을 일단 켜봤는데, 
와.. 다른 기기 쓰는 기분이네요. 긴 말이 필요 없습니다. 화질 별로라 지웠던 어플들 다 다시 받고있습니다.

궁금한 점은 댓글 남겨주세요. 바로바로 대답해드리겠습니다. 감사합니다.

(혹시나 해서 적습니다... 까샨님 글에 나와있는 Max CPU/GPU 설정은 하지 마세요.. 오히려 프레임 드랍이 생깁니다 ㅠㅠ)

참고

아.. 그리고 한가지 부탁드릴게 있습니다 ㅠㅠ

If the user picks 1536 & no FFR is set yet, automatically set it to 1/low. At this resolution, the extra pixels in the periphery is already wasted, and low FFR won't make any visible difference. (By the way, 1536 is 1.52 = 2.25x the resolution of 1024 since you're comparing areas.) 

FFR (Fixed Foveated Rendering) 옵션에 대해서는 조금 더 정보가 필요한데, 저 위의 말이 무슨 뜻인지 정확히 알려주실 분 계신가요? ㅠㅠ

//////////////// 

Add higher video bitrates, or make it a user input (in Mbps) from 5-80. Depending on the experience, even 10Mbps would be insufficient from removing blocking artifacts. In the worst case I've seen (Twobit), even 40Mbps will contain artifacts. 

이 부분도 정확히 번역해 주실 분 계신가요? 부탁드립니다..

Double video capture bitrate (60Mbps)
adb shell setprop debug.oculus.videoBitrate 60000000


오큘러스 고 에서의 4k, 8k

내용: https://developer.oculus.com/documentation/mobilesdk/latest/concepts/mobile-media-overview/#mobile-media-overview

간단히 설명하자면 4k까지는 재생할 수 있지만, 그 이하로 낮춰 재생하길 권장한다.

영상 제작

- 이번에 작업물을 오큘러스 고에 설치 해야 하는 일이 있었는데, 원래 초기 버전은 PC플랫폼 Vive를 활용한 프로젝트를 카메라만 교체해 오큘러스 고에 설치 해야 했다.

당연하게도, 최적화의 문제와 최적화의 문제... 최적화의 문제가 존재했다.
그래서 해당 프로젝트를 영상으로 촬영하여, 오큘러스 고에 넣기로 했다.

https://assetstore.unity.com/packages/tools/video/vr-panorama-360-pro-renderer-35102
위의 에셋을 구매하여 360 영상을 촬영 한 후에 영상을 오큘러스 고에 설치하려 했지만,
프로젝트와 버전이 맞지 않아 애를 조금 먹었다.


영상 출력

설명: https://blogs.unity3d.com/kr/2018/01/19/getting-started-in-interactive-360-video-download-our-sample-project/

에셋 설치: https://www.assetstore.unity3d.com/en/#!/content/108974

위의 설명과 링크에서 제공하는 에셋을 활용 하여, 360영상의 실행을 기본적으로 익혔는데,
우선Unity 207.3.0이상에서 제공하는 Video Component 를 활용하는 방식이었다.
영상을 Material 에다가 입힐지 해상도에 맞춰서 Texture에 Mesh모양대로 출력할지... 대체적으로 간단한 컴포넌트 이니  https://docs.unity3d.com/kr/2018.3/Manual/class-VideoPlayer.html 이 문서를 참고하면 좋을것 같다.



정보 상 잘 못된 자료가 있다면 댓글 남겨주시면 감사하겠습니다.



2019년 1월 29일 화요일

[C#] UnityEditor를 활용 하여 클래스 표시하기









처음에 어떤 이벤트가 들어있는 클래스들을 넣으면 유니티에 인스펙터 뷰에 있는 프로퍼티
값들이 변경 되었으면 좋는데~ 로 시작해서 제작 하게 되었다.

출처:
https://www.linkedin.com/pulse/unity-hack-dynamic-property-field-inspector-zhen-stephen-gou?trk=portfolio_article-card_title

위의 링크를 참고하여 제작하였습니다.

총 3개의 모듈이 필요하다.

1. 인스펙터에 출력할 클래스
- 기능에 따른 클래스들을 나눔

2. 인스펙터에 정말로 그려줄 에디터
- 실질적인 출력 부분을 담당 할 클래스

3. 실질적으로 오브젝트에 달라붙을 클래스.
- MonoBehaviour 이 녀석을 상속 받아야 오브젝트에 추가 가능

거두절미하고 코드부터 확인하자.


using System;
using UnityEngine;

[Serializable]
public abstract class ObjectEvent
{
    public GameObject m_EventObject;
    public float m_fTime;

    public abstract bool Execute();
}

[Serializable]
public class CreateObj : ObjectEvent
{
    public Transform m_Position;
    public override bool Execute()
    {
        Debug.Log("Create Object!!");
        return true;
    }
}

[Serializable]
public class CreateVecPosObj : ObjectEvent
{
    public Vector3 m_Position;
    public override bool Execute()
    {
        Debug.Log("Create Object!!");
        return true;
    }

}

[Serializable]
public class DestroyObj : ObjectEvent
{
    public override bool Execute()
    {
        Debug.Log("Destroy Object!!");
        return true;
    }
}

[Serializable]
public class AnimPlayObj : ObjectEvent
{
    public override bool Execute()
    {
        Debug.Log("Play Animation!!");
        return true;
    }
}

[Serializable]
public class AudioPlayObj : ObjectEvent
{
    public float m_fValue;
    public override bool Execute()
    {
        Debug.Log("Audio Play!!");
        return true;
    }
}
1. 각 이벤트들에 필요한 변수값들을 담은 클래스들을 정의해 놓는다.
- 위의 코드는 이벤트에서 중복되는 변수를 둔 부모 클래스를 정의하고
- 아래 각 세부 기능들을 담당하는 자식클래스를 두면서 작업한다.
- 위의 장점은 기능들을 세분화 하기에 용의하고, 코드 재활용성이 올라감에 따른 사람눈이 편-안 하다.



using System;

[Serializable]
public class EventFactory
{
    public enum EventType
    {
        CreateEvent,
        CreateVectorObj,
        DestroyEvent,
        AnimPlayEvent,
        AudioPlayEvent,
    }

    public EventType Type = EventType.CreateEvent;
    public CreateObj CreateObj = new CreateObj();
    public CreateVecPosObj CreateVecPosObj = new CreateVecPosObj();
    public DestroyObj DestroyObj = new DestroyObj();
    public AnimPlayObj AnimPlayObj = new AnimPlayObj();
    public AudioPlayObj AudioPlayObj = new AudioPlayObj();


    public ObjectEvent GetEvent()
    {
        return GetEventFromType(Type);
    }

    public System.Type GetClassType(EventType evnetType)
    {
        return GetEventFromType(evnetType).GetType();
    }

    private ObjectEvent GetEventFromType(EventType type)
    {
        switch (type)
        {
            case EventType.CreateEvent:
                return CreateObj;
            case EventType.CreateVectorObj:
                return CreateVecPosObj;
            case EventType.DestroyEvent:
                return DestroyObj;
            case EventType.AnimPlayEvent:
                return AnimPlayObj;
            case EventType.AudioPlayEvent:
                return AudioPlayObj;
            default:
                return CreateObj;
        }
    }

}
1-1. 이벤트들을 조합하는 팩토리 클래스를 하나 둔다.
- 위의 각 자식객체들을 동적 생성 해놓은 후에 선택 되는 값에 따른 리턴 벨류를 두도록 한다.
- 유저는 enum값으로 값을 선택 할 것이기 때문에 간편하고 빠른 스위치 케이스문으로 대체 한다.
- 여기서 클래스 들의 이름은 변수의 명과 같아야 하는데 Editor class 에서 그 이유를 설명 할 것이다.


using UnityEditor;

[CustomEditor(typeof(ActiveEvent), true), CanEditMultipleObjects]
public class ActiveEventEditor : Editor
{

    protected static string EVENT_FACTORY_NAME = "PrimaryEvent";
    protected ActiveEvent ActiveEventobject;
    protected SerializedObject serializedEvent;
    protected SerializedProperty SerializedEventProp;

    void OnEnable()
    {
        ActiveEventobject = (ActiveEvent)target;
        serializedEvent = new SerializedObject(ActiveEventobject);
        SerializedEventProp = serializedEvent.FindProperty(EVENT_FACTORY_NAME);
    }

    public override void OnInspectorGUI()
    {
        serializedEvent.Update();

        DrawPropertiesExcluding(serializedEvent, new string[] { EVENT_FACTORY_NAME });
        DrawPrimaryEvent();
        serializedEvent.ApplyModifiedProperties();
    }

    protected void DrawPrimaryEvent()
    {
        EditorGUILayout.LabelField(EVENT_FACTORY_NAME, EditorStyles.boldLabel);
        EditorGUILayout.PropertyField(SerializedEventProp.FindPropertyRelative("Type"));

        //display relevant ability properties based on ability type
        EventFactory EventFactory = ActiveEventobject.PrimaryEvent;
        System.Type typeOfAbility = EventFactory.GetClassType(EventFactory.Type);

        SerializedProperty specificEvent = (SerializedEventProp.FindPropertyRelative(typeOfAbility.ToString())).Copy();

        string parentPath = specificEvent.propertyPath;
        while (specificEvent.NextVisible(true) && specificEvent.propertyPath.StartsWith(parentPath))
        {
            EditorGUILayout.PropertyField(specificEvent);
        }

    }
}
2. 당연한 소리지만 Editor를 상속받아야 유니티 안에 에디터 기능들을 사용 할 수 있다.
- WindowEditor, Editor 다 비슷한 기능들을 지원하니까 레퍼런스 검색을 통해 입맛에 맞는 기능들을 사용하면 될것 같다.
- OnEnable() 이 함수안에서 초기화를 시작한다.
- OnInspectorGUI() 이 함수에서 그려준다.
- DrawPrimaryEvent() 그려줄 기능들을 분할한 함수이다.

- 왜 1-1 글에서 클래스 이름과 동일한 변수명을 써주어야 하는 이유는 바로 여기에 있다. SerializedProperty specificEvent 변수에 typeOfAblility type변수를 가져와서 ToString()해주기 때문이다. 기본적으로 FindPropertyRelative 인자값으로 클래스 이름을 string값으로 넣어 주어야 하기 때문이다.

https://docs.unity3d.com/Manual/script-Serialization.html
- Serialization 에 대한 설명 글

https://docs.unity3d.com/ScriptReference/SerializedObject.html
- SerializationObject에 대한 설명 글

https://docs.unity3d.com/ScriptReference/EditorGUILayout.html
- EditorGUILayout 클래스 기능 설명 글



* 그래픽디자이너와 같이 작업 하기엔 프로그래머가 편하려면 에디터를 사용해서 기능들을 추가 해주는것이 더 효육적인것 같다.
더 이상 public 을 남발하는 그런 상황은 다메요..
물론, serializable serializefield를 사용 해도 괜찮지만, 상황에 따라서 맞춰 써야할 것 같다.

2017년 1월 23일 월요일

[C#] .Net Winform with FireBird and FlameRobin (Feat.Thread and Timer)

*허접한 프로그램입니다. 공부겸 해서 만든것이니, 실무에서는 이렇게 만드시지 않는걸 권장합니다.

*잘못된 점이나, 이해가 안되는 부분 찝어주시면 정중히 감사합니다.

C# 윈폼과 Firebird를 연동하던중 리스트뷰가 자동으로 갱신되었음 좋겠다.생각해서 시작한건데.... 오랫만에 닷넷을 만지려니 이것저것 어려움이 있었다...
그래도 그나마 원하는 기능을 구현 할 수 있어서 좋았던것 같다.



애쓴것 보다 허접한 데이터 관리 프로그램이다...

코드를 보자
using System;
using System.Data;
using System.Threading;
using System.Windows.Forms;
using FirebirdSql.Data.FirebirdClient;

namespace DB_TEST
{
    public partial class Form1 : Form
    {
        FbConnectionStringBuilder fdb = new FbConnectionStringBuilder();
        FbConnection connection;
        FbCommand cmd;

        ThreadStart ts;
        Thread reFresh;

        private static System.Timers.Timer atimer;
        
        public string connectionString = "";
        public int ID_Count = 0;
        public Form1()
        {
            InitializeComponent();
            DB_Conected();

            atimer = new System.Timers.Timer();
            atimer.Interval = 10000;
            atimer.Elapsed += DelayTime;
            atimer.AutoReset = true;
            atimer.Enabled = true;
        }

        public void Thread_Refresh()
        {
            try
            {
                this.Invoke(new MethodInvoker(delegate ()
                {
                    string strSql = "SELECT * from IDCARDTABLE";
                    cmd = new FbCommand(strSql, connection);
                    cmd.CommandType = CommandType.Text;
                    FbDataReader reader = cmd.ExecuteReader();

                    listView1.Items.Clear();
                    if (reader.HasRows)
                    {
                        while (reader.Read())
                        {
                            ListViewItem LV_item = new ListViewItem(reader.GetString(0));
                            LV_item.SubItems.Add(reader.GetString(1));
                            LV_item.SubItems.Add(reader.GetString(2));
                            LV_item.SubItems.Add(reader.GetString(3));
                            listView1.Items.Add(LV_item);
                            listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
                            listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
                        }
                    }
                }));
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        //딜레이함수
        private void DelayTime(object myObject, System.Timers.ElapsedEventArgs myEventArgs)
        {
            //스레드
            ts = new ThreadStart(Thread_Refresh);
            reFresh = new Thread(ts);
            reFresh.IsBackground = true;
            reFresh.Start();
        }
        private void DB_Conected()
        {
            try
            {
                fdb.DataSource = "127.0.0.1";
                fdb.Port = 3050;
                fdb.Database = @"C:\Users\JHY\Documents\Visual Studio 2015\Projects\DB_TEST\DB_TEST\DB.fdb";
                fdb.ServerType = FbServerType.Default;
                fdb.UserID = "SYSDBA";
                fdb.Password = "masterkey";
                fdb.Dialect = 3;
                connectionString = fdb.ToString();
                connection = new FbConnection(connectionString);
                connection.Open();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
        private void Input_Data_Click(object sender, EventArgs e)
        {
            try
            {
                DateTime da = new DateTime();
                string strSql = "INSERT INTO IDCARDTABLE(ID_NUMBER, NAME, ADDRESS, LOGDATE) VALUES(@ID_NUMBER, @NAME, @ADDRESS, @LOGDATE)";
                cmd = new FbCommand(strSql, connection);
                cmd.Parameters.Add("@NAME", textBox1.Text);
                cmd.Parameters.Add("@ADDRESS", textBox2.Text);
                cmd.Parameters.AddWithValue("@ID_NUMBER", ID_Count++);
                cmd.Parameters.Add("@LOGDATE", da = DateTime.Now.Date);
                cmd.ExecuteNonQuery();
                //connection.Close();
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.Message.ToString());
            }
        }

        //private void DB_ListView()
        //{
        //    string strSql = "SELECT * from IDCARDTABLE";
        //    cmd = new FbCommand(strSql, connection);
        //    cmd.CommandType = CommandType.Text;
        //    FbDataReader reader = cmd.ExecuteReader();
            
        //    listView1.Items.Clear();
        //    if (reader.HasRows)
        //    {
        //        while (reader.Read())
        //        {
        //            ListViewItem LV_item = new ListViewItem(reader.GetString(0));
        //            LV_item.SubItems.Add(reader.GetString(1));
        //            LV_item.SubItems.Add(reader.GetString(2));
        //            LV_item.SubItems.Add(reader.GetString(3));
        //            listView1.Items.Add(LV_item);
        //            listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent);
        //            //listView1.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize);
        //        }
        //    }
        //}
        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            try
            {
                //디비가 열려있다면 닫고 종료
                if (connection.State == ConnectionState.Open)
                {
                    atimer.Enabled = false;
                    reFresh.Join();
                    connection.Close();
                }
            }
            catch (Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }

        private void Delete_Data_Click(object sender, EventArgs e)
        {
            try
            {
                string strSql = "DELETE from IDCARDTABLE WHERE NAME = @NAME and ADDRESS = @ADDRESS";
                cmd = new FbCommand(strSql, connection);
                cmd.CommandType = CommandType.Text;

                cmd.Parameters.Add("@NAME", textBox3.Text);
                cmd.Parameters.Add("@ADDRESS", textBox4.Text);
                //cmd.Parameters["@NAME"].Value = listView1.SelectedItems[1].Text;
                cmd.ExecuteNonQuery();
            }
            catch(Exception ee)
            {
                MessageBox.Show(ee.ToString());
            }
        }
    }
}

1. 폼을 로드한다.
1.1 디비를 연결하는 함수를 호출한다.
1.2. 딜레이 타임함수를 호출한다.
2.(쓰레드)안 리스트뷰에 뿌려주는 기능을 호출한다.
2.1 10초마다 뿌려준다.
3. 인풋버튼을 누르면 데이터베이스에 데이터를 넣는다.
4. 딜리트버튼을 누르면 데이터베이스에있는 데이터를 삭제한다.
5. 종료할때 쓰레드와 딜레이타임을 정지시키고, 데이터베이스와 연결을 끊는다.
 끗

2017년 1월 17일 화요일

[C#] .Net Winform 애니메이션 연동(간단한 게임만들기)


회사 업무시간에 간단히 윈폼으로 아무거나 만들라고 지시가 내려와서
그냥 닷넷으로 횡스크롤 슈팅게임이나 만들려고 했다가....
메탈슬러그 캐릭터가 넘나 귀여워서 동영상을 찍었다 ㅋㅋㅋ

2016년 12월 8일 목요일

[C#] 유니티 재귀함수 (Feat.구구단) ([C#] Unity Recursive function(Feat.multiplication table)

using UnityEngine;
using System.Collections;

public class Data : MonoBehaviour {

    int num_1, num_2;

    void Start()
    {
        num_1 = 2;
        num_2 = 1;

        mul(num_1, num_2);
    }
    public int mul(int a, int b)
    {
        int num = 0;
        if (b == 10)
        {
            a += 1;
            b = 1;
            return mul(a, b + 1);
        }

        if (a > 10)
        {
            return 0;
        }
        num = a * b;
        show(a, b, num);
        return mul(a , b + 1);
    }

    public void show(int a, int b, int num)
    {
        Debug.Log(a + "단 :" + a + " x " + b + " = " + num);        
    }
}
- 간단한 코드라 자세한 설명은 생략한다.

결과

2016년 11월 29일 화요일

[C#] 유니티로 미니게임 제작하기(2) ([C#]Creating a mini game with Unity(2))

툴을 설치해 보자.

1. Magica Voxel
- 게임에 필요한 그래픽 제작하기


- 위 홈페이지 링크에 들어가게 되면, 다운받기가 바로 나올것이다. 
홈페이지에 업데이트되는 부분과 어떤 기능들을 할 수 있는지 있으니 참고하기 바란다. 



- 다운받은 파일 모습


- MagicaVoxel 을 실행시킨 모습이다. 현재 이미지는 Chr_sword를 선택한 모습이다.
==========================================================
Palette : 색
==========================================================
Brush L : line Mode
Brush C : Center Mode
Brush P : Patten Mode
Brush V : Vertex Mode
Brush F : Face Mode
Brush B : Box Mode
==========================================================
Attach : Attach Voxels          
Erase : Erase Voxels
Paint : Paint Voxels
Move : Move Model
==========================================================
Mirror x,y,z : x,y,z좌표에 그린다면 -x, -y, -z 좌표에 그려준다.
==========================================================


이렇게 만든 오브젝트를 Export부분으로 추출해준다.
파일형식은 obj로 추출해서 유니티에 넣으면

- 이런식이 되는데 폴리곤수가 이상하게 많아보인다. 빗금 표시가 캐릭터 블럭 색 마다 표시되어있는데 최적화가 필요할것 같다.

최적화 하지않은 오브젝트를 사용하면 게임이 커질수록 불리해 지게 된다.

2. Voxel Shop
- 오브젝트 최적화 방법




- 다운로드를 클릭하면 밑에 사용자 OS에 맞게 다운받을 수 있다.


- file -> import


- Downloads\MagicaVoxel-0.98.-win-mac\MagicaVoxel-0.98\vox 경로에 있는 .vox 파일형식을 import 시켜준다. 
- import 시켜준 모습.
우리는 폴리곤만 줄이면 되기때문에 여기서 추가 작업은 없고, export만 시켜주도록 하겠다.



- export할 이름을 적고 경로를 지정해주면 되겠다.


- export 한 파일


- 파일을 유니티 에셋 폴더에 끌어다 놓은 모습.


- 뭔가 이상하다. 텍스쳐가 들어간것 같지만, 매우 부자연스럽게 입혀져있다.



- 텍스쳐를 선택하고 텍스쳐 타입을 Advanced를 선택한다.



- Non Power of 2를 None으로 선택하고 Generate Mip Maps를 체크해제 해준다.
그리고 Filter Mode를 Point(no filter)로 선택해 준다. 그리고 Apply를 눌러주면


-이렇게 입혀지게된다. 근데 뭔가 어딘가 이상하다...ㅜㅜ;;
색이 번지는듯한 느낌....

찾아보니까~ 음~~~~~ 하...

Generate Mip Maps를 잘못 사용해서 그런것이라고 한다.
https://docs.unity3d.com/kr/current/Manual/class-TextureImporter.html

유니티 메뉴얼에 따르면,
" 이것을 선택하면 밉맵의 생성이 활성화됩니다. 밉맵은 더 작은 텍스처로, 텍스처가 화면상에서 아주 작은 경우에 사용됩니다. 자세한 내용은 페이지 마지막의 Details 섹션을 참조하십시오."

라고 하는데... 이것을 다시 활성화 시켜주면!




- 잘 나온다.!!!!!!!!!!!
이렇게 유니티에 넣어주면,


- 캐릭터의 폴리곤이 최적화 된것을 두 눈으로 직접 확인할 수 가 있다.


==========================================================솔직히 너무 귀찮다... 시간이 된다면, 조금더 간단한 방법으로 최적화 하는 방법을 생각해 봐야겠다.... 아...  블로그 글쓰기 힘들다... ;;;

애니메이션은 언제하고... 배경은 언제만들고... 유아이 스크립트는!! 서버는!! 언제!!!


하... 들어가서 작업하면 안될까?