'TA'에 해당되는 글 104건

  1. 2017.01.22 ngui - 계속 누르기 버튼 스크립트
  2. 2017.01.05 RTT 스크립트 제작(업무용)
  3. 2016.12.29 Unity 해상도 설정
  4. 2016.12.16 반(?)자동 암벽 제작 스크립트 제작
  5. 2016.12.05 [언리얼4_블루프린트] For Each Loop B
  6. 2016.12.05 [언리얼4_블루프린트] For Each Loop A
  7. 2016.12.02 [언리얼4_블루프린트] for문을 활용한 벽 생성기
  8. 2016.12.02 [언리얼4_블루프린트] For Loop
  9. 2016.11.30 [언리얼4_블루프린트] 배열 다루기 테스트 (랜덤 회전)
  10. 2016.11.30 [언리얼4_블루프린트] 실시간 메터리얼 교체 테스트
  11. 2016.11.18 3Dmax Script PPT 자료
  12. 2016.11.17 자주쓰는 코드 정리(작성중)
  13. 2016.11.09 3DMax MCG 설치방법
  14. 2016.11.09 MCG 3VectorClone 1.0 수정및 배포
  15. 2016.04.02 MCG 3VectorClone 1.0 제작 완료
  16. 2016.03.22 Max Creation graph help
  17. 2016.03.22 SCRIPTSPOT / MCG
  18. 2016.03.22 Max Creation Graph (MCG) Sample Pack for 3ds Max 2016
  19. 2016.03.22 Unity Editer 단축키
  20. 2016.03.22 XML의 문법
  21. 2016.03.22 Unity json 파싱
  22. 2016.03.22 ngui 줄바꿈 버그 수정
  23. 2015.07.12 객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..
  24. 2013.10.25 카메라 이동 영역 제한을 위한 방법(4개 포지션)
  25. 2013.10.15 간단한 씬 로딩
  26. 2013.09.27 VSync - WaitForTargetFPS
  27. 2013.09.11 유니티 NGUI 논리 해상도와 픽셀 퍼펙트
  28. 2013.09.11 unity 에디터 확장 2
  29. 2013.09.11 로딩 페이지 및 로딩 프로그래스바 사용하기.
  30. 2013.09.09 효과적인 C# 메모리 관리 기법 1
TA/Unity2017. 1. 22. 20:35

스크립트(ngui -> UI 폴더)



  1. using UnityEngine;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4.  
  5. public class UIRepeatButton : MonoBehaviour
  6. {
  7.         public float interval = 0.0001525F;
  8.        
  9.         bool mIsPressed = false;
  10.         float mNextClick = 0f;
  11.  
  12.         public List<EventDelegate> onPressListeners = new List<EventDelegate>();
  13.  
  14.        
  15.         void OnPress (bool isPressed) { mIsPressed = isPressed; mNextClick = Time.realtimeSinceStartup + interval; }
  16.        
  17.         void Update ()
  18.         {
  19.                 if (mIsPressed && Time.realtimeSinceStartup > mNextClick)
  20.                 {
  21.                         mNextClick = Time.realtimeSinceStartup + interval;
  22.                        
  23.                         // Do what you need to do, or simply:
  24.                         //SendMessage("OnClick", SendMessageOptions.DontRequireReceiver);
  25.                         //cleaner:
  26.                         EventDelegate.Execute(onPressListeners);
  27.                 }
  28.         }
  29. }
  30.  


에디터 스크립트(ngui -> editer폴더)



  1. using UnityEngine;
  2. using UnityEditor;
  3.  
  4. [CanEditMultipleObjects]
  5. [CustomEditor(typeof(UIRepeatButton))]
  6. public class UIRepeatButtonEditor : UIWidgetContainerEditor
  7. {
  8.         public override void OnInspectorGUI ()
  9.         {
  10.                 serializedObject.Update();
  11.  
  12.                 UIRepeatButton button = target as UIRepeatButton;
  13.  
  14.                 //this allows to more conveniently set the event handler:
  15.                 NGUIEditorTools.DrawEvents("On Repeated Press", button, button.onPressListeners);
  16.         }
  17.        
  18.         enum Highlight
  19.         {
  20.                 DoNothing,
  21.                 Press,
  22.         }
  23. }


'TA > Unity' 카테고리의 다른 글

쉐이더 LOD(Shader Level of Detail)  (0) 2017.03.14
쉐이더 포지 TRANSFORM_TEX()  (0) 2017.02.23
Unity 해상도 설정  (0) 2016.12.29
Unity Editer 단축키  (0) 2016.03.22
XML의 문법  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2017. 1. 5. 11:16

RTT 스크립트 제작


회사 작업에 필요해 RTT를 쉽게 할수있도록 스크립트 제작했습니다.


미완성 예제 파일과 레퍼런스를 확인해며 만들었습니다.

만들면 만들수록 노가다성.... 


1차로 노멀맵과 AO맵만 적용했고 나중에 시간되면 여러가지 텍스쳐를 추가할 생각입니다.

 



'TA > 3Dmax Script Work' 카테고리의 다른 글

반(?)자동 암벽 제작 스크립트 제작  (0) 2016.12.16
3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unity2016. 12. 29. 09:37

출처 :http://smilejsu.tistory.com/990

Unity 해상도 설정

Unity3D 2016.04.30 14:33

해상도 설정

모바일 기기와 OS에 따라 해상도 설정 방법이 다르다.

<  안드로이드 설정 >

Screen.SetResolution(int width, int heignt, bool bFullScreen)
640×480 해상도 일 경우, SetResolution(600, 480, true)

< IOS 설정 >

유니티 4.0 이상에서만 SetResolution( ) 사용 가능
File / Build Settings / Player Settings / Other Setting에서 Target Resolution을 Native, Standard, HD 중에서 선택

< 안드로이드 기기 대응 >

모든 기기에 맞게 화면 설정을 할 수 없기 때문에, 해상도를 비율을 맞게 화면이 맞게 보이도록 한다.

2:3 비율로 개발 한다면 
SetResolution( Screen.width, Screen.width * 3 / 2, ture )로 한다.

< 2D 텍스쳐 확면 꽉차게 그리기 >

유니티의 1unit은 1미터이다.

640 X 480 해상도에서 640 X 480 텍스쳐를 화면 꽉차게 그리고 싶다면 어떻게 할것인가?

카메라 orthographicSize = 세로 사이즈 / ( 유닛당 픽셀 * 2 )
                                  = 480 / ( 100 * 2 ) = 2.4

한 유닛당 100 Pixel일때 계산이다. 100픽셀이면 화면 꽉차 보이는 값이다.

이하 size는 orthographicSize와 동일한 표현이다.

가로 유닛 크기 = (가로 / 세로 ) * size * 2
                     = (640 / 480 ) * 2.4 * 2 = 6.4 m

세로 유닛 크기 = (세로 / 세로 ) * size * 2
                      = (480 / 480 ) * 2.4 * 2 = 4.8 m

코드상에서는 다음과 같이 한다.

Camera.main.orthographicSize = Screen.height / (100.0f * 2.0f)

< 2D 급조 테스트 >

안드로이드에서 급하게 해상도 테스트를 하였다.
테스트 해상도를 640 X 480에서 작업 하였기 때문에 모바일 기기 해상도를 강제로 640 X 480으로 설정하였다. 머리 굴려서 계산식이 통하지 않아서 아래처럼 기냥 하였다.

Screen.SetResolution( 640, 480, true );        
Camera.main.orthographicSize = 480 / (100.0f * 2.0f);

머리가 맑을때 더 우아한 방법을 찾아 봐야겠다.

< 안드로이드 기기 해상도 >

480 x 800 : 갤럭시S, 갤럭시S2, 옵티머스 2X, Nexus S, Nexus One, HTC Desire HD, HTC Desire HD2
800 x 1280 : 갤럭시탭 10.1, 갤럭시노트1, 넥서스 7
720 x 1280 : 갤럭시S3, 갤럭시S2 HD, 갤럭시노트2, 옵티머스G
1200 x 1920 : 넥서스 7(2013)
1080 x 1920 : G2, 갤럭시S4, 갤럭시노트3

< iOS 기기 해상도 설정 >

320 x 480 : 아이폰 3
640 x 960 : 아이폰 4
640 × 1136 : 아이폰 5
768 x 1024 : 아이패드1, 아이패드2, 아이패드 미니
1536 x 2048 : 아이패드3, 아이패드4

< 기기들의 화면비율 >

(3:4) : 768 x 1024(아이패드 1), 1536 x 2048(아이패드 3)
(2:3) : 320 x 480(아이폰 3, 옵1), 640 x 960(아이폰 4)
(10:16) : 800 x 1280(넥7, 갤탭10.1, 갤노1), 1200 x 1920(넥7_2013)
(3:5) : 480 x 800(넥원, 갤2)
(9:16) : 640 × 1136(아이폰 5), 720 x 1280(갤3, 옵G, 갤노2), 1080 x 1920(G2, 갤4, 갤노3)

< Windows 환경 Satandalone 해상도 수정 >

메뉴 / File / BuildSettings / Platform을 PC, Mac & Linux Standalone으로 설정  / Player Settings... 버튼 클릭  / Inspector View / Resolution And Presentation 설정


'TA > Unity' 카테고리의 다른 글

쉐이더 포지 TRANSFORM_TEX()  (0) 2017.02.23
ngui - 계속 누르기 버튼 스크립트  (0) 2017.01.22
Unity Editer 단축키  (0) 2016.03.22
XML의 문법  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 12. 16. 23:37

반(?)자동 암벽 제작 스크립트 제작했습니다.

회사 업무용으로 제작한거라 외부 공개는 못하고 영상만 올려둡니다. ㅜㅜ




'TA > 3Dmax Script Work' 카테고리의 다른 글

RTT 스크립트 제작(업무용)  (0) 2017.01.05
3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unreal42016. 12. 5. 01:10

언리얼4 For Each Loop B



Posted by 프리랜서 디자이너
TA/Unreal42016. 12. 5. 01:09

For Each Loop



Posted by 프리랜서 디자이너
TA/Unreal42016. 12. 2. 15:26

언리얼4 for문을 활용한 벽 생성기





Posted by 프리랜서 디자이너
TA/Unreal42016. 12. 2. 09:53


For Loop







Posted by 프리랜서 디자이너
TA/Unreal42016. 11. 30. 10:38

배열 다루기 테스트 (랜덤 회전)





Posted by 프리랜서 디자이너
TA/Unreal42016. 11. 30. 10:36

실시간 메터리얼 교체 테스트





Posted by 프리랜서 디자이너
TA/3Dmax Script 자료2016. 11. 18. 01:03
Posted by 프리랜서 디자이너

A

 

모디파이어 추가

 

modPanel.addModToSelection ( 모디파이어 이름 ()) ui:on


예)

modPanel.addModToSelection (Twist ()) ui:on

modPanel.addModToSelection (Displace ()) ui:on




모디파이어 속성 알아내기(리스너 실행)

