반응형

How to get multiple files with different extension?



You can't set more search filters, because GetFiles only accepts a single search pattern. Instead, you can call GetFiles with no pattern, and filter the results in code, using Lambda expressions:



using System.Linq;



   public YourMethodToStart()
    {
      FileInfo[] fiels = GetFilesFromFolder(@"C:\1");
    }

    private FileInfo[] GetFilesFromFolder(string path)
    {     
      string[] extensions = new[] { ".txt", ".zip", ".exe" };
      DirectoryInfo dInfo = new DirectoryInfo(path);

      FileInfo[] files = dInfo.GetFiles()
        .Where(f => extensions.Contains(f.Extension.ToLower()))
        .ToArray();
      return files;
    }



ref : https://social.msdn.microsoft.com/Forums/en-US/3e29882f-238e-473b-91c1-8639a51d39cd/get-multiple-files-with-different-extension?forum=csharplanguage

반응형
반응형


Does DirectoryInfo GetFiles work on Android ?


I need to list up all png files in a sub folder of the assets folder in my exported android project.

I have verified that the sub folder is in the assets folder of my exported android project. It has some .png files in it.

However, I keep getting the error: DirectoryNotFoundException .

Does DirectoryInfo GetFiles work on android ?

Sample Code:

string streamingAssetsPathAndroid = "jar:file://" + Application.dataPath + "!/assets/";

string subFolderPath = "Some/Other/" + "folder";

string fullpath = streamingAssetsPathAndroid + subFolderPath;

DirectoryInfo dirInfo = new DirectoryInfo(fullpath);

FileInfo[] fileInfo = dir.GetFiles("*.png");

Result:

DirectoryNotFoundException: Directory 'jar:file:/data/app/com.your.company.game.apk!/assets/Some/Other/folder' not found.

I have tried various other paths like:

string streamingAssetsPathAndroid = "jar:file://" + Application.dataPath + "!/assets/";

Or

string streamingAssetsPathAndroid = "file://" + Application.dataPath + "!/assets/";

Or

string streamingAssetsPathAndroid = "Application.dataPath + "!/assets/";

Or

string streamingAssetsPathAndroid = "assets/";

and then combining it with the sub folder path. But I get a DirectoryNotFoundException error every time.

Please let me know if there is a proper/recommended way to do this.





avatar image
1
Best Answer

Answer by whydoidoit 

Well resources just in Assets aren't going to be in your build, they get packed up and all sorts, and then only if they are used. Assets in the Streaming Assets folder will be copied into the jar for your build.

The sub folders and pngs are in my streaming assets folder in unity. When it gets exported as a google android project, they are in the android project's assets folder.

Is there some (alternative) way to list up all .png files in my sub folder ?

Hmm, is there a reason you don't use Application.streamingAssetsPath like suggested on this page?

Also keep in mind, like mentioned on the doc page, on android the files are not in a folder, they are within a jar file. So GetFiles won't work on this path since it's not an actual path. If you want to "browse" your folder you need a zip-library to open the jar manually.

GetFiles works perfectly well on real folders as long as you have file permission.

I actually have tried using: string streamingAssetsPathAndroid = Application.streamingAssetsPath;

Which pretty much results in the same path string and the same folder not found error for me.

I may have to consider that it may not be possible to use Directory.GetFiles on a unity game exported to google android project...

Bunny's right you can't scan the directory because it is in a ZIP file. You can use a ZIP library to examine the JAR and get files from it.

Show more comments
avatar image
0

Answer by shadyshrif 

To solve this problem I created pre-compilation script which will run before the compilation; this script will list the names of the files you want to access later in text file

  1. #if UNITY_EDITOR
  2. using UnityEditor.Build;
  3. using UnityEditor;
  4. using System.IO;
  5. public class BM_AndroidBuildPrepartion : IPreprocessBuild
  6. {
  7. public int callbackOrder { get { return 0; } }
  8. public void OnPreprocessBuild(BuildTarget target, string path)
  9. {
  10. // Do the preprocessing here
  11. string[] fileEntries = Directory.GetFiles("Assets/Resources/Prefabs/alphabet", "*.prefab");
  12. System.IO.Directory.CreateDirectory("Assets/StreamingAssets/");
  13. using (StreamWriter sw = new StreamWriter("Assets/StreamingAssets/alphabet.txt", false))
  14. {
  15. foreach (string filename in fileEntries) {
  16. sw.WriteLine(Path.GetFileNameWithoutExtension(filename));
  17. }
  18. }
  19. }
  20. }
  21. #endif

then I read the text file and you can access the files in same way but you have to put them inside your project in Assets/StreamingAssets/

  1. #if UNITY_ANDROID
  2. string path = "jar:file://" + Application.dataPath + "!/assets/alphabet.txt";
  3. WWW wwwfile = new WWW(path);
  4. while (!wwwfile.isDone) { }
  5. var filepath = string.Format("{0}/{1}", Application.persistentDataPath, "alphabet.t");
  6. File.WriteAllBytes(filepath, wwwfile.bytes);
  7. StreamReader wr = new StreamReader(filepath);
  8. string line;
  9. while ((line = wr.ReadLine()) != null)
  10. {
  11. //your code
  12. }
  13. #endif


ref : http://answers.unity3d.com/questions/569445/does-directoryinfo-getfiles-work-on-android-.html



반응형
반응형

Note that 

on some platforms it is not possible to directly access the StreamingAssets folder because there is no file system access in the web platforms, and because it is compressed into the .apk file on Android. 








Reading file from StreamingAssets, Android

Save

  1. public void SaveLevel(string name)
  2. {
  3. BinaryFormatter formatter = new BinaryFormatter();
  4. using (FileStream fs = new FileStream(Path.Combine(Application.streamingAssetsPath, name), FileMode.Create))
  5. {
  6. formatter.Serialize(fs, this);
  7. }
  8. }

Load

  1. public void LoadLevel(string name)
  2. {
  3. string path = Path.Combine(Application.streamingAssetsPath, name);
  4. byte[] FileBytes;
  5. if (path.Contains("://"))
  6. {
  7. WWW www = new WWW(path);
  8. FileBytes = www.bytes;
  9. }
  10. else
  11. FileBytes = File.ReadAllBytes(path);
  12. MemoryStream MS = new MemoryStream(FileBytes);
  13. BinaryFormatter formatter = new BinaryFormatter();
  14. LevelInfo LI = (LevelInfo)formatter.Deserialize(MS);
  15. SetLevel(LI);
  16. }

