출처 : http://answers.unity3d.com/questions/458207/copy-a-component-at-runtime.html


1
2
3
4
5
6
7
8
9
10
11
12
T CopyComponent<T>(T original, GameObject destination) where T : Component
 {
     System.Type type = original.GetType();
     Component copy = destination.AddComponent(type);
     System.Reflection.FieldInfo[] fields = type.GetFields();
     foreach (System.Reflection.FieldInfo field in fields)
     {
         field.SetValue(copy, field.GetValue(original));
     }
     return copy as T;
 }
 
cs


Type.GetFiels

(https://msdn.microsoft.com/ko-kr/library/ch9714z3(v=vs.110).aspx)

 - 현재 타입에서 public 필드를 가져오는 함수입니다. [SerializeField]는 가져오지 않습니다.

Save

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
// (1) 스크린샷용 카메라를 준비합니다.
screenShotCamera.gameObject.SetActive(true);
 
// (2) 화면 크기를 지정합니다.
Vector2 screenSize = new Vector2(Screen.width, Screen.height);
 
// (3) 저장할 이미지의 크기를 지정합니다.(화면 크기 그대로 저장을 원하면 screenSize로 대체하시면 됩니다.)
Vector2 imageSize = new Vector2(
    cat.pictureSize.x / cameraSize.x * screenSize.x,
    cat.pictureSize.y / cameraSize.y * screenSize.y);
 
// (4) 저장할 이미지의 Offset을 지정합니다.
Vector2 imageOffset = cat.pictureOffset;
imageOffset.x += cameraSize.x * 0.5f - transform.position.x;
imageOffset.y += cameraSize.y * 0.5f - transform.position.y;
 
// (5) OpenGL의 경우 y축이 Upwards이고, 나머지의 경우 y축이 Downwards입니다.
if (SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.OpenGL2 &&
    SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore &&
    SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2 &&
    SystemInfo.graphicsDeviceType != UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3)
    imageOffset.y = cameraSize.y - imageOffset.y;
 
imageOffset.x *= screenSize.x / cameraSize.x - imageSize.x * 0.5f;
imageOffset.y *= screenSize.y / cameraSize.y - imageSize.y * 0.5f;
 
// (6) RenderTexture에 screenShotCamera가 보고 있는 화면을 Render 합니다.
RenderTexture rt = new RenderTexture((int)screenSize.x, (int)screenSize.y, 32);
screenShotCamera.targetTexture = rt;
screenShotCamera.Render();
RenderTexture.active = rt;
 
// (7) RenderTexture를 Texture2D로 옮깁니다.
Texture2D cache = new Texture2D((int)imageSize.x, (int)imageSize.y, TextureFormat.ARGB32, false);
cache.filterMode = FilterMode.Bilinear;
cache.ReadPixels(new Rect(imageOffset, imageSize), 00);
 
// (8) 저장합니다.
byte[] bytes = cache.EncodeToPNG();
string filename = Application.persistentDataPath + "/filename.png";
System.IO.File.WriteAllBytes(filename, bytes);
 
// (9) 뒷정리합니다.
screenShotCamera.targetTexture = null;
RenderTexture.active = null;
Destroy(rt);
screenShotCamera.gameObject.SetActive(false);
 
cs
  1. 스크린샷 카메라를 준비합니다. MainCamera를 사용해도 무방하다면 MainCamera를 사용합니다.
  2. 화면 크기를 지정합니다.
  3. 저장할 이미지의 크기를 지정합니다. (특정 영역을 저장할 때 사용합니다. 그것이 아니라면 화면 크기와 동일하게 지정합니다.)
  4. 저장할 이미지의 Offset을 지정합니다.
  5. 참고 : http://chessire.tistory.com/entry/%EB%A0%8C%EB%8D%94%ED%85%8D%EC%8A%A4%EC%B3%90-%EC%A2%8C%ED%91%9C%EA%B3%84Render-Texture-coordinates
  6. RenderTexture에 screenShotCamera가 보고 있는 화면을 Render 합니다.
  7. RenderTexture를 Texture2D로 옮깁니다.
  8. 저장합니다.
  9. 뒷정리합니다.



Load

1
2
3
4
5
byte[] bytes = System.IO.File.ReadAllBytes(Application.persistentDataPath + "/filename.png");
Texture2D texture = new Texture2D(11, TextureFormat.ARGB32, false);
texture.filterMode = FilterMode.Bilinear;
texture.LoadImage(bytes);
Sprite sprite = Sprite.Create(texture, new Rect(00, texture.width, texture.height), new Vector2(0.5f, 0.5f));
cs



참고 : http://docs.unity3d.com/Manual/SL-PlatformDifferences.html



문제

오늘 스크린샷 기능 만들다가 Unity RenderTexture의 이상한 점을 발견했습니다.


바로 그래픽스 sdk에 따라 Coordinate system이 다르다는 사실...

참고 URL을 가보시면 이런 글을 확인하실 수 있습니다.



해결방법


1
2
3
4
5
if (SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGL2 ||
    SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLCore ||
    SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES2 ||
    SystemInfo.graphicsDeviceType == UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3)
 
cs

위 조건일 때, y값을 반전시켜주시면 됩니다.

C++에서 C#함수를 호출하는 방법에 대해 포스팅하겠습니다.(곁다리로 C#에서 C++함수 호출하는 방법도...)


MonoPInvokeCallback를 사용하시면 됩니다.


in C# ↓

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
using AOT;

public class SomePlugin //어떤 플러그인입니다.
{
    public delegate void SomeCallback( string result ); // c++에서 호출해줄 Callback형입니다.

    public static extern void ConnectCallback (
SomeCallback someCallback); // c#에서 호출할 c++함수입니다.

    public SomePlugin()
    {
        ConnectCallbackSomeFunction ); // 생성자에서 c++함수를 호출합니다.
    }

    [MonoPInvokeCallback(typeof(
SomeCallback))] // 핵심입니다.
    public static void SomeFunction(string result)
    {
        //something...
    }
}