show $.modifiers[#Displace]


결과는 아래 출력


  .axis : integer

  .bitmap : bitmap

  .blur : float

  .height : float

  .length : float

  .map : texturemap

  .width : float

  .cap : boolean

  .U_Tile : float

 .

 .

 .

 .





Editable poly 속성 링크(맥스 스크립트 레퍼런스)


http://help.autodesk.com/view/3DSMAX/2015/ENU/?guid=__files_GUID_0196A023_116F_47F8_99E9_AF3CB52F302C_htm


 

 

 

object type 대입 (리스너 테스트)

 

b = $ -- 선택은 오브젝트를 b에 대입
$Box:Box002 @ [0.000000,0.000000,0.000000]
ffd_mod = ffdBox() -- ffd4 모디파이어를 ffd_mod에 대입
FFDBox:FFD(box) 4x4x4
addmodifier b ffd_mod -- addmodifier 함수를 사용해 b에 ffd_mod를 추가함
OK
showproperties b
  .height : float
  .length : float
  .lengthsegs : integer
  .width : float
  .widthsegs : integer
  .mapcoords : boolean
  .heightsegs : integer
  .realWorldMapSize : boolean
false
showproperties ffd_mod -- 프로퍼티 확인
  .dispLattice (Lattice) : boolean
  .dispSource (Source_Volume) : boolean
  .deformType : integer
  .falloff : float
  .tension : float
  .continuity : float
  .inPoints (Inside_Points) : boolean
  .outPoints (Outside_Points) : boolean
  .offset : float
  .Lattice_Transform : transform
false

 

 

** 사실 왜 b가 아닌 ffd_mod에서 프로퍼티가 접근해야하는지 이해가 안가지만

위 절차대로 해야 모디파이어 프로퍼티에 접근 할 수 있습니다.

 


 

UI 체크박스 예제

 

checkbox 'AAA' "Smoothing Groups" pos:[12,141] width:132 height:21 checked:true align:#left

 

on AAA changed theState do
 (
  taget . smoothResult = AAA.checked
 )

 

 

 

 

 

if 문

a = 1

if a == 1 then messageBox "hello"

 

if a == 1 do messageBox "hello"

 

if a == 1 then (

 messageBox "hello1"

)

else(

 messageBox "hello2"

)

 

(ex)

if null != select do (messagebox ("Select Object!! "))

 

 

 

 Projection Pick Object


addPModObjects #($적용할 오브젝트 이름) false false objList:#($선택할 대상 오브젝트 이름)


또는


theProj.addObjectNode highpoly

 



Projection Export Cage


$.modifiers[#Projection].exportCage "생성될 오브젝트 이름"

 



이름으로 오브젝트 선택하기


select $objName

 



Editable poly 스무스 그룹


페이스 선택

subobjectLevel = 4


모든 페이스 선택

max select all


스무스 그룹 Clear All

polyop.setFaceSmoothGroup $ #all 0


스무스 그룹 넘버 선택

polyop.setFaceSmoothGroup $ #all 1


오토 스무스

$.EditablePoly.autosmooth ()



 




Assign Renderer


랜더러 선택

renderers.current = RendererClass.classes[1]()


메터리얼 잠김 풀기

renderers.medit_locked = false


메터리얼 바꾸기

renderers.medit = mental_ray_renderer()


엑티브 쉐이드 바꾸기

renderers.activeShade = iray_Renderer()






Color


local WGray = color 200 200 200

 



스켄라인 랜더러 안티얼라이싱 필터 클래스


showClass"*:filter*"


Area : filter {77912301,0}

Quadratic : filter {77912304,0}

cubic : filter {77912305,0}

Catmull_Rom : filter {77912306,0}

Blackman : filter {77912313,0}

Sharp_Quadratic : filter {77912314,0}

Video : filter {77912319,0}

Plate_Match_MAX_R2 : filter {7791231b,0}

Cook_Variable : filter {77912330,0}

Soften : filter {77912331,0}

Mitchell_Netravali : filter {77912350,0}

Blendfilter : filter {77912352,0}

Filter_kernel_plug_in_not_found : filter {ffffffff,0}

OK


 


사용 예시

renderers.production.antiAliasFilter =Mitchell_Netravali()






스켄라인 랜더러 다이얼로그 클래스


show renderers.production


  .mapping : boolean

  .shadows : boolean

  .autoReflect : boolean

  .forceWireframe : boolean

  .antiAliasing : boolean

  .filterMaps : boolean

  .objectMotionBlur : boolean

  .imageMotionBlur : boolean

  .imageBlurEnv : boolean

  .imageBlurTrans : boolean

  .conserveMemory : boolean

  .enablePixelSampler : boolean

  .enableSSE : boolean

  .wireThickness : float

  .objectBlurDuration : float

  .imageBlurDuration : float

  .antiAliasFilterSize : float

  .objectBlurSamples : integer

  .objectBlurSubdivisions : integer

  .autoReflectLevels : integer

  .colorClampType : integer

  .antiAliasFilter : maxObject

  .globalSamplerEnabled : boolean

  .globalSamplerClassByName : TSTR by value

  .globalSamplerSampleMaps : boolean

  .globalSamplerQuality : float

  .globalSamplerAdaptive : boolean

  .globalSamplerAdaptiveThresh : float

false

 




드롭다운 UI 사용법(string)


local formatSel 


on Format_Texture selected sel do

(

formatSel =  Format_Texture.selected 

)

 



 

 엠비언트 오클루전 베이크 프로퍼티


be = Ambient_Occlusion()


show be

  .enabled : boolean

  .filterOn (FilteringOn) : boolean

  .outputSzX (output_width) : integer

  .outputSzY (output_height) : integer

  .autoSzOn (Auto_Size_On) : boolean

  .fileName : filename

  .filenameUnique (Filename_Unique) : boolean

  .fileType : filename

  .elementName : string

  .bitmap : bitmap

  .backgroundColor (Background_Color) : fRGBA color

  .targetMapSlotName : string

  .samples : integer

  .bright : fRGBA color

  .dark : fRGBA color

  .spread : float

  .maxDistance (Max_Dist) : float

  .falloff : float

false

 


 

 다이얼로그 켜고 끄기


해당 매크로 스크립트를 열고 명령어를 찾아보면 됨

macros.run "Render" "BakeDialog"

destroydialog gTextureBakeDialog


 


 

 


 

Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 11. 9. 14:58

3DMax MCG 설치방법



설치 파일


설치 경로

C:\Program Files\Autodesk\3ds Max 2016\MaxCreationGraph\Tools



설치방법

맥스를 재실행해야 자동으로 설치가 됩니다.



'TA > 3Dmax Script Work' 카테고리의 다른 글

RTT 스크립트 제작(업무용)  (0) 2017.01.05
반(?)자동 암벽 제작 스크립트 제작  (0) 2016.12.16
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 11. 9. 14:57

MCG_3VectorClone 사용 방법


같은 형태의 반복적인 오브젝트를 빠르고 편하게 제작할 수 있도록 돕는

MCG 스크립트를 제작했습니다.


(이미 거의 동일한 MCG가 Script 사이트에 있지만 새로 제작해봤습니다.)


작업 예시

3VectorClone + pathDeform을 활용한 쇠사슬 제작


3VectorClone + Band를 활용한 쇠사슬 제작


3VectorClone을 활용해 제작한 지브러쉬용 나노브러쉬 체인메일 제작


3VectorClone을 활용해 반복적인 기둥 모델링 활용




장점

- Modifier 로 제작되어서 원본의 손실 없이 수정이 용이합니다.

- max의 다른 스크립트와 같이 사용하면 활용도가 높아집니다.



메뉴 설명


Clone : xyz 축 방향으로 오브젝트를 복사합니다.

distance는 오브젝트와의 간격을 조절합니다.

Rotation : 오브젝트 각각의 로컬축 회전을 조절합니다.

MCG_3VectorClone.zip



Scale : 오브젝트의 크기를 점차 크게 또는 작게 조절합니다.




설치방법


http://usroom.tistory.com/entry/3DMax-MCG-%EC%84%A4%EC%B9%98%EB%B0%A9%EB%B2%95


'TA > 3Dmax Script Work' 카테고리의 다른 글

반(?)자동 암벽 제작 스크립트 제작  (0) 2016.12.16
3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
SCRIPTSPOT / MCG  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 4. 2. 12:01



MCG 3VectorClone 1.0 제작 완료


첫 3ds MCG Script 를 제작 했습니다.

정보가 너무 없어서 힘들었지만 알아가는 기쁨이 있네요.


시간되면 주요 노드 구성방법을 올려볼까 합니다.




'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
Max Creation graph help  (0) 2016.03.22
SCRIPTSPOT / MCG  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

http://help.autodesk.com/view/3DSMAX/2016/ENU/?guid=GUID-608EC963-75ED-4F63-96B7-D8AE57E75959






'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
SCRIPTSPOT / MCG  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

http://www.scriptspot.com/3ds-max/mcg

 

 

 

 

'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
Max Creation Graph (MCG) Sample Pack for 3ds Max 2016  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/3Dmax Script Work2016. 3. 22. 01:23

Max Creation Graph (MCG) Sample Pack for 3ds Max 2016





Now that 3ds Max 2016 is available for download I'm pleased to announce a set of 30 sample MCG tools and 80 new compounds that you can download, use, and learn from! 

In order to install the tools unzip the contents of MaxCreationGraph.zip and it into your Max Creation Group user folder. You can navigate directly to that folder by pasting
%userprofile%/Autodesk/3ds Max 2016/Max Creation Graph
into your Windows explorer. 

If you have any other compounds and tools installed, be sure to back them up first, in case some files in the zip package have the same name as existing files. When you restart 3ds Max you should see a number of new modifiers in the modifier pull-down. Each name of the new modifier starts with "A" (e.g. "APush"). There will also be a new category in the object creation panel called "Max Creation Graph" with several new tools. 

I have also created a sample test file that you can load once you unzip the tools and restart 3ds Max here: MCGSampleTestScene.zip

Your comments and questions are certainly appreciated, enjoy!



'TA > 3Dmax Script Work' 카테고리의 다른 글

3DMax MCG 설치방법  (0) 2016.11.09
MCG 3VectorClone 1.0 수정및 배포  (0) 2016.11.09
MCG 3VectorClone 1.0 제작 완료  (0) 2016.04.02
Max Creation graph help  (0) 2016.03.22
SCRIPTSPOT / MCG  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:21

에디터 단축키 사용법 팁


하이어라키 하위노드 펼치고 닫기 -> 마우스를 화살표위에 두고 + Ctrl + 마우스 오른클릭


하이어라키 하위노무 모두 펼치고 닫기 -> 마우스를 화살표위에 두고 + Alt + 마우스 오른클릭


인스펙터창 끄고 켜기 -> 하이어라키에서 노드선택 + Shift + Alt + A

'TA > Unity' 카테고리의 다른 글

ngui - 계속 누르기 버튼 스크립트  (0) 2017.01.22
Unity 해상도 설정  (0) 2016.12.29
XML의 문법  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:20

Well-formed XML Document

 

XML 1.0 스펙에 정의된 특정한 문법규칙을 따르는 XML.
XML문서를 설계하려면 게층구조의 트리로 표현하는것이 가장 바람직하다.

XML 문서는 Well-formed 를 만족시키기 위해 반드시 요소 작성 규칙을 따라야 한다.

 

XML의 문법

<?xml version="1.0"?>
<note>
    <to>철수</to>
    <from>다니엘</from>
    <heading>기억할 것!</heading>
    <body>이번 주 약속 잊지말길!</body>
</note>
* '<' 와 '>' 사이의 단어들은 xml태그이다.
* 정보는 시작 태그와 끝 태그 사이에 담겨지게 된다
* 태그는 짝을 이루고 있다.
요소의 시작 태그와 끝 태그 사이에 들어 있는 텍스트는 요소 컨텐츠(element content)라고
부른다. 태그 사이에 있는 컨텐츠는 대부분의 경우 다른 요소와 달리 그냥 데이터이다.
이런 경우에는 요소 컨텐츠는 파싱된 문자 데이터(Parsed Character DATA)라고 부른다.
보통 줄여서 PCDATA라고도 한다.
* XML요소 작성 규칙
1. 모든 XML 요소는 시작태그 다음에는 반드시 마침태그(closing tag)를 가져야 한다.
HTML에서는 마침 태그(closing tag)가 필요 없는 요소들도 있었다:
<p>이것은 하나의 ?騈甄?amp;lt;p>이것은 또 다른 문단이다
하지만 XML에서는 다음과 같이 마침 태그를 반드시 가져야 한다:
<p>이것은 하나의 문단이다</p>
<p>이것은 또 다른 문단이다</p>
2. XML 태그는 대소문자 구분이 있다.
XML 태그들은 대소문자 구분을 한다. 태그 <Letter>와 태그 <letter>는 서로 다르다.
그러므로 시작 태그와 마침 태그는 반드시 대소문자를 맞춰줘야 한다:
<Message>이렇게 하면 잘못된 것이다</message> 
<message>이렇게 해야 맞다</message>
3. 모든 XML 요소는 중첩 원리를 잘 지켜야 한다.
HTML에서 어떤 요소들은 다음과 같이 서로 부적절하게 중첩될 수 있었다:
<b><i>이 문장은 볼드체와 이탤릭체로 나타난다</b></i>
 
하지만 XML에서는 다음과 같이 서로 중첩을 잘 시켜줘야 한다:
<b><i>이 문장은 볼드체와 이탤릭체로 나타난다</i></b>
4. 모든 XML 문서는 반드시 최상위 요소를 하나만 가질수 있다.
모든 XML 문서는 한 쌍의 최상위 요소 태그를 포함해야 한다. 
모든 다른 요소들은 이 최상위 요소 하부로 중첩되어야 한다.
모든 요소들은 자신의 하부로 자식 요소들을 가질 수 있으며
모든 요소들은 시작 태그와 마침 태그의 쌍을 정확히 이뤄야 한다.
또한 모든 하위 요소들은 자신의 부모 밑에서 순서를 잘 지켜 중첩되어 있어야 한다:
<root>
  <child>
    <subchild>
    </subchild>
  </child>
</root>
5. 속성 값은 쿼테이션(' 또는 ")으로 감싸줘야 한다.
XML 요소들은 HTML에서와 마찬가지로 name과 value 속성을 가질 수 있다. 
XML에서 속성 값은 항상 쿼테이션(' 또는 ")으로 감싸줘야 한다.
아래 두 개의 XML 문서를 보면, 첫 번째 것은 틀렸고, 두 번째 것은 맞았다.
어떤 부분이 맞고 틀렸는지 살펴보기 바란다:
<?xml version="1.0"?>
<note date=12/11/99>
 <to>철수</to>
 <from>다니엘i</from>
 <heading>기억할 것</heading>
 <body>이번 주 약속 잊지말길!</body>
</note>
 
<?xml version="1.0"?>
<note date="12/11/99">
 <to>철수</to>
 <from>다니엘i</from>
 <heading>기억할 것</heading>
 <body>이번 주 약속 잊지말길!</body>
</note>
6. XML은 텍스트 안에 들어 있는 공백을 유지한다.
HTML에서는 두개 이상의 공백문자을 포함하기 위해서는 &nbsp;를 사용해야만 했다. 
<b>&nbsp; 텍스트 &nbsp;</b>
하지만 XML에서는 태그사이에 텍스트와 함께 사용된 공백문자는 그대로 유지된다.
<b> 텍스트 </b>
-------------------------------------------------------------------------------------------------------
* XML 요소이름 작성규칙
    -  시작문자 가능 ' 문자, '_'
    -  시작문자 불가능 ' 숫자, xml
    -  첫문자 뒤에는 '-'나 '.', 숫자를 사용할 수 있다.
    -  이름은 공백을 포함할 수 없다.
    - ':'를 포함하지 말자. 예약어 이다.
    -  대소문자를 확실히 구분한다.
    -  여는 문자('<') 뒤에 공백을 두면 안 된다.

* 빈요소
아무 데이터도 갖지 않는 요소
<name></name> // 다음과 같은 방법도 사용가능 : <name/>
빈 요소가 자주 쓰이는 장소는 PCDATA를 전혀 갖지 않거나 요소에서 모든 정보를
속성으로 지정하는 경우에 많이 쓰인다.


* XML 선언

문서가 어떤 특정한 타입이라고 알려줄 수 있는 것은 종종 매우 유용하다. XML은 파서에게 몇가지 정보를 알려줌으로써 XML이 어떤 문서인지를 알려 줄 수 있는 XML선언을 제공한다.

 

<?xml version='1.0' encoding='euc-kr' standalone='yes'?>

 

 위의 코딩은 전형적인 XML선언이다.
 XML선언은 "<?xml"문자로 시작해서 "?>"로 끝이 난다.
 선언문에 반드시 version 속성을 표시 해야 하지만 encoding과 standalone 속성은 선택적이다.  속성은 반드시 version, encoding, standalone 순으로 쓰여져야 한다.


Encoding : XML 파서에게 텍스트가 어떤 인코딩을 사용할 것인지 지정해 준다.
standalone : 이 속성의 값으로는 "yes"또는 "no"를 가져야만 한다.
    - Yes : 이 문서가 외부 파일의 요소에 의존하고 있지 않다는 것을 알림.
             (파서에게 이 문서가 갖고 있는 정보를 완벽하게 처리하기 위해 
               필요한 파일이 단지 이것 하나면 충분하다는 사실을 알림)
    - No : 이 문서가 다른 파일에 의존 한다.(외부 파일의 그림, 개체 등을 참조)
 

 


* 주석사용

 

    -  주석은 '<!--'로 시작해서 '-->'로 끝난다

    -  주석은 XML선언 이전에 표현할수 없다.

    -  태그 안에는 주석을 쓸수 없다.
        <first></first <!--이름을 나타낸다-->>

 

    -  주석문 안에다 '--'를 사용할 수 없다. 
        <!--여기 부터는 해석을 하지 않는다 --주석이니까^^-->


 

[사용예제]


    <?xml version="1.0" encoding="euc-kr"?>
    <birth>
          <!--생일의 월 정보-->
              <month>09</month>
          <!--생일의 날짜 정보-->
          <day>33</day>
    </birth>

 

IE에 포함되어 있는 XML파서는 주석을 애플리케이션에 전달한다. 그래서 IE는 우리가 작성한 주석을 화면에 표시한다. 하지만 대부분의 경우, 이런 정보는 오직 소스 파일을 읽는 사람만이 사용할 것이라고 가정하고 만들어졌기 때문에, 주석에 적혀 있는 정보가 애플리케이션에 전달될 지 안될 지는 어떤 파서를 사용하느냐에 달려 있는 것이다.

 

자료출처 : http://korea.internet.com

[출처] [펌] XML문법|작성자 메멘토


'TA > Unity' 카테고리의 다른 글

Unity 해상도 설정  (0) 2016.12.29
Unity Editer 단축키  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..  (0) 2015.07.12
Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:19

Unity json 파싱


외부 텍스트 파일을 불러 와야 할일이 있어서 XML을 살펴봣는데 생각보다 예외 상황도 많은듯하고, 복잡했습니다. @..@;;

제겐 어렵네요... 구조 파악도 잘안되고.. 어휴...


결국 조금더 편리하다는 json으로 다시 살펴봤고 일단 구현은 했습니다.


배열구조로 가야하나 했지만 제가 만들려는 것이 배열구조와는 맞지 않아 key : value 로만 구성했습니다.

힘드네요. ㅋㅋ



가장 심플한 기본 코드만 작성해 봤습니다.

json 파일을 불러와 ngui  Labal에 text를 찍는 과정입니다.



-Litjson.dll 넣기-

Litjson.dll 파일을 유니티에 Plugins폴더에 넣어 주셔야 합니다.





-json파일 구조(key : value)-

{

"key" : "value",

"key" : "value",

"key" : "value"

}





-json 예제 파일-

메모장을 열고 아래처럼 적고 유니티에 넣으세요.(txt)

{

   "Lines_00":"멍멍멍"

}






-C# 예제 코드-

using UnityEngine;

using System.Collections;

using LitJson;

using System;



public class LoadLines : MonoBehaviour {


public TextAsset JsonFile;

public GameObject Lines_00;

private string Out_Lines_00;



void Awake(){


LitJson.JsonData getData = LitJson.JsonMapper.ToObject(JsonFile.text);


Out_Lines_00 = getData["Lines_00"].ToString();

Lines_00.GetComponent<UILabel>().text = Out_Lines_00;


}



}



Posted by 프리랜서 디자이너
TA/Unity2016. 3. 22. 01:19

ngui 줄바꿈 문제가 있었습니다.

아래 글 긁어왔습니다.

 

출처 : http://www.tasharen.com/forum/index.php?topic=4114.msg20026#msg20026

 

 

UI Label.cs

From


   public string text
   {
      get
      {
         return mText;
      }
      set
      {
         if (string.IsNullOrEmpty(value))
         {
            if (!string.IsNullOrEmpty(mText))
               mText = "";
            hasChanged = true;
         }
         else if (mText != value)
         {
            mText = value;   <=== Here
            hasChanged = true;
         }
      }
   }


To


   public string text
   {
      get
      {
         return mText;
      }
      set
      {
         if (string.IsNullOrEmpty(value))
         {
            if (!string.IsNullOrEmpty(mText))
               mText = "";
            hasChanged = true;
         }
         else if (mText != value)
         {
            mText = value.Replace("\\n", "\n");  <== Here
            hasChanged = true;
         }
      }
   }

'TA > Unity' 카테고리의 다른 글

XML의 문법  (0) 2016.03.22
Unity json 파싱  (0) 2016.03.22
객체의 자식을 찾을 떄, 객체를 자식으로 넣을때..  (0) 2015.07.12
카메라 이동 영역 제한을 위한 방법(4개 포지션)  (0) 2013.10.25
간단한 씬 로딩  (0) 2013.10.15
Posted by 프리랜서 디자이너
TA/Unity2015. 7. 12. 17:51

 

객체의 자식구조를 찾을 떄

transform.FindChild("이름");

 

 

 

새로 생성한 프리팹을 특정 오브젝트 자식으로 넣을때..

Player = Instantiate(Player_01, Player_pos.transform.position, transform.rotation) as GameObject;
Player.transform.parent = gameObject.transform.FindChild ("Player_pos").transform;

 

 

 

또는....

Player = Instantiate(Player_01, Player_pos.transform.position, transform.rotation) as GameObject;
Player.transform.parent = Player_pos.gameObject.transform;

 

 

'TA > Unity' 카테고리의 다른 글

Unity json 파싱  (0) 2016.03.22
ngui 줄바꿈 버그 수정  (0) 2016.03.22
카메라 이동 영역 제한을 위한 방법(4개 포지션)  (0) 2013.10.25
간단한 씬 로딩  (0) 2013.10.15
VSync - WaitForTargetFPS  (0) 2013.09.27
Posted by 프리랜서 디자이너
TA/Unity2013. 10. 25. 08:42

카메라의 이동 영역을 제한하기 위해 4개의 포지션을 설정하고 에디터에 라인을 그려 눈으로 확인하게 해줌.(중략)


 public float minPosX=-10;
 public float maxPosX=10;
 public float minPosZ=-10;
 public float maxPosZ=10;


 public bool showGizmo=true;
 .

.

.

.

.
  //Camera Area Line- (Editor & Mobile)
  float x=Mathf.Clamp(thisT.position.x, minPosX, maxPosX);
  float z=Mathf.Clamp(thisT.position.z, minPosZ, maxPosZ);
  thisT.position=new Vector3(x, thisT.position.y, z); .

.

.

.

.

.
.
 //Camera Area Line Void
 void OnDrawGizmos(){
  if(showGizmo){
   Vector3 p1=new Vector3(minPosX, transform.position.y, maxPosZ);
   Vector3 p2=new Vector3(maxPosX, transform.position.y, maxPosZ);
   Vector3 p3=new Vector3(maxPosX, transform.position.y, minPosZ);
   Vector3 p4=new Vector3(minPosX, transform.position.y, minPosZ);
   
   Gizmos.color=Color.green;
   Gizmos.DrawLine(p1, p2);
   Gizmos.DrawLine(p2, p3);
   Gizmos.DrawLine(p3, p4);
   Gizmos.DrawLine(p4, p1);
.

.

.


 

Posted by 프리랜서 디자이너
TA/Unity2013. 10. 15. 15:24

using UnityEngine;
using System.Collections;
using UnityEngine.SceneManagement;


public class GameStart : MonoBehaviour {

    public string SceneName;


    // Use this for initialization
    void Start () {
    }
    
    // Update is called once per frame
    void Update () {
        
    }

    public void StartGame() {
        SceneManager.LoadScene (SceneName);
    }

}

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 27. 14:58

VSync를 꺼서 최적화를 시켜보자 Unity3D

2013/07/18 17:35

출처 http://blog.naver.com/dieofflee/40193299588

 

 

 

 

 

VSync는 모니터의 화면 갱신에 맞추는 것인데

이게 생각외로 프로세스를 잡아 먹는다

 

 

첫번째 사진은 VSync옵션을 킨 상태인데 

보시다시피 대부분의 프로세스를 VSync가 사용하고 있다.



 

두번째 사진은 VSync옵션을 끈 상태인데

FPS가 2배로 오르고 그래프자체도 더 보기 편해졌다.

 

 

이제 이 옵션을 끄는 방법인데

Edit > ProjectObtion > QualitySetting 을 눌러보면

아래쪽에 VSync Count라는 항목이 있는데 Dont Sync로 바꿔주면 된다

 

 

 

아이폰의 경우 VSync를 기기에서 자체적으로 해주기 때문에

처음부터 Dont Sync를 해주면 되고

 

안드로이드의 경우 자체적으로 해주지 않아서 

키던 안 키던 그건 개발자가 결정하면 될듯 하다

 

 

참고로 이 옵션을 끄게 되면 베터리 소모가 많아 진다고 한다 ㅇㅇ

 

 

깨알팁 

- 맨위 옵션도 최적화에 도움이 되는 건데 그냥 텍스쳐의 질을 결정하는 거라 보면 된다.

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 14:13

http://myevan.cpascal.net/articles/2013/unity_ngui_pixel_perfect.html

 

유니티 NGUI 논리 해상도와 픽셀 퍼펙트

다양한 해상도를 지원하는 가장 간단한 방법 중 하나는 고정된 논리 해상도를 사용하는 것입니다. 레퍼런스 장치 해상도를 기준으로 그래픽 리소스를 만든 다음 다른 장치에서 적당히 확대 혹은 축소해서 렌더링하는 방법입니다.

예를 들어 아이폰3GS 는 320x480 (2:3) 해상도를 사용하는데, 아이폰4는 640x960 (2:3) 해상도이므로 2배 확대해서 그대로 실행할 수 있습니다. 아이패드 768x1024 (3:4) 해상도에서 실행하려면 세로 길이를 1024에 맞추고 가로길이를1024 / 480 * 320 = 682 로 확대하면 리소스나 레이아웃 수정없이 작동시킬 수 있습니다.

NGUI 에서는 2.5.x 버전 기준으로 UIRoot 컴포넌트의 Scaling Style 프로퍼티를FixedSize 로 선택한 다음 ManualHeight 프로퍼티만 설정해주면 고정된 논리 해상도를 사용할 수 있습니다.

문제는 논리 해상도 모드로 아틀라스 위젯들을 사용할 경우 위젯 경계에 검은색 선이 종종 등장한다는 것입니다. 아틀라스 스프라이트 영역 경계에 있는 검은색 컬러가 살짝 보이는 것인데, 해당 위치에 알파를 확인해보면 0으로 보이지 않아야 정상입니다. 더구나 padding 을 아무리 넓게 주어도 검은색 선 문제는 사라지지 않습니다. 선형 보간 기능의 특성상 주변 텍셀을 참조하기 때문인데, 텍스쳐 Filter Mode 를 Point 로 변경해 선형 보간을 사용하지 않으면 검은 색 선이 사라지는 것을 확인 할 수 있습니다.

그럼 이제 UIWidget 의 MakePixelPerfect 코드를 살펴보도록 하겠습니다.

virtual public void MakePixelPerfect ()
{
    Vector3 scale = cachedTransform.localScale;

    int width  = Mathf.RoundToInt(scale.x);
    int height = Mathf.RoundToInt(scale.y);

    scale.x = width;
    scale.y = height;
    scale.z = 1f;

    Vector3 pos = cachedTransform.localPosition;
    pos.z = Mathf.RoundToInt(pos.z);

    if (width % 2 == 1 && (pivot == Pivot.Top || pivot == Pivot.Center || pivot == Pivot.Bottom))
    {
        pos.x = Mathf.Floor(pos.x) + 0.5f;
    }
    else
    {
        pos.x = Mathf.Round(pos.x);
    }

    if (height % 2 == 1 && (pivot == Pivot.Left || pivot == Pivot.Center || pivot == Pivot.Right))
    {
        pos.y = Mathf.Ceil(pos.y) - 0.5f;
    }
    else
    {
        pos.y = Mathf.Round(pos.y);
    }

    cachedTransform.localPosition = pos;
    cachedTransform.localScale = scale;
}

MakePixelPerfect 는 픽셀 좌표를 정수화시킨 후 위젯 길이가 홀수면 버텍스 좌표를 0.5만큼 보정하는 기능입니다. 즉, 픽셀 퍼펙트를 적용 하면 검은색 선은 보여서는 안된다는 말입니다. 그렇다면 어디에 문제가 있는 것일까요?

그렇습니다. 당연하겠지만 이번 글의 주제인 논리 해상도가 문제의 원흉입니다. 논리 해상도에서 힘들여 설정한 픽셀 퍼펙트한 좌표가 실제 해상도에서는 엉뚱한 좌표로 변환되어 버린 것입니다 =ㅁ=)!!

예를 들어 1000x1000 논리 해상도에서 0.5 보정한 좌표는 2000x2000 해상도에서는 1을 변경한 것으로 바뀝니다. 500x500 해상도라면 보정값이 0.25가 됩니다. 1000x1000 해상도에서 보정이 필요한 5픽셀은 2000x2000해상도에서는 10픽셀 짝수 길이가 되어 보정이 불필요해지고, 1000x1000 해상도에서 보정이 필요없는 10픽셀 길이는 500x500 해상도에서 5픽셀 길이가 되어 보정이 필요해집니다.

실제로 검은색 선이 보이는 위젯의 위치를 0.1 단위로 조금씩 변경해보면 어느순간 검은색 선이 사라지는 상황을 만들 수 있습니다. 다만 해당 값이 짐작하기 어려운 수치라는것이 문제죠.

그럼 논리 해상도에서 픽셀 퍼펙트한 좌표를 계산해 내는 방법을 알아보도록 하겠습니다.

일단 논리 좌표와 논리 크기를 사용해 실제 좌표와 실제 크기를 구합니다.

화면 배율 scale =  논리 해상도 / 실제 해상도
논리 크기 lsize = 위젯 transform.localScale
논리 좌표 lpos = 위젯 transform.localPosition
실제 좌표 rpos = lpos / scale
실제 크기 lsize = lsize / scale

실제 크기를 토대로 실제 좌표에 픽셀 퍼펙트를 적용합니다.

보정 좌표 = 실제 크기가 홀수면 실제 좌표 보정

보정된 실제 좌표를 논리 좌표계로 다시 가져오면 완료입니다!

위젯 transform.localPosition = 보정 좌표 * scale
위젯 transform.localScale = 실제 크기  * scale

한가지 주의해야 할 점은 위젯 transform 을 보정된 값으로 가지고 있을 경우 유니티 인스펙터 값이 그로테스크해진다는 것 입니다.

작업 편의를 위해서는 UpdateGeometry 에서만 위젯 transform 변경해 사용한 다음 바로 원래 값으로 복원해주는 것이 좋습니다.

마지막으로 직접 코딩하기 귀찮으신 분들을 위한 NGUI UIWidget.cs 수정 사항입니다.

// BUGFIX
public static int rootManualHeight; // TODO: 외부에서 UIRoot 의 manualHeight 값을 넣어주어야 합니다
// BUGFIX_END

public bool UpdateGeometry (ref Matrix4x4 worldToPanel, bool parentMoved, bool generateNormals)
{
    if (material == null) return false;

    if (OnUpdate() || mChanged)
    {
        mChanged = false;
        mGeom.Clear();
        OnFill(mGeom.verts, mGeom.uvs, mGeom.cols);

        if (mGeom.hasVertices)
        {
            Vector3 offset = pivotOffset;
            Vector2 scale = relativeSize;

            offset.x *= scale.x;
            offset.y *= scale.y;

            mGeom.ApplyOffset(offset);

            // DELETEME: mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);

            // BUGFIX
            ApplyPixelPerfectGeometryTransform(ref worldToPanel, generateNormals);
            // BUGFIX_END
        }
        return true;
    }
    else if (mGeom.hasVertices && parentMoved)
    {
        // DELETEME: mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);
        // BUGFIX
        ApplyPixelPerfectGeometryTransform(ref worldToPanel, generateNormals);                  
        // BUGFIX_END
    }
    return false;
}

// BUGFIX
void ApplyPixelPerfectGeometryTransform(ref Matrix4x4 worldToPanel, bool generateNormals)
{
    float screenScale = rootManualHeight / Screen.height;
    float invScreenScale = 1.0f / screenScale;  

    var widgetTransform = this.cachedTransform;
    var oldSize = widgetTransform.localScale;
    var oldPos = widgetTransform.localPosition;
    var realSize = oldSize * invScreenScale;
    var realPos = oldPos * invScreenScale;

    int realWidth  = Mathf.RoundToInt(realSize.x);
    int realHeight = Mathf.RoundToInt(realSize.y);

    if (realWidth % 2 == 1 && (pivot == UIWidget.Pivot.Top || pivot == UIWidget.Pivot.Center || pivot == UIWidget.Pivot.Bottom))
    {
        realPos.x = Mathf.Floor(realPos.x) + 0.5f;
    }
    else
    {
        realPos.x = Mathf.Round(realPos.x);
    }

    if (realHeight % 2 == 1 && (pivot == UIWidget.Pivot.Left || pivot == UIWidget.Pivot.Center || pivot == UIWidget.Pivot.Right))
    {
        realPos.y = Mathf.Ceil(realPos.y) - 0.5f;
    }
    else
    {
        realPos.y = Mathf.Round(realPos.y);
    }

    widgetTransform.localPosition = realPos * screenScale;
    widgetTransform.localScale = realSize * screenScale;

    mGeom.ApplyTransform(worldToPanel * cachedTransform.localToWorldMatrix, generateNormals);

    widgetTransform.localPosition = oldPos;
    widgetTransform.localScale = oldSize;
}
// BUGFIX_END

뭔가 좀 더 최적화가 가능할 것 같지만, 일단 여기까지만 다루도록 하겠습니다.

'TA > Unity' 카테고리의 다른 글

간단한 씬 로딩  (0) 2013.10.15
VSync - WaitForTargetFPS  (0) 2013.09.27
unity 에디터 확장  (2) 2013.09.11
로딩 페이지 및 로딩 프로그래스바 사용하기.  (0) 2013.09.11
효과적인 C# 메모리 관리 기법  (1) 2013.09.09
Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 11:31

출처 : http://www.unitystudy.net/bbs/board.php?bo_table=writings&wr_id=146&sca=&sfl=wr_subject&stx=%EC%97%90%EB%94%94%ED%84%B0+%ED%99%95%EC%9E%A5&sop=and

unitystudy.net

 

 

 

 

 

 

 

 

1. 에디터 확장 01 - 커스텀 인스펙터

안녕하세요, 우영씨입니다. 이번에 에디터 확장과 관련된 글 연재를 맡게 되었습니다. 많은 경험이 있는 것은 아니지만 최대한 제가 알고 있는 선에서 지식과 경험을 공유해보고자 합니다. 이 연재가 여러분의 개발을 즐겁게 해줄 수 있기를 기대해봅니다.
문의 사항이 있으신 분은 아래 덧글로 남겨주시거나 whoo24@gmail.com 또는 @whoo24 으로 문의주십시오. 편의상 존칭은 생략하였습니다.

에디터

에셋스토에서 구입한 툴이나 플러그 인들은 대체로 자신들만의 에디터를 가지는 경우가 많다. 툴이 필요한 이유는 실수를 줄여주고 컨텐츠의 빠른 추가가 가능하기 때문이다. 특히 프로그래머가 아닌 기획자나 아트 쪽 작업자가 건드리기에도 무척 유용하다.
유니티의 경우 엔진 소스에 접근하지 않고 유니티 툴을 확장 시켜서 개발 효율을 향상시키는 방법을 제공하고 있다. 아래의 이미지는 2D Toolkit의 스프라이트 콜랙션에 관련된 에디터이다. 

TK2D 에디터 이미지


인스펙터

인스펙터는 게임 오브젝트에 대한 속성값을 보여주고 설정을 변경할 수 있도록 도와주는 창이다. 기본적으로 MonoBehaviour의 public 으로 설정한 멤버 변수들이 인스펙터에 읽기/쓰기가 가능한 상태로 노출된다. 대부분의 경우는 맴버 변수의 타입에 따라서 인스펙터가 자동으로 필드를 생성시켜준다.

카메라의 경우 Clear Flags 같이 콤보 필드와 Background 같은 컬러 필드, 그리고 Culling Mask처럼 여러 값이 선택 가능한 마스크 필드를 비롯한 여러가지 필드들을 볼 수 있다.

Main Camera의 인스펙터



인스펙터를 다시 한번 보면 게임 오브젝트에 추가된 컴포넌트 별로 속성이 정리되어 있다. 우리는 이 인스펙터가 보여주는 뷰 자체를 뜯어고칠 수는 없다. 다만 하나의 컴포넌트에 대해 개별적으로 수정할 수 있을 뿐이다.

컴포넌트 인스펙터 확장

유니티 프로젝트를 하나 만들면 두 가지 프로젝트가 생성된다. Assembly-CSharp 프로젝트와 Assembly-CSharp-Editor 프로젝트가 자동으로 만들어진다.

에디터에 관계된 코드들은 게임 상에서는 실행될 수 없다. 빌드조차 되지 않는다. 그 이유는 대부분의 기능이 UnityEditor 라는 네임스페이스 내에 구현되어 있고 이 UnityEditor 참조는 게임 프로젝트에 포함되지 않는다. 

반드시 루트의 Editor 폴더에 스크립트가 있어야 하는 것은 아니다. 어느 서브 폴더든지 Editor 폴더안에만 있다면 OK.

image2013-2-3 23-18-9.png



씬에 큐브를 하나 만들고 이 큐브를 회전시키는 스크립트를 하나 만들어 보자. 두 스크립트 파일을 만든다.

그리고 이 스크립트를 조절할 수 있도록 인스펙터를 확장해보자. 우선 CubeRotator.cs 파일을 생성한다.

using UnityEngine;
using System.Collections;
public class CubeRotator : MonoBehaviour {
    public float rotationSpeed = 180.0f;
     
    void Update () {
        gameObject.transform.Rotate(Vector3.up, Time.smoothDeltaTime * rotationSpeed);
    }
}

image2013-2-3 21-1-10.png



큐브에 이 컴포넌트를 추가하고 플레이 버튼을 누르면 이 큐브는 그 자리에서 뱅글뱅글 돌 것이다.

컴포넌트를 확장해서 해당 큐브의 회전 방향을 바꿔주는 버튼을 하나 만들어보자. 우선은 인스펙터를 확장하기 위한 뼈대를 만들자. Editor/CubeRotatorInspector.cs 파일을 만든다.

using UnityEngine;
using UnityEditor;
 
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor {
    public override void OnInspectorGUI()
    {
        base.DrawDefaultInspector();
    }
}
 
image2013-2-3 21-8-16.png



CubeRorator에 대한 인스펙터가 미묘하게 달라진 것을 볼 수 있다. 코드를 보면 우선 네임스페이스에 UnityEditor를 추가했다. 그리고 CubeRotatorInspector 클래스가 UnityEditor.Editor 를 상속 받았다. 

상속 받은 CubeRotatorInspector CustomEditor클래스 어트리뷰트를 가지고 있다. 이 CustomEditor는 인자값으로 받은 타입의 인스펙터에 대한 처리를 담당하게 된다. 따라서 여기서는 CubeRotator 컴포넌트에 대한 인스펙터를 처리하게 되는 것이다.

OnInspectorGUI() 메쏘드는 UnityEditor.Editor 클래스에서 파생된 메쏘드이다. 이 메쏘드를 오버라이드 해서 우리가 원하는 모습을 만들 수 있다.
UnityEditor.Editor 클래스에 대한 더 자세한 사항은 다음의 자료를 찾아보도록 하자.
 
 계속해서 인스펙터를 바꿔보자.

using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor
{
    public override void OnInspectorGUI()
    {
        EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator"); 
         
        base.OnInspectorGUI();
         
        if(GUILayout.Button("Change Direction"))
        {
            CubeRotator cube = target as CubeRotator;
            if (cube)
            {
                cube.rotationSpeed = -cube.rotationSpeed;
            }
        }
    }
}

OnInspectorGUI() 메쏘드에서 LabelField를 추가했다. Custom Inspector for 라는 라벨은 왼쪽에, CubeRotator는 오른쪽에 위치하게 된다. 

그리고 기본 인스펙터 데이터를 그리고(스크립트와 Public 변수들이 포함된다) Change Direction 이라는 버튼을 생성한다. 이 버튼을 누르면 Editor.target 변수를 이용해 컴포넌트를 가지고 온다. Editor.target 변수는 UnityEngine.Object 형이므로 적절한 형으로 캐스팅해줘야 한다. target의 rotationSpeed를 반대 방향으로 바꿔준다. 이제 이 버튼을 누르는 순간 큐브는 반대로 회전하게 될 것이다.

위에서 보는 것처럼 EditorGUILayout  GUILayout 모두 사용 가능하다. EditorGUILayout은 Field와 관련있는 메쏘드들이 대부분이다. GUILayout은 기본 필드, 스크롤바, 토글 등이 있다.
자세한 사항은 다음 링크에서 알아보자.

image2013-2-3 22-12-13.png



플레이 버튼을 누르고 플레이 중에 Change Direction을 눌러보자. Rotation Speed 값이 -180 으로 변경될 것이다. 커스텀 인스펙터도 기본 인스펙터와 마찬가지로 플레이 중이 아니더라도 작동한다. 에디트 모드에서 버튼을 눌러보면 마찬가지로 동작한다.

여러 객체 편집하기

우리가 만든 Cube 객체를 복사(Duplicate)해서 하나 더 만들어보자.

image2013-2-3 22-18-56.png



그리고 둘 다 선택하면 인스펙터에 우리가 제작한 CubeRotator 컴포넌트에 해당하는 부분에 Multi-object editing not supported. 라는 메시지가 뜬다.

image2013-2-3 22-20-21.png



이 경우에는 CanEditMultipleObjects 어트리뷰트를 추가해주면 된다. 단 이 경우 Editor.target은 Hierarchy에서 가장 상위에 위치한 오브젝트를 지칭하게 되니 Editor.target 대신 Editor.targets를 써주면 된다.

using UnityEngine;
using UnityEditor;
using System.Linq;
using System;
 
[CanEditMultipleObjects]
[CustomEditor(typeof(CubeRotator))]
public class CubeRotatorInspector : Editor
{
    public override void OnInspectorGUI()
    {
        EditorGUILayout.LabelField("Custom Inspector for", "CubeRotator");
        base.OnInspectorGUI();
        if (GUILayout.Button("Change Direction"))
        {
            CubeRotator[] cubes = Array.ConvertAll(targets, _t => _t as CubeRotator);
            foreach (CubeRotator cube in cubes)
            {
                if (cube)
                {
                    cube.rotationSpeed = -cube.rotationSpeed;
                }
            }
        }
    }
}

보면 알겠지만 Array를 캐스팅할 때 Linq를 사용했다. Linq 구문 대신 foreach 문으로 대신해도 된다. 저 Linq 구문은 Object[]인 targets에서 _t 이름으로 하나씩 꺼낸 후 CubeRotator 로 컨버팅 한 배열을 돌려주는 것이다. 만약 Linq에 대해서 모른다면 최소한 기본 문법 만큼은 배워두도록 하자.
 
image2013-2-3 22-38-56.png



이제 Cube를 두 개 선택해도 인스펙터가 정상적으로 작동한다.

정리

첫 강은 컴포넌트 타입에 대한 인스펙터 확장법에 대해 알아보았다. 인스펙터와 관련된 코드들이 어디에 있어야 하는지에 대해서 이야기 했고, 인스펙터에 GUI 요소를 추가하는 방법도 알아보았다. 하나의 오브젝트 뿐만아니라 여러 오브젝트들도 동시에 편집하는 방법을 알아보았다.
다음번 강좌에서는 더 많은 GUI 요소들과 메뉴 확장에 대해서 알아보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2. 에디터 확장 02 - 메뉴 확장(1)

메뉴 확장

이전 강에서는 인스펙터를 확장해 보았다. 
유니티를 확장할 수 있는 요소로는 인스펙터 뿐만 아니라 메뉴를 확장할 수도 있다. 
메뉴에는 메뉴바에 추가되는- 일반 메뉴와 인스펙터 옆에서 작동하는 컨택스트 메뉴가 있다. 
이번 연재에는 메뉴바에 새로운 메뉴를 넣어보고 메뉴를 작동하는 방법에 대해서 알아보도록 한다.

주메뉴 추가

메뉴를 추가하는 방법은 매우 간단하다. 
MenuItemAttribute를 이용하면 된다. 
주의할 점은 static 함수에만 가능하다는 점이다. 
대신에 액세스 한정자는 무엇이 되든 상관이 없다. public 이나 protected나 심지어 private 여도 상관없이 지정할 수 있다.
 또 어떤 클래스에 있던지 상관이 없다. 일반 클래스여도 상관이 없고, MonoBehavior를 상속받은 컴포넌트내에 있어도 상관이 없다.
 다만 MenuItemAttribute는 UnityEditor 네임 스페이스 안에 있으므로 Editor 폴더 밖이라면 적당한 전처리기(#if UNITY_EDITOR ... #endif) 로 묶어줘야 한다.

02-1.png

using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu")]
    static public void CustomMenuItem()
    {
        Debug.Log("public customMenuItem");
    }
    [MenuItem("Custom Menu/protected custom Menu")]
    static protected void CustomMenuItemProtected()
    {
        Debug.Log("protected customMenuItem");
    }
    [MenuItem("Custom Menu/private custom Menu")]
    static private void customMenuItemPrivate()
    {
        Debug.Log("private customMenuItem");
    }
}

MenuItemAttribute는 다음과 같은 생성자를 가진다.
public MenuItem(string itemName);
public MenuItem(string itemName, bool isValidateFunction);
public MenuItem(string itemName, bool isValidateFunction, int priority);

itemName은 메뉴의 경로를 나타낸다. 위에서 사용한 것 처럼 '/' 를 구분자로 이용해 서브 메뉴를 자동으로 생성한다. 
유니티에서 MenuItem의 메뉴 경로를 바꾸면 자동으로 새로운 경로로 메뉴를 생성하여 준다.
 다만 루트 경로를 바꾼 경우에는 에디터를 껐다가 켜야 한다.

itemName 인자를 이용해 단축키를 지정할 수도 있다.
02-2.png


using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu %1")]
    static public void CustomMenuItem()
    {
        Debug.Log("public customMenuItem");
    }
    [MenuItem("Custom Menu/protected custom Menu #1")]
    static protected void CustomMenuItemProtected()
    {
        Debug.Log("protected customMenuItem");
    }
    [MenuItem("Custom Menu/private custom Menu %&1")]
    static private void customMenuItemPrivate()
    {
        Debug.Log("private customMenuItem");
    }
}

%는 Ctrl키(맥은 cmd키)를 나타낸다. #는 Shift 키를 나타내고 _는 옵션키를 아무것도 지정하지 않은 키를 나타낸다. &는 alt 키를 나타낸다.
%& 처럼 두 가지 이상의 키를 조합할 수도 있다.

isValidateFunction 인자는 메뉴의 유효성 검사를 할 수 있는 옵션이다. 
유효성 검사를 통해 해당 메뉴를 켜거나 끄거나 할 수 있다.

02-3.png


using UnityEngine;
using UnityEditor;
public class ValidateMenu
{
    [MenuItem("Custom Menu/Validate/Get GameObject Id", validate = true)]
    static public bool GetGameObjectIdValidator()
    {
        bool selected = (Selection.activeGameObject != null);
        Debug.Log(string.Format("Validate({0})", selected));
        if (selected)
            return true;
        return false;
    }
    [MenuItem("Custom Menu/Validate/Get GameObject Id", validate = false)]
    static public void GetGameObjectId()
    {
        Debug.Log(string.Format("GetGameObjectId({0})", Selection.activeInstanceID));
    }
}

유효성 평가 함수가 되려면 bool 형을 리턴하는 함수여야 한다. 
만약 void 형이나 다른 형이 들어온다면 false로 평가된다.

현재 아무 오브젝트도 선택하지 않았기 때문에 유효성 평가 함수인 GetGameObjectIdValidator() 는 false를 리턴한다.
로그를 보면 Validate(false) 를 볼 수 있는데, 매번마다 메뉴를 보여질 때 마다 평가하게 된다.

02-4.png



이제 씬의 아무 오브젝트나 선택하고 메뉴를 열면 Get GameObject Id 메뉴가 활성화 된 것을 볼 수 있다.
로그를 보면 Validate가 두 번 불려진 것을 확인할 수 있는데, 메뉴가 보여질 때 한 번, 메뉴가 눌려저서 실행될 때 한 번 불려지는 것을 알 수 있다.

priority 인자는 메뉴의 배치 순서를 나타낸다.
Custom Menu를 보면 public, protected, private 사이에 Validate 메뉴가 있는 것을 볼 수 있다. 
이 메뉴를 정렬하는 법을 알아보자.

priority 인자를 설정하지 않으면 priority 인자를 설정한 메뉴들과 구분선이 생기게 된다.
priority를 양수로 설정하면 메뉴 하단에 위치하게 되고, 음수로 설정하면 메뉴 상단에 위치하게 된다. 
validate가 true 인 함수는 표시 되지 않으므로 의미가 없고 validate가 false로 설정된 함수에 priority를 설정해두면 우선순위 설정이 정상적으로 된다.

02-5.png


using UnityEngine;
using UnityEditor;
public class CustomMenu
{
    [MenuItem("Custom Menu/public custom Menu", priority = 0)]
    static public void CustomMenuItem()
    {
        ...
    }
    [MenuItem("Custom Menu/protected custom Menu", priority = -1)]
    static protected void CustomMenuItemProtected()
    {
        ...
    }
    [MenuItem("Custom Menu/private custom Menu", priority = 1)]
    static private void customMenuItemPrivate()
    {
        ...
    }
}

정리

이번 강의는 조금 짧게 진행이 되었다. 
이번에는 메뉴바에 MenuItemAttribute를 이용해 새로운 메뉴를 추가하는 방법을 알아보았고 MenuItemAttribute의 다양한 속성에 대해서 알아보았다.
다음 번에는 컨택스트 메뉴에 대해서 자세히 알아보는 시간을 가져보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

에디터 확장 03 - 메뉴 확장(2) Context Menu와 Generic Menu

컨텍스트 메뉴

컨텍스트 메뉴는 주로 마우스 오른쪽 버튼을 눌렀을 때 동작하는 메뉴를 뜻하는데, 유니티에서는 타겟이 존재하는 컴포넌트에 행하는 메뉴라고 보는 편이 더 적합하다고 본다.

컨텍스트 메뉴를 만드는 방법은 두 가지가 있다.

먼저 첫 번째 방법은 주 메뉴를 만드는 것 처럼 MenuItemAttribute를 사용하는 방법이 있다.

그리고 두 번째 방법은 ContextMenuAttribte를 사용하는 것이다.

image2013-3-6 9-50-17.png



우선 예제 씬을 구성한다.

녹색 큐브를 놓고 그 아래에 터레인을 깔았다.

Hierarchy는 다음과 같다.

image2013-3-6 9-52-19.png



NewContext.cs 파일과 Editor/ContextEditor.cs 파일을 만들었고, 지형 데이터와 씬, 머테리얼 애셋을 미리 만들어서 설정해두었다.

 

유니티에서 컨택스트 메뉴는 주 메뉴를 만들었을 때 처럼 특정한 attribute 를 이용해서 설정하면 된다.

주 메뉴를 만들듯이 MenuItemAttribute를 이용해서 컨택스트 메뉴를 만들어 보자.

NewContext.cs
using UnityEngine;
using System.Collections;
 
public class NewContext : MonoBehaviour {
#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]
    static void Push(UnityEditor.MenuCommand command)
    {
        Rigidbody body = (Rigidbody)command.context;
        body.AddForce(Vector3.up * 500f);
    }
#endif
}


이렇게 하면 RigidBody 컴포넌트에 Push 라는 컨택스트 메뉴를 만들어준다. MenuItem은 UnityEditor 네임스페이스 내에 있으므로 전처리기를 이용해 묶어주었다.

플레이를 누르고 RigidBody 컴포넌트의 Push 메뉴를 누르면 큐브가 위로 올라갔다가 내려올 것이다.

image2013-3-10 21-6-16.png



MenuItemAttribute를 사용하는 이 방법은 이미 만들어진 컴포넌트에 메뉴를 붙일 수 있는 유일한 방법이기도 하다.

주 메뉴를 만드는 것과 비슷하게 메뉴의 경로에 CONTEXT/[컴포넌트 이름]/[메뉴명] 순으로 주었다는 것을 눈여겨 보자.

주 메뉴와 다른 점은 MenuCommand 를 인자로 받는다는 점이다.

static 함수는 인스턴스가 없이 호출되므로 어느 컴포넌트에서 불렸는지 알 수가 없다.

커스텀 인스펙터처럼 Editor 를 상속 받은 경우라면 target 정보를 이용해서 처리할 수 있지만, 지금처럼 MonoBehavior를 상속받은 경우에는 static 함수로는 현재의 컨택스트를 가져올 수가 없다.

이를 위해 MenuCommand 인자를 이용해서 처리할 수 있게 배려해놓았다.

MenuCommand.context 멤버는 해당 컴포넌트(선택된 컴포넌트)를 알고 있으므로 이를 이용해 처리할 수 있다.

 

컨텍스트 메뉴라면 좀 더 편하게

위에서 MenuItemAttribute를 이용해 컨택스트 메뉴를 만드는 방법을 알아보았다.

컨텍스트 메뉴를 만들때는 조금 더 편하게 만들 수 있도록 ContextMenuAttribute를 준비해 놓았다.

using UnityEngine;
using System.Collections;
 
public class NewContext : MonoBehaviour {
#if UNITY_EDITOR
    [UnityEditor.MenuItem("CONTEXT/Rigidbody/Push")]
    static void Push(UnityEditor.MenuCommand command)
    {
        Rigidbody body = (Rigidbody)command.context;
        body.AddForce(Vector3.up * 500f);
    }
#endif
    [ContextMenu("Turn")]
    void Turn()
    {
        gameObject.transform.Rotate(Vector3.up, 30);
    }
}

ContextMenuAttribute는 MenuItemAttribute와 다르게 UnityEngine 네임 스페이스 안에 있다.

그리고 static 함수가 아닌 일반 메서드에 attribute를 설정할 수 있다.
MenuCommand 인자로 받지 않지만 일반 클래스 메소드이기 때문에 this를 컨택스트 정보로 사용하면 된다.

MenuItemAttribute처럼 어느 컴포넌트에 위치할 지 결정하지 못한다.

image2013-3-10 22-7-2.png



따라서 ContextMenuAttribute 가 달린 컴포넌트는 해당 컴포넌트에 컨택스트 메뉴를 만든다.

해당 컨텍스트 메뉴를 선택하면 게임 오브젝트를 30도 회전한다.

 

Generic Menu


이제 메뉴의 마지막으로 제너릭 메뉴에 대해서 알아보도록 하자.

제너릭 메뉴는 커스텀 메뉴를 만들 수 있도록 도움을 준다.

일반적인 드롭다운 방식의 메뉴 뿐만 아니라 컨텍스트 메뉴까지 만들 수 있다.

Generic Menu를 설명하기 위해서는 EditorWindow 클래스를 이용하는 편이 설명하기 쉬우므로 다음 강에 배우게 될 EditorWindow 클래스를 미리 이용해보도록 하겠다.

Editor/ContextEditor.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
public class ContextEditor : EditorWindow {
    [MenuItem("Window/Show Context Editor Window")]
    static void Init()
    {
        EditorWindow.GetWindow<ContextEditor>();
    }
 
    void OnGUI() {
        Event evt = Event.current;
        Rect contextRect  = new Rect (10, 10, 100, 100);
         
        if (evt.type == EventType.ContextClick)
        {
            Vector2 mousePos  = evt.mousePosition;
            GenericMenu menu = new GenericMenu();
            menu.AddItem(new GUIContent("MenuItem1"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 1");
            menu.AddItem(new GUIContent("MenuItem2"), false, new GenericMenu.MenuFunction2(MenuArg1), "Menu Item 2");
            menu.AddSeparator("");
            menu.AddItem(new GUIContent("SubMenu/MenuItem3"), false, new GenericMenu.MenuFunction(MenuNoArg));
            menu.ShowAsContext();
            evt.Use();
        }
    }
 
    void MenuNoArg()
    {
        Debug.Log("Selected menu");
    }
 
    void MenuArg1(object obj)
    {
        Debug.Log ("Selected: " + obj);
    }
}


Init() 함수를 이용해 커스텀 윈도우를 생성한다.

Window/Show Context Editor Window 메뉴를 만들었고, 이 메뉴를 선택하면 새로운 윈도우가 생성된다.

image2013-3-10 22-25-48.png

image2013-3-10 22-26-54.png



아무것도 없는 윈도우지만 마우스 오른쪽 버튼을 누르면 메뉴가 생성된다.

image2013-3-10 22-29-48.png





이제 코드를 살펴볼 시간이다.

OnGUI() 에서 제너릭 메뉴를 생성하는데 마우스 오른쪽 버튼을 누르면 ContextClick 이라는 이벤트가 발생한다.

GenericeMenu를 만들 때 AddItem 으로 메뉴 항목을 추가하게 된다.

AddItem은 다음과 같은 방식으로 선언되어 있다.

function AddItem (content : GUIContent, on : boolean, func : MenuFunction) : void

function AddItem (content : GUIContent, on : boolean, func : MenuFunction2, userData : object) : void



3번째 인자로 MenuFunction에 대한 델리게이트를 전달해줘야 하는데 2가지 방법이 있다.

MenuFuction은 아무런 인자가 없는 델리게이트이고 MenuFunction2는 1개의 인자를 가지는 델리게이트이다.

image2013-3-10 22-42-33.png



위 그림은 메뉴를 하나씩 순서대로 클릭한 결과이다.

MenuFunction2 의 인자는 object 타입이므로 아무거나 넘길 수 있다. 이 경우 userData로 문자열 "Menu Item 1"과 문자열 "Menu Item 2"를 넘겨주었고 해당 델리게이트에서 인자로 userData를 받아서 처리한 것이다.

그리고 메뉴 추가 아랫줄에 menu.ShowAsContext() 함수가 있다.

메뉴를 추가한 후에는 메뉴를 표시해주어야 하는데 ShowAsContext() 메서드 또는 DropDown() 메서드로 메뉴를 표시할 수 있다.

ShowAsContext()는 마우스 클릭한 위치에 메뉴를 표시해준다.

반면 DropDown()은 Rect 인자를 하나 받는다.

Rect.left와 Rect.top에 메뉴를 펼쳐보이는데, 3.5.6f 버전에서는 y 위치에 Rect.height 를 더해서 표시해준다. x 위치는 Rect.width에 영향을 받지 않는 것으로 보인다.

결론

컨택스트 메뉴를 만드는 방법 3가지를 알아 보았다.

MenuItemAttribute를 이용해서 컨택스트 메뉴를 만들어 보았고, MonoBehaviour에 ContextMenuAttribute를 추가해서 간단하게 만들어보기도 했다.

그리고 GenericMenu를 이용해서 커스텀 메뉴를 만들어보기도 했다.

이 예제에서는 GenericMenu를 컨택스트 클릭 이벤트를 받아서 컨택스트 메뉴처럼 사용했지만, 처리 방식에 따라서 어떠한 메뉴라도 만들 수 있다.

다음 강에서는 에디터 확장의 꽃 EditorWindow 클래스를 이용하는 방법에 대해서 알아보도록 하겠다.

 

 

 

 

 

 

 

 

 

 

 

4. 에디터 확장 04 - EditorWindow

 

 

EditorWindow

이번에는 EditorWindow에 대해 알아보겠다.

EditorWindow는 유니티를 툴로 만들기 위해서 사용해야하는 클래스이다.

인스펙터와 달리 EditorWindow는 프로그래머가 원하는 데로 자유롭게 꾸밀 수 있게 해준다.

기본적인 사용법은 인스펙터와 거의 비슷하지만 훨씬 자유도가 있고 다양한 응용이 가능하다.

인스펙터는 컴포넌트가 반드시 게임오브젝트에 포함되어 있어야 하고 컴포넌트를 가진 게임 오브젝트를 선택해야만 보여진다.

그런 반면 EditorWindow는 언제든 창을 띄워둘 수 있어서 그야말로 툴에 적합한 클래스라고 할 수 있다.

이번에는 CanvasWindow 라는 EditorWindow를 확장시킨 클래스를 만들어보도록 한다.

캔버스를 만들자


image2013-4-11 20-58-14.png



 

위 Window는 우리가 이번 시간에 만들 EditorWindow의 확장 클래스인 CanvasWindow의 샘플이다.

펜의 색상과 크기를 고를 수 있고 팔레트에서 다양한 펜을 고를 수 있다(하지만 펜의 적용을 구현하지는 않을 것이다. 내용이 너무 길어지기 때문이다).

단순하게 메뉴를 생성하고 캔버스를 지우고 출력하는 간단한 구현만 해보도록 하겠다.

우선은 Editor\CanvasWindow.cs 파일을 생성한다.

그리고 윈도우를 띄우기 위해서 메뉴를 생성한다.

using UnityEngine;
using UnityEditor;
using System;
 
public class CanvasWindow : EditorWindow {
 
    [MenuItem("Canvas/Show")]
    public static void ShowWindow()
    {
        CanvasWindow window = (CanvasWindow)EditorWindow.GetWindow(typeof(CanvasWindow));
    }
}

이 코드는 Canvas/Show 메뉴를 만든다.

EditorWindow.GetWindow() 를 이용해서 CanvasWindow의 인스턴스를 만든다. 만약에 창이 띄워져 있는 경우라면 해당 창을 가져오고, 생성한 창이 없다면 새로 창을 생성한다.


image2013-4-11 21-4-51.png



 


위처럼 Canvas 메뉴가 생겼고 Show를 누르면 아무것도 없는 윈도우가 생성된다.

이제 이 윈도우에 컨트롤을 배치한다.

여기에 배치할 컨트롤은 펜의 크기, 펜의 색상, 그리고 가운데 표시할 텍스쳐이다.

...
	Texture2D mytexture;
    Color penColor;
    int penSize;
    readonly Rect texSize = new Rect(0, 0, 300, 400);
    Rect windowRect = new Rect(3, 3, 100, 400);

    void OnGUI()
    {
	}
}

가운데 그려지는 캔버스는 Texture가 되고 나머지는 int형 펜 크기, Color형 펜 색상이다.

나머지 rect 들은 각각 텍스처 크기와 팔렛트박스의 크기이다.

...
	void OnGUI()
	{
		if (mytexture == null)
            mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false);
        Rect canvasPos = new Rect((position.width - texSize.width) / 2, (position.height - texSize.height) / 2, texSize.width, texSize.height);
        
        EditorGUI.DrawPreviewTexture(canvasPos, mytexture);
        penColor = EditorGUILayout.ColorField("Pen Color", penColor);
        penSize = EditorGUILayout.IntSlider("Pen Size", penSize, 1, 10);
	}
}

우선 GUI 로직에 들어와서 texture가 null 이라면 texture를 생성한다.

캔버스의 위치는 화면의 가운데이고, 텍스처를 먼저 그리고 나머지 컨트롤을 그린다.


image2013-4-11 21-23-18.png



 


만약 텍스처를 나중에 그리면 윈도우를 줄였을 때 컨트롤이 텍스처 아래로 들어가서 보이지 않는다.

텍스처를 먼저 그리면 컨트롤이 위에 그려져서 화면이 작을 때에도 불편함 없이 사용할 수 있다.

image2013-4-11 21-24-40.png



 

이제 팔레트 윈도우를 그려보자.

...
	void OnGUI()
    {
        ...
        
        BeginWindows();
        windowRect = GUILayout.Window(1, windowRect, DoWindow, "Palette");
        EndWindows();
	}
 
	enum DrawMode
    {
        point,
        line,
        rectangle,
        ellipse,
    }
    DrawMode drawmode;
    void DoWindow(int id)
    {
        EditorGUILayout.BeginVertical();
        drawmode = (DrawMode)GUILayout.SelectionGrid((int)drawmode, Enum.GetNames(typeof(DrawMode)), 1);
        if (GUILayout.Button("clear"))
        {
            mytexture = new Texture2D((int)texSize.width, (int)texSize.height, TextureFormat.RGBA32, false);
        }
        EditorGUILayout.EndVertical();
    }
}

이제 그럴싸하게 기본 뷰는 다 나왔다.

팔레트 윈도우를 만드는 방법은 EditorWindow.BeginWindow() 와 EditorWindow.EndWindows() 사이에 GUILayout.Window() 함수를 이용해서 새로운 윈도우를 생성하였다.

GUILayout.Window() 에서는 윈도우 GUI에 대한 콜백을 받는다. 여기서는 DoWindow(window_id) 함수를 콜백으로 지정했고 이 안에서 GUILayout.SelectionGrid를 이용해서 여러 메뉴가 선택가능하게 만들었다. 다만 각 메뉴의 처리는 우리의 주제와 맞지 않기 때문에 point만 처리하도록 했다.

image2013-4-11 21-28-26.png



 

그런데 문제가 발생했다. 팔렛트 윈도우가 마우스 드래그로 창의 위치를 바뀌지가 않는다.

윈도우의 메시지 콜백에 GUI.DragWindow() 를 불러주면 이 창은 움직일 수 있게 된다.

...
	void DoWindow(int id)
	{
		...
		GUI.DragWindow();
	}
}

이제 텍스처에 그리기를 구현할 차례다.

다시 OnGUI() 함수의 가장 끝으로 돌아가서 마우스 이벤트에 대한 처리를 추가한다.

...
    void OnGUI()
    {
		...
        
        if (Event.current.type == EventType.MouseDrag)
        {
            int bx = (int)(Event.current.mousePosition.x - canvasPos.x);
            int by = (int)(texSize.height - (Event.current.mousePosition.y - canvasPos.y));
                
            for (int x = 0; x < penSize; ++x)
            {
                for (int y = 0; y < penSize; ++y )
                    mytexture.SetPixel(bx - penSize / 2 + x, by - penSize / 2 + y, penColor);
            }
            mytexture.Apply(false);
            Repaint();
        }
    }
...

Event.current 객체에 마우스의 이벤트가 넘어온다.

MouseDrag 뿐만 아니라 다운이나 업에 관련된 이벤트도 있다.

MouseDrag 이벤트가 오면 캔버스에 그림을 그린다.

캔버스로 사용하고 있는 mytexture에  Texture2D.SetPixel() 함수를 이용해 텍스처를 편집하고 마지막으로 Texture2D.Apply() 를 이용해 텍스처에 적용해준다. 이때 인자로 들어가는 Boolean 값은 밉맵을 갱신 시킬지에 관련된 함수다.

텍스처에 대한 접근이 끝난 후에는 EditorWindow.Repaint() 함수를 불러서 윈도우를 갱신시켜야 한다.

이제 모든 작업이 완료되었다.

정리

이번에 살펴본 EditorWindow는 우리가 지금까지 배워온 것들을 응용해보는 과정에 불과했다.

그래서 하나의 예제를 구현하는 방식으로 진행하였고, 자잘한 처리들은 패스했다.

우리는 EditorwWindow를 상속받은 새로운 윈도우를 생성하는 법과 텍스처를 직접 편집하고 그려보는 일도 해보았다.

이것을 조금 활용하면 다양한 툴을 만들 수 있을 것이다.

이 강좌의 전체 코드는 여기에 있다: https://gist.github.com/whoo24/5334402

다음 강에서는 Handles와 Gizmos에 대해서 배울 것이다.

 

 

 

 

 

Posted by 프리랜서 디자이너
TA/Unity2013. 9. 11. 10:32

 출처 : http://www.unitystudy.net/bbs/board.php?bo_table=tip&wr_id=52

 

 

안녕하세요 유니티스터디에서 도움만 받는 잉여 회원 입니다.
 
로딩 페이지 구현? 에 고생해서 정식으로 유니티스터디를 통해서 올립니다.
 
 AsyncOperation.progress라는 놈이 pc에서는 제대로된 값을 얻을 수 없어서 삽질을 많이 하셨습니다 ㅠㅠ
 
using UnityEngine;
using System.Collections;
public class EnterLoadPage : MonoBehaviour {
 
 public UISlider  Script;
 public UISlider  ScriptPercent;
 public UILabel  textScript;
 
 AsyncOperation   async;
 
 bool IsLoadGame = false;
 
 public IEnumerator StartLoad( string strSceneName )
 {
        if (IsLoadGame == false) 
        {
            IsLoadGame = true;
            
            AsyncOperation async = Application.LoadLevelAsync ( strSceneName );
        
            while(async.isDone == false) 
            {
                float p = async.progress *100f;
                int pRounded = Mathf.RoundToInt(p);
    
                textScript.text = pRounded.ToString();
 
                //progress 변수로 0.0f ~ 1.0f로 넘어 오기에 이용하면 됩니다.
                ScriptPercent.sliderValue = async.progress;
                
                yield return true;
           }
        }
    }
 
 void Start()
 {
       StartCoroutine( "StartLoad", "SceneGame" );
 }
 
 float fTime = 0.0f;
 
//로딩 페이지에서 연속으로 애니메이션 만들때 Update 함수 내에서 만들면 됩니다.
 void Update () {
  fTime += Time.deltaTime;
  
      if( fTime >= 1.0f )
      {
            fTime = 0.0f; 
      }
  
        Script.sliderValue = fTime;
  }
}
 
 
사용법은
 
Scene과  로딩 부하가 심한 Scene 사이에 해당 ScenePage를 추가 하여 사용하시면 됩니다.
 
ex)
 