So what do I do. I open my own written level editor on Unity, create some levels that gonna be custom afterwards, and save them to StreamingAssets (I serialize a class that consists of arrays and some small vars). Then I build the game and access those files whenever I need. Everything works fine on PC, but nothing happens on Android platform. I checked File.Exists(path), and it returned false (however I'm not sure if it works correctly on Android). Path is "jar:file:///mnt/asec/com.Vol4icaGames.WR-1/pkg.apk!/assets/TestLevel"

1 Reply

 · Add your reply
avatar image
1

Answer by HenryStrattonFW 

Some platforms like android do not give you direct access to the streaming assets directory. On android the streaming assets directory is compressed into the apk, and so is not available for you to just read/write files to normally.

https://docs.unity3d.com/ScriptReference/Application-streamingAssetsPath.html

It is advised that if you want to save/load files on mobile devices, you should be doing so to the persistantDataPath instead, as you should have no problem reading/writing files to this directory.

That's true. But persistantDataPath is for saving some data after the build and reading it afterwards. What I want to do is to save information about game levels themselves, not the player progress in them. This means that I have to have pre-written info files that I have to be able to read when I want to load level. Even if I manage to copy level files into persistantDataPath, somehow, it is located on SD-card (is it?) and if user has no such card (inserted or card slot at all) the game gonna break... That's what I don't want. I have no idea how to store level info in other way.. To create hundreds of similar scenes is not what I would want to do..

Ah sorry I missread part of the initial question. But now that I have more of an idea of what you are trying, is there any specific reason that these level files you're saving at edit time need to be in streaming assets? could they not be in resources instead? and then loaded via Resources.Load instead of manually reading the files yourself?

avatar imageVol4ica  HenryStrattonFW · Feb-04 PM 11:58 0

StreamingAssets is a folder that keeps files in it in the form they were created. Not compressed, not converted into some other format etc. So I can access any file by its name. Can I do same thing with Resources folder? I know how to do it with sprites etc, but have no idea what to do with byte file.




sdf



Application.streamingAssetsPath

public static string streamingAssetsPath;

Description

Contains the path to the StreamingAssets folder (Read Only).

If you have a "StreamingAssets" folder in the Assets folder of your project, it will be copied to your player builds and be present in the path given by Application.streamingAssetsPath.

Note that on some platforms it is not possible to directly access the StreamingAssets folder because there is no file system access in the web platforms, and because it is compressed into the .apk file on Android. On those platforms, a url will be returned, which can be used using the UnityWebRequest class.

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour { public string filePath = System.IO.Path.Combine(Application.streamingAssetsPath, "MyFile"); public string result = ""; IEnumerator Example() { if (filePath.Contains("://")) { Networking.UnityWebRequest www = Networking.UnityWebRequest.Get(filePath); yield return www.SendWebRequest(); result = www.downloadHandler.text; } else result = System.IO.File.ReadAllText(filePath); } }


ref :

http://answers.unity3d.com/questions/1308831/reading-file-from-streamingassets-android.html

https://docs.unity3d.com/ScriptReference/Application-streamingAssetsPath.html


반응형
반응형

유니티로 어플 개발을 하다가

통 이미지를 로딩해야 할일이 생겼습니다.



보통 이런 이미지나 기타 파일들은

유니티 내부에 있는 클래스인
WWW
에 "file://" 을 붙여서
로딩 하는데..


이렇게 통 이미지를 로딩 했더니

로딩시간이 너무 오래 걸립니다.



그래서 로딩시간을 좀 줄이고
싶어서 찾아보던 중에
WWW 를 사용하지 않고

그냥 이미지 자체를
Byte Array
로 읽어오는 방법이
더 빠르다는걸 알게 되었죠.



간단히 테스트를 통해서
각각의 로딩시간을 측정해보면..


우선 WWW 를 사용한

이미지 로딩 입니다



IEnumerator loadImgFromWWW()
{
    WWW fileRequest = new WWW ( "file://" + 이미지 경로 );
    yield return fileRequest;
         
    while ( !fileRequest.isDone )
    {
        yield return null;
    }
         
    if ( fileRequest.bytes.Length > 0 )
    {
    }
    fileRequest.Dispose ();
    fileRequest = null;
}






그다음 Byte Array 를
사용한 이미지 로딩 입니다.

IEnumerator loadImgFromBytes()
{
    Texture2D tex = new Texture2D ( 1, 1, TextureFormat.ARGB32, false );
    byte[] binaryImageData = System.IO.File.ReadAllBytes(  이미지 경로  );
    while ( !tex.LoadImage(binaryImageData) )
    {
        yield return null;
    }
}





이렇게 하고 돌려서

시간을 체크해 보면

각각의 이미지 로딩에 걸린
시간을 체크 할 수 있습니다.


테스트로 원본 이미지 ( 640x640 ) 를

아이패드2 에 돌려 보았습니다.


먼저 WWW 로 로딩한 경우.




640x640 이미지 한장을
로딩할때 359 밀리세컨이 걸리네요.


그다음 Byte Array 로 로딩한 경우



이번에는 99 밀리세컨이

걸립니다.


그냥 대충 보기에도 엄청나게
차이가 나는걸 볼 수 있습니다.

( 대략 3-4배 정도 차이나네요. )



정확한 이유는 잘 모르겠지만..

아무래도 WWW 가 단순 로컬파일을
로딩할때도 네트워크 관련 기능을
사용하는 것 같네요.


이미지의 크기가 작거나
갯수가 적으면 WWW 를 쓰는게

더 좋겠지만..


이번 상황처럼 큰 이미지들을
동적으로 빠르게 로딩해야 할
경우에는 그냥 Byte Array 로

읽어와서 로딩하는 편이 훨씬

좋은 것 같습니다.



이상 Unity3D 에서 이미지 로딩
시간을 단축하는 방법 이었습니다.



ref: http://lhh3520.tistory.com/307

반응형
반응형

스프라이트 객체를 AssetBundle로 만들고 난뒤 AssetBundle을 통해 스프라이트 객체로 로드를 했는데 로드를 하지 못하고 null을 리턴하는 현상이 발생했습니다. Unity 4.3 버젼에서는 스프라이트는 어셋번들로 만들면서 데이터가 떨어져 나가고 텍스쳐로 로드해서 스프라이트로 변환 해야 한다고하는 군요.


그래서 텍스쳐를 스프라이트로 변경하는 방법에 대해서 살짝 짚고 넘어가보도록 하겠습니다.




Rect rect = new Rect(0, 0, texture.width, texture.height);


sprite.GetComponent<SpriteRenderer>().sprite = Sprite.Create(texture, rect, new Vector2(0.5f, 0.5f)); 




추가 : Sprite Packer를 이용해 패킹한 sprite는 asset bundle을 통해서도 정상적으로 로드 되었다.



ref : http://kukuta.tistory.com/195

반응형
반응형

4장 ScriptableObject

4.1 ScriptableObject란
ScriptableObject란 독자적인 Asset을 작성하기 위한 구조입니다. 또한, 유니티의 Serialize 구조를 사용하는 형식이라고도 할 수 있음.
유니티에서는 독자적인 Serialize 구조를 갖고 있어, 모든 오브젝트(UnityEngine.Object)는, 그 Serialize 구조를 통해서 데이터의 Serialize / Deserialize를 실행해, 파일과 Unity 에디터간의 조합을 하고 있음. Serialize 구조에 대해서는 5장 SerializedObject에 대해서 를 참조.

유니티 내부의 Asset(Material과 애니메이션 클립 등)은 모두 UnityEngine.Object의 자식 클래스임. 독자적인 Asset을 만들기 위해서, UnityEngine.Object의 자식 클래스를 작성하고 싶은데, 유저 측에서는 UnityEngine.Object의 자식 클래스를 작성하는게 금지되어 있음. 유저가 Unity의 Serialize 구조를 이용한, 독자적인 Asset을 작성하기 위해서는 ScriptableObject를 사용할 필요가 있음.

4.2 ScriptableObject는 유니티 에디터의 요소
ScriptableObject는 유니티 에디터의 다양한 곳에서 사용되고 있음. 씬 뷰나 게임 뷰 등의 에디터 윈도우는, ScriptableObject의 자식 클래스에서 생성되었고, 또한 인스펙터에 GUI를 표시하는 Editor 오브젝트도 ScriptableObject의 자식 클래스에서 생성됨. 유니티 에디터는 ScriptableObject로 작성되어 있다고 해도 과언이 아님.


4.3 ScriptableObject를 작성
ScriptableObject를 작성하기 위해서는 먼저 ScriptableObject 클래스를 상속한 클래스를 작성해야함. 이때, 클래스 이름과 Asset이름은 통일시켜야함. MonoBehaviour와 같은 제한.

using UnityEngine; public class ExampleAsset : ScriptableObject { }


[인스턴스 화]
ScriptableObject는 ScriptableObject.CreateInstance로 생성합니다. new를 사용해서 인스턴스 화해선 안됨. 이유는 MonoBehaviour와 마찬가지로, 유니티의 Serialize 구조를 경유해서 오브젝트를 만들 필요가 있으니까.

using UnityEngine; using UnityEditor; public class ExampleAsset : ScriptableObject { [MenuItem ("Example/Create ExampleAsset Instance")] static void CreateExampleAssetInstance () { var exampleAsset = CreateInstance<ExampleAsset> (); } }


[Asset으로 저장]
다음은 인스턴스화시킨 오브젝트를 Asset으로 저장시킵니다. Asset의 작성은 AssetDatabase.CreateAsset을 사용해서 작성할 수 있음.
Asset의 확장자는, 반드시, .asset이 아니면 안됨. 다른 확장자로 해버리면, 유니티는 ScriptableObejct를 상속한 Asset으로서 인식하지 못함.

[MenuItem ("Example/Create ExampleAsset")] static void CreateExampleAsset () { var exampleAsset = CreateInstance<ExampleAsset> (); AssetDatabase.CreateAsset (exampleAsset, "Assets/Editor/ExampleAsset.asset"); AssetDatabase.Refresh (); }

또한, CreateAssetMenu 속성을 사용하는 것으로 간단하게 Asset을 작성할 수 있습니다.

using UnityEngine; using UnityEditor; [CreateAssetMenu(menuName = "Example/Create ExampleAsset Instance")] public class ExampleAsset : ScriptableObject { }

CreateAssetMenu를 사용한 경우에는 [Assets/Create] 아래에 메뉴가 작성됩니다.


[스크립트에서 Asset의 ScriptableObject를 로드]
불러들이는 방법은 간단한데, AssetDatabase.LoadAssetAtPath를 사용해 불러들입니다.

[MenuItem ("Example/Load ExampleAsset")] static void LoadExampleAsset () { var exampleAsset =     AssetDatabase.LoadAssetAtPath<ExampleAsset> ("Assets/Editor/ExampleAsset.asset"); }


[인스펙터에 프로퍼티를 표시]
MonoBehaviour와 마찬가지로, 필드에 SerializeField를 붙이면 표시됨. 또한 PropertyDrawer도 적용시킴.

using UnityEngine; using UnityEditor; public class ExampleAsset : ScriptableObject { [SerializeField] string str; [SerializeField, Range (0, 10)] int number; [MenuItem ("Example/Create ExampleAsset Instance")] static void CreateExampleAssetInstance () { var exampleAsset = CreateInstance<ExampleAsset> (); AssetDatabase.CreateAsset (exampleAsset, "Assets/Editor/ExampleAsset.asset"); AssetDatabase.Refresh (); } }


4.4 ScriptableObjecet의 부모 자식 관계
먼저, [부모인 ScriptableObject]와, 그 부모가 변수로서 가지는 [자식인 ScriptableObject]를 상상해주세요. 아래 코드는 그 이미지를 코드로 옮긴겁니다.

<부모 ScriptableObject>

using UnityEngine; public class ParentScriptableObject : ScriptableObject { [SerializeField] ChildScriptableObject child; }

<자식 ScriptableObject>

using UnityEngine; public class ChildScriptableObject : ScriptableObject { // 아무것도 없으면 인스펙터가 허전하므로 변수 추가 [SerializeField] string str; public ChildScriptableObject () { // 초기 Asset 이름을 설정 name = "New ChildScriptableObject"; } }

다음으로, ParentScriptableObject를 Asset으로 해서 저장합니다. child도 인스턴스화시킨 상태로 해봤습니다. 

<자식 ScriptableObject>

using UnityEngine; using UnityEditor; public class ParentScriptableObject : ScriptableObject { const string PATH = "Assets/Editor/New ParentScriptableObject.asset"; [SerializeField] ChildScriptableObject child; [MenuItem ("Assets/Create ScriptableObject")] static void CreateScriptableObject () { // 부모를 인스턴스화 var parent = ScriptableObject.CreateInstance<ParentScriptableObject> (); // 자식을 인스턴스화 parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> (); // 부모를 Asset으로 저장 AssetDatabase.CreateAsset (parent, PATH); // Import 해서 최신 상태로 함 AssetDatabase.ImportAsset (PATH); } }

ParentScriptableObject를 Asset으로 저장한 후, 인스펙터를 보면, child 프로퍼티가 Type mismatch로 되어 있음. 

시험삼아, Type mismatch 부분을 더블클릭하면, ChildScriptableObject의 정보가 인스펙터에 표시되어, 문제 없이 올바른 동작을 하는걸 볼 수 있음.

[UnityEngine.Object를 Asset으로써 다루기 위해서는 디스크에 저장해야함]
Type mismatch 상태의 child를 가진 ParentScriptableObject를 만들었으면, 그대로 유니티를 다시 실행해보세요. 또다시 ParentScriptableObject의 인스펙터를 보면 child 부분이 None(null)로 되어 있음.

이유는 ScriptableObject의 루트 클래스인 UnityEngine.Objec를 Serialize 데이터로써 다루기 위해서는, 디스크 상에 저장해야 하기 때문입니다. Type mismatch 상태는, 인스턴스가 존재하지만, 디스크 상에 Asset으로써 존재하지 않는 상태를 가리킵니다. 즉, 그 인스턴스가 어떤 상황(유니티 재시작 등)에서 파괴되어버리면 데이터에 접근할 수 없게 됩니다.


[ScriptableObject는 모두 Asset으로써 저장할것]
Type mismatch 상태를 회피하는것은 간단합니다. ScriptableObject를 모두 Asset으로써 저장해, 그 참조를 Serialize 가능한 필드로 가지면 해결됩니다.

단, 이번과 같은 부모 자식 관계가 있는 상태에서, 각자 독립된 Asset을 작성해버리는것은 관리 면에서 보면 효율적이지 않습니다. 그 수가 늘어나거나, 리스트를 다루는 경우가 되면 그 만큼 Asset을 작성하는 것은 파일 관리가 매우 귀찮아집니다.

그래서 Sub Asset이라는 기능을 사용해 부모 자식 관계인 Asset을 하나로 뭉칠 수 있습니다.

[Sub Asset]
부모인 메인 Asset에 Asset 정보를 추가하는 것으로 UnityEngine.Object가 Sub Asset이 됩니다. 이 Sub Asset의 예시로 가장 알기 쉬운게 Model Asset입니다.

Model Asset의 안에는, 메쉬와 애니메이션 등의 Asset이 포함되어 있습니다. 이들은 보통, 독립된 Asset으로 존재해야 하지만, Sub Asset으로써 다뤄지면, 메쉬와 애니메이션 등의 Asset을 메인 Asset 정보에 포함해 디스크 상에 저장하는 일 없이 사용할 수 있습니다.

ScriptableObject도 Sub Asset의 기능을 사용하는 것으로, 디스크 상에 쓸데없는 Asset을 늘리지 않고 부모 자식 관계의 ScriptableObject를 만들 수 있음.

[AssetDatabase.AddObjectToAsset]
UnityEngine.Object를 Sub Asset으로써 등록하기 위해서는, 메인이 되는 Asset에 오브젝트를 추가합니다.
<자식 ScriptableObject>

using UnityEngine; using UnityEditor; public class ParentScriptableObject : ScriptableObject { const string PATH = "Assets/Editor/New ParentScriptableObject.asset"; [SerializeField] ChildScriptableObject child; [MenuItem ("Assets/Create ScriptableObject")] static void CreateScriptableObject () { // 부모를 인스턴스화 var parent = ScriptableObject.CreateInstance<ParentScriptableObject> (); // 자식을 인스턴스화 parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> (); // 부모에 자식 오브젝트를 추가 AssetDatabase.AddObjectToAsset (parent.child, PATH); // 부모를 Asset으로서 저장 AssetDatabase.CreateAsset (parent, PATH); // Import 해서 최신 상태를 유지 AssetDatabase.ImportAsset (PATH); } }

부모인 ParentScriptableObject가 둘 있는듯 보이는 점, 실질적인 데이터를 가지고 있는 것은 계층적으로는 Sub Asset의 ParentScriptableObject인 점 등 좀 특수한 계층구조로 되어 있습니다. 

이 상태는, 유저가 (Sub Asset을 작성하는 것으로 인해) 특수한 Asset을 만들었다고 유니티가 판단해서, 메인 Asset을 아무것도 맡지 않은 Default Asset으로써 표시한 상태입니다.
메인 Asset으로써 다루고 싶은 Asset을 Sub Asset 쪽에 이동시키는 상황은 매우 안좋습니다. 이런 식으로 유저의 손으로 Sub Asset을 작성하는 것은 할 수 있지만, 이걸 Model같이 최대한 활용하는것은 안됩니다.

[HideFlags.HideInHierarchy로 Sub Asset을 숨기기]
Sub Asset 자체를 숨기는 것으로, 메인 Asset만이 존재하는듯이 외관을 만들 수 있습니다.

[MenuItem ("Assets/Create ScriptableObject")] static void CreateScriptableObject () { var parent = ScriptableObject.CreateInstance<ParentScriptableObject> (); parent.child = ScriptableObject.CreateInstance<ChildScriptableObject> ();  // Sub Asset인 child 를 표시하지 않는다 parent.child.hideFlags = HideFlags.HideInHierarchy; AssetDatabase.AddObjectToAsset (parent.child, PATH); AssetDatabase.CreateAsset (parent, PATH); AssetDatabase.ImportAsset (PATH); }

이와 같이, 계층 표시는 안되지만 2개의 Asset을 하나로 뭉쳐 관리할 수 있게 되었습니다.






이 Sub Asset을 숨기는 방법은, AnimatorController에서도 이루어지고 있습니다. 확인해봅시다.

[MenuItem ("Assets/Set to HideFlags.None")] static void SetHideFlags () { // AnimatorController를 선택한 상태에서 메뉴를 실행 var path = AssetDatabase.GetAssetPath (Selection.activeObject); // Sub Asset 포함해서 모두 얻기 foreach (var item in AssetDatabase.LoadAllAssetsAtPath(path)) { // flag를 모두 None으로 하고 비표시 설정을 해제 item.hideFlags = HideFlags.None; } // 다시 Import해서 최신 상태로 유지 AssetDatabase.ImportAsset (path); }

위에 적은 코드를 실행하면, HideFlags가 해제되어, Sub Asset이 표시됩니다.


[메인 Asset에서 Sub Asset을 제거하기]
Sub Asset의 제거 방법은 간단합니다. Object.DestroyImmediate를 사용하는 것으로 Sub Asset을 해제할 수 있습니다.

[MenuItem ("Assets/Remove ChildScriptableObject")] static void Remove () { var parent = AssetDatabase.LoadAssetAtPath<ParentScriptableObject> (PATH); // Asset의 CarentScriptableObject 를 파괴 Object.DestroyImmediate (parent.child, true); // 파괴하면 Missing 상태가 되므로 null 을 대입 parent.child = null; // 다시 Import해서 최신 상태를 갱신 AssetDatabase.ImportAsset (PATH); }


4.5 아이콘의 변경
기본 아이콘


딱히 중요한 부분은 아닌데 이 아이콘을 변경하는 방법을 알아봅시다.

[스크립트에 아이콘을 설정]
스크립트 Asset을 선택해서 아이콘 부분을 선택하면, 아이콘 변경 패널이 뜨고, 거기에 Other 버튼을 클릭하면 변경하고싶은 아이콘으로 변경 가능.

[Gismos에 아이콘을 설정]
한가지 아이콘을 변경하는 방법으로써는, Gizmos 폴더에 [클래스 명] Icon이라고 이름지어서 이미지를 넣으면 변경됨. Gizmos 폴더가 Assets 폴더 아래에 있어야 하는 형태로 쓰기 힘들지도 모르겠는데, 같은 아이콘 이미지가 3개 늘어놔져 있어도 괜찮다는 점에서, 이 방법도 기억해두면 편리.






http://anchan828.github.io/editor-manual/web/scriptableobject.html

http://hammerimpact.blog.me/220770261760



반응형
반응형

public 변수로 선언하되 유니티 inspecto 창에는 보이지 않게 하려면 HideInInspector 키워드를 사용 하면 된다



[HideInInspector] Description

변수를 인스펙터 뷰에서 보이지 않도록 설정합니다.

using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour { [HideInInspector] public int p = 5; }

https://docs.unity3d.com/kr/current/ScriptReference/HideInInspector.html







[SerializeField] Description

유니티가 private 필드를 직렬화(Serialization)하도록 설정합니다.

특별한 경우가 아니면 사용하지 않습니다. 유니티가 사용자의 스크립트를 직렬화 하는 경우에, public 필드만 직렬화합니다. 추가로 유니티가 private 필드를 직렬화하도록 설정하고 싶은 경우에, SerializeField 속성(attribute)을 필드에 추가할 수 있습니다.

직렬화를 하게되면 인스펙터에서 해당 필드가 노출됩니다. 유니티는 사용자의 모든 스크립트 컴포넌트를 직렬화 하고, 스크립트 컴포넌트를 직렬화된 버젼으로 다시 로드하고 재생성합니다. .NET의 serialization 기능과는 아무런 영향을 주지 않고, 유니티 내부에서만 동작합니다.

사용되는 직렬화 시스템은 다음을 수행 할 수 있습니다:

- CAN serialize public nonstatic fields (of serializable types)
- CAN serialize nonpublic nonstatic fields marked with the [SerializeField] attribute.
- CANNOT serialize static fields.
- CANNOT serialize properties.


유니티에서 직렬화할 수 있는 타입인 경우에만 사용자의 필드가 직렬화 될 수 있습니다:

직렬화될 수 있는(Serializable) 타입은 다음과 같습니다:

- All classes inheriting from UnityEngine.Object, for example GameObject, Component, MonoBehaviour, Texture2D, AnimationClip.
- All basic data types like int, string, float, bool.
- Some built-in types like Vector2, Vector3, Vector4, Quaternion, Matrix4x4, Color, Rect, LayerMask.
- Arrays of a serializable type
- List of a serializable type)
- Enums
- Structs

요점 정리: List(또는 Array)에 같은 element를 넣은 경우, List가 직렬화 될 때, 새 list를 두번 복사하는 대신 element의 두 사본을 얻게됩니다.

Awake 함수에서 직렬화하지 않는 Dictionary에 연결해서 활용할 수 있습니다. 이 방법은 Dictionary를 수정하고 다시 저장 하고 싶은 경우의 해결방법은 아니지만, 다른 많은 클래스에서 편리한 기능이 될 수 있습니다. 유니티 스크립트 사용자들을 위해: c#의 필드는 UnityScript안의 스크립트 변수이고, [SerializeField]는 @SerializeField가 됩니다. 클래스 상의 [Serializable]는 UnityScript에서 @script Serializable이 됩니다.

For UnityScript users: Fields in c# is a script variable in UnityScript, and [SerializeField] becomes @SerializeField. [Serializable] on a class becomes @script Serializable in a UnityScript.



https://docs.unity3d.com/kr/current/ScriptReference/SerializeField.html





FormerlySerializedAs 의 경우 너무 많이 스게 되면 코드가 복잡해 질 수 수있다


[Unity] 데이터 손실 없이 SerializedField 이름 바꾸기



유니티로 작업하다 보면 데이터를 저장하기 위해 SerializedField와 같은 어트리뷰트를 사용한다. 문제는 개발 도중 이 필드의 이름이 변경되어 기존 저장된 데이터들을 사용하지 못하게 되는 경우가 있다. 이런 경우 FormerlySerializedAs 어트리뷰트를 이용해 기존 버전과의 호환성을 유지해 줄 수 있다.


만일 아래와 같이 m_MyVariable이라는 멤버 변수를 사용하고 있는데 :

class MyClass : MonoBehaviour

{

  [SerializeField]

  private string m_MyVariable;

}


m_MyVariable의 이름이 다른 것으로, 예를 들자면 m_ABetterName와 같이 바뀐다면 기존에 m_MyVariable의 이름으로 저장된 데이터를 읽어 들이지 못하게 된다.

이럴 경우 아래와 같이 작성해주게 된다면 기존  m_MyVariable의 데이터를 m_ABetterName으로 치환해서 읽어 들일수 있다 :


class MyClass : MonoBehaviour

{

  [FormerlySerializedAs("m_MyVariable")]

  [SerializeField]

  private string m_ABetterName;

}


그리고 FormerlySerializedAs 는 여러번 중복해서 사용가능하다. 만일 이름이 여러 번 변경 된다고 하더라도 변경 히스토리에 따라 FormerlySerializedAs 어트리뷰트를 유지해 준다면 여전히 데이터 손실 없이 사용이 가능하다.


class MyClass : MonoBehaviour

{

  [FormerlySerializedAs("m_MyVariable")]

  [FormerlySerializedAs("m_ABetterName")]

  [SerializeField]

  private string m_EvenBetterName;

}


원문 : https://blogs.unity3d.com/2015/02/03/renaming-serialized-fields/



http://kukuta.tistory.com/203







RequireComponent

class in UnityEngine

Description

RequireComponent 속성은 요구되는 의존 컴포넌트를 자동으로 추가해줍니다.!!

RequireComponent를 사용하는 스크립트를 추가하면, 요구되는 컴포너트가 자동으로 해당 게임오브젝트에 추가됩니다. 설정 오류를 피하는 데 유용합니다. 예를 들어, 리지드 바디가 요구되는 스크립트가, 항상 같은 게임오브젝트에 첨부되는 경우. RequireComponent를 사용하면 이 작업이 자동으로 이루어 지기 때문에, 설정에 대한 실수를 할 염려가 없습니다. 

Note that RequireComponent only checks for missing dependencies during the moment the component is added to a GameObject. Existing instances of the component whose GameObject lacks the new dependencies will not have those dependencies automatically added.

using UnityEngine;

// PlayerScript requires the GameObject to have a Rigidbody component [RequireComponent (typeof (Rigidbody))] public class PlayerScript : MonoBehaviour { Rigidbody rb; void Start() { rb = GetComponent<Rigidbody>(); } void FixedUpdate() { rb.AddForce(Vector3.up); } }

Constructors

RequireComponentRequire a single component.


반응형
반응형




[SphereCast ] : 광선을 따라 구를 캐스팅하고 무엇이 맞았는지에 대한 자세한 정보를 반환합니다.


Raycast가 충분한 정밀도를 제공하지 않을 때 유용합니다. 왜냐하면 캐릭터와 같은 특정 크기의 객체가 도중에 충돌하지 않고 어딘가로 이동할 수 있는지 알아 내려고하기 때문입니다. 두꺼운 레이 캐스트와 같은 캐스트를 생각해보십시오. 이 경우 광선은 시작 벡터와 방향으로 지정됩니다.





public static bool SphereCast(Vector3 origin, float radiusVector3 direction, out RaycastHit hitInfo, float maxDistance = Mathf.Infinity, int layerMask = DefaultRaycastLayers, QueryTriggerInteractionqueryTriggerInteraction = QueryTriggerInteraction.UseGlobal);



Parameters

originThe center of the sphere at the start of the sweep.
radiusThe radius of the sphere.
directionThe direction into which to sweep the sphere.
hitInfoIf true is returned, hitInfo will contain more information about where the collider was hit. (See Also: RaycastHit).
maxDistanceThe max length of the cast.
layerMaskLayer mask that is used to selectively ignore colliders when casting a capsule.
queryTriggerInteractionSpecifies whether this query should hit Triggers.






설명

구체를 씬 상의 모든 충돌체를 대상으로 발사하여 충돌한 것에 대한 자세한 정보를 반환합니다.

Raycast가 충분한 정밀도를 제공하지 않아 캐릭터와 같이 이동하는 길에 아무것도 충돌하지 않는 물체의 특정 크기를 알아내야 할때 이 함수가 유용할 수 있습니다. Sphere cast를 두꺼운 raycast라고 생각하십시오. 

선 대신 구를 던져서 캐스팅

"스윕" 설명

( 스윕 교차) : 어떤 물체를 치고 지나갈때 멈추게함을 말함 (https://www.unrealengine.com/ko/blog/moving-physical-objects)


[OverlapSphere]


지정한 위치로 부터 지정한 범위안에 접촉한 콜라이더 배열로 반환 (접촉 또는 범위안의 것들)


public static Collider[] OverlapSphere(Vector3 position, float radius, int layerMask = AllLayers, QueryTriggerInteraction queryTriggerInteraction = QueryTriggerInteraction.UseGlobal);


layerMask는 레이 캐스팅 시에 콜라이더를 선택적으로 무시하는데 사용





[사용]



using UnityEngine;
using System.Collections;

public class ExampleClass : MonoBehaviour { void ExplosionDamage(Vector3 center, float radius) { Collider[] hitColliders = Physics.OverlapSphere(center, radius); int i = 0; while (i < hitColliders.Length) { hitColliders[i].SendMessage("AddDamage"); i++; } } }


Physics.OverlapSphere()

ex) Collider[] cols = Physics.OverlapSphere(tr.position(원점), 10.0f(반경));


반경내에 들어와 있는 GameObject를 검출

GameObject는 Collider컴포넌트가 추가돼 있어야 함.

반환값은 Collider 배열 형태로 반환.





http://sharkmino.tistory.com/1437

http://sellingout.blog.me/221033922714

https://docs.unity3d.com/ScriptReference/Physics.OverlapSphere.html

http://www.devkorea.co.kr/reference/Documentation/ScriptReference/Physics.SphereCast.html


반응형
반응형


강체(Rigidbody) 의 연속 충돌 감지(Continuous Collision Detection)와 충돌 감지 옵션들







Rigidbody 의 충돌 감지 옵션(Collision Detection) 설정

  • 불연속(Discrete)    일정 tick 간격으로 충돌 확인, 다른 콜라이더들은 충돌할때 불연속 충돌 감지를 사용한다. 일반 충돌들에 대해 사용되어진다, 이건 default 값임

간 간격이 크거나 엄청 빨리 움직일때 충돌이 안될 수 있음, 성능이 크게 떨어지거나 할때 발생 할 수 있음, 하지만 연산은 빠름


  • 연속(Continuous)       보간으로 충돌 확인, 리지드 바디가 있는 동적 콜라이더에는 불연속 충돌을, 리지드 바디가 없는 정적 메쉬 콜라이더에는 연속 충돌을 적
                                연속 동적을 적용한 리지드 바디에 대한 충돌은 Continuous 한 충돌 감지를 한다, 즉 연속 동적을 적용한 오브젝트의
                                충돌 상대로서 사용함 물링 연산 성능에 크게 영향을 주기 때문에, 빠른 오브젝트의 충돌로문제가 나오지 않는 경우는
                                불연속을 사용하는 것이 좋다

  • 동적연속(Continuous Dynamic) Continuous 및 Continuous Dynamic 충돌을 적용한 오브젝트에 대해 Continuous 충돌 감지를 수행한다
                                                리지드 바디가 없는 정벅 메쉬 콜라이더에도 Continuous충돌을적용한다
                                                다른 콜라이더에 대해서는 불연속 충돌 감지를 합니다, 고속으로 움직이는 오브젝트에 사용한다





연속 충돌 감지(Continuous Collision Detection)

연속 충돌 감지는 빠르게 움직이는 콜라이더가 서로 통과하지 않도록 하는 기능입니다. 이것은 일반 (Discrete) 충돌 감지 사용시, 오브젝트가 한 프레임에서 콜라이더의 한 쪽에 있고 다음 프레임에서 이미 콜라이더을 통과하는 경우에 발생할 수 있습니다. 이것을 해결하려면 빠르게 움직이는 오브젝트의 리지드 바디에서 연속 충돌 감지를 사용할 수 있습니다. 충돌 감지 모드를 Continuous로 전환하여, 리지드 바디가 정적 (즉, 리지드 바디가 없는) 메쉬 콜라이더를 통과하지 않도록 합니다. 충돌 감지 모드를 Continuous Dynamic로 전환하여, 리지드 바디가 충돌 탐지 모드를 Continuous 또는 Continuous Dynamic으로 설정하여 다른 지원되는 리지드 바디를 통과하지 않도록 합니다. 연속 충돌 감지는 Box, Sphere 및 Capsule의 콜라이더에서 지원됩니다. 연속 충돌 감지는 오브젝트에서 통과가 발생한 경우에 충돌을 잡기 위한 안전망이 목적이지만, 물리적으로 정확한 충돌 결과를 얻을 수 있는 것은 아니기 때문에 빠르게 움직이는 오브젝트에 문제가 있는 경우, TimeManger 인스펙터의 고정 Time step 변수를 줄여 시뮬레이션을 더 정밀하게 할 수 있습니다.






강체

Rigidbody를 통해 GameObject가 물리적 제어하에 동작하게 됩니다. 리지드 바디는 힘과 토크를 받아 현실적으로 오브젝트를 움직일 수 있습니다. GameObject는 중력의 영향을 받는 리지드 바디를 포함하거나, 스크립팅을 통해 더해진 힘으로 작동하거나, NVIDIA PhysX 물리 엔진을 통해 그 외의 오브젝트와 상호 작용해야 합니다.




프로퍼티

프로퍼티:기능:
MassThe mass of the object (in kilograms by default).
Drag힘에 의해 움직일 때, 오브젝트에 영향을 주는 공기 저항의 양. 0이면 공기 저항이 0이고, 무한대면 오브젝트는 즉시 움직임을 멈춥니다.
Angular Drag토크에 의해 회전할 때 오브젝트에 영향을 주는 공기 저항의 양. 0이면 공기 저항 없음. 이 파라미터의 값을 무한대(infinity)로 설정하는 것만으로는 오브젝트의 회전을 즉시 중지할 수 없다는 점에 유의하십시오.
Use Gravity활성화하면 오브젝트는 중력의 영향을 받습니다.
Is Kinematic활성화하면 오브젝트는 물리 엔진에 의해 주도되지 않으며, Transform에 의해서만 조작될 수 있습니다. 이것은 플랫폼을 옮기려고 하는 경우나, HingeJoint를 추가한 리지드 바디를 애니메이션하려는 경우에 편리합니다.
InterpolateRigidbody의 움직임이 어색할 때 다음 옵션 중 하나를 시도하십시오.
None보간을 적용하지 않는다.
Interpolate이전 프레임의 Transform에 따라 Transform 스무딩.
Extrapolate다음 프레임의 Transform에 따라 Estimated Transform 스무딩.
###연속 충돌 감지(Continuous Collision Detection)고속으로 움직이는 오브젝트가 충돌을 감지하지 않고 빠져나가는 것을 방지하기 위해 사용합니다.
DiscreteUse Discreet collision detection against all other colliders in the scene. Other colliders will use Discreet collision detection when testing for collision against it. Used for normal collisions (This is the default value).
Continuous동적 콜라이더(리지드 바디 있음)에는 Discrete(불연속) 충돌, 정적 메쉬 콜라이더(리지드 바디 없음)에는 Continuous(연속) 충돌을 적용합니다. Continuous Dynamic을 적용한 리지드 바디에 대한 충돌은 Continuous한 충돌 감지를 합니다. 이것은 Continuous Dynamic을 적용한 오브젝트의 충돌 상대로서 사용합니다. (물리 연산 성능에 크게 영향을 주기 때문에, 빠른 오브젝트의 충돌로 문제가 나오지 않는 경우는 Discrete대로 하십시오.)
Continuous DynamicContinuous 및 Continuous Dynamic 충돌을 적용한 오브젝트에 대해 Continuous 충돌 감지를 수행합니다. 정적 메쉬 콜라이더(리지드 바디 없음)에도 Continuous 충돌을 적용합니다. 다른 콜라이더에 대해서는 Discrete 충돌 감지를 합니다. 고속으로 움직이는 오브젝트에 사용합니다.
Constraints리지드 바디의 움직임에 대한 제한:-
Freeze Position월드 좌표계의 X, Y, Z 축으로 이동하는 리지드 바디를 선택적으로 중지합니다.
Freeze Rotation월드 좌표계의 X, Y, Z 축으로 회전하는 리지드 바디를 선택적으로 중지합니다.




https://docs.unity3d.com/kr/current/Manual/class-Rigidbody.html

반응형
반응형


Object.Destroy

Other Versions
public static void Destroy(Object obj, float t = 0.0F);

Parameters

objThe object to destroy.
tThe optional amount of time to delay before destroying the object.

Description

Removes a gameobject, component or asset.

The object obj will be destroyed now or if a time is specified t seconds from now. If obj is a Component it will remove the component from the GameObject and destroy it. If obj is a GameObject it will destroy the GameObject, all its components and all transform children of the GameObject. Actual object destruction is always delayed until after the current Update loop, but will always be done before rendering.

using UnityEngine;

public class ScriptExample : MonoBehaviour { void DestroyGameObject() { Destroy(gameObject); }

void DestroyScriptInstance() { // Removes this script instance from the game object Destroy(this); }

void DestroyComponent() { // Removes the rigidbody from the game object Destroy(GetComponent<Rigidbody>()); }

void DestroyObjectDelayed() { // Kills the game object in 5 seconds after loading the object Destroy(gameObject, 5); }

// When the user presses Ctrl, it will remove the // BoxCollider component from the game object void Update() { if (Input.GetButton("Fire1") && GetComponent<BoxCollider>()) { Destroy(GetComponent<BoxCollider>()); } } }

Destroy is inherited from the UnityEngine.Object base class. Javascript users should consider making a call to UnityEngine.Object.Destroy, rather than Object.Destroy to avoid references being resolved to the .Net System.Object class.


반응형
반응형

Unity JIT Spike (JIT 강제로 컴파일 하기) Just In Time




[방법.1]


var preJitCompile =typeof(MyComponent).GetMethod("MethodName");


//preJitCompile != null 체크 생략


preJitCompile.MethodHandle.GetFunctionPointer();




[방법.2]


using System.Reflection;



var method = typeof(A).GetMethod("MethodName", BindingFlags.NonPublic | BindingFlags.Instance);







회사 프로젝트를 최적화 하던 중 몇몇 함수에서 메모리 할당이나 테이블 로드등의 


최초 수행 시 시간이 소모되는 작업이 전혀 없음에도

함수의 최초 1번째 수행시간과 2번째부터의 수행시간이 10:1 이상 차이나는것을 보고 검색과 질문을 해보던 중

JIT Spike라는 개념을 찾았습니다.



- 인터프리터 방식에서 최초 번역시에 만들어진 기계어를 캐싱해 놓는 인터프리터와 컴파일 방식의 혼용입니다.


JIT Spike란 최초번역과 기계어를 캐싱되는 시간에 의한 딜레이 발생을 의미합니다.

해결방법으로는 특정 코드를 미리 캐싱되게 하는 것으로 3가지의 방법이 있는데

1. system.runtime.compilerservices.runtimehelpers.preparemethod("methodname")

 - 이것은 Mono에선 작동하지 않으며 
   (관련 링크- http://www.slideshare.net/flashgamm/unity-internals-memory-and-performance  35, 36페이지)
   Method.MethodHandle.GetFunctionPointer()를 권장하고 있습니다.


2. Method.MethodHandle.GetFunctionPointer()

 - 포럼의 글을 찾다보니 
   효과가 있다고 나왔지만
   에디터&안드로이드 세팅을 한 상태에서 사용 시 수행시간의 변화를 찾지 못했습니다.
   (휴대폰에서 로그를 확인 할 수 있는 플러그인을 사용해 PC에선 변화가 없어도 휴대폰에선 변화가 있는지 보려고 했으나 
    최근에 빌드에 계속 문제가있어서 확인해보지 못했습니다.)

3. fake 로직 수행

 - 최초전투 발생시 렉이 생기는것이 문제였으므로 fake parameter등으로 가짜로 먼저 로직을 수행해놓게 했습니다.


현재 저는 3번방식으로 해결한 상태입니다.

PC에서 확인시에는 그 차이가 10:1임에도 시간 자체가 매우 작지만

휴대폰에서는 수행시간의 차이가 체감되는 수준이었습니다.

안드로이드에서만 적용되며 IOS는 작동방식이 다르므로 신경쓸 필요가 없습니다.

틀린부분도 있을 수 있지만 혹시 도움되는 분들이 있으실 것 같아서 용기내서 올립니다.





---- 이 이하는 2번방법에 관심이 있으신 분들만 보시면 됩니다 ----

현재진행형인 부분으로 저는 typeof(클래스네임).GetMethods() (반환값 MethodInfo[])를 사용해서 

휴대폰에서 작동이 잘 되는지 확인해보려고 하고있습니다.

이때 주의점은 MethodInfo.DeclaredType 이 UnityEngine.Component인 함수를 캐싱하려고하면 유니티가 죽어버리고

또한 오버라이딩 함수의 경우에도 죽게 되 있습니다.

때문에 GetMethods를 사용해서 이름을 얻어와 오버라이딩 함수의 이름을 제거 후 DeclaredType은 if문으로 걸러낸뒤에

적용시키는 코드를 작성해놓았습니다. 빌드문제가 해결되는대로 테스트해볼 예정입니다.


http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_Lecture&page=1&sn1=&divpage=1&sn=off&ss=on&sc=on&select_arrange=hit&desc=asc&no=421

반응형
반응형






using UnityEngine; using UnityEditor;

public class SimpleRecorder : EditorWindow { string fileName = "FileName";

string status = "Idle"; string recordButton = "Record"; bool recording = false; float lastFrameTime = 0.0f; int capturedFrame = 0;

[MenuItem ("Example/Simple Recorder")]         //상단 메뉴 툴바에 Example 서브 메뉴로 Simple Recorder 메뉴가 생김 static void Init () { SimpleRecorder window = (SimpleRecorder)EditorWindow.GetWindow(typeof(SimpleRecorder)); }

void OnGUI () { fileName = EditorGUILayout.TextField ("File Name:", fileName);//File Name 레벨 과 파일명 얻어옴

if(GUILayout.Button(recordButton)) {                           //Record 버튼 생성 및 이벤트 가져옴

if(recording) { //recording status = "Idle..."; recordButton = "Record"; recording = false; } else { // idle capturedFrame = 0; recordButton = "Stop"; recording = true; } } EditorGUILayout.LabelField ("Status: ", status);             //Status: 라벨 추가 }

void Update () { if (recording) { if (EditorApplication.isPlaying && !EditorApplication.isPaused){ RecordImages(); Repaint();                                //에디터 custom window 에 변경 사항이 생길경우 다시 그려주는 작업 } else status = "Waiting for Editor to Play"; } }

void RecordImages() { if(lastFrameTime < Time.time + (1/24f)) { // 24fps status = "Captured frame " + capturedFrame; Application.CaptureScreenshot(fileName + " " + capturedFrame + ".png");        //현재 실행화면 캡쳐 capturedFrame++; lastFrameTime = Time.time; } } }

https://docs.unity3d.com/ScriptReference/EditorWindow.OnGUI.html

반응형
반응형




테스트한 기기에 따라 결과가 다를 수 있음

[윈도우 에디터]
Application.persistentDataPath : 사용자디렉토리/AppData/LocalLow/회사이름/프로덕트이름
파일 읽기 쓰기 가능
Application.dataPath : 프로젝트디렉토리/Assets
Application.streamingAssetsPath : 프로젝트디렉토리/Assets/StreamingAssets
파일 읽기 쓰기 가능


[윈도우 응용프로그램]
Application.persistentDataPath : 사용자디렉토리/AppData/LocalLow/회사이름/프로덕트이름
파일 읽기 쓰기 가능
Application.dataPath : 실행파일/실행파일_Data
Application.streamingAssetsPath : 실행파일/실행파일_Data/StreamingAssets
파일 읽기 쓰기 가능

[맥 에디터]
Application.persistentDataPath : 사용자디렉토리/Library/Caches/unity.회사이름.프로덕트이름
파일 읽기 쓰기 가능
Application.dataPath : 프로젝트디렉토리/Assets
Application.streamingAssetsPath : 프로젝트디렉토리/Assets/StreamingAssets
파일 읽기 쓰기 가능

[맥 응용프로그램]
Application.persistentDataPath : 사용자디렉토리/Library/Caches/unity.회사이름.프로덕트이름
파일 읽기 쓰기 가능
Application.dataPath : 실행파일.app/Contents
Application.streamingAssetsPath : 실행파일.app/Contents/Data/StreamingAssets
파일 읽기 쓰기 가능

[웹 플랫폼]
웹에서는 명시적인 파일 쓰기 불가능. 애셋번들로 해야함
Application.persistentDataPath : /
Application.dataPath : unity3d파일이 있는 폴더
Application.streamingAssetsPath : 값 없음.

[안드로이드 External]
Application.persistentDataPath : /mnt/sdcard/Android/data/번들이름/files
파일 읽기 쓰기 가능
Application.dataPath : /data/app/번들이름-번호.apk
Application.streamingAssetsPath : jar:file:///data/app/번들이름.apk!/assets 
파일이 아닌 WWW로 읽기 가능

[안드로이드 Internal] 
Application.persistentDataPath : /data/data/번들이름/files/
파일 읽기 쓰기 가능
Application.dataPath : /data/app/번들이름-번호.apk
Application.streamingAssetsPath : jar:file:///data/app/번들이름.apk!/assets
파일이 아닌 WWW로 읽기 가능

[iOS]
Application.persistentDataPath : /var/mobile/Applications/프로그램ID/Documents 
파일 읽기 쓰기 가능
Application.dataPath : /var/mobile/Applications/프로그램ID/앱이름.app/Data
Application.streamingAssetsPath : /var/mobile/Applications/프로그램ID/앱이름.app/Data/Raw 
파일 읽기 가능, 쓰기 불가능


http://memocube.blogspot.kr/2014/04/blog-post.html







유니티 예약 폴더이름 



유니티 에셋 폴더의 예약된 이름들

유니티에서 몇몇 폴더 이름은 특별한 속성을 가지고 있다.

숨겨진 폴더들

점(.)으로 시작하는 폴더(예. “.UnitTests/”, “.svn/”)는 유니티에 의해 무시된다. 이 폴더에 들어 있는 어떠한 에셋도 임포트되지 않으면 어떠한 스크립트도 컴파일되지 않는다. 또한 프로젝트 뷰에서도 보이지 않는다.

“Standard Assets”

이곳에 위치한 스크립트들은 항상 가장 먼저 컴파일된다. 스크립트는 언어에 따라 Assembly-CSharp-firstpass, Assembly-UnityScript-firstpass, Assembly-Boo-firstpass 중의 하나로 출력된다.

Standard Assets 폴더의 스크립트들은 다른 스크립트들보다 먼저 컴파일 된다. 그로므로 스크립트를 Standard Assets 폴더에 두는 것은 C# 스크립트가 .js 스크립트에 접근할 수 있게 하거나 또는 .js 스크립트가 C# 스크립트에 접근할 수 있게 하기를 위한 한 방법이다.

“Pro Standard Assets”

Standard Assets과 같지만 이곳의 파일들은 Pro 버전에서만 의미가 있다. 이는 여기에 있는 에셋들은 Render Texture나 Screen-space 효과 등과 같은 Pro-only 기능에서만 사용된다는 것을 의미한다.

이 곳의 스크립트들은 먼저 컴파일되며, Pro Standard Assets 폴더 밖의 다른 스크립트(어떠한 언어라도)에서 접근이 가능하다.

“Editor”

Editor는 여러분이 작성한 스크립트가 유니티 에디터 스크립팅 API에 접근할 수 있도록 허가해주는 특별한 폴더 이름이다. 만약 여러분의 스크립트가 UnityEditor 네임스페이스의 클래스나 기능을 사용한다면, Editor라는 폴더 안에 위치해야 한다.

Editor 폴더 안의 스크립트들을 게임 빌드에는 포함되지 않을 것이다. 오직 유니티 에디터에서만 사용된다. 프로젝트에 여러개의 Editor 폴더가 존재해도 된다.

주의: 특수 폴더에 위치하지 않은 Editor 폴더는 프로젝트 어디에서 위치/포함 될 수 있다. 하지만 “Standard Assets”, “Pro Standard Assets”, “Plugins” 안에 위치할 경우, 이 폴더들의 직접적인 자식이어야 한다. 그렇지 않으면 정상적으로 처리되지 않는다. 예를 들어 “My Entension/Scripts/Editor”는 OK이다. 하지만 특수 폴더에 위치할 경우 반드시 ‘Standard Assets/Editor/MyExtension/Scripts”, 또는 “Pro Standard Assets/Editor/My Extension/Scripts”, 또는 “Plugins/Editor/My Extension/Scripts”이어야 한다.

“Plugins”

“Plugin” 폴더는 스크립트에서 접근하기를 원하는 네이티브 플러그인들이 위치하는 곳이다. 이것들은 자동으로 빌드에 포함될 것이다. 이 폴더는 어떠한 다른 폴더 안에 들어가 있으면 안된다( Assets 폴더 최상위에 위치해야 한다)

Windows에서는 네이티브 플러그인들이 .dll 파일들로 존재한다. Mac OS X에서는 .bundle 파일들로 존재한다. Linux에서는 .so 파일들로 존재한다. Standard Assets 폴더와 같이 이 곳의 스크립트들은 먼저 컴파일되며, Plugins 폴더 밖의 스크립트(어떠한 언어라도)에서 접근이 가능하다.

“Plugins/x86”

만약 32-bit나 유니버셜(32와 64 bit 모두) 플랫폼으로 빌드를 하고, 이 하위 폴더가 존재한다면, 이 폴더의 모든 네이티브 플러그인 파일들이 자동으로 빌드에 추가 될 것이다. 만약 이 폴더가 존재하지 않는다면, 유니티는 대신 부모인 Plugin 폴더에서 네이티브 플러그인들을 찾을 것이다.

“Plugins/x86_64”

만약 64-bit이나 유니버셜(32와 64 bit 모두) 플랫폼으로 빌드를 하고, 이 하위 폴더가 존재한다면, 이 폴더의 모든 네이티브 플러그인 파일들이 자동으로 빌드에 추가 될 것이다. 만약 이 폴더가 존재하지 않는다면, 유니티는 대신 부모인 Plugin 폴더에서 네이티브 플러그인들을 찾을 것이다.

만약 유니버셜 빌드를 생성한다면, x86과 x86_64 하위 폴더를 모두 만드는 것을 권장한다. 그리고 32-bit와 64-bit 버전의 네이티브 플러그 인을 적절한 하위 폴더에 넣어 준다.

“Plugins/Android”

Java-based 플러기인에 사용될 안드로이드 프로젝트에 포함시키고 싶은 Java .jar 파일들을 이곳에 위치시킨다. 모든 .so 파일(Android NDK-based 플러그인들)도 역시 포함될 것이다. See http://docs.unity3d.com/Documentation/Manual/PluginsForAndroid.html

“Plugins/iOS”

생성된 Xcode 프로젝트에 어떠한 .a, .m, .mm, .c 또는 .cpp 파일들을 자동으로 (심볼릭 링크로써) 추가하는 제한적이고 간단한 벙법이다. See http://docs.unity3d.com/Documentation/Manual/PluginsForIOS.html

만약 Xcode 프로젝트에 자동으로 파일들을 추가하는 방법에 대해 더 많은 제어를 하고 싶다면, PostprocessBuildPlayer 기능을 사용해야 한다. 이렇게 하는 것은 파일들을 Plugins/iOS 폴더에 위치시키지 않아도 된다. See http://docs.unity3d.com/Documentation/Manual/BuildPlayerPipeline.html

“Resources”

Resources 폴더는 스크립트에서 일반적인 (그리고 권유하는) 직접 참조 (유니티 에디터에서 드래그&드롭하는 스크립트의 변수 등) 하는 대신 파일 경로나 이름으로 에셋들에 접근할 수 있게 해주는 특수 폴더이다.

이러한 이유로 사용할 때 주의 사항이 있다. Resources 폴더의 모든 에들은 비록 사용되지 않더라도 여러분의 빌드에 포함된다. 그 이유는 이 에셋들은 스크립트에 의해 사용되기 때문에 유니티는 리소스 기반의 에셋들이 사용되는지 사용되지 않는지 판단할 수 없기 때문이다.

프로젝트에 여러개의 Resources 폴더를 가질 수 있다. 한 Resources 폴더의 어떠한 이름을 가지는 에셋을 넣어두고 또 다른 Resources 폴더에 같은 이름을 가지는 에셋을 넣어두는 것은 추천하지 않는다.

게임이 한번 빌드되고 나면 모든 Resources 폴더의 모든 에셋들이 에셋을 위한 아카이브로 패킹된다. 이는 기술적으로 최종 빌드에서는 더이상 Resources 폴더가 존재하지 않는 다는 것을 의미한다. 비록 여러분의 코드에서 이들을 마치 프로젝트에 존재하던 경로를 통해 접근하긴 하지만 말이다.

에셋들이 MonoBehavior 스크립트의 변수로 접근될 경우, 이 에셋들은 MonoBehaviour 스크립트가 Instantiate될 때 (즉, 게임 오브젝트나 프리팹이 씬에 존재하게 될 때) 메모리에 한번에 로드 된다는 것을 주목하자. 만약 에셋이 너무 크고 이것이 메모리에 로딩될 때 여러분이 이에 대한 더 많은 제어를 하고 싶다면 이렇게 동작하기를 원치 않을 것이다.

그럴 경우 큰 에셋들을 Resources 폴더에 넣고 Resources.Load를 통해 불러오는 것을 고려해보자. 에셋이 더 이상 사용되지 않을 경우에는 오브젝트에 대해 Object.Destory를 호출한 후 Resources.UnloadUnusedAsset를 호출함으로써 메모리를 해제할 수 있다.

“Editor Default Resources”

이 폴더는 Resources 폴더와 유사하지만 에디터 스크립트들 에서만 의미를 가진다. 만약 에디터 플러그 인에 에셋들(예를 들어 아이콘, GUI 스킨 등)을 로드해야 하지만 빌드에는 포함되어야 하지 않다면 이 폴더를 사용해라 (이러한 파일들은 그냥 Resources 폴더에 넣는다면 빌드에도 포함된다는 것을 의미한다).

에디터 스크립트들은 MonoBehavior 스크립트가 아니기 때문에 에셋에 접근하는 일반적인 방법(즉, 인스펙터를 통한 드래그 & 드랍)을 사용할 수 없다. “Editor Default Resources”는 이를 위한 편리한 방법이다.

“Editor Default Resources”안에 위치한 에셋들에 접근하기 위해서는 EditorGUIUtility.Load를 사용해야 한다.

Resources.Load와 달리 EditorGUIUtility.Load는 로드 하려고 하는 에셋의 파일 이름 확장자를 명시할 필요가 있다. 그러므로 “myPlugin/mySkin” 대신 “myPlugin/mySkin.guiskin”이어야 한다.

EditorGUIUtility.Load에 의해 사용된 메모리를 해제하기 위해서는 오브젝트에 대해 Object.Destroy를 호출한 후 EditorUtility.UnloadUnusedAssets를 호출하라.

알고 있는 바로는 오직 하나의 “Editor Default Resources” 폴더만이 존재할 수 있으며, Assets 폴더 바로 아래에 위치해야 한다.

“Gizmos”

Gizmos.DrawIcon에 사용되는 모든 텍스쳐/아이콘들을 가지는 폴더. 이 폴더의 텍스쳐 에셋들은 이름으로 불려질 수 잇으며, 에디터에서 기즈모로써 화면에 그려진다.

“WebPlayerTemplates”

웹 빌드에 사용되는 디폴트 웹페이지를 교체하기 위해 사용된다. 여기에 위치하는 스크립트들은 전혀 컴파일 되지 않을 것이다. 이 폴더는 Assets 폴더의 최상위에 위치해야 한다.

“StreamingAssets”

이곳에 위치한 파일들은 어떠한 변경도 없이 빌드 폴더에 복사된다(최종 빌드 파일에 포함되어야 하는 모바일과 웹빌드는 제외). 이들의 경로는 플랫폼 마다 다를 수 있으며 Application.streamingAssetsPath((http://docs.unity3d.com/Documentation/ScriptReference/Application-streamingAssetsPath.html))를 통해 접근할 수 있다. Also see http://docs.unity3d.com/Documentation/Manual/StreamingAssets.html


link # 1 : http://wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder

link # 2 : http://www.devbb.net/viewtopic.php?f=37&t=1191

반응형
반응형





일정 loop 에 대한 시간을 측정 한다고했을 경우 JIT 컴파일은 


첫 실행에서만 효과를 누릴 수 있는 만큼 평균치에선 효과가 드러나지 않는다




JIT 컴파일(just-in-time compile) 또는 동적 번역(dynamic translation)은 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일 기법이다. 이 기법은 프로그램의 실행 속도를 빠르게 하기 위해 사용된다.

전통적인 입장에서 컴퓨터 프로그램을 만드는 방법은 두가지가 있는데, 인터프리트 방식과 정적 컴파일 방식으로 나눌 수 있다. 이 중 인터프리트 방식은 실행 중 프로그래밍 언어를 읽어가면서 해당 기능에 대응하는 기계어 코드를 실행하며, 반면 정적 컴파일은 실행하기 전에 프로그램 코드를 기계어로 번역한다.

JIT 컴파일러는 두 가지의 방식을 혼합한 방식으로 생각할 수 있는데, 실행 시점에서 기계어 코드를 생성하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다.

최근의 자바 가상 머신에서는 JIT 컴파일을 지원한다. 즉, 자바 컴파일러가 자바 프로그램 코드를 바이트코드로 변환한 다음, 실제 바이트코드를 실행하는 시점에서 가상 머신이 바이트코드를 기계어로 변환한다.


http://linuxism.tistory.com/258




좀 더 Jit 에 대한 자세한 설명




1. 닷넷의 이해, 컴파일과 실행

 

1.1 닷넷 (.NET)

1.1.1 C#이란

▣ Visual Basic

​  ◈ 프로그램을 쉽고 빠르게 할 수 있었다.

​  ◈ 문제점

    - 완벽한 객체지향을 지원해주지 않았다.

    - 다른 언어와의 호환성 지원이 미흡했다.

​▣ C#의 가장 큰 이슈

  ​◈ 더 쉽고 빠르게 프로그램하는 것이다.

​  ◈ C#에서 할 수 없는 것을 완벽하게 지원해주자는 것이다.

​​▣ C#의 파워

  ​◈ 2줄이면 C언어로 만들어진 함수 라이브러리를 사용할 수 있다.

​▣ 변화​

  ​◈ 많은 개발자들이 C#을 이해하지 못하고 있었기에 C#에 대한 혼돈 상태에 있었다.

  ◈ C#을 이해하기 시작하면서 C#은 MS의 대체 언어가 되고 있으며, 개발자의 주 개발 언어가 되고 있다.

1.1.2 닷넷의 의미와 특징

​▣ 닷넷이란(프로그래머 입장)

  ◈ 모든 것을 포괄하고 있는 이상적인 개발환경

▣ 이상적인 개발환경

  ◈ 닷넷은 개발에 필요한 개발 언어, 개발 툴, 라이브러리, 기반 기술 등을 제공하는 이상적인 개발환경이다.​

​​▣ 개발환경의 통일

  ◈ 개발자는 닷넷 환경하에서 다양한 언어로 개발

▣ 실행환경의 통일

  ◈ 다양한 언어로 개발된 프로그램을 하나의 방식으로 번역한 후 닷넷에서 하나의 방식으로 동작

▣ C#​​

  ◈ 닷넷을 대표하는 언어

1.1.3 닷넷 프레임웍(.NET Framework)

1.1.4 닷넷의 특징

▣ 플랫폼 독립성(Platform Independent)

  ◈ 닷넷 프레임웍만 설치되어 있다면 어떠한 운영체제에서도 닷넷 애플리케이션이 동작할 수 있다.

▣ 다양한 언어의 지원

  ◈ 개발자는 다양한 언어로 개발, 닷넷에서 하나의 방식으로 동작

  ◈ CLS(Common Language Specification)

    - 닷넷 프레임웍에서 동작하기 위해서 언어들이 지켜야 하는 표준 스펙

▣ 상호 운용성(Interoperability)

  ◈ 서로 다른 언어끼리의 호환성

    - C#에서의 COM 사용

    - C#에서의 Win32 API 사용

  ◈ 닷넷 지원 언어들끼리의 호환성

1.2 ​닷넷 언어의 핵심

​1.2.1 중간언어

1.2.2 동적 컴파일러

​▣ 중간언어(Intermediate Language)

  ◈ 기계어로 변환하기 쉬운 상태의 중간 단계의 언어

  ◈ 닷넷에서 번역되고 실행되기 위해서는 중간언어 형태로 컴파일되어야 한다. 

▣ 중간언어의 단점

  ◈ 완전한 기계어가 아니기 때문에 언젠가는 완전한 기계어로 번역해야 한다.

  ◈ 속도가 느리다.​

▣ 중간언어의 장점

  ◈ 중간언어를 기계어로 번역하는 번역기만 제공된다면 어떠한 플랫폼에서도 실행 가능하다.​

▣ 중간언어와 동적 컴파일​​​ 

  ◈ 중간언어

     - C#의 최종 결과물(컴파일한 결과파일)

  ◈ 재컴파일

     - 중간언어는 완벽한 기계어로 컴파일되어야만 실행될 수 있다.

  ◈ JIT(Just In Time Compiler)

     - 중간언어를 동적으로 컴파일하는 컴파일러


1.2.3 CLR







1.2.4 컴파일과 실행

​▣ 닷넷의 컴파일과 실행

  ◈ 컴파일

    - 중간언어(IL) 형태로 만든다.

  ◈ 실행

    - .NET 환경의 CLR은 IL형태의 언어를 JIT가 재컴파일한 후 시행시킨다.

​▣ C#의 어셈블리

  ◈ 중간언어 형태의 .exe나 .dll파일들을 C#에서는 어셈블리(Assembly)라고 한다.

​▣ 어셈블리의 종류 ​

  ◈ 사설 어셈블리

    - Copy & Paste 개념의 프로그램

    - 복사해서 사용하면 된다.

  ◈ 공용 어셈블리

    - 레지스터 개념에서 발전된 형태

    - 공용 어셈블리를 등록하는 것은 레지스터와 동일하다.

    - 버전별로 독립적으로 관리하며 복사버젼을 보관한다.

    - CLSID 대신 디지털 서명 방식으로 관리한다.   ​

1.3 .NET과 언어​

1.3.1 .NET과 C#

▣ .NET과 C#의 호환

  ◈ .NET을 위해서 새롭게 만들어진 언어

  ◈ .NET의 특징은 곧 C#의 특징이다.

▣ C#언어의 특징

  ◈ 기존 언어들의 장점만을 살려서 만든 언어

  ◈ C#은 C++의 문법을 표준화하고 정리하고 발전시킨 언어이다.

  ◈ C#에서 C문법을 사용할 수 있다. (포인터)

  ◈ C#에서 기존의 COM 컴포넌트를 사용할 수 있다.

▣ C#의 라이브러리​

  ◈ XML.NET

  ◈ ADO.NET

  ◈ ASP.NET

  ◈ XML Web Services

​1.3.2 C#과 자바



[출처] [C#] 1.닷넷의 이해, 컴파일과 실행


반응형
반응형




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

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

 

 

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

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



 

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

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

 

 

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

Edit > ProjectObtion > QualitySetting 을 눌러보면

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

 

 

 

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

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

 

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

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

 

 

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

 

 

깨알팁 

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



반응형
반응형

MonoBehaviour

유니티 상에서 스크립트를 생성하게 되면 해당 클래스는 항상 MonoBehaviour의 상속을 받게 만들어진다.

기본 스크립트를 생성하면 MonoBehaviour를 상속받는 것을 알 수 있다.
MonoBehaviour는 Behaviour를 상속받는다

위와 같이 상속받는 대상을 파고 들어가본다면 
MonoBehaviour -> Behaviour -> Component -> Object 로 상속형태가 이루어져있다.
그리고 MonoBehaviour를 상속받지 않는 스크립트를 inspector창에서 컴포넌트로 붙힐 수는 없다.

컴포넌트로 붙히려면 이런 Error가 뜬다

또한 MonoBehaviour를 상속받는 클래스는 코드상 new로 할당 받을 수 없다.
아래의 코드가 쓰여진다면 newTest는 null이 들어간다.

public Class Test : MonoBehaviour { } Test newTest = new Test();

그리고 MonoBehaviour에는 Unity의 중요하며 기본적인 함수들을 지원한다.
 - Awake() : Start()함수와 같이 실행될 때 제일 먼저 한번만 호출된다. 게임오브젝트가 비활성화 상태라도 호출이 된다.
 - Start() : Awake()함수와 같이 실행될 때 Awake()함수 뒤에 한번만 호출된다. 하지만 비활성화 상태라면 호출되지 않는다.
 - Update() : 유니티 내부의 프레임 속도에 맞춰 매 프레임마다 한번씩 호출되는 함수이다.
 - LateUpdate() :  Update()함수 뒤에 호출되는 함수로서 Update()함수와 마찬가지로 프레임마다 한번씩 호출된다.
 - FixedUpdate() : 프레임마다 진행되는 시간은 일정하지 않은데 이 함수는 고정된 일정 시간을 간격으로 한번씩 호출된다.

중요한 것은 만약 스크립트가 여러개 존재한다면 각 스크립트마다 Awake(), Start(), Update()함수들이 언제 호출되는지
정해져 있지 않기 때문에 신경써서 코딩을 해주어야 한다.

위의 함수들이 잘 실행이 되는지 확인해 보는 방법으로 Console창에 글을 띄우는 방법이 있다.
'Debug.Log("띄울 글")' 코드를 통해 Console창에 내용을 출력할 수 있다.

GameObject 이동방법
게임오브젝트의 transform에는 오브젝트의 위치값, 각도, 스케일등을 가지고 있다.
게임오브젝트를 이동시키려면 transform의 위치값을 변경해 주면 된다.
Start()함수나 Update()함수에 transform.position = new Vector3(0, 0, 5); 를 해준다면 z값이 5인 위치로 변경된다.
position에는 localPosition도 따로 존재하기때문에 로컬좌표를 변경하기 위해선 localPosition을 사용해주면 된다.
오브젝트의 각도를 조절하기 위해선 Quaternion을 사용해주면 된다.


데이터를 Import하는 방법
유니티 프로젝트를 생성하면 Assets 폴더가 자동 생성되는데 
외부에서 리소스 파일들을 받아오기위해선 이 폴더 안에 리소스들을 넣어주면 된다.

Unity 프로젝트 폴더 내부에 존재하는 Assets폴더

Assets폴더 내부에 리소스를 넣고 유니티 화면으로 전환하면 자동으로 Import 로딩이 뜨며 임포트가 완료된다.

Material 변경
만약 임포트한 파일의 텍스쳐가 마음에 들지 않아 사용자 임의로 바꾸기를 원한다면 material 파일을 만들어서
게임오브젝트에 컴포넌트로 넣어주면된다.

현재 Material을 넣어주기 전 plane의 색은 회색이다.
Plane의 색이 초록색으로 바뀌었다

Material을 만들고 원하는대로 변경한 후 변경할 게임오브젝트에 드래그만 해준다면 위의 두번째 사진처럼
Material이 New Material로 변화되는것을 확인할 수 있다.


Layer 추가 방법

게임오브젝트를 클릭하면 컴포넌트 위에 Tag란과 Layer를 볼 수 있다.
Layer를 설정하기 위해선 Add Layer... 를 선택하여 2번째 사진처럼 원하는 Layer 이름을 작성해준다.
그러면 바로 위 사진처럼 원하는 Layer 네임을 가지는 것을 볼 수 있다.



반응형
반응형

늦어서 죄송합니다. 그동안 잠시 다른 일이 생겨서 마지막 부분 업로드가 늦었습니다.
해당 글 원문은 http://unitygems.com/memorymanagement/에서 보실수 있습니다.
번역중에 이상한 부분은 댓글 남겨주시면 확인 후에 수정하겠습니다.
(저도 공부하면서 올리는 거라 미흡한 점이 많이 있습니다. 미흡한 부분이 있더라도 조금만 양해부탁드립니다^^;)


Static Variables
정적 변수


많은 초보자들은 이 간단한 실수를 한다:

1
2
3
4
public static int health;
void Start(){
    health=100;
}

다른 스크립트에서 우리는 다음과 같은 코드를 발견할 것이다.

1
2
3
4
5
6
void OnCollisionEnter(Collision other){
    if (other.gameObject.tag == “Enemy”){
        EnemyScript.health-=10;
        if(EnemyScript.health <=0)Destroy(other);
    }
}

그들은 적을 죽이려고 할 것이고, 그를 죽이는데 성공한다. 그러나 그때 더 많은 적들이 추가되고, 모두 갑자기 죽는것에 의아해 한다.

이유는 간단하다. 당신은 많은 enemy 클래스 인스턴스를 가지고 있다.(그것은 static이 아니다) 그러나 그들은 모든 적에 대해 오직 하나의 health 인스턴스를 가진다. 하나를 죽이는 것은 모두를 죽이는 것이다!

사실, 클래스에 선언된 정적 변수는 객체들에 속해 있는 것이 아니라 클래스 그 자체에 속해 있다.
이것이 바로 우리가 정적 변수에 접근할 때 인스턴스 이름이 아닌 클래스 이름을 사용하는 이유이다.

그것은 적의 체력에 대해서 올바르게 실행되지 않았다. 왜냐하면 각각의 적들은 그들 스스로의 health 변수를 가져야 하기 때문이다.

정적 클래스와 같은 방법으로, 정적변수를 선언한 클래스가 첫번째로 접근될 때 정적 변수는 인스턴스화된다.
이것은, 게임에서 클래스의 어떤 인스턴스도 없더라도, 우리가 정적변수에 접근하려고 할 때 정적 변수는 존재한다는 것을 의미한다.

적 카운터를 고려할 때, 우리는 게임 안에서 적들의 숫자를 추적하기를 원한다.

static var counter;
 
public void CreateEnemy(){
    GameObject obj=Instantiate(enemyPrefab, new Vector3(0,0,0).Quaternion.identity);
 
    if(obj)counter++;
}
 
public void DestroyEnemy(){
    Destroy(gameObject);
    counter--;
}

이 스크립트는 각 적들에게 첨부될 것이다. 모든 적들이 다른 스크립트를 가지고 있더라도, 모든 적들은 같은 counter 변수를 공유한다

우리의 게임에 이 함수를 사용하는 것은 우리가 계속해서 적들의 숫자를 추적 가능하게 한다.
Instantiate가 객체를 만드는 것을 실패했을 경우에(예를 들면, 힙 파편화로 인한), Instantiate는 null 참조를 반환하기 때문에, 참조 변수 사용에 주의해야 한다. 

만약 어떤 이유로 인스턴스 생성이 실패되었을 경우, 다음과 같은 if문의 확인없이 적 카운트를 증가시킨다면, 우리는 잘못된 counter를 가질 것이다.

지금 만약 다음과 같이, counter변수를 사용하여 레벨을 끝내고, 새로운 레벨을 불러오려고 한다면,
if(counter<=0)Application.LoadLevel(nextLevel);
만약 확인을 하지 않고 만약 인스턴스화를 실패했다면, 모든 적을 죽인 후에, 우리가 counter가 1이고 우리의 게임은 버그가 생긴 것이다.

우리의 플레이어는 어떤가, 정적 health를 사용할 수 있는가? 그렇다. 우리는 우리의 플레이어의 체력을 위해서 정적 변수를 사용할 수 있다. 사실 정적 변수에 접근하는 것은 심지어 인스턴스 맴버보다 더 효율 적이다.

인스턴스의 멤버를 호출할 때, 컴파일러는 객체의 존재를 확인하기 위한 작은 함수 요청이 필요하다. 왜냐하면 존재하지 않은 데이터를 수정할 수 없기 때문이다.


Static Functions
정적 함수

정적 함수는 비정적 클래스에서 구현될 수 있다. 이 경우에 있어서, 오직 그 클래스의 정적 변수만이 정적함수안에서 접근될 수 있다. 정적 함수는 인스턴스가 아닌 클래스를 통해 호출되기때문에, 
존재하는 것이 확신되지 않는 맴버에 접근하는 것은 문제가 될 수 가 있다.

반면에, 정적 함수에 매개변수를 전달할 수 있고, 당신은 아마 이런 함수를 꽤 많이 사용하고 있을 것이다.
1
2
3
4
5
6
Vector3.Distance(vec1.vec2);
vec1.Normalize();
 
Application.LoadLevel (1);
 
if (GUI.Button(Rect(10,10,50,50),btnTexture)){}

그 목록들을 다 말하기에는 너무 많다. 두번째 라인은 정적 함수가 아니다. 그것은 vector3타입의 vec1 인스턴스를 통해 호출된다. 그 밖의 예는, 클래스가 정적 함수를 호출하기위해 사용되는 것을 보여 준다.

정적 함수는 비정적함수보다 더 빠른 경향이 있다. 컴파일러는 인스턴스함수의 호출이후에 인스턴스가 존재하는지 확인을 하기위해 작은 오버헤드가 발생하기 때문이다. 정적 함수는 존재의 유무가 확실한 클래스를 사용한다. 그렇지만 시간의 이득은 작다.


정적 클래스의 또 다른 이점은 그들의 존재가 영구적이고, 컴파일러에게 알려져 있으며,
따라서 C#에서 정적 클래스는 확장메소드 를 정의하기 위해 사용되어 질 수 있다.
확장메소드는 다른 타입의 변수에 첨가된 추가적인 함수처럼 보인다.
이는 syntactic sugar이다. 이런 식으로 확장된 클래스는 사실 열려있지 않지만, 
(그들의 private과 protected 변수는 확장메소드에서 접근이 불가능하다.)
이는 겉보기에 기존의 객체에 새로운 함수를 주는 깔끔한 방법 - 코드의 가독성을 향상시키고, 개발자들에게 더 쉬운 API를 제공하는 - 이다.

예를 들어 표면상으로 다음과 같이 Transform에 새로운 함수를 추가할 수 있다.

transform.MyWeirdNewFunction(x);
확장 메소드의 예:


기존의 함수(OrderBy, Sort,...)를 확장하는 배열에 함수를 만들기를 원한다고 생각해보자.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
using UnityEngine;
using System.Collections;
 
public static class ExtensionTest {
 
    public static int BiggestOfAll(this int[] integer){
        int length = integer.Length;
        int biggest = integer[0];
        for(int i = 1;i<length;i++){
            if(integer[i]>biggest)biggest = integer[i];
        }
        return biggest;
    }
}


우리는 정적 함수를 정의한다. 전달된 매개변수가 어떻게 함수를 호출하는 인스턴스가 되는지를 봐라.
매개변수의 타입을 수정하는 것은 다른 객체에서 작동하도록 한다.


이제 선언해서 배열을 채워보자:

01
02
03
04
05
06
07
08
09
10
11
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
 
        int[] array = {1,25,12,120,12,6};
 
    void Start () {
        print(array.BiggestOfAll());
        }
}

배열 이름 뒤에 .(점)을 입력하면, 위에서 입력한 새로운 함수가 나타나는 것을 보게 될 것이다.

그들의 타입과 상관없는 다른 배열들을 받아들이는 더 제너릭한 함수를 만들기를 원한다면, 
정적 제너릭 확장 메소드를 구현해야 할 것이다.


01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
 
public static class ExtensionTest {
 
    public static T TypelessBiggestOfAll<T>(this T[] t){
        int length = t.Length;
        var biggest = t[0];
        for(int i = 1;i<length;i++){
            biggest =((Comparer<T>.Default.Compare(t[i], biggest)>0)?t[i]:biggest);
        }
        return biggest;
    }
}


using System.Collections.Generic의 추가를 주목해라. 컴파일러가 T타입이 무슨타입인지 알지 못하기 때문에, 우리는 간단하게  < 또는 >를 통해서 값을 비교할 수 없다. 우리는 Comparer<T>를 사용해야 한다.
예를 들어,  삼항연산자가 사용된다. 우선 약간 혼란스러운 코딩을 만드는 경향이 있지만, 또한 약간의 라인을 줄여준다.


삼항 연산자는 다름과 같에 생겼다.  a ? b : c;

a는 수행된다. 그리고 만약 참이면 b가 명령문에서 내보내진다. 만약 거짓을 경우 c가 보내진다. 
일반적인 if문과 비교해봤을 때 장점은 비교후 값을 리턴한다는데 있다. 
result = a ? b : c; result는 a에 의존하여 b 또는 c값을 받는다.
이제 다른 타입의 배열을 선언한 뒤 같은 함수를 사용하는 것이 가능하다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
 
    int[] arrayInt = {1, 25, 12, 120, 12, 6};
    float[] arrayFl = {0.5f, 52.456f, 654.25f, 41.2f};
    double[]arrayDb = {0.1254, -15487.258, 654, 8795.25, -2};
 
    void Start () {
        print(arrayInt.TypelessBiggestOfAll());
        print (arrayFl.TypelessBiggestOfAll());
        print (arrayDb.TypelessBiggestOfAll());
       }
}

이는 값 120, 654.25, 8798.25를 출력한다.

이제 나는 당신에게 이것이 유용하다는 것을 보여주고싶다.  많은 유니티 유저는 객체 내부의 지역을 통제하는 방법에 대해서 묻는다. 예를 들어, 화면 내부에 객체를 유지하고 싶기를 원한다면, 당신은 이 함수를 사용하여 그 객체의 Transform을 고정해야 한다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
using UnityEngine;
using System.Collections;
 
public static class GameManager{
 
    public static void ClampTransform(this Transform tr,Vector3 move, Vector3 min, Vector3 max){
        Vector3 pos = tr.position + move;
        if(pos.x < min.x ) pos.x = min.x;
        else if(pos.x > max.x) pos.x = max.x;
 
        if(pos.y  < min.y ) pos.y = min.y;
        else if(pos.y> max.y) pos.y = max.y;
 
        if(pos.z < min.z) pos.z = min.z;
        else if(pos.z > max.z) pos.z = max.z;
 
        tr.position = pos;
    }
}

그리고 당신이 필요한 어떤 곳이든 사용하면 된다:

1
2
3
Vector3 minVector = new Vector3(-10,0,-10);
Vector3 maxVector = new Vector3(10,0,10);
transform.ClampTransform(move,minVector,maxVector);

이 예에서,  객체는 (0, ,0, 0)을 중심으로한 20*20 사각형의 2D 환경에 제약이 걸려 있다.

Heap Fragmentation, Garbage Collection and Object Pooling
힙 파편화, 가비지 컬렉션, 오브젝트 풀링


힙은 데이터가 무작위로 저장되어 있는 큰 메모리 공간이다.  사실 그렇게 무작위는 아니다. OS는 메모리를 조사할 것이고, 요구된 데이터에 적합한 크기의 첫번째 메모리 지역을 찾을 것이다. 
힙의 크기와 위치는 당신이 선택한 플랫폼에 따라 달라진다.

예를 들어, 정수에 대해 요청할 때, OS 연속적인 4바이트의 메모리공간을 찾는다. 사실은, 프로그램이 실행될 때, 메모리 지역은 실제 규칙없이 사용되고, 해제된다. 그리고 당신의 프로그램은 힙 파편화가 발생될 것이다.


이 사진은 힙 파편화의 예를 보여준다. 명백하게 이것은 좀 지나치게 강조하고 있지만, 본질은 틀리지 않다.

어떤 데이터도 추가 되지 않았다는 것을 명심해라. state1에서의 같은 양의 데이터가 있다.
오직 움직임이 힘 파편화를 만들었다.



유니티는 .NET managed 메모리를 사용한다. C 프로그램에서 힙 파편화는 메모리 블럭을 할당하는 것이 불가능해지는 상황을 초래할 수 있다. 왜냐하면 사실 충분한 메모리 공간이 있더라도 적합한 크기의 인접한 블록이 없기 때문이다. managed 메모리는 이 제한으로 부터 영향을 받지 않는다. 만약 메모리 블록 발견되지 않는다면, 실제 .NET 메모리 관리와 가비지 컬렉션 시스템은 메모리를 움직임으로써 힙의 파편화를 제거할 수 있다. 유니티에서는 가비지 컬렉션의 실행으로 힙 파편화 현상이 일어나지 않는다.
한번에 하나 이상의 객체가 제거됨으로써 공간을 찾을 것이다. 그러나 이것은 그들이 적합할 때까지 블록을 움직이는 것만큼 효율적이지는 않다.
프로그래머의 해답은 오브젝트 풀링을 사용하는 것이다. state1에서 data3를 파괴하는 대신에 우리는 간단하게 나중의 사용을 위해서 그것을 비활성화 시키면 어떻게 될까? 아마 힙 파편화를 피했을 것이다.

우리는 data를 파괴하지 않는다. 우리는 그것을 잠자게 할 것이고, 필요할 때 우리는 그것을 깨울 것이다.

이 해법에 많은 장점이 있다. 우선 우리는 위에서 봤던 경우를 피할 것이고, 우리는 또한 Instantiate과 Destroy 함수 호출을 피할 것이다. 우리는 오직 gameobject.SetActiveRecursevely(true/false)를 사용하면 된다.

마지막으로 우리는 파괴하지 않기 때문에, 우리는 비용이 비싼 행위인 가비지 컬렉터를 호출하지 않는다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
ObjectScript.cs
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour {
    public GameObject prefab;
    GameObject[] objectArray = new GameObject[5]; 
        public static int counter;
 
         void Start(){
        for(int i = 0;i<objectArray.Length;i++){
            objectArray[i] = (GameObject)Instantiate(prefab,new Vector3(0,0,0),Quaternion.identity);
            objectArray[i].SetActiveRecursively(false);
        }
        }
        void Createobject(){
        if(counter < objectArray.Length){   
                // iterate through array
            for(int i = 0;i<objectArray.Length;i++){ 
                                // Find an inactive object                                    
                if(!objectArray[i].active){   
                               // Increase the counter, activate the object, position it                                             
                    counter++;                                             
                    objectArray[i].SetActiveRecursively(true);            
                    objectArray[i].transform.position = new Vector3(0,0,0); 
                    return;
                }
            }return;
        }else return;
    }
}
1
2
3
4
void OnCollisionEnter(CollisionEnter){
    if(other.gameObject.tag=="Object"){
        other.gameobject.SetActiveRecursively(false);       //Deactivate the object
        ObjectScript.counter--;                                             //Decrease counter

이 씬에서는 한번에 절대 5개의 객체를 가지지 않는다. 1개가 비활성화 될 때, 우리 원하는 곳에 다시 재활성화 시킬 수 있다. 가비지 컬렉션은 필요하지 않는다.


만약 당신 앞에 나타나는 적들의 웨이브를 가진다면, 죽은 적들을 파괴하는 대신에, 당신의 적에 대해서 이 트릭을 사용할 수 있고, 그들을 다시 재위치 시킬 수 있다. 


탄약에서도 똑같이, 각각의 총알을 파괴하는 대신에 그것을 비활성화 시킨다.
만약 당신의 총 앞의 위치와 적절한 속도로 다시 발사할 때, 그것을 다시 활성화 시키면 된다.


Note that pooling at the memory level is a different process managed by the OS consisting in reserving always the same amount  of memory whatever size the object is. As a result there will never be tiny memory locations lost all over the memory.
메모리 단계에서 풀링은 객체의 사이즈가 얼마이든 간에, 항상 같은 양의 메모리를 확보하는 OS에 의해 관리되는 다른 프로세스이다.

Array Reuse
배열 재사용

객체를 재사용하는 것에 더하여, 우리는 각 호출 또는 같은 호출에서 반복적인 코드를 재사용하는 버퍼를 생성하는 것은 생각해볼 만한 가치가 있다.

01
02
03
04
05
06
07
08
09
10
11
12
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          var nearestFiveEnemies = new Enemy[5];
 
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(nearestFiveEnemies);
      }
}

이 예제에서, 우리는 가장 가까운 5개의 적을 타겟팅 할 것이고, 자동적으로 그들에게 발사할 것이다.
문제는 발사 버튼을 누를 때 마다 메모리가 할당된다는 점이다. 대신에 만약 우리가 오랫동안 유지되는 적들의 배열을 만들었다면, 우리는 코드 어디에서든 사용할 수 있을 것이고, 우리는 메모리를 할당하지 않아도 되며 느려지는 현상을 피할 수 있을 것이다.

이것은 간단한 케이스가 될 수 있다:

01
02
03
04
05
06
07
08
09
10
11
12
Enemy[] _nearestFiveEnemies = new Enemy[5];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(_nearestFiveEnemies);
      }
}

그러나 어쩌면 우리가 종종 적들의 배열이 필요할 때,  우리는 그것을 더 일반적으로 만들 수 있을 것이다.

01
02
03
04
05
06
07
08
09
10
11
12
Enemy[] _enemies = new Enemy[100];
 
void Update()
{
      if(readyToFire && Input.GetKeyDown(KeyCode.F))
      {
          //Do something to fill out the list finding the nearest
          //enemies
 
          TargetEnemies(_enemies, 5);
      }
}

카운트를 가지는 TargetEnemies에 대한 코드를 재작성함으로써, 우리는 효율적으로 우리의 코드 어떤 곳에서든 일반적인 적의 목적 버퍼를 사용할 수 있을 것이고, 훨씬 더 할당의 필요성을 피할 수 있을 것이다.


두번째 예제는 꽤 지나친 경우이다. 그리고 만약 실제로 메모리 문제를 야기시키는 거대한 집합을 가진다면 반드시 이것을 해야한다. - 일반적으로 우리는 언제나 읽기쉽고, 약간의 메모리를 아낄 수 있는 이해할 수 있는 코드를 작성해야 한다.



출처: http://unityindepth.tistory.com/14 [UNITY IN DEPTH]

반응형
반응형

원문이 약간 길어 3~4개로 나눠어서 올릴 예정입니다. 
해당 글 원문은 http://unitygems.com/memorymanagement/에서 보실수 있습니다.
번역중에 이상한 부분은 댓글 남겨주시면 확인 후에 수정하겠습니다.
(저도 공부하면서 올리는 거라 미흡한 점이 많이 있습니다. 미흡한 부분이 있더라도 조금만 양해부탁드립니다^^;)






Struct vs Class

그래서 어떤 상황에서 어느 것을 사용해야 하는가? 클래스를 사용하는 것은 추가적인 변수(참조변수)가 추가 된다는 것을 알았다. 만약 수천 개의 객체를 만든다면, 수천 개의 추가적인 변수를 얻을 것이다. 하지만 구조체는 이들을 생성하지 않는다.

밑의 예제를 보자:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class DogC{
    public string name { get; set;}
    public int age { get; set;}
}
 
public struct DogS {
    public string name;
    public int age;
}
 
using UnityEngine;
using System.Collections;
 
public class Memory : MonoBehaviour {
    DogC dogC = new DogC();
    DogS dogS = new DogS();
 
    void Update () {
        if(Input.GetKeyDown(KeyCode.Alpha1))
            Print (dogS);
        if(Input.GetKeyDown(KeyCode.Alpha2))
            Print (dogC);
        if(Input.GetKeyDown(KeyCode.Alpha3))
            print ("Struct: The dog's name is "+dogS.name +" and is "+dogS.age);
        if(Input.GetKeyDown(KeyCode.Alpha4))
            print ("Class: The dog's name is "+dogC.name +" and is "+dogC.age);
     }
 
    void Print(DogS d){
        d.age = 10;
        d.name = "Rufus";
        print ("Struct:The dog's name is "+d.name +" and is "+d.age);
     }
 
    void Print(DogC d){
        d.age = 10;
        d.name = "Rufus";
        print ("Class:The dog's name is "+d.name +" and is "+d.age);
    }
}

우선, 세번째 입력은 우리가 예상했던 값을 출력하지 않을 것이다. 구조체의 name과 age는 값이 사라졌다. 
반면에 클래스는 값을 유지한다.
(역주: 유니티에서 테스트해보시면 쉽게 이해하실 수 있습니다. 업데이트문에 있는 순서대로 입력을 해보시기 바랍니다. 참고로 KeyCode.Alpha1은 숫자1키를 말합니다.)  

우리가 말했던 것을 기억해라. 클래스는 참조 타입이고 구조체는 값 타입이다. 구조체에 대해서 함수를 호출 할 때, 구조체의 복사본을 전달하지만, 그 복사본은 원래의 데이터가 아니다. 그 데이터를 수정할 때, 우리는 단지 스택에 있는 복사본의 데이터를 수정하는 것이다. 이들은 함수의 마지막 부분에서 사라진다.

클래스를 매개변수로 받는 함수는 원래의 데이터로 접근할 수 있는 참조값을 전달 받는다. 
그리고 함수안에서 수정도 가능하다. 함수가 끝나더라도 수정한 것은 그대로 유지된다.

우리가 생각해야 하는 것은 무엇인가? 만약 구조체가 10개의 변수를 포함할정도로 크다면, 우리가 함수에 구조체를 전달할 때 스택에는 10개의 공간이 생성될 것이다. 지금 내가 이 구조체 50개를 가지고 있는 구조체배열을 전달한다면 스택에는 500개보다 더 큰 공간이 발생할 것이다. 스택의 사이즈는 제한되어 있고, 만약 구조체와 배열의 크기가 더 크다면 스택 오버플로우가 발생할 수도 있다.



클래스를 전달 할 때는, 오직 하나의 변수만 전달한다. 50개의 클래스 인스턴스를 전달할 때는 스택에 50개의 변수에 대한 공간만 생성될 것이다.

종합해보면, 클래스는 메모리를 절약하지만 구조체는 대신 더 빠르다. 핵심은 2개의 균형을 알아내는 것이다. 
5개~8개의 멤버변수를 가질 때 구조체를 사용하는 것을 고려해라. 그 보다 위에는 클래스를 사용해라.


You must also bear in mind that every time you access a variable which is a structure (for example by doing transform.position) you are creating a copy of that structure on the stack - that copy takes time to create.
구조체 변수에 접근할 때마다(예를 들어 transform.position를 사용할 때) 스택에 구조체의 복사본을 만들고, 복사하는데도 시간이 든다는 것을 명심해야 한다.
(역주: 구조체 변수에 접근할 때마다 스택에 구조체의 복사본을 만든다는게 잘 이해가 안되서
원문과 같이 나뒀습니다. 혹시 이글을 보시고 부연설명이 가능하신분은 댓글로 좀 부탁드리겠습니다.)

클래스를 만들때 마다, 힙에 메모리를 할당할 것이다. 이는 빠른속도로, 버려진 클래스들을 위한 가비지 컬렉션 사이클로 이어진다. 구조체는 항상 스택에 할당되며 이런 이유로 가비지 컬렉션을 절대 유발시키지 않는다.

유니티에서 구조체는 종종 3~4개의 변수를 가진다. 
(예를 들어, position, color, quaternion, rotation, scale...이들 모두다 구조체이다)


오래가지 못하는, 자주 할당되는 객체는 구조체의 좋은 후보가 된다. 왜냐하면 이들은 가비지 컬렉션을 유발시키지 않기 때문이다. 오래가고 큰 객체들은 클래스의 좋은 후보들이다. 왜냐하면 그들은 접근될 때 마다 복사되지 않으며, 그들의 지속된 존재는 가비지 컬렉션을 유발시키지 않는다. 
Vector3, RayCastHit같이 많은 작은 객체들은 바로 이 이유때문에 유니티에서 구조체인 것 이다.
다음을 넘어가기전에 마지막 핵심 하나가 있는데,
함수에서 구조체를 수정하고 값을 계속 유지하기 위해서, 구조체를 참조로써 전달하는 것이 가능하다.


구조체 변수를 정의한 함수가 종료되었을 때 구조체에 대한 참조를 유지할 수 없다.이 같은 문제를 해결하기 위해 클로저의 사용(람다 함수 또는 인라인 함수에 의해서 생성된)처럼 많은 방법이 있지만 그것은 꽤 고급 기술에 속한다.
1
2
3
4
5
void PrintRef(ref DogS d){
    d.age = 10;
    d.name = "Rufus";
    print ("Structure:The dog's name is "+d.name +" and is "+d.age);
}

우리는 위에 것을 다음처럼 호출할 것이다:

1
2
3
4
void Update(){
    PrintRef(ref dogS);
    print ("Structure:The dog's name is "+dogS.name +" and is "+dogS.age);
}

당신은 키워드 ref에 대해서 알았다. 이것은 컴파일러에게 스택에 구조체를 복사하지 말고 구조체에 대한 참조를 만들라고 말하는 것이다. 함수 밖에서 구조체는 함수 안에서 받은 값들이 유지된다.
이 키워드는 어떤 값타입 형식과 사용될 수 있다.


Creating a Reference
참조 만들기


우리는 방금 전에 값타입 변수에 참조를 만드는 것이 가능하다는 것을 봤다. 우리는 함수 안에 정수형 변수를 만들고, 그것이 후에 사용되기 위해서 계속해서 유지되는 것을 원한다고 생각해보자.
간단하게, 함수내부에 변수를 선언하는 것은 함수의 끝에 사라지는 automatic 변수를 만드는 것이다.


우리는 동적으로 할당되는 변수를 만들 수 있다. 이는 참조와 함께 힙에 저장될 것이다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using UnityEngine;
using System.Collections;
 
public class Test:MonoBehaviour{
    int number;
 
    void Update () {
        if(Input.GetKeyDown (KeyCode.C))
            CreateVariable();
        if(Input.GetKeyDown (KeyCode.Space))
            PrintVariable(number);
    }
 
    void CreateVariable(){
         //int number = new int();
         number = new int();
         number = 10;
        print ("In function "+number);
    }
 
    void PrintVariable(int n){
        print (n);
        n+=10;
    }
}

첫 번째로, 스크립트에 전역으로 정수형 변수 number를 선언한다. 우리는 이 변수를 CreateVariable함수에서 사용하고, new int()를 사용하여 number에 정수형 변수 참조를 대입한다.



이 상황을 명확히 하기 위해서, new 키워드는 힙영역에 만들어진 변수에 대한 참조를 반환한다.
이 참조는 number 변수안에 저장되고, 이제 number 변수는 새롭게 생성된 변수의 주소를 가지고 있다. number를 사용하는 것은 간접적으로 이 변수를 사용하는 것이다. 우리가 새롭게 생성한 변수는 함수 밖에서도 여전히 존재한다.










주석으로 된 부분을 고려해보면, (//int number = new int();)
만약 이 같은 방법으로 사용했다면, 우리는 함수의 끝에서 참조를 잃었을 것이다.
다음 청소 라운드에서, 가비지 컬렉터는 참조가 없는 데이터(함수 안에 선언된 number)를 찾을 것이고 그것을 메모리로부터 제거할 것이다.
이 같이 우리가 함수 내부에서 했던 것처럼 지역 변수를 선언하는 것을 생각해 보면, int number은 전역 int 
number 변수를 숨긴다는 것을 알 수 있다.


참조안에서 int같은 원시 변수를 유지할 때,  컴파일러는 boxing(박싱)이라고 불리는 함수를 수행한다. 이는 기본적으로 원시 변수를 클래스 안으로 싸는 것이다.
이런 식으로 선언된 변수는 힙에 할당되고, 이들이 해제될 때 가비지 컬렉션이 발생하도록 한다. 이들은 기본적인 원시 변수보다 더 많은 메모리를 차지한다.
(역주: 박싱에 대한 그림 참조)

기본적인 박싱 상황을 생각해보자. Debug.Log(object)함수를 사용할 때, 매개변수는 object이다. 
이는 어떤 변수의 객체도 함수로 전달할 수 있다는 의미이다. 그러나 정수형을 전달할 때, 컴파일러는 정수형을 오브젝트 안으로 넣어 버린다. 이렇게 처리되는 것을 예상함으로인해 박싱을 최대한 적절하게 사용하는 것이 가능하다.



1
2
3
4
5
void PrintingManyTimes(int n){
    object obj = n;
    for(int i = 0;i<500;i++)
        Debug.Log(obj);
}
이 상황에서 컴파일러는 매번 Debug.Log를 호출할 때마다 박싱을 수행하는 것이 필요가 없다. 박싱은 한번 일어나고 500번 호출될 뿐이다.






Static
정적

우리는 이제 튜토리얼의 마지막 부분으로 가고 있다. 한번 더 말하지만, 당신이 C에 대한 배경지식을 가지고 있다면, 당신이 static(정적)에 대해서 아는 것은 잊어 버려라. 
여기서의 static의 사용은 다르다.


.NET의 정적 변수는 High Frequency Heap이라고 불리는 Heap의 특별한 공간에 저장된다.

(역주: High Frequency Heap에 대한 추가 설명)
정적 변수가 참조타입이든 값타입이든 상관없이 모든 정적 변수는 힙에 저장된다.
얼마나 많이 인스턴스가 만들어졌는지 상관없이 통틀어서 오직 하나의 슬롯(공간)만이 있다.
(그렇지만 그 공간을 채우기 위해 인스턴스를 만들필요는 없다.)
이 힙은 "High frequency heap"으로 알려져 있으며, 이 힙은 보통의 가비지 컬렉션되는 힙으로 부터 분리되어 있다. 이는 어플리케이션 도메인당 하나씩 존재한다.


앞에서 언급한바와 같이, 우리는 튜토리얼에서 종종 정적변수를 만나며, 초심자는 단지 쉽게 사용할 수 있기를 기도한다. 점잖은 교사는 종종 정적변수의 이익과 위험에 대해서 충분히 설명하는데 시간을 쓰지 않는다. 우리는 이제 정적 객체가 무엇인지, 어떻게 사용하는지, 어디에 그것을 사용하고, 사용하지 않는지에 대해서 명확히 하려고 한다.

여기 정적 변수의 개념을 소개한 빠른 비디오 영상이 있다.(영어)


Static classes
정적 클래스


정적 클래스는 객체 인스턴스를 가지지 않는 클래스이다. 당신은 정적 클래스의 객체 인스턴스를 만들 수 없을 것이다.

그 결과로, 오직 하나의 인스턴스를 가진다. 방금 내가 "그것의 어떤 인스턴스도 만들 수 없다"고 말한 것때문에 혼란스러울수도 있다.
프로그램은 Heap에 이 정적객체를위해 메모리를 할당할 것이다. 그리고 유저의 정적객체에 대한 어떤 인스턴스화도 허용하지 않을 것이다.

정적 객체는 그들의 첫번째 호출에서 생성되며, 프로그램이 끝날 때 파괴될 것이다.
(혹은 크래쉬가 일어나거나..)


1
2
3
4
public static class GameManager{
    public static int score; 
    public static float amountOfTime;
}

당신은 GameManager타입의 객체를 선언할 수 없을 것이다.
GameManager GM = new GameManager();
이는 에러를 리턴할 것이다.


우리는 간단하게 다음의 명령어를 통해서 정적 클래스의 멤버에 접근할 수 있을 것이다.
GameManager.score = 10;
이는 게임안에서 언제 어디서나 가능하다. 정적 클래스 또는 멤버는 모든 파일안에서 접근가능하고 수명또한 전체 프로그램의 수명과 같다.


An example for using static class
정적 클래스 사용 예시

새로운 씬을 불러올 때, 이전 씬의 모든 변수는 파괴된다. 만약 예를들어 score같은 변수를 보존하기를 원한다면 다음과 같은 해야한다.
DontDestroyOnLoad(score);
정적 클래스의 유용한 장점은 그들의 수명에 있다. 정적 클래스는 죽지 않기때문에, 만약 씬을 불러올 때 score변수가 살아남기를 원한다면, 우리는 간단하게 정적 클래스를 사용할 수 있다. 레벨의 끝부분에 저장되기 원하는 값을 정적 멤버에 저장하고, 새로운 씬을 시작할 때 그 값을 복원한다.

if(newLevel){
    GameManager.score = tempScore;
    Application.LoadLevel(nextLevel);
}

그리고 새로운 레벨에 대한 스크립트에서:

1
2
3
void Start(){
    scoreTemp = gameManager.score;
}

여기서 작은 트릭은, 끝날때까지 GamaManager.score를 사용하지 않는 것이다.

한 레벨당 최대 200포인트를 얻을수 있는 레벨이 10개가 있는 게임을 생각해보자.

한 유저는 한번에 게임을 끝냈다. 그리고 2000포인트를 얻었다.

다른 유저는 여러번 플레이해서 게임을 끝냈고, 5000포인트를 얻었다.

두번째 플레이어는 좋지 않을 뿐만 아니라 더 많은 포인트를 얻었다. 왜냐하면 그는 각 레벨을 여러번 시도했고 그 포인트가 축적되었다. 만약 다음 레벨을 불러올 때 정적변수에 포인트를 전달한다면, 레벨을 재시도할때는 반드시 축적된 포인트를 취소해야 한다.

게임을 나가는 동안, 정적 변수의 데이터는 저장공간으로 보내질 수 있다.


정적 클래스와 정적 변수는 프로그램 실행중에 그들이 처음으로 맞닥뜨릴때 할당된다.
당신이 실제로 A 정적 클래스의 변수나 함수에 한번도 접근하지 않는다면 A 정적 클래스는 절대 만들어 지지 않는다.



출처: http://unityindepth.tistory.com/12 [UNITY IN DEPTH]

반응형
반응형

원문이 약간 길어 3~4개로 나눠어서 올릴 예정입니다. 
해당 글 원문은 http://unitygems.com/memorymanagement/에서 보실수 있습니다.
번역중에 이상한 부분은 댓글 남겨주시면 확인 후에 수정하겠습니다.



참조형식과 힙


참조형식 변수는 참조형으로 메모리에 저장된 객체를 말한다. 새로운 클래스 인스턴스를 만들 때,
이들의 데이터 위치의 참조변수를 만들뿐만 아니라 데이터도 힙에 저장된다.
이 참조는 값형식으로 객체의 주소값을 가지는 변수이다.(참조변수) 클래스의 인스턴스를 만들기 위해서 new 키워드를 사용하는데, 이는 OS에게 객체의 타입에 맞는 메모리 공간과 객체를 초기화 하는데 필요한 코드를 수행하도록 요청한다. 밑에 Dog타입의 객체의 예시를 보자.

(역주: 위에서 말한 참조형식 변수는 Class, Object, String같은 클래스를 의미한다. 이는 힙에 저장되며
참조변수는 객체의 주소값을 의미하는데, 이는 스택에 저장된다. 
참조 형식 변수와 참조변수가 햇갈리지 않도록 주의할 것)



1
2
3
4
5
6
7
8
public class Dog{
     public string name { get; set;}
     public int age { get; set;}
     public Dog(string s, int n){
           name = s;
           age = n;
    }                         
}
01
02
03
04
05
06
07
08
09
10
11
public class Test : MonoBehaviour {
    Dog dog1 = new Dog("Rufus",10);
    Dog dog2 = new Dog("Brutus",8);
 
    void Update (){                                                 
        if(Input.GetKeyDown(KeyCode.Alpha1))
            print ("The dog1 is named "+dog1.name+" and is "+dog1.age);
        if(Input.GetKeyDown(KeyCode.Alpha2))
             print ("The dog2 is named "+dog2.name+" and is "+dog2.age);
    }
}

dog1과 dog2 변수는 메모리안에서 이들 객체와 연관된 데이터가 저장된 위치를 참조한다.



밑에 Instantiate 함수와 관련된 행동을 봐라. 
우리는 3D모델이 포함된 Enemy 프리팹과 약간의 컴포넌트와 스크립트를 가지고 있다고 생각하자.

01
02
03
04
05
06
07
08
09
10
11
using UnityEngine;
using System.Collections;
 
public class Test : MonoBehaviour{
    public GameObject enemyPrefab;
 
    void Update(){
        if(Input.GeyKeyDown(KeyCode.Space))
               Instantiate(enemyPrefab,new Vector3(0,0,0), Quaternion.identity);
    }
}

우리는 새로운 enemyPrefab의 인스턴스를 만들었고, 그것은 사용되고 있다. 우리는 새로 생성한 enemyPrefab의 인스턴스를 참조하지 않고 있기 때문에, 만약 인스턴스에 접근하기 원한다면 문제가 생길 것이다. 우리가 새롭게 생성한 객체에 어떤 행동을 적용하길 원한다면 우리는 그 객체를 참조하는 변수를 만들어야 한다.

1
2
3
4
5
6
7
void Update(){
    if(Input.GeyKeyDown(KeyCode.Space))
       GameObject enemyRef = Instantiate(enemyPrefab, new Vector3(0,0,0), Quaternion.identity);
        enemyRef.AddComponent(Rigidbody);
        Destroy(enemyRef);
     }
}

예를 들어, enemyRef는 선언되었고, 새로 생성된 객체의 주소값을 할당받았다. enemyRef는 이제 그 객체를 참조한다. 어떻게 우리가 Componet를 추가하는지 봐라. 이 변수는 객체의 위치를 알기때문에 객체의 public변수에 접근하거나 추가하는 것이 가능하다. 마지막 줄을 보면 객체가 파괴되는데 이런 방식으로 거의 코드를 짜지 않지만, 이것은 단지 예시를 위한 목적이다.

참조변수는 오직 if문{ } 안에서만 생존하는 automatic 변수이다.
그 밖에서는 참조변수는 더 이상 존재하지 않으며, enemy 객체를 다시 찾기위해서는
우리는 다음과 같은 방법을 사용할 수도 있다.
GameObject enemyRefAgain = GameObject.Find(“Enemy”);

참조형식 변수는 힙공간에 저장된다. 그들은 다음과 같다. 
(역주: 위에서도 말했지만 참조형식 변수의 주소값을 가지는 참조변수는 스택에 저장된다.)

  • class   
  • object 
  • string 
    (string이 변수처럼 보일지 몰라도, 사실은 Class String의 실제 객체이다.)
값형식 변수와 참조형식 변수의 주된 차이는 데이터가 저장된 곳을 가리키는 참조변수의 추가이다.
참조변수는 우리가 데이터를 찾기전에 첫번째로 접근해야한다.
이것은 클래스가 직접적으로 바로 접근할 수 있는 구조체보다 느리게 만든다.


다른 주요 문제는 다음과 같은 연산이다.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
using UnityEngine;
using System.Collections;
 
public class Memory : MonoBehaviour {
    DogC dogC1 = new DogC();
    DogC dogC2 = new DogC();
    int number1;
    int number2;
 
    void Update () {
        if(Input.GetKeyDown(KeyCode.Space)){
           number2=number1 = 10;
           number1 = 20;
           print (number1+" "+number2);
           dogC1.name = "Rufus";
           dogC1.age = 12;
           dogC2 = dogC1;
           dogC1.name = "Brutus";
           print (dogC1.name+" "+dogC1.age+" "+dogC2.name+" "+dogC2.age);
        }
    }
}

우리는 하나가 다른 하나를 수정하지 못한다는 것을 보았을 때 두개의 number변수는 독립적이라는 것을 알게 될 것이다.

반면에, 클래스에 다른 클래스를 대입할 때
하나의 수정은 다른 하나에 영향을 미친다.

이것은 dogC2 = dogC1를 할때 우리는 dogC1의 주소를 dogC2 참조 변수에 대입하기 때문이다.
메모리에서 dogC2의 원래 주소값을 잃어 버리고
dogC1또는 dogC2를 바꾸는 것은 같다.
우리는 하나의 지역을 가리키는 2개의 참조변수를 가지고 있다.


수명과 범위 그리고 가비지 컬렉션

참조형식 변수인 경우에, 수명을 가비지 컬렉터(GC)가 담당하기 때문에 불분명하다.
C와 C++의 옛시절로 돌아가보면, 동적으로 할당된 변수가 메모리로부터 제거되는 것은 프로그래머의 의무였다. 
(C에서는 Free(), C++에서는 ~(파괴자)에서 담당한)


C#에서, 가비지 컬렉터는 변수가 아직 사용되고 있는지, 그것(실행코드안에 있는)을 참조하고 있는 것이 있는지 없는지 확인할 것이다. 그리고는 자유롭게 사용가능한 메모리 지역을 표시할 것이다.
COM인터페이스 또는 다른 레퍼런스 카운팅 시스템에 익숙한 프로그래머는 종종 가비지 컬렉션의 이상한 개념을 발견한다. 왜냐하면 레퍼런스 카운팅 시스템에서 서로를 참조하는 두 객체를 가지는 것은
(부모가 자식을 참조하고, 자식이 부모를 참조하는것 같은) 메모리가 절대 해제되지 않을 수 있기 때문이다. 가비지 컬렉터는 이러한 제약을 가지지 않는다.


가비지 컬렉터는 큰 주제이고, 유니티는 콜렉션 프로세스의 수행에 관한 많은 특이점을 가지고 있다. - 만약 당신이 전통적인 C#에 익숙하다면 기본적으로 GC는 짧은 수명을 가지는 오브젝트에 대해 처리비용이 싸야된다고 예상하겠지만, 유니티에서는 그렇지 않다.

메모리 블럭이 가득찰 때마다, 시스템은 힙에 할당된 모든 객체에 대한 도달가능성(객체에 대한 참조가 있는지 없는지)을 확인해야 한다. 이것은 그들이 다른 코드로 부터 접근되고 있는지 혹은 실행되고 있는지를 확인하는 것을 의미한다. 만약 그들이 실행되고 있거나 접근되어지고 있다면 메모리에 유지될 것이며, 그렇지 않으면 해제가 될 것이다.
일반적인 .Net 가비지 컬렉션은 제너레이션(세대)안에서 발생한다. 이는 도달가능성 테스트와 관련된 처리를 최소화하도록 하는데, 새롭게 만들어진 객체(1세대)를 먼저 검사하고, 그래도 여전히 메모리가 부족하다면 오래된 객체(2세대, 3세대)를 검사한다.
유니티는 항상 모든 객체를 검사하고 그래서 가비지 컬렉션은 주된 과부하 요소이며,
일반적으로 .NET 언어에서 예상되는 것보다 훨씬 크다.

접근성 테스트는 가비지 컬렉션 뒤에서 작용하는 마법같은 것이다. - 
이는 컬렉터가 현재 어떤 코드가 실행되고 있는지 혹은 실행될 것인지를 알아내도록 한다. 
왜냐하면 컬렉션의 후보가 되는 객체를 참조하는, 라이브 참조변수가 있을 수도 있기 때문이다.
이는 함수안에서 클로저의 사용을 포함한다. - 그것은 매우 복잡하고, 매우 강력하며, 그러나 게임을 빠르게 하는 방법은 아니다.
클로저는 지역 변수에 대한 참조 또는 함수 내부 안에 정의된 익명의 함수안의 파라미터에 의해 만들어 진다. 익명의 함수가 호출될 때, 그 루틴이 실행했을 때와 동일하게 하기 위해서 변수의 값이 유지된다 

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
List<Action> actionsToPerformLater = new List<Action>();
 
void DisplayAMessage(string message)
{
      var t = System.DateTime.Now;
      actionsToPerformLater.Add(()=>{
          Debug.Log(message + " @ " + t.ToString());
      });
}
 
void SomeOtherFunction()
{
      DisplayAMessage("Hello");
      DisplayAMessage("World");
      SomeFunctionThatTakesAWhile();
      DisplayAMessage("From Unity Gems");
 
      foreach(var a in actionsToPerformLater)
      {
           a();
      }
 
}

코드내부에서 DisplayAMessage 함수를 호출할 때 마다, 전달 받은 message와 현재 시스템 타임(t)에 대한 클로저를 생성한다. 우리는 끝에 있는 foreach 루프를 실행할 때마다.
디버그 메시지는 전에 리스트에 추가하기 위해 함수를 호출했을 때 전달된 파라미터와 함께 기록될 것이다.
클로저는 매우 강력한 도구이며 그것은 다음 글에 대한 주제가 될 것이다.

가비지 컬렉션은 시스템이 연산을 위해 메모리가 충분치 않다고 판단될 때 일어난다. 이는 버려진 객체가 상당한 기간 동안 수거되지 않을 수도 있다는 것을 의미한다. - 그러므로 객체가 더 이상 필요하지 않을 때 명시적으로 외부의 연결을 끊는 것 또는 외부 리소스를 해제하는 것은 흔히 있는 일이다.
(역주: 언제 가비지 컬렉션이 발동될지 모르기 때문에 수동으로 메모리를 해제한다는 의미이다.)

 명시적으로 내부의 객체(당신의 프로젝트에 있는)를 해제할 필요는 없다. 그러나 스트림(파일)과 시스템에 영향을 주는 데이터베이스 연결을 가지는 프로젝트의 외부에서 사용할 경우에는 필요하다.
예를 들어 파일을 닫지 않는 것은 다음에 파일에 접근할 때 실패하게 만든다.
파일은 여전히 버려진 객체에 의해 열려있기 때문이다.
가비지 컬렉션은 매우 비싼 연산이며, 게임에 영향이 없을 때 - 레벨을 로드할 때, 플레이어가 일시정지 메뉴로 들어갔을 때,  플레이어가 알지 못하는 많은 경우 - 수동적인 가비지 컬렉션의 가동은 적절하다. 
System.GC.Collect(); 명령어를 통해서 수동적으로 가비지 컬렉션을 작동시킬 수 있다.

참조타입 변수에 관해서, GameObject.Find()를 사용하는 것은 어떤 스크립트에서든 객체에 대한 참조를 리턴한다. 이처럼 객체를 찾기 위한 적절한 행동을 한다면, 프로그램안에서 어디서든 접근이 가능하다. 
게임 플레이 동안 클래스의 인스턴스를 찾는 방법에 대한 좀 더 자세한 설명은 GetComponent 튜토리얼에 나와있다.




출처: http://unityindepth.tistory.com/10 [UNITY IN DEPTH]

반응형
반응형


원문이 약간 길어 3~4개로 나눠어서 올릴 예정입니다.
해당 글 원문 http://unitygems.com/memorymanagement/에서 보실수 있습니다.
번역중에 이상한 부분은 댓글 남겨주시면 확인 후에 수정하겠습니다.



만약 당신의 게임이 실행되고 있는 컴퓨터나 장치의 

메모리에서 떤 일이 일어나는지 궁금했던 적이 있다면 

이 글은 당신에게  좀 더 효율적인 게임을 만들고 클래스와

함수의 메모리사용을 최적화하는데 도움을 줄 것이다.

이번 글에서는
  • 메모리 구역
  • 값타입과 스택
  • 참조타입과 힙
  • 구조체와 클래스
  • 참조타입을 만드는 것
  • 정적 클래스
  • 정적 변수
  • 정적 함수
  • 힙 파편화, 오브젝트 풀링, 가비지 컬렉션
이 튜토리얼은 유니티 개발을 위해 C#을 사용하게된 C/C++ 프로그래머에게 특별히 유용하다. C#은 처음에는 혼란과 마법같은 방식으로 메모리를 다루고, 그것은 그것 자체의 위험과 특이한 방식을 가지고 있다.
유니티 프로그래밍을 시작할 때 그리고 일반적인 프로그래밍을 시작할 때 변수 타입의 용도에 많이들  골머리를 앓게 될 것이다. 예를 들어, 왜 어떤때는 변수들이 업데이트되고 다른 변수들은 똑같은 값으로 유지되며, 왜 내가 여전히 필요로 하는 변수는 사라지는지 말이다.

또 다른 초심자의 공통적인 문제는 정적 변수의 사용이다. 인터넷의 다양한 튜토리얼에서 정적 변수를 종종 만나지만, 강사는 시간을 들여 명확하게 설명을 해주지 않으며, 오직 "변수에 대한 쉬운 접근"이라고만 말한다.

문제는 정적 변수(다르게 클래스 변수라고도 불리는)는 쉬운 주제가 아니라는 것이다.

몇몇은 심지어 정적 변수로 작성된 것은 다른 타입으로 처리가 될 수 있다며, 그것을 사용하는 것을 피하라고 한다. 우리는 그것이 진실인지, 그것이 어쩔수 없는 최고의 해법이 아닌지에 대해 알아 볼 것이다.


시작하기에 앞서, 당신이 C 프로그래밍에 대한 지식을 가지고 있거나 객체지향 프로그래밍에 대해 잘 알지 못한다면, 당신이 메모리 관리와 저장 키워드에 대해 알고 있는 것은 OOP(객체지향 프로그래밍)에서는 전체적으로 유사하지 않다는 것을 명심해라.
C#의 C를 연관시키지 않으려고 노력해라.
이 튜토리얼에서, 우리는 각각의 메모리 지역을 다룰것이며, 값형식, 참조형식, 동적메모리 할당 그리고 정적 변수도 다룰것이다.


메모리 지역

프로그램이 시작할 때, 운영체제는 메모리의 일정부분을 프로그램에 할당한다.  할당된 메모리에는 4개의 메모리 지역이 존재하는데, 콜 스택, 힙, 레지스터 그리고 정적 메모리 지역이다.

C#언어 개발자는 친절하게도 4개의 메모리 지역을 2개로 좁혔는데, 2개의 지역이 바로 
스택과 힙 영역이다. 스택은 순서가 있고 빠르지만 제한적이다. 힙은 무작위이고, 크지만 느리다.

프로그래머는 각각의 변수에 어느 메모리 영역을 사용할 것인지를 변수를 사용하는 목적과 변수의 생명주기를 고려하여 결정할 수 있다.

메모리 할당을 위한 3개의 키워드가 있다. 바로 auto, static(우리가 이제 막 다룰려고 하는 것), extern이다.
(C언어에서는 여기에 register라는 키워드가 추가되어 총 4개의 키워드가 존재한다.)

extern 키워드는 아마 본적이 있을 것이다. 그것은 당신의 프로젝트에 있는 다른 코드에 선언된 변수를 선언한다는 의미이다. 
예를 들면 .NET 언어가 아닌 다른 언어로 쓰여진 DLL파일에 포함된 변수가 있다.


만약 당신이 C 프로그래머라면, extern의 의미는 C#과 미묘하게 다르다는 것을 알아야 한다. C#에서는 managed memory에 할당되지 않는 변수를 참조한다.

*(역주: managed memory와 unmanaged memory에 대해)
이것은 모두 똑같은 물리적 메모리이다. 차이는 단지 누가 그것을 제어하는가이다.
마이크로소프트는 managed memory를 Garbage Collector(GC)에 의해서 정리된다고 정의한다. 예를 들어, 정기적으로 물리적인 메모리의 어떤 부분이 사용되고 사용되지 않는지를 알아내는 프로세스가 여기에 해당된다.

unmanaged memory는 그밖의 다른 것에 의해서 정리된다.
예를 들어 당신의 프로그램자체내에서 또는 운영체제가 여기에 해당된다.
1
extern int number;

컴파일러는 number를 메모리에 할당하지 않는다. 그리고 변수는 다른 어느 곳에서 발견 될 것이라고
예상한다. 우리는 더이상 이 주제에 대해서 연연에 할 필요가 없을 것이다.
이 링크에서 더 많은 정보를 찾을 수 있을 것이다.


값형식과 스택

auto는 automatic 변수를 나타낸다. 하지만 C#에서 더이상 사용되지 않기 때문에 당신은 이 키워드를
한번도 보지 못했을지도 모른다.

1
2
int variable = 10;
auto int variable = 10;

두 라인은 정확하게 같다.

automatic 변수의 특징은 콜 스택에 할당된다는 것이다. 
스택에서는 오직 맨윗부분에 변수를 추가하거나 제거할 수 있고, 맨 아랫 부분에는 변수를 추가하거나 삭제를 할 수가 없다.  이것은 접시 한무더기와 비슷하다. 밑에 그림을 봐라


가장 위에 있는 스택을 알기위해서 프로그램은 스택 포인터를 사용한다. 스택 포인터는 CPU안에 있으며, 위 그림에서 보이는 검은색 화살표에 의해서 나타나는 현재 위치를 추적한다. 변수를 추가할 때 마다 스택 포인터는 증가된다.(또는 자신이 사용하는 OS의 메모리 관리에 따라 감소될수도 있다.)

스택은 함수 호출을 위해 사용되며, 전체 프로그램은 다른 함수들을 차례로 호출하는 Update함수를 호출하는 메인 함수이다. 
그래서 당신의 스크립트안에 있는 변수는 다른 어떤 것으로 정의되어 있지않다면 stack에 저장되는
automatic 변수이다.

또한 스택 프레임(Stack frame)에 대한 개념을 만날 것이다. 간단히 말해서 이것은 현재 실행된 함수에 의해 지역적으로 할당된 변수들의 집합이다.(또한 return 주소값을 포함하는데, 이것은 해당 함수가 리턴되고 난후에 바로 다음에 실행될 명령 주소를 뜻한다.)
컴파일러는 스택 프레임의 끝을 가리키는 포인터를 유지하며, 실제로는 당신의 로컬 변수가 차지하는 메모리를 찾기위해 이 포인터로부터 -offest을 사용한다.
이것의 실제적인 결말은 함수가 리턴되고 난 후이다. 그리고 그 함수의 로컬 변수가 사용된 공간들중 어느 것도 사용되거나 할당되지 않는다. (다음 함수 호출을 위한 여분의 공간을 제외하고는)

만약 재귀 프로그램(함수가 자기자신을 호출하는) 작성한다면, 어쩌면 스택의 공간을 모두 써버릴수도 있다.
각 함수 호출은 모든 지역변수와 리턴 주소값이 스택에 존재하기 위한 공간을 요청한다.
만약 당신의 코드에 그들 스스로 멈추지 않고 실행되는 루틴이 존재한다면, 아마 스택 오버플로우 메시지를 보게 될 것이다.

그래서 생명주기와 automatic 변수의 범위는 서로 관련되어 있다. 변수는 중괄호{와 }안에서 존재한다.
이것은 설명이 필요하다.


01
02
03
04
05
06
07
08
09
10
11
void Update(){
    int number = 10;
    Fct(number);
    print(number);
}
 
void Fct(int n){
    int inside=20;
    n = n + inside;
    print(n);
}


업데이트는 호출되고 첫번째 명령어는 변수 선언이다. 스택포인터는  밀어올려지고, number의 값(10)은  메모리 장소에 위치한다.
두번째 명령어는 함수 호출이다. 함수를 호출할 때 프로그램은 멈추고, 함수의 주소로 이동하며,
프로그램의 원래의 위치로 되돌아 가기위한 약간의 데이터는 스택에 전달된다.
매개변수는 원래의 값을 복사하여 스택에 저장된다.
변수 number는 Fct함수에서 사용되기위해 스택에 저장되지 않고 number의 값을 복사한 새로운 변수 n이 만들어져 스택에 저장된다.
제 Fct함수안에는 number 변수의 값을 가지고 있는 변수 n을 가지고 있다. 그렇다고 Fct함수에 number함수가 존재하는 것은 아니기때문에 number변수에는 접근할 수가 없는 것이다.
함수안에서 새로운 변수 inside가  선언되었다. 스택은 다음과 같은 상태이다.(그림에서는 스택의 상황을 매우 단순화 시켰다는 것을 명심하라)




함수의 끝부분에, n은 출력되고 30의 값을 가진다. 함수 내부에서 만들어진 모든 변수는 파괴되고 사라진다. 그것은 n과 inside을 포함한다. 변수가 여전이 메
모리에 존재할지라도, 그들은 간단하게 무시되고 시스템에 의해 더 이상 고려되지 않는다.

Update함수로 돌아왔을 때, 원래 값 10을 가지는 number변수를 출력하는데, Fct함수에서 사용된 것은 number변수의 복사값이기 때문이다.
스택 포인터는 n과 inside의 위치를 잃어버리는데, 만약 Update함수로 돌아와서 다시 n과 inside함수에 접근하려고 한다면 컴파일러는 그 변수는 현재 지역에 존재하지 않는다는 에러메시지를 반환할 것이다.

우리는 automatic 변수는 범위는 중괄호{, }에의해 정의 된다는 것을 보았다. 그 밖에서는, 변수는 존재하지 않고 보이지 않는다. 중괄호는 함수, if문또는 반복문의 범위를 표시할 수 있다.

변수의 수명과 범위

수명은 프로그램안에서 변수가 존재하는 시간을 의미하며, 범위는 프로그램에서 변수가 보이는 지역을 정의한다. automatic 변수의 경우에는 수명과 범위가 유사하다.

값타입의 변수는 스택에 저장된다. 그들은 다음과 같다

  • int
  • unsigned
  • float
  • double
  • char
  • struct
  • bool
  • byte
  • enum
  • long
  • short
그 당시에 존재하지 않았던 bool형을 제외한 거의 모든 타입을 C로부터 상속받았다.

automatic 변수에 대해서, 그것의 생명은 compiler에 의해 관리된다. 선언된 범위의 끝은 변수의 끝을 표시한다. 프로그래머는 메모리 지역을 해제하는데 신경쓰지 않아도 된다. 그것은 자동으로 처리된다.




(다음 글에서 계속)




반응형
반응형

DontDestroyOnLoad 사용시 주의 점 


전부터 저 함수를 자식 오브젝트에 사용하면 장면이 교체되어도 자식 포함해서 부모까지 전부 남는 건지 궁금했었는데 당장 사용하질 않아서 당시에는 그냥 넘기고 잊고 있다가 이번에 ios로 테스트하려니 켜자마자 그냥 죽길래 뭐가 문제인가 찾아보다가 이것 때문인 걸 알게 되어서 몇 자 적습니다.

 

5.3버전부터 경고를 했었나 보네요. DontDestroyOnLoad only work for root GameObjects or components on root GameObjects. 라고 뜨는데요. 말 그대로 최상위 게임오브젝트나 최상위 게임오브젝트에 붙어있는 컴포넌트에서만 작동한다는 경고 메시지입니다. 그런데 이 메시지를 저만 그런지는 모르겠으나 에디터에서는 안 띄워주고 빌드 했을 때만 띄워줘서 전혀 모르고 있다가 게임 내 콘솔 창을 만들어서 띄우고 윈도우즈에서 테스트할 때 알았습니다. 경고로만 뜨고 플레이에 전혀 문제가 없어 큰 문제 아니구나 하고 알고만 있고 넘어갔는데 ios에서는 빌드해서 보니 유니티 스플래시 뜨기도 전에 켜자마자 바로 죽네요.

 

xcode 로그가 딱 저렇게만 나오는데 놓치고 있는 부분이 있는지 모르겠지만 봐도 어디가 문제인지는 알 수가 없어서 뭐를 잘못했을까 생각하다가 문득 저 경고가 생각나서 메시지 말대로 최상위가 아닌 얘들한테 사용하는 곳을 찾아 수정하고 빌드를 해서 보니 문제없이 잘 됩니다. (그런데 며칠 전까지도 같은 코드를 사용하고 있었는데 그때는 왜 잘 됐었을까요 ?ㅁ?)

 

5.3버전 넘어가면서 내부적으로 어디가 바뀌어서 그런 건지 정확히 원인이 이것인지 이것으로 인해 사이드 이펙트가 생기는 건지는 모르겠지만, 관련이 된 것은 맞는 것 같네요. 혹시 ios 테스트하시다가 그냥 죽는 경우가 생기면 이쪽도 한 번 봐보시기 바랍니다. 조금 불만인 것은 변경 점이나 이런 문제가 생길 수 있으면 레퍼런스에 적어 놓았으면 좋을 텐데 저 경고에 해당하는 부분은 내용이 아직 없네요. 함수도 UnityEngine.Object 형을 인자로 받고 있어서 넣을 수 있으면 다 되는 줄 알고 머티리얼에 사용하고 있었는데 원래라면 사용하면 안 되는 거였군요. 저처럼 사용자가 실수할 수 있는 부분인데 UnityEngine.GameObject나 UnityEngine.Component 형만 넣을 수 있게 했으면 더 좋지 않았을까 합니다.

 

여담으로 파괴되지 않아야 하는 해당 오브젝트가 자식인 경우에는 해당 오브젝트의 transform.root.gameObject를 인자로 넣어서 함수를 실행시키면 자신을 포함하는 최상위 오브젝트가 효과를 받아 밑으로 있는 자식까지 파괴되지 않습니다.

 

참고 - http://answers.unity3d.com/questions/996282/wierd-console-errors.html


출처 : https://fetchinist.com/blogs/?p=2061





반응형
반응형

리소스 로딩하기



유니티IDE의 Assets 안 에는 수 많은 객체를 정의해 둘 수 있습니다. 이미지나 사운드를 비롯하여 스크립트와 프리펩까지 모두 들어갑니다.

이러한 에셋은 컴파일타임에 특정 게임오브젝트와 바인딩되지만 런타임에 스크립트를 통해 사용하려면 리소스를 로딩하는 과정을 거쳐야 합니다.


 

특별한 이름의 폴더top

유니티의 Assets 폴더 밑에는 서브폴더를 자유롭게 만들 수 있지만 미리 지정된 이름으로 폴더를 만들면 특별한 작동을 합니다.

http://wiki.unity3d.com/index.php/Special_Folder_Names_in_your_Assets_Folder

위 목록에서 다양한 특수 폴더명을 볼 수 있습니다. 대소문자는 구별하지 않습니다. 이 중 이번 포스팅에서 눈여겨 볼 폴더명은 Resources 입니다.

Assets/resources

에셋폴더 밑에 일단 resoureces 라는 이름으로 폴더를 만들면 이 폴더를 기준으로 여러 에셋을 런타임에 로딩하여 사용할 수 있게 됩니다.

예를들어 프리펩을 만들어 로딩하고 싶다면 다음과 같은 구조로 서브폴더를 만들어봅시다.

Screenshot_1

resources/block 폴더 안에 들어있는 R1~R5까지의 프리펩을 런타임에 불러오려면 어찌 해야할까요?

 

Resources.Load와 Resources.LoadAlltop

각각의 자원을 부르는 방법은 Resources.Load를 사용하는 것입니다.

GameObject temp1 = Resources.Load( "block/R1" as GameObject;
GameObject temp2 = Resources.Load( "block/R2" as GameObject;
GameObject temp3 = Resources.Load( "block/R3" as GameObject;
GameObject temp4 = Resources.Load( "block/R4" as GameObject;
GameObject temp5 = Resources.Load( "block/R5" as GameObject;

귀찮기 때문에 그저 서브폴더만 넘겨주면 전부 처리해주는 LoadAll을 제공합니다.

object[] temp = Resources.LoadAll( "block" );

LoadAll의 경우는 as GameObject[] 로 형 변환할 수 없습니다. 하지만 개별 요소에 대해서는 형변환할 수 있으므로 큰 문제는 없습니다.

object[] temp = Resources.LoadAll( "block" );
 
forint i = 0 ; i < temp.Length ; i++ ){
  GameObject go = temp[i] as GameObject;
  //go...
}

 

제네릭컬렉션으로 캐쉬하기top

일단 로딩한 후에는 해당 객체를 반복해서 사용하는 경우가 많습니다. 예를 들어 R1(녹색큐브)를 10개 복제한다고 하면 GameObject.Instantiate를 사용하기 위해 개별 객체를 인식해두지 않으면 안됩니다.

로딩된 배열을 해시테이블(HashTable)로 잡아두면 편리하게 사용할 수 있지만 제네릭 타입으로 정리하면 형변환도 필요없게 되므로 더욱 안정적이고 간단히 사용할 수 있을 것 입니다.

일단 제네릭컬렉션을 사용하려면 네임스페이스를 불러와야겠죠.

using System.Collections.Generic;

다음은 RSC클래스에 간단히 정적 필드로 캐쉬 공간을 만들어 둡시다.

class RSC {
  static private Dictionary<string,GameObject> _cache =
      new Dictionary<string,GameObject>();

 

로딩하기와 캐쉬에서 꺼내기top

그럼 이제 서브폴더를 로딩하는 함수를 일반화하여 캐쉬에 곧장 넣어버리죠.

static public void Load( string subfolder ){
  object[] t0 = Resources.LoadAll( subfolder );
  forint i = 0 ; i < t0.Length ; i++ ){
    GameObject t1 = (GameObject)(t0[i]);
    _cache[t1.name] = t1;
  }
 }
 
//서브폴더 로딩하기
RSC.Load( "block" );

그럼 그 뒤로는 캐쉬에서 직접 꺼내가던가 아니면 인스턴스를 반환하는 함수를 생각해 볼 수 있습니다. 우선 간단히 가져가는 함수는 다음과 같습니다.

static public GameObject Get( string key ){
  return _cache[key];
}
 
// R1을 가져오자
GameObject temp = RSC.Get( "R1" );

인스턴스를 직접 반환받고 싶다면 다음과 같은 함수로 편리하게 사용합시다.

static public GameObject Instance( string key, string name,
    float x, float y, float z ){
  GameObject t0 = (GameObject)(GameObject.Instantiate(
      _cache[key], new Vector3( x, y, z ), Quaternion.identity
    ));
  t0.name = name;
  return t0;
}
 
//R1으로부터 R1Clone을 가져오자.
GameObject temp = RSC.Instance( "R1""R1Clone", 0, 0, 0 );

 

캐쉬를 메모리에서 삭제top

메모리 절약을 위해 다양한 리소스를 로딩하려면 필요없는 리소스는 다시 삭제해줘야합니다. params인자를 통해 간단히 삭제하는 함수를 구현합시다.

static public void Remove( params string[] arg ){
  forint i = 0 ; i < arg.Length ; i++ ){
    string key = arg[i];
    _cache.Remove( key );
  }
}
 
//R1~R3까지 삭제
 
RSC.Remove( "R1""R2""R3" );

이를 통해 간단히 메모리에서 몇 개라도 한꺼번에 삭제할 수 있게 되었습니다[1. 참고로 GameObject의 경우 Resources.UnloadAsset 으로 삭제되지 않고 더 이상 사용되지 않으면 GC가 될 뿐입니다.]

 

간단한 샘플top

이상의 내용을 바탕으로 간단히 큐브를 화면에 채워봅시다.

Load ("block");
forint i = 0 ; i < 10 ; i++ ){
  forint j = 0 ; j < 10 ; j++ ){
    Instance( "R" + Random.Range(1,6), i+":"+j, i-5, j-5, 0 );
  }
}
Remove( "R1""R2" );

다음과 같은 화면이 된다면 정상입니다.

Screenshot_2

 

결론top

이번 포스팅에서는 간단히 런타임 에셋로딩을 알아봤습니다. 실제 소스는 거의 GameObject 로딩 중심으로 가볍게 설명했으나 이를 바탕으로 다양한 에셋로더를 구현하는 것은 또 다른 기회에 해보겠습니다(과연..)

종합 소스는 아래에 있습니다만, resources/block 폴더를 만들고 R1~R5까지의 색깔별 큐브를 프리펩으로 만들지 않으면 실행되지 않습니다.



using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class bside002 : MonoBehaviour {
	
	static private Dictionary<string,GameObject> _cache = new Dictionary<string,GameObject>();
	
	static public void Load( string subfolder ){
		object[] t0 = Resources.LoadAll( subfolder );
		for( int i = 0 ; i < t0.Length ; i++ ){
			GameObject t1 = (GameObject)(t0[i]);
			_cache[t1.name] = t1;
		}
	}
	static public GameObject Get( string key ){
		return _cache[key];
	}
	static public GameObject Instance( string key, string name, float x, float y, float z ){
		GameObject t0 = (GameObject)(GameObject.Instantiate( _cache[key], new Vector3( x, y, z ), Quaternion.identity ));
		t0.name = name;
		return t0;
	}
	static public void Remove( params string[] arg ){
		for( int i = 0 ; i < arg.Length ; i++ ){
			string key = arg[i];
			_cache.Remove( key );
		}
	}

	void Start () {
		Load ("block");
		for( int i = 0 ; i < 10 ; i++ ){
			for( int j = 0 ; j < 10 ; j++ ){
				Instance( "R" + Random.Range( 1, 6 ), i + "_" + j, i - 5, j - 5, 0 );
			}
		}
		Remove( "R1", "R2" );
	}
}




http://www.bsidesoft.com/?p=215





반응형
반응형

아래 내용을 구글링 하다가 알게된 것인데

유니티를 지우지 않고 유니티를 끈 상태에서 C:ProgramData/Unity 경로에 폴더를 제외한 

ulf  및  기타 라이센스 파일들을 지운다 (약 3개 정도 인가 있음)

그런 후 다시 유니티를 키면 제인증 하는 화면이 나타남





Give the steps below a try. The files may be hidden so ensure you show hidden files.

  1. Uninstall Unity.

  2. Delete This file:

  • WinXP/Vista: C:/Documents and Settings/All Users/Application Data/Pace Anti-Piracy/License Files

  • Win7: C:/ProgramData/PACE Anti-Piracy/License Files

  • Mac: HD/Library/Application Support/PACE Anti-Piracy/License Files

  • Windows: C:ProgramData/Unity

  • Mac: Library/Application Support/Unity

    1. Restart

    2. Download Unity again from the website, since your original install might be corrupt.http://unity3d.com/unity/download

    3. Reinstall and test




http://answers.unity3d.com/questions/516259/license-information-is-invalid-.html

반응형
반응형

check Player setting => Resolution => (Run In Background) 


OnApplicationPause/OnApplicationFocus has been all over the place for me. In the Editor, it seems to be different on PC and MAC. On MAC it seems to get called on press Play. On PC it doesn't. On iOS it seems like one of them doesn't work. Can somebody tell me the different scenarios that they work?

And can somebody explain what they mean in each scenario? Editor? Standalone? iOS? Android? (like does OnApplicationFocus fire when you minimize or when you just click on another window in the Editor)

I know about the documentation, I read the documentation. But the behavior doesn't match it. It's ridiculous. If somebody has figured it out I'd greatly appreciate it.






Since this UnityAnswer is one of the first (if not the first) to be returned on a search for OnApplicationFocus/Pause & iOS, an important update in Unity 4.6.1 has changed the behavior for iOS.

As of 4.6.1, both OnApplicationFocus and OnApplicationPause will be called in iOS.

The order is :

App initially starts:

  • OnApplicationFocus(true) is called

App is soft closed:

  • OnApplicationFocus(false) is called

  • OnApplicationPause(true) is called

App is brought forward after soft closing:

  • OnApplicationPause(false) is called

  • OnApplicationFocus(true) is called

Hope that helps




http://answers.unity3d.com/questions/496290/can-somebody-explain-the-onapplicationpausefocus-s.html

반응형
반응형



*Start와 Awake와 OnEnable


기본은 유니티 매뉴얼(https://docs.unity3d.com/Manual/ExecutionOrder.html ) 과동일한 순서라고 생각하면 됨.





Awake는 게임오브젝트가 유니티 내부에서 Instantiate될때 첫 호출되고, Start는 모든 오브젝트의 Instantiate가 마무리된 후에 호출된다.




그런데, 오브젝트 풀링 등을 이용하기 시작하면서 헷갈리는 문제가 발생하기 시작함.


오브젝트 풀링은 보통 gameObject.setActive(true) 와 setActive(false)를 이용하여 필요한 오브젝트를 여러번 Instantiate 하지 않고 재활용하는게 요점인데,


여기서부터 Awake, Start, OnEnable, OnDisable 호출 순서가 점점 골치아파지기 시작하는거지.



------------------------------------


본론으로 들어가기 전에 확실하게 짚고 넘어가야 하는 부분은 이거임.


1. gameObject.SetActive(false)

2. this.enabled = false


는 완전히 다르다는 점이다!


뭐가 다른 것인가 하면





gameObject.SetActive와 this.enabled는 위 캡쳐의 1번 2번 각각에 대해 대응된다고 생각하면 됨.


게임 오브젝트 자체를 disable시키느냐, 게임오브젝트 속의 스크립트 컴포넌트 하나를 disable시키느냐의 차이라고 이해하자.


그럼 이게 Awake, Start, OnEnable과 무슨 상관일까?


-----------------------------------------


경우 1) 게임오브젝트가 Disabled 상태일때


위 스샷의 1번 체크박스를 Disable시켜둔 프리팹, 또는 씬 속의 게임오브젝트가 있다고 치자. 

예를 들어 이펙트를 씬에다 갖다놓고, disable 상태로 게임을 시작했다가, 플레이어가 적을 히트하는 등의 특정 행동에 다다르면 이펙트를 enable시켜서 재생하는 경우를 생각할 수 있겠다.


이 경우에는 게임오브젝트가 활성화되지 않은 것으로 간주, Awake가 호출되지 않는다. 즉, Awake는 게임오브젝트의 활성화 여부에 따라 호출된다.


게임 오브젝트가 애초에 비활성화 상태니, 그 하위 컴포넌트들의 초기화 함수인 Start 역시 호출되지 않는다. 


즉, 

A) 게임오브젝트를 비활성화한 상태로 게임을 시작하면, 또는

B) 프리팹 설정에서 1번 체크박스를 꺼놓은 프리팹을 게임 중에 Instantiate(prefab) 으로 생성할 경우,


Awake도, Start도, OnEnable도 모두 호출되지 않는다.


경우 2)게임오브젝트가 Enabled지만 컴포넌트는 Disabled 상태일때


위 스샷의 1번 체크박스는 켜져 있지만, 2번 체크박스는 꺼진 상태를 뜻한다.


이 경우 일단 게임오브젝트 자체는 활성화되어 있기에, Awake는 호출된다.


그러나 OnEnable() 과 Start()는 호출되지 않는다. 이 두 함수는 컴포넌트의 활성화 여부에 따라 호출된다.


게임이 돌아가는 도중에 체크박스를 켜거나 GetComponent<Component>().enabled = true 설정을 하면, 그 이후 OnEnable과 Start가 호출되게 된다.




전체적으로 재정리하면 다음 그림과 같다.








http://blog.naver.com/1mi2/220928872232 중에서



반응형
반응형

Unity를 C#으로 개발하다보면 “리플렉션”, 즉 reflection 이라는 단어를 종종 듣는다. 이는 C/C++에서 넘어온 개발자들이라면 다소 생소한 단어일 수 있다. Java는 java.lang.reflection 이라는 패키지로 reflection을 지원하고, C#에서는 System.reflection 네임스페이스를 통해서 지원한다.

Reflection의 한글 뜻은 “거울에 비친 상” 이라는 뜻이다. Language에서 “상” 이라는 의미가 뭘까 라고 떠올렸을 때 언뜻 잘 떠오르지 않는 것이 Reflection 개념에 대한 1차 Hurdle이다.

Programming Language에서 Reflection이라는 말은 Runtime 시에 Object 객체의 형태나 타입에 대한 정보를 읽어 내는 것을 말한다. C++에서도 제한적이지만 RTTI(Run-time type information) 메커니즘을 통해서 Reflection 기능을 제공하고 있다. C++ RTTI 메커니즘 중 대표적인 것이 dynamic_cast와 typeid 이다. 아래는 dynamic_cast의 예를 보자. (Refrence)

위 코드를 실행하면 “basePointer is pointing to a Derived class object” 이 출력된다.

스크린샷 2015-03-03 오전 12.18.54

즉, dynamic_cast는 부모 클래스의 포인터 변수에서 자식 Instance의 Address를 저장하고 있더라도, 실제 자식의 타입이 특정 타입이 맞는지 체크해 준다. 맞으면 해당 Instance의 address를 틀리면 NULL을 리턴한다. 사실 C++에서 Parent Class에 virtual function을 선언하여 파생 클래스들을 다룬다면 굳이 RTTI를 사용하지 않고도 Runtime시에 실행되는 Method들을 Polymorphism(다형성)의 특성을 이용하여 제어할 수는 있다.
아래의 코드는 C++ RTTI의 다른 형태인 typeid의 용례다. (Refrence)

실행 시 출력은 다음과 같다. 원 Class 이름과 다른 runtime 시의 type name이 적절하게 출력이 된다. (Class 와 이름이 정확히 일치하지 않는 이유 : Reference)

스크린샷 2015-03-03 오전 12.20.39

즉, Class Pointer들이 담고 있는 진짜 Instance들을 알아낼 때 사용 한다.

C#은 Source Code를 .Net Assembly, 즉 CIL(Common Intermediate Language) 형태로 컴파일 한 후에 .Net Runtime(CLR(Common Language Runtime))으로 실제 실행 시키는데, 이 때 CIL 안에 Meta Data 정보를 담고 있다. Unity에서는 ScriptAssemblies 폴더 아래에 보면, Assembly-CSharp.dll 등 여러개의 컴파일된 .NET Assembly 파일들이 있다. 이 .Net Assembly 안의 저장된 Meta Data를 통해서 내부 Class들의 정보들을 알아 냄으로써 Reflection 기능이 동작할 수 있게 된다. 이를 통해서 Method Invocation, Serialization, Object Dump, Database Interface 등의 작업이 가능하다. Type을 묻고, Class Descriptor를 얻은 다음에 해당 클래스의 Method와 Field Descriptor들을 통해서 세부 접근할 수 있게 되는 것이다. C++의 Reflection 기능이었던 RTTI가 단순 타입 체크 정도 수준으로 지원한 반면에, C#의 Reflection은 Instance의 Type을 체크하거나, Field 정보를 알아오는 것은 물론, Method를 실행할 수도 있고, Field나 Method를 동적으로 추가할 수도 있다. 아래는 C#의 기본적인 Reflection의 예이다.

출력은 아래와 같다.

ReflectionTest라는 Static Class가 자기 안에 있는 3개의 Int32 Field와 1개의 String Field의 정보를 Runtime 시에 Reflection 형태로 접근하는 것을 보여준다. typeof operator로 읽어서 FieldInfo Class를 통해서 GetFields, GetValue Method들을 Access하고 있다.

Unity 엔진의 C#에서도 동일하게 Reflection을 사용할 수 있다. 그러나, iOS의 경우에는 자체 저수준 가상 머신인 LLVM 환경 에서 제한적으로 Reflection을 허용한다. 그 LLVM 상에서 돌아가는 Mono Runtime에 맞도록 미리 컴파일하는 AOT(Ahead of Time) 컴파일 방식을 채용하고 있어서 Reflection 기능 중에 매우 큰 강점인 동적인 코드 생성을 할 수 없다. 즉, Reflection.Emit 같이 JIT(Just In Time) Compile되어서 동적으로 코드를 생성하여 Native Code로 변환시키는 동작을 보안상 할 수 없게 된 것이다. 또한 C# Reflection 기능을 이용하는 Linq 쿼리는 동적 코드 생성이 발생하는 지뢰밭이므로 사용은 가능하나 섣불리 사용했다가는 iOS에서 낭패를 볼 수 있다. 다만 AOT를 요구하는 iOS 플랫폼이라도 Field 타입과 Method 리스트를 받아오거나, Method를 실행시키는 등의 기본적인 Reflection 기능들은 모두 동작한다.

이 Reflection의 용도는 다양하다. Unity에서 커스텀 Inspector를 만들 수도 있다. .NET Assembly Dll 파일을 Reflection 통해 분석하면, C++에서 Access할 수 있는 Interface Class 들을 자동으로 만들 수도 있다. 전반적으로, 해당 클래스의 타입을 알고 싶을 때, 속도와 크게 관련이 없을 때는 Reflection을 이용해서 정보들을 당겨와서 이용하면 된다.

다음은 Inspector에서 선택된 Game Object에 Attach되어 있는 스크립트의 Method들을 Inspector에서 Reflection을 이용하여 알아내고, 실행할 수 있게 해주는 Custom Inspector 스크립트의 예이다. (Reference)

custom_inspector

아래 코드는 Object에 Attach 시키는 스크립트이다.

아래는 Scripts 폴더에 정의하는 스크립트이고,

마지막으로 Editors 폴더에 정의하여 실행시에 버튼을 표시하고, 클릭 시에 Method를 Invoke시키는 Reflection 구현이 포함된 클래스이다.

Reflection 기능을 잘 이용한 Asset을 만들 수도 있다. vBug는 Reflection 이용의 끝판왕 이라고 할 만한 Unity Asset Tool 이다.

다만, Reflection 코드의 Runtime이라는 특성을 보면 알 수 있듯이 실시간으로 무엇을 체크한다는 것은 효율적인 면에서 Static보다는 느리다는 것을 짐작할 수 있다. 실제로 Reflection은 속도면에서는 Direct Access하는 것보다 수천배 느리다. GetComponent를 Update() 구문에서 남발하지 않듯이 Reflection 기능들은 Performance를 요하는 부분에서는 사용하지 않거나 매우 주의해서 사용해야 한다. (Benchmark Reference)

 


출처 : http://rapapa.net/?p=2550


반응형
반응형

처음 호출은 Awake, OnEnable, Start 이 순으로 호출이 되는데

Object가 Enable = true (활성화 ) 될 때마다 OnEnable() 이 호출 된다



If a script is attached to more than one gameObject, will Start() & Awake() run more than once?

The same script. Different gameObjects disabled at runtime. Enabled at different time periods throughout the game.

Will Start() & Awake() run for each gameObject becoming enabled?

Or do I need to use OnEnable()?

2 Replies

 · Add your reply
avatar image
1

Answer by Bodrp 

Start() and Awake() will be called when the component becomes active for the first time only. They will act on their own instance of the component (since they are not static methods). OnEnable() gets called everytime the component becomes active. By "acting on their own instance" I mean that for every instance of the component, they will be called and will have access to their instance's attributes, whatever may be their access level.

Awake() will be called first, then OnEnable(), then Start(). If multiple components are activated at the same time, Awake() and OnEnable() methods will be called jointly for each instance. When that's done, each instance's Start() method will be called.

For more information about the methods Unity calls with reflection on your components, you can refer to the execution order page of the manual.



http://answers.unity3d.com/questions/1346838/if-a-script-is-attached-to-more-than-one-gameobjec.html

반응형
반응형




"Tag"and "Layer"

there are two parameters in the window of inspector named "Tag" and "Layer".can anybody tell me what is their the meaning

2 Replies

avatar image
5

Answer by Mike 3 

Tags are used for finding objects with a specific tag ( e.g. FindWithTag(str) ).

Layers are used for determining which objects are raycast to, which get renderered, lit, and which objects collide together

Check the manual for more info:

http://unity3d.com/support/documentation/Components/Layers.html

http://unity3d.com/support/documentation/Components/Tags.html

avatar image
1

Answer by blueLED 

Layers can be used to identify objects at a high level. Some examples would be:

  • level collision

  • pick up item

  • water

  • enemy

So when your code is checking for where the player can walk, you would only check for objects in the "level collision" layer and ignore the rest. If you didn't want the player to collide against something, say, a hologram, then you wouldn't set its layer to "level collision" and the player could pass right through it.

Tags could then be used to define the object at a finer level, for example, if the object is set to the "level collision" layer, its tag could then be set to:

  • dirt

  • metal

  • wood

So your code can use the tag to determine which sound effect to play when the player walks over or touches the object. Or it could be used to determine what kind of bullet mark should be left on the surface if you shoot it.

Another use for layers is to decide which objects get lit by which lights. For example, make a layer called "RedLightObjects". Then make a red light and set its culling mask to "RedLightObjects". Now, the red light will only light up those objects in the scene set to that layer and ignore the rest. Tags would not be efficient for this purpose.



http://answers.unity3d.com/questions/31892/tagand-layer.html



반응형
반응형
SLATE_ATTRIBUTE 와 TAttribute 에 대해서 알아보겠습니다




먼저 언리얼 내부를 보다보면 SLATE_ATTRIBUTE  라는 것을 볼 수있다 그런데 이 매크로를 보면 다음과 같습니다



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define SLATE_ATTRIBUTE( AttrType, AttrName ) \
        TAttribute< AttrType > _##AttrName; \
        WidgetArgsType& AttrName( const TAttribute< AttrType >& InAttribute ) \
        { \
            _##AttrName = InAttribute; \
            return this->Me(); \
        } \
    \
        /* Bind attribute with delegate to a global function
         * NOTE: We use a template here to avoid 'typename' issues when hosting attributes inside templated classes */ \
        template< typename StaticFuncPtr > \
        WidgetArgsType& AttrName##_Static( StaticFuncPtr InFunc )    \
        { \
            _##AttrName = TAttribute< AttrType >::Create( TAttribute< AttrType >::FGetter::CreateStatic( InFunc ) ); \
            return this->Me(); \
        } \
        template< typename Var1Type > \
        WidgetArgsType& AttrName##_Static( typename TAttribute< AttrType >::FGetter::template TStaticDelegate_OneVar< Var1Type >::FFuncPtr InFunc, Var1Type Var1 )    \
        { \
            _##AttrName = TAttribute< AttrType >::Create( TAttribute< AttrType >::FGetter::CreateStatic( InFunc, Var1 ) ); \
            return this->Me(); \
        } \
cs


이 안에  TAttribute 라는 변수를 생성하고 있습니다

그런데 언뜻 보면   Slate_Attribute 매크로에서 이 TAttribute 타입 변수에 별의별 타입을 다 담는것 처럼 보여서 이것의 정체가 무엇인지
혼동 할 수가 있는데 이것은 함수와 인자를 담는 델리게이트의 역할을 같이 하고 있습니다

또한 델리게이트를 생성하는 방법은 다양합니다 함수를 델리게이트에 담는 방법, reference count 가 있는 shared 포인터와 연관된 
델리게이트 등 다양하게 델리게이트와 연관 될 수 있게 설계 되어 있습니다



example..
1
2
3
4
5
6
template< class UserClass, typename Var1Type >    \
WidgetArgsType& AttrName( TSharedRef< UserClass > InUserObjectRef, 
typename TAttribute< AttrType >::FGetter::template TSPMethodDelegate_OneVar_Const< UserClass, Var1Type >::FMethodPtr InFunc, 
Var1Type Var1 )    \
{ \
     _##AttrName = TAttribute< AttrType >::Create( TAttribute< AttrType >::FGetter::CreateSP( InUserObjectRef, InFunc, Var1 ) ); \
     return this->Me(); \
} \
cs




정리하자면 

언리얼 내부에 TAttribute 라는 template  클래스를 볼 수 가 있는데 

예를 들어 TAttribute<bool>  해주면 bool 이라는 변수 타입을 갖는 변수가 하나 생성 되고, 
또한 bool 변수에 대해 처리하고자 하는 바인딩 함수를 걸 수 있습니다

변수 타입의 확장형이라고 볼 수 있습니다 (확장이란 말이 약간 오해의 소지가 있을 수 있지만 코드를 보면 무슨 의미인지 아실 수 있을 겁니다)

SLATE_ATTRIBUTE 는 곧 변수와  델리게이트를 갖는 TAttribute 를 만들고 생성시 지정한 AttrName 에 델리게이트를 담는 함수를
define 한 매크로이다

Tip : 
또 매크로가 있는 특성상 지정한 이름에 뒤에 _Static 등을 붙여 함수로 만들어 치환 하는 경우가 많아서 그냥 소스를 보다가 갑자기
듣보 보도 못한 함수나 변수가 나오는게 이러한 이유때문이다, 자세한건 아래 소스를 참고하면 됩니다





아래 분석 구문을 보면.. 중간 중간 주석을 달아 놓았습니다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
#define FUNC_DECLARE_DELEGATE( DelegateName, ... ) \
 
    typedef TBaseDelegate<__VA_ARGS__> DelegateName;
 
// 이것이 delegate 생성기, 함수 등을 bind 할 수 있다
 
 
 


 
#define DECLARE_DELEGATE_RetVal( ReturnValueType, DelegateName ) FUNC_DECLARE_DELEGATE( DelegateName, ReturnValueType )
 
 

 


 
/*예로
아래 구문 중 이런 구문이 있다
    static TAttribute Create( typename FGetter::FStaticDelegate::FFuncPtr InFuncPtr )
    {
        const bool bExplicitConstructor = true;
        return TAttribute( FGetter::CreateStatic( InFuncPtr ), bExplicitConstructor );                //이 것이 델리게이트에 InFuncPtr 함수를 바인딩 하면서 생성 하는 과정
    }
FGetter 는 아래 정의된 델리게이트인데 , 델리게이트 FGetter 를 통해서 CreateStatic 함수로 InFuncPtr이라는 수를 바인딩해서 만든 델리게이트를 리턴해
TAttribute 를 생성한다
 
*/





//예를 들어 TAttribute<bool>  해주면 bool 이라는 변수 타입을 갖는 변수가 하나 생성 되고, 
//또한 bool 변수에 대해 처리하고자 하는 바인딩 함수를 걸 수 있다
/**
 * Attribute object
 */
template< typename ObjectType >
class TAttribute
{
public:
    /**
     * Attribute 'getter' delegate
     *
     * ObjectType GetValue() const
     *
     * @return  The attribute's value
     */
    DECLARE_DELEGATE_RetVal( ObjectType, FGetter );
    /** Default constructor. */
    TAttribute()
        : Value()         // NOTE: Potentially uninitialized for atomics!!
        , bIsSet(false)
        , Getter()
    { }
    /**
     * Construct implicitly from an initial value
     *
     * @param  InInitialValue  The value for this attribute
     */
    template< typename OtherType >
    TAttribute( const OtherType& InInitialValue )
        : Value( InInitialValue )        
        , bIsSet(true)
        , Getter()
    { }
........
중략
........
    /**
     * Static: Creates an attribute that's pre-bound to the specified 'getter' delegate
     *
     * @param  InGetter        Delegate to bind
     */
    static TAttribute Create( const FGetter& InGetter )
    {
        const bool bExplicitConstructor = true;
        return TAttribute( InGetter, bExplicitConstructor );
    }
    
    /**
     * Creates an attribute by binding an arbitrary function that will be called to generate this attribute's value on demand. 
     * After binding, the attribute will no longer have a value that can be accessed directly, and instead the bound
     * function will always be called to generate the value.
     *
     * @param  InFuncPtr Member function to bind.  The function's structure (return value, arguments, etc) must match IBoundAttributeDelegate's definition.
     */
    static TAttribute Create( typename FGetter::FStaticDelegate::FFuncPtr InFuncPtr )
    {
//이 것이 델리게이트에 InFuncPtr 함수를 바인딩 하면서 생성 하는 과정
        const bool bExplicitConstructor = true;
        return TAttribute( FGetter::CreateStatic( InFuncPtr ), bExplicitConstructor );   
    }
    /**
     * Sets the attribute's value
     *
     * @param  InNewValue  The value to set the attribute to
     */
    template< typename OtherType >
    void Set( const OtherType& InNewValue )
    {
        Getter.Unbind();
        Value = InNewValue;
        bIsSet = true;
    }
    /** Was this TAttribute ever assigned? */
    bool IsSet() const
    {
        return bIsSet;
    }
    /**
     * Binds an arbitrary function that will be called to generate this attribute's value on demand.
     * After binding, the attribute will no longer have a value that can be accessed directly, and instead the bound
     * function will always be called to generate the value.
     *
     * @param  InUserObject  Instance of the class that contains the member function you want to bind.
     * @param  InFunctionName Member function name to bind.
     */
    template< class SourceType >
    void BindUFunction( SourceType* InUserObject, const FName& InFunctionName )
    {
        bIsSet = true;
        Getter.BindUFunction(InUserObject, InFunctionName);
    }
    /**
     * Creates an attribute by binding an arbitrary function that will be called to generate this attribute's value on demand.
     * After binding, the attribute will no longer have a value that can be accessed directly, and instead the bound
     * function will always be called to generate the value.
     *
     * @param  InUserObject  Instance of the class that contains the member function you want to bind.
     * @param  InFunctionName Member function name to bind.
     */
    template< class SourceType >
    static TAttribute< ObjectType > Create(SourceType* InUserObject, const FName& InFunctionName)
    {
        TAttribute< ObjectType > Attrib;
        Attrib.BindUFunction<SourceType>(InUserObject, InFunctionName);
        return Attrib;
    }
........
중략
........

    /**
     * Is this attribute identical to another TAttribute.
     *
     * @param InOther The other attribute to compare with.
     * @return true if the attributes are identical, false otherwise.
     */
    bool IdenticalTo(const TAttribute& InOther) const
    {
        const bool bIsBound = IsBound();
        if ( bIsBound == InOther.IsBound() )
        {
            if ( bIsBound )
            {
                return Getter.GetHandle() == InOther.Getter.GetHandle();
            }
            else
            {
                return Value == InOther.Value;
            }
        }
        return false;
    }
........
중략
........
private:
    /** Special explicit constructor for TAttribute::Create() */
    TAttribute( const FGetter& InGetter, bool bExplicitConstructor )
        : Value()        
        , bIsSet( true )
        , Getter(InGetter)
    { }
    // We declare ourselves as a friend (templated using OtherType) so we can access members as needed
    template< class OtherType > friend class TAttribute;    


    /** Current value.  Mutable so that we can cache the value locally when using a bound Getter (allows const ref return value.) */
    mutable ObjectType Value;            //이건 이전에 이전에 지정한 타입에 대한 변수이고


    /** true when this attribute was explicitly set by a consumer, false when the attribute's value is set to the default*/
    bool bIsSet;
    /** Bound member function for this attribute (may be NULL if no function is bound.)  When set, all attempts
        to read the attribute's value will instead call this delegate to generate the value. */
    /** Our attribute's 'getter' delegate */
    FGetter Getter;                        //그래서 이게 delegate 가 됨
};
----------------------------------------------------------------------------------------
/**
 * Use this macro to add a attribute to the declaration of your widget.
 * An attribute can be a value or a function.
 */
#define SLATE_ATTRIBUTE( AttrType, AttrName ) \
        TAttribute< AttrType > _##AttrName; \
        WidgetArgsType& AttrName( const TAttribute< AttrType >& InAttribute ) \
        { \
            _##AttrName = InAttribute; \
            return this->Me(); \
        } \
    \
    ......
//some example..
SLATE_ATTRIBUTE( FText, ToolTipText )
SLATE_ATTRIBUTE( TOptional<EMouseCursor::Type>, Cursor )
SLATE_ATTRIBUTE( bool, IsEnabled )
SLATE_ATTRIBUTE( EVisibility, Visibility )
SLATE_ATTRIBUTE( TOptional<FSlateRenderTransform>, RenderTransform )
SLATE_ATTRIBUTE( FVector2D, RenderTransformPivot )
cs




그렇다면 왜 Slate_ATTRIBUTE 를 만들언 놓은 것인가 왜 델리게이트와 엮이는 매크로를 만들어 놓은 것인가..?




다음 구문을 한번 봅시다


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* Base class for named arguments. Provides settings necessary for all widgets. */
template<typename WidgetType>
struct TSlateBaseNamedArgs
{
    typedef typename WidgetType::FArguments WidgetArgsType;
    
    TSlateBaseNamedArgs()
    : _ToolTipText()
    , _ToolTip()
    , _Cursor( TOptional<EMouseCursor::Type>() )
    , _IsEnabled( true )
    , _Visibility( EVisibility::Visible )
    , _RenderTransform( )
    , _RenderTransformPivot( FVector2D::ZeroVector )
    , _ForceVolatile( false )
    {
    }
 
    /** Used by the named argument pattern as a safe way to 'return *this' for call-chaining purposes. */
    WidgetArgsType& Me()
    {
        return *(static_cast<WidgetArgsType*>(this));
    }
 
    /** Add metadata to this widget. */
    WidgetArgsType& AddMetaData(TSharedRef<ISlateMetaData> InMetaData)
    {
        MetaData.Add(InMetaData);
        return Me();
    }
 
    /** Add metadata to this widget - convenience method - 1 argument */
    template<typename MetaDataType, typename Arg0Type>
    WidgetArgsType& AddMetaData(Arg0Type InArg0)
    {
        MetaData.Add(MakeShareable(new MetaDataType(InArg0)));
        return Me();
    }
 
    /** Add metadata to this widget - convenience method - 2 arguments */
    template<typename MetaDataType, typename Arg0Type, typename Arg1Type>
    WidgetArgsType& AddMetaData(Arg0Type InArg0, Arg1Type InArg1)
    {
        MetaData.Add(MakeShareable(new MetaDataType(InArg0, InArg1)));
        return Me();
    }
 
//이 아래 부분을 보면 됨..
    SLATE_ATTRIBUTE( FText, ToolTipText )
    SLATE_ARGUMENT( TSharedPtr<IToolTip>, ToolTip )
    SLATE_ATTRIBUTE( TOptional<EMouseCursor::Type>, Cursor )
    SLATE_ATTRIBUTE( bool, IsEnabled )
    SLATE_ATTRIBUTE( EVisibility, Visibility )
    SLATE_ATTRIBUTE( TOptional<FSlateRenderTransform>, RenderTransform )
    SLATE_ATTRIBUTE( FVector2D, RenderTransformPivot )
    SLATE_ARGUMENT( FName, Tag )
    SLATE_ARGUMENT( bool, ForceVolatile )
 
    TArray<TSharedRef<ISlateMetaData>> MetaData;
};
cs

 






여기에서 보면 SLATE_ATTRIBUTE 를 뭉태이로 쓴 것이 보이는데 변수 명 들을 보면 델리게이트와 역일 법한 것들로 보이죠?

즉 SLATE_ATTRIBUTE 로 변수를 만든 다는 것은 나중에 이 변수를 통해서 함수를 바인딩 하고 원하는 시점에 바인딩 된 함수들을 호출하기 위해

존재하는 것 정도로 그 의도를 추측 할 수가 있습니다


이것들을 묶고 있는 클래스 명 (TSlateBaseNamedArgs) 또한 이와 유사한 성향의 연관성을 띈 네이밍으로 볼 수 있습니다













3dmpengines.tistory.com

참고 : 언리얼엔진4 소스
URL : https://www.unrealengine.com


 


반응형
반응형

Unreal Engine

Welcome to the Unreal Engine source code!

From this repository you can build the Unreal Editor for Windows and Mac, compile Unreal Engine games for Android, iOS, Playstation 4, Xbox One, HTML5 and Linux, and build tools like Unreal Lightmass and Unreal Frontend. Modify them in any way you can imagine, and share your changes with others!

We have a heap of documentation available for the engine on the web. If you're looking for the answer to something, you may want to start here:

If you need more, just ask! A lot of Epic developers hang out on the forums or AnswerHub, and we're proud to be part of a well-meaning, friendly and welcoming community of thousands.

Branches

We publish source for the engine in three rolling branches:

The release branch is extensively tested by our QA team and makes a great starting point for learning the engine or making your own games. We work hard to make releases stable and reliable, and aim to publish new releases every few months.

The promoted branch is updated with builds for our artists and designers to use. We try to update it daily (though we often catch things that prevent us from doing so) and it's a good balance between getting the latest cool stuff and knowing most things work.

The master branch tracks live changes by our engine team. This is the cutting edge and may be buggy - it may not even compile. Battle-hardened developers eager to work lock-step with us on the latest and greatest should head here.

Other short-lived branches may pop-up from time to time as we stabilize new releases or hotfixes.

Getting up and running

The steps below will take you through cloning your own private fork, then compiling and running the editor yourself:

Windows

  1. Install GitHub for Windows then fork and clone our repository. To use Git from the command line, see the Setting up Git and Fork a Repo articles.

    If you'd prefer not to use Git, you can get the source with the 'Download ZIP' button on the right. The built-in Windows zip utility will mark the contents of zip files downloaded from the Internet as unsafe to execute, so right-click the zip file and select 'Properties...' and 'Unblock' before decompressing it. Third-party zip utilities don't normally do this.

  2. Install Visual Studio 2015. All desktop editions of Visual Studio 2015 can build UE4, including Visual Studio Community 2015, which is free for small teams and individual developers. Be sure to include C++ support as part of the install, which is disabled by default.

  3. Open your source folder in Explorer and run Setup.bat. This will download binary content for the engine, as well as installing prerequisites and setting up Unreal file associations. On Windows 8, a warning from SmartScreen may appear. Click "More info", then "Run anyway" to continue.

    A clean download of the engine binaries is currently 3-4gb, which may take some time to complete. Subsequent checkouts only require incremental downloads and will be much quicker.

  4. Run GenerateProjectFiles.bat to create project files for the engine. It should take less than a minute to complete.

  5. Load the project into Visual Studio by double-clicking on the UE4.sln file. Set your solution configuration to Development Editor and your solution platform to Win64, then right click on the UE4 target and select Build. It may take anywhere between 10 and 40 minutes to finish compiling, depending on your system specs.

  6. After compiling finishes, you can load the editor from Visual Studio by setting your startup project to UE4 and pressing F5 to debug.





https://github.com/EpicGames/UnrealEngine


반응형

+ Recent posts