in C++ 

typedef void (*SomeCallback)(const char* result);
SomeCallback someCallback = NULL;

extern "C"
{
    void ConnectCallback(SomeCallback _someCallback) //c#에서 호출할 함수입니다.
    {
        someCallback = _someCallback;
    }
}

void SomeFunction(const char* result)
{
    if( someCallback != NULL )
        someCallback(result);
}



사용법은 함수포인터 쓰듯이 사용하시면 됩니다.다만 native code에서 액세스 할 수 있도록 MonoPInvokeCallback 속성을 적용해줘야합니다.

유니티에서는 비추천하는 방법입니다(함수 포인터가 주소값 접근이라 위험해서 그런건지 뭔지는 모르겠지만요). 추천하는 방법으로는 UnitySendMessage가 있는데 느린 특징을 가지고 있기에 저는 이 방법으로 해결했습니다.


Unity 빌드 시, 유의할 점

  1. Plugins 폴더
    • 빌드를 수행할 때, 자동으로 Plugins폴더 안의 Android, iOS 폴더안에 있는 "특정" 소스파일이나 리소스파일들을 프로젝트에 추가해줍니다. iOS같은 경우는 Header search path까지 연결해줍니다.
    • 문제는 한 프로젝트로 여러가지 빌드(서로 다른 플러그인을 사용하는)를 뽑을 때 문제가 된다는 것! 잘 삭제 해주지 않으면 쓸데없이 용량을 잡아먹게됩니다.(자동화할때 유의해주세요.)
  2. PostprocessBuildPlayer
    • Plugins 폴더에 넣어놓는다고 하더라도 "특정"파일만 넣는 성격때문에 누락되는 파일들이 있습니다. 손으로 넣어주시는 분들은 상관 없지만 자동화할 땐 엄청난 불편함으로 다가옵니다. 그럴때 이것을 사용합니다.(전 모듈로 mod_pbxproj.py를 사용합니다. 엄청 편해요~)
    • 문제는 Assets/Editor폴더 안에 있는 단 한개의 PostprocessBuildPlayer만 실행이 된다는것입니다. 사용할 PostprocessBuildPlayer들을 PostprocessBuildPlayer_*로 이름을 변경한 후, 메인 PostprocessBuildPlayer가 PostprocessBuildPlayer_*를 전부 찾아 실행해주면 됩니다.
    • 말로하니 복잡하네요. 아래의 소스코드를 참고해주세요.
===========================================================================

#!/usr/bin/python


import sys

import subprocess

import glob

import os


def main(argv):

    paths = glob.iglob( 'Assets/Editor/PostprocessBuildPlayer_*' )

    

    for path in paths:

        if os.path.splitext(path)[1] != '.meta':

            os.chmod( path, 0755 )

            subprocess.call( [os.path.realpath(path), sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]] )



if __name__ == "__main__":

    main(sys.argv)

===========================================================================


  • il2cpp
    1. 유니티에서 64bit지원을 위해 부랴부랴 내놓은 아이입니다. il2cpp로 빌드를 하시고 Architecture를 Universial 혹은 ARM64로 설정해 놓아야 64비트 지원이 됩니다.
    2. C#을 빌드하면 나오는 IL파일을 cpp파일로 변환해주는 역할을합니다. Classes 폴더 하위에 Native폴더가 생기네요. 그 안의 파일들을 둘러보긴 했지만... 여하튼 소스가 많이 생겼습니다.
    3. 문제점
      1. 일단 실행파일 용량이 늘어납니다.(소스량이 적다면 조금 늘어나거나 안늘어날 수도 있습니다.) 
      2. 컴파일 시간이 길어집니다.(Unity에서의 컴파일 시간은 의외로 느리지 않은데 xcode에서 빌드시 cpp파일을 전부 빌드해줘야하기 때문에 그로인해 컴파일 시간이 길어집니다.
      3. mono_domain같은 c++에서 c#함수를 가져다 사용할 수 있게해주는 함수를 사용하지 못합니다.
    4. 해결방안
      1. 늘어난 용량은 코드최적화 레벨을 올려주면(ex, strip assembly) 약간 줄어듭니다만 기대치는 낮습니다.
      2. 컴파일 시간은 그냥 감내해야된달까요....
      3. 해당 부분은 유니티에서 권장하는 UnitySendMessage를 사용하면 되지만 느립니다. Object와 함수명을 string으로 찾아서 사용하는 방식이기 때문에 빈번하게 불리는 환경이 아니면 이것을 사용해줍니다.
        1. 빈번하게 사용하는 환경이라면 MonoPInvokeCallback를 사용해줍니다.




+ Recent posts