SceneMenu -> SceneLoadingPage -> SceneGame
 
 
읽어 주셔서 감사합니다.
Posted by 프리랜서 디자이너
TA/Unity2013. 9. 9. 17:56

http://www.imaso.co.kr/?doc=bbs/gnuboard.php&bo_table=article&wr_id=40743

 

조명근 narlamy@ndoors.net|엔도어즈 기술 지원팀 팀장으로 근무하고 있으며, MMORPG ‘아틀란티카’를 개발했다. 덴마크에 있는 유니티 개발팀과 긴밀히 협조해 메모리 문제 등 여러 가지 이슈들을 처리하고 있으며 현재 유니티 3D엔진을 사용해 웹과 모바일 플랫폼을 지원하는 MMORPG ‘삼국지를 품다’를 개발하고 있다.

C#은 상당히 좋은 언어다. 가장 많이 알려진 C#의 특징 중 하나는 메모리 관리에 부담이 없다는 점이다.

So Cool~ C# 메모리
C/C++를 사용하면서 포인터 때문에 괴로워 해본 적이 있는가? 그렇다면 C#에 관심을 가져보는 것이 좋다. C#은 다음과 같은 특징들을 제공하기 때문이다.

- 메모리 해제에 신경 쓰지 않아도 된다.
- 이미 삭제된 메모리에 접근하는 실수를 방지해준다.
- 잘못된 캐스팅으로 엉뚱한 메모리에 접근하지 않게 한다.
- 배열 크기보다 큰 메모리에 접근하지 못한다.
- 메모리 단편화에 대해 신경 쓰지 않아도 된다.

편한 C#, 마구잡이로 사용하면 낭패
골치 아픈 메모리 관리를 신경 쓰지 않아도 된다는 점은 사용자들에게 무척 편리하게 다가온다. 하지만 C#에서도 메모리를 다루기 위해서는 세심한 주의가 필요하다. 마음 놓고 개발하다 당황했던 과거 필자의 경험을 살펴보도록 하자. 

개발 초창기, 게임 플레이 중에 주기적으로 랙이 발생했다. 로직을 확인해 봤지만 특별히 로딩 중이거나 초기화된 부분이 없어 의아했다. 유니티 엔진에서 제공하는 프로파일러로 한 프레임에 걸리는 시간을 측정해봤다. 측정 결과, System.GC.Collect() 호출에 오랜 시간이 걸린다는 점이 발견됐다. 플레이 중에 프레임마다 소모되는 시간을 그래프로 보여주는 <그림 1>을 보면 System.GC.Collect() 호출 시 그래프가 크게 튀어 오른 모습이 확인된다. C#에서 사용하지 않는 메모리를 정리하면서 가비지 컬렉션(Garbage collection) 랙이 발생한 것이다.


< 그림 1> 프로파일러 이미지

이때는 가비지 컬렉션이 동작하는 횟수를 줄여서 랙 발생을 줄이면 된다. 가비지 발생을 줄이면 가비지 컬렉션이 호출되는 시간을 늦출 수 있어 동작 횟수가 줄어든다. 가비지란 프로그램이 실행되면서 어디에서든 더 이상 참조되지 않는 메모리를 의미하므로 가능한 한 메모리를 할당했다 금방 버려지는 상황을 만들지 않는 것이 좋다. 몇 가지 사례들을 살펴보자.

‘+’ operator를 통한 문자열 조합 
C#은 문자열 조합이 쉽다. <리스트 1>에 보이는 것처럼 ‘+’로 연결하면 간단히 문자열 조합이 이뤄진다. 모든 객체가 ToString()을 지원하기 때문에 문자열끼리만 조합되는 게 아니라 int, float 등의 값도 알아서 문자열로 변환·조합된다.

<리스트 1> ‘+’로 연결한 문자열 조합

class Names
{
    public string[] name = new string[100];
    public void Print()
    {
        for (int index = 0; index < name.Length; index++)
        {
            string output = "[" + index + "]" + name;
            Console.WriteLine(output);
        }
    }
}

 

문제는 <리스트 1>에서 가비지가 많이 발생한다는 점이다. ‘+’ 연산자로 두 값을 연결할 때마다 새로운 string 인스턴스가 생성된다. 연이어 ‘+’ 연산자가 나오기 때문에 다시금 새로운 string 인스턴스가 생성되고, 이전에 만들어진 string 인스턴스는 가비지가 된다. string 조합을 위해 ‘+’ operator 호출이 많아질수록 많은 가비지가 만들어지는 것이다.

그래서 문자열을 조합하는 동안 새로운 객체를 생성하지 않는 System.Text.StringBuilder 객체를 소개한다. ‘+’ operator가 아닌 Append() 메소드를 통해 문자열을 추가하며, string 객체를 만들어내는 게 아니라 이미 잡아놓은 메모리 공간에 문자열만 복사해 뒀다가 한번에 ToString()으로 string 객체를 생성해낸다.

<리스트 2> System.Text.StringBuilder 객체 사용

class NewNames
{
    public string[] name = new string[100];
    private StringBuilder sb = new StringBuilder();

    public void Print()
    {
        sb.Clear();     // sb.Length = 0;
        for (int index = 0; index < name.Length; index++)
        {
            sb.Append("[");
            sb.Append(index);
            sb.Append("] ");
            sb.Append(name);
            sb.AppendLine();
        }
        Console.WriteLine(sb.ToString());
    }
}

 

과다한 Append() 메소드 호출이 필요해 ‘+’ 코드보다 깔끔하지 못하다고 생각된다면 AppendFormat()을 사용하는 것도 좋다.

 <리스트 3> AppendFormat() 활용

class NewNames
{
    public string[] name = new string[100];
    private StringBuilder sb = new StringBuilder();

    public void Print()
    {
        sb.Clear();     // sb.Length = 0;
        for (int index = 0; index < name.Length; index++)
        {
            sb.AppendFormat("[{0}] {1}", index, name.ToString());
        }
        Console.WriteLine(sb.ToString());
    }
}

 

string처럼 Immutable pattern을 사용한 객체들의 값에 접근할 때는 기존 메모리를 수정하지 않고 새로운 메모리를 만들어 반환하거나 입력받으므로 사용 시 주의가 필요하다.

메소드 안에서 생성한 객체
C#은 C++과 달리 클래스를 인스턴싱하려면 반드시 new를 해줘야 한다. 이때 heap에서 메모리 할당이 일어난다. 

< 리스트 4>와 같이 메소드 안에서 new로 생성된 인스턴스는 메소드를 빠져나오면 더 이상 사용하지 않게 돼 가비지로 처리된다. 이런 패턴의 메소드가 자주 호출될수록 가비지도 많이 발생한다.

<리스트 4> new로 생성된 인스턴스

public class MyVector
{
    public float x, y;
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    public static void PrintVectorLength(float x, float y)
    {
        MyVector v = new MyVector(x, y);
        Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
    }
}

 

Vector 클래스를 구조체로 바꿔보면, new 연산자로 인스턴스를 만들어도 heap 영역에 메모리가 할당되지 않는다. 구조체 역시 Value type이기 때문에 stack 영역에 메모리가 할당되며, 메소드를 빠져나갈 경우 자동으로 삭제된다. 물론 heap 영역에 생성된 메모리가 아니기 때문에 가비지 컬렉션의 대상이 되지도 않는다. 

<리스트 5> Vector 클래스를 구조체로 변환

public struct MyVector
{
    public float x, y;
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    public static void PrintVectorLength(float x, float y)
    {
        MyVector v = new MyVector(x, y);
        Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
    }
}

 

구조체로 바꿀 수 없다면, <리스트 6>처럼 멤버변수 사용을 권장한다.

<리스트 6> 멤버변수 사용

public class MyVector
{
    public float x, y;
    public MyVector() { x = .0f; y = .0f; }
    public MyVector(float x, float y) { this.x = x; this.y = y; }
    public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
    private static MyVector m_cachedVector = new MyVector();
    public static void PrintVectorLength(float x, float y)
    {
        m_cachedVector.x = x;
        m_cachedVector.y = y;

        Console.WriteLine("Vector=({0},{1}), lenght={2}",
x, y, m_cachedVector.Length());
    }
}

 

속도 저하가 큰 Boxing
Boxing이란 Value type 객체를 Reference type 객체로 포장하는 과정을 뜻한다. C#의 모든 객체는 object로부터 상속되는데, 심지어 상속받지 못하는 int, float 등의 Value type 조차도 object로부터 상속된 것처럼 사용할 수 있다. 하지만 가비지 컬렉션에 의한 부하 못지않게 boxing으로 인한 부하도 크다. 무심코 만든 코드에서 boxing 과정이 일어나는 경우가 있으니 잘 이해하고 사용해야 한다.

< 리스트 7>을 보면 리스트에 서로 다른 type의 값을 추가했지만, loop 안에서 추가 값을 object type으로 받아 하나의 코드로 처리할 수 있음을 알 수 있다.

<리스트 7> 서로 다른 type 값 추가

class MyClass
{
    public override string ToString() { return "다섯"; }

    static public void Sample()
    {
        ArrayList list = new ArrayList();
        list.Add(1);
        list.Add(1.5f);
        list.Add(‘3’);
        list.Add("four");
        list.Add(new MyClass());

        foreach (object item in list)
            Console.WriteLine(item.ToString());
    }
}

 


< 그림 2> <리스트 7>의 실행 결과

매력적인 C#이지만 Value type의 값을 Reference type인 object로 바꿔주는 과정에는 많은 시간이 걸리며 변환 시에는 System.Object 내부에 랩핑하고 관리되는 heap에 저장된다. 즉 새로운 객체가 만들어지는 셈이다. MSDN에서 발췌한 <그림 3>을 참조하길 바란다.


< 그림 3> boxing과 unboxing의 비교

따라서 한 번에 다양한 type을 처리하는 경우가 아니라면 collection에 사용된 값의 type을 명시해주는 Generic collection 사용을 권한다. Generic은 C++의 template와 비슷하다. 그래서 Generic collection들은 C++의 STL container들과 비슷하게 생겼다. <리스트 8>을 참고하자.

<리스트 8> Generic collection

class Example
{
    static public void BadCase()
    {
        ArrayList list = new ArrayList();
        int evenSum = 0;
        int oddSum = 0;

        for (int i = 0; i < 1000000; i++)
            list.Add(i);

        foreach (object item in list)
        {
            if (item is int)
            {
                int num = (int)item;
                if(num % 2 ==0) evenSum += num;
                else oddSum += num;
            }
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }

    static public void GoodCase()
    {
        List<int> list = new List<int>();
        int evenSum = 0;
        int oddSum = 0;

        for (int i = 0; i < 1000000; i++)
            list.Add(i);

        foreach (int num in list)
        {
            if (num % 2 == 0) evenSum += num;
            else oddSum += num;
        }
           
        Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
    }
}

 

메모리가 계속 늘어나는 또 다른 문제의 발생!
이 글을 시작하며 C#에서 사용자는 메모리 해제에 신경 쓸 필요가 없다고 했지만 의도하지 않게 메모리가 늘어나기도 한다. C#에는 delete 같은 메모리 해제 명령이 없기에 메모리 릭(memory leak) 현상이 발생하면 당혹스러울 수 있다. 여기서 C# 메모리의 특징을 다시 한 번 떠올려보자.

시스템에서 더 이상 참조가 없는 메모리를 알아서 해제하는 것을 우리는 가비지 컬렉션이라 부른다. 가비지는 더 이상 참조가 없는 메모리다. C# 애플리케이션이 메모리가 해제되지 않고 계속 증가되고 있다면 어디선가 의도하지 않는 참조가 일어나고 있다고 보면 된다. 그렇다면 어디에서 의도하지 않은 참조가 일어나는 것일까? 예를 통해 확인해 보자.


< 그림 4> #1 - 케릭터 매니저에서 케릭터를 생성한다


<그림 5> #2 - 누군가 디버깅을 위해 '캐릭터 위치 표시' 객체를 만들고 캐릭터 매니저에 접근해 등록된 캐릭터를 모두 참조한다


<그림 6> #3 - 캐릭터 매니저에서 필요없는 캐릭터를 삭제한다


<그림 7> #4 - 캐릭터 매니저에서 삭제됏지만 '캐릭터 위치 표시' 객체에서는 여전히 참조 중이다. 가비지가 아니기 때문에 메모리에 계속 남아있으며, 구현에 따라서는 의도하지 않게 화면에 남을 수도 있다.

WeakReference로 의도하지 않은 참조를 없애보자
System.WeakReference는 가비지 컬렉션에 의한 객체 회수를 허용하면서 객체를 참조한다. 인스턴스를 참조하려면 Weak Reference.Target으로 접근해야 하는데 원본 인스턴스가 가비지 컬렉터에 의해 회수되면 WeakReference.Target은 null이 반환된다.

<리스트 9> WeakReference.Target

public class Sample
{
    private class Fruit
    {
        public Fruit(string name) { this.Name = name; }
        public string Name { private set; get; }
    }

    public static void TestWeakRef()
    {
        Fruit apple = new Fruit("Apple");
        Fruit orange = new Fruit("Orange");
           
        Fruit fruit1 = apple;   // strong reference
        WeakReference fruit2 = new WeakReference(orange);
        Fruit target;
           
        target = fruit2.Target as Fruit;
        Console.WriteLine(" (1) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
            fruit1.Name, target == null ? "" : target.Name);

        apple = null;
        orange = null;

        System.GC.Collect(0, GCCollectionMode.Forced);
        System.GC.WaitForFullGCComplete();

        // fruit1과 fruit2의 값을 바꾼 적은 없지만, fruit2의 결과가 달라진다.
        target = fruit2.Target as Fruit;
        Console.WriteLine(" (2) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
            fruit1==null ? "" : fruit1.Name,
            target == null ? "" : target.Name);
    }
}

 

<리스트 9>의 실행으로 <그림 8>을 확인할 수 있다. Fruit2가 참조하고 있던 orange 인스턴스는 가비지 컬렉터에 의해 회수돼 null이 됐다.


< 그림 8> <리스트 9>의 실행 결과

‘캐릭터 매니저’처럼 객체의 생성·삭제를 직접 관여하는 모듈이 아닌 곳에서는 가능한 WeakRefernce를 사용하는 것이 좋다. ‘객체 위치 표시 객체’처럼 인스턴스를 참조하는 모듈에서 WeakReference를 사용하면, 의도하지 않은 참조로 메모리가 해제되지 않는 실수를 방지할 수 있다. 주의할 점은 Weak Reference.Target 값을 보관하면 안 된다는 것이다. 만약 그대로 보관하고 있으면 강한 참조(strong reference)가 일어나 이를 인식한 가비지 컬렉터는 회수를 실행하지 않게 된다.

C/C++처럼 원하는 시점에 객체를 삭제하고 싶다면
C#에서는 할당된 메모리를 임의로 해제할 수 없다. 컬렉션에 보관된 인스턴스를 제거하거나 인스턴스를 담고 있던 변수에 null을 넣어 더 이상 참조하지 않는 방법이 있지만 실제 인스턴스가 삭제되는 시점은 가비지 컬렉션 동작 이후가 되므로, 언제가 될 지 정확히 알 수 없다. 의도한 시점에 맞춰 정확히 삭제할 수 없다는 점이 그렇게 중요하지는 않다. 하지만 캐릭터 매니저에서 캐릭터를 제거했는데도 여전히 캐릭터 인스턴스가 남아서 화면에 한동안 계속 나타나는 경우가 발생할 수 있다.

Dispose pattern 소개C#에서는 관리되지 않는 메모리(리소스)를 해제하는 용도로 System.IDisposable이라는 인터페이스를 제공한다. IDisposable 인터페이스를 상속받은 클래스라면 용도에 맞게 Dispose()를 구현해줘야 하는데 이는 FileStream 관련 객체들에서 많이 볼 수 있다.

리소스를 강제로 해제시키려면 직접 Release(), Delete(), Destroy(), Close() 등의 메소드를 만들어 사용하면 되는데 굳이 IDisposable을 사용할 필요가 있을까? 서로 다른 type의 객체여도 IDisposable 인터페이스를 상속받고 있다면, 하나의 코드로 다양한 type의 메모리를 정리할 수 있기 때문에 IDisposable을 사용할 필요가 있다. 또한 Dispose() 메소드만 보고도 “아, 이 클래스는 사용이 끝나면 Dispose()를 호출해서 메모리를 정리해야 하는구나” 라고 금방 알 수 있다.

캐릭터 객체에서 IDisposable 인터페이스를 구현해보자. 업데이트 목록에서도 제외시키고 렌더링 정보도 다 지우자. 캐릭터의 Dipsose()를 호출한 이후에 캐릭터는 어떠한 동작도 하지 못하게 된다. 물론 Dispose()를 호출한다고 캐릭터가 가비지 컬렉터에 의해 메모리 해제되는 것은 아니다.

WeakReference과 IDisosalbe의 조합원하는 시점에 메모리를 해제하려면 앞서 설명한 Weak Reference와 IDisposable을 개별적으로 사용하는 것으로는 부족하다. 둘을 함께 사용하는 것이 좋다. <리스트 10>을 보자.

<리스트 10> Disposable 인터페이스를 상속받아 구현된 캐릭터 클래스

namespace MyApp
{
    public class SampleChar : IDisposable
    {
        private IRenderObject m_Render = Renderer.CreateRenderObject();

        public void Dispose()
        {
            SampleCharManager.Remove(this);
            m_Render = null;
        }

        public bool isRemoved { get { return m_Render == null; } }

        public void Render()
        {
            if (m_Render == null) return;
            // 렌더링
        }

        public void Update() { }
    }
}

 

예제로 만들어 본 캐릭터 클래스는 Disposable 인터페이스를 상속받아 구현된다. Dispose 후에는 더 이상 업데이트가 되지 않도록 SampleCharManager에서 제거되며, 렌더링 객체를 null로 만들어 화면에 그려지지 않도록 했다.

IRenderObject 인터페이스는 <리스트 11>과 같이 구현된다.

<리스트 11> IRenderObject 인터페이스

namespace MyApp
{
    public interface IRenderObject
    {
        void Render();
    }

    public static class Renderer
    {
        public static IRenderObject CreateRenderObject()
{
return new DumyRenderObject(); // IRenderObject를 상속받은 더미 객체
}
    }
}

 

<리스트 12>의 캐릭터 매니저 클래스는 등록된 캐릭터들을 일괄적으로 업데이트시키고 렌더링한다.

<리스트 12> 등록 캐릭터 일괄 업데이트 및 렌더링

namespace MyApp 
{
    static class SampleCharManager
    {
        private static List<SampleChar> m_list = new List<SampleChar>();

        public static void Update()
        {
            foreach (SampleChar obj in m_list) 
                obj.Update();
        }

        public static void Render()
        {
            foreach (SampleChar obj in m_list)
                obj.Render();
        }

        public static void Add(SampleChar obj)
        {
            m_list.Add(obj); 
        }

        public static void Remove(SampleChar obj)
        {
            m_list.Remove(obj);
        }
    }
}

 

<리스트 13>의 디버깅을 위한 ‘캐릭터 위치 표시 객체’는 WeakReference를 통해 SampleChar 객체를 참조하도록 구현돼 있고, SampleCharManager에서 캐릭터를 삭제하더라도 안전하게 가비지가 회수된다. 업데이트 시 DisplayCharInfo는 삭제된 캐릭터를 스스로 판단해 목록에서 제거한다.

<리스트 13> 디버깅을 위한 캐릭터 위치 표시 객체

namespace MyDebug
{
    static class DisplayCharInfo
    {
        private static List<WeakReference> m_list = new List<WeakReference>();
        private static Queue<WeakReference> m_removeQueue =
new Queue<WeakReference>();

        public static void Update()
        {
            foreach (WeakReference item in m_list)
            {
                MyApp.SampleChar obj = (item.Target != null) ?
 item.Target as MyApp.SampleChar : null;

                if (obj == null || obj.isRemoved)
                {
                    m_removeQueue.Enqueue(item);
                }
                else 
                { 
                    /* 캐릭터 정보 표시 */ 
                }
            }

            while(m_removeQueue.Count > 0)
            {
                WeakReference item = m_removeQueue.Dequeue();
                m_list.Remove(item);
            }
        }

        public static void Add(MyApp.SampleChar obj)
        {
            m_list.Add(new WeakReference(obj));
        }
    }
}

 

C#에서 메모리를 관리하는 데 도움되길 바라며, 지금까지 설명한 내용을 요약하면 다음과 같다.

- string 조합이 많다면, StringBuilder 활용
- Immutable 객체의 값 접근 시 매번 메모리가 생성될 수 있으므로 주의
- 매번 호출되는 메소드 안에서 반복해서 일회성 인스턴스가 생성되지 않도록 주의
- Boxing / unboxing이 가능한 일어나지 않도록 주의
- WeakReference를 사용해서 의도하지 않은 참조 줄이기
- IDisposable 인터페이스를 사용해 사용자가 원하는 시점에 객체 삭제하기

Value type과 Reference type 비교

Value type은 stack 영역에 할당되며 값이 통째로 복사된다.

 

유니티 3D엔진에서의 메모리 관리
유니티 3D엔진으로 개발하면서 주의할 내용을 알아보자. 유니티 3D엔진은 크게 모노 메모리와 엔진에서 관리하는 메모리로 나뉜다. 둘 다 메모리가 부족하면 내부에 관리하는 heap 영역을 늘려 메모리를 할당한다. 이렇게 한 번 늘어난 heap은 줄어들지 않는 특징을 가진다. 물론 늘어난 heap 안에서 메모리가 재사용되므로, 무턱대고 늘어나진 않는다. 하지만 가비지를 너무 많이 생성시키면 GC.Collect()로 인한 성능저하와 더불어 최대 메모리가 늘어날 수도 있으니 주의해야 한다. 가능한 가비지가 덜 생성되도록 코드를 구현하는 게 좋다. 메모리는 한 번에 잡는 것이 좋고, caching이나 memory pool을 사용하는 것도 도움이 된다.

 

 

<리스트 14> Value typepublic static class Sample
{
    public static void TestValueType()
    {
        int a = 100;
        int b = a;
       
        a = 200;
        Console.WriteLine(" a={0}, b={1}", a, b);
    }
}

<리스트 14>를 실행하면 <그림 9>와 같은 결과를 확인할 수 있다. a와 b는 서로 다른 메모리 공간을 가지고 있다.


< 그림 9> <리스트 14>의 실행 결과

Reference Type은 heap 영역에 할당되며, C/C++의 포인터나 레퍼런스처럼 new로 생성한 인스턴스를 참조한다.

<리스트 15> Reference type

public class MyInt
{
    public int Value { get; set; }
    public MyInt(int val) { this.Value = val; }

    public static void TestReferenceType()
    {
        MyInt a = new MyInt(100);
        MyInt b = a;

        a.Value = 200;
        Console.WriteLine(" a={0}, b={1}", a.Value, b.Value);
    }
}

<리스트 15>의 실행 결과로 <그림 10>을 확인할 수 있다. a와 b는 같은 메모리를 참조한다.


< 그림 10> <리스트 15>의 실행 결과

Posted by 프리랜서 디자이너