반응형

Prefab_Skin = (GameObject)EditorGUILayout.ObjectField("_Skin Object", Prefab_Skin, typeof(GameObject));

라고 코딩을 하자 아래와 같은 에러 로그가 발생했다.

 

warning CS0618: `UnityEditor.EditorGUILayout.ObjectField(string, UnityEngine.Object, System.Type, params UnityEngine.GUILayoutOption[])' is obsolete: `Check the docs for the usage of the new parameter 'allowSceneObjects'.'

 

에러 로그가 발생하지 않으려면 마지막에 Bool 값을 넣어서 아래와 같이 입력해줘야 에러가 발생하지 않는다.

 

_prefabObj = (GameObject)EditorGUILayout.ObjectField("Target Prefab", _prefabObj, typeof(GameObject), true);

 

 

반응형
반응형

UnityEngineInternal.APIUpdaterRuntimeServices를 사용하면 아래와 같은 Warning Log 가 발생한다.

 

warning CS0618: `UnityEngineInternal.APIUpdaterRuntimeServices.AddComponent(UnityEngine.GameObject, string, string)' is obsolete: `AddComponent(string) has been deprecated. Use GameObject.AddComponent<T>() / GameObject.AddComponent(Type) instead.

 

이 로그가 발생하지 않도록 하기 위해서는 툴을 거쳐서 스크립트를 집어넣도록 설정했던것을 해당 스크립트로 바로 불러오도록 값을 수정해 주면 에러로그가 발생하지 않는다.

 

예)

string ParticleStrScript = "ParticleCtrl";

 

UnityEngineInternal.APIUpdaterRuntimeServices.AddComponent (PrefabBipProp, "Assets/Scripts/Editor/Art/CHAR_Prefab_FbxChanger.cs", ParticleStrScript); 

 

위 코드를 사용하면 warning이 발생하게 되며 아래와 같이 수정해주면 로그가 사라진다.

 

-->  PrefabBip.AddComponent (typeof(ParticleCtrl));

 

 

반응형
반응형

이번에 기존에 등록된 스크립트를 제거하고 새로운 스크립트를 등록하는 툴을 유니티 스크립트로 제작하게 됐는데 이번 툴을 제작하면서 C#에서의 이름 찾기와 재귀 함수 사용법에 대해서 알게 됐습니다.

 

아래 스크립트는 기존 있던  Animated_Death 컴포넌트를 삭제하고 Animated_Death_Combine 이라는 새로운 컴포넌트를 추가하면서 해당 컴포넌트에 하위 오브젝트들의 이름을 뒤져 필요한 Transform을 이름으로 검색하여 자동으로 넣어주는 툴입니다.

아래 스크립트를 참고하면 여러가지 응용이 가능할거 같습니다.

 

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

public class AnimateDeathChange : EditorWindow
{
    GameObject Target = null;
    GameObject pObj = null;

    string strScript = "Animated_Death_Combine";

    string DelScript = "Animated_Death";

    [MenuItem("RK Tools/Art/AnimateDeathChange")]

    //윈도우 생성
    static void InitWindow () 
    {
        AnimateDeathChange window = (AnimateDeathChange)EditorWindow.GetWindow(typeof(AnimateDeathChange));
        window.autoRepaintOnSceneChange = true;

        if (window == null)
        {
            Debug.Log("error: cannot find Dummy");
        }
    }

    //오브젝트 선택창
    void OnGUI()
    { 
        SelectTargetObject ();
        ScriptSetting ();
    }


    //원본소스 선택창
    void SelectTargetObject()
    {
        GUILayout.Space(16f);
        GUI.contentColor = Color.white;
        EditorGUILayout.LabelField("해당 모델에 죽기 스크립트를 추가해 줍니다.");
        EditorGUILayout.LabelField("각각의 항목을 채워주세요.");

        EditorGUILayout.BeginVertical("box");
        Target = (GameObject)EditorGUILayout.ObjectField("Base Model (Project)", Target, typeof(GameObject), false);
        strScript = EditorGUILayout.TextField ( "Add Script Name", strScript);
        DelScript = EditorGUILayout.TextField ( "Del Script Name", DelScript);
        EditorGUILayout.EndVertical();
    }

    //복사 오브젝트는 하이어라키 창에서 실시간 교체 가능
    void OnSelectionChange()
    {        
        if (Selection.gameObjects == null || Selection.gameObjects.Length < 1) {
            Focus();
            Target = null;
            return;
        }

        if (Target != Selection.gameObjects[0])
        {
            Target = Selection.gameObjects[0];
            Focus();           
        }
        else
            Target = Selection.gameObjects[0];
    }

    //실행시 기존 스크     
    void ScriptSetting()
    {
        GUILayout.Space(16f);
        if (GUILayout.Button ("Set Script To Object")) 
        {

            // 새로운 컴포넌트를 넣어주는 명령어 
            UnityEngineInternal.APIUpdaterRuntimeServices.AddComponent(Target, "Assets/Scripts/Editor/Art/AnimateDeathChange.cs", strScript);

            SerchObjectsInChild(Target.transform);

            // 기존 컴포넌트를 제거해주는 명령어
            GameObject.DestroyImmediate (Target.GetComponentInChildren<Animated_Death>());
            GameObject.DestroyImmediate (Target.GetComponentInChildren<Animated_Death>());
            GameObject.DestroyImmediate (Target.GetComponentInChildren<Animated_Death>());




            if (null == Target)
                return;
            if (null == pObj)
                return;
            else 
            {
                SerchObjectsInChild(Target.transform);
            }


        }
    }
    //
    //    //원본 오브젝트의 하위 더미를 찾는 재귀 함수
    private void SerchObjectsInChild(Transform tr)
    {
        int iCount = tr.childCount;

        for (int i = 0; i < iCount; ++i) {
            Transform tempTr = tr.GetChild (i);
//            Debug.Log (tempTr);

            // Contains 명령어는 문장을 뒤져 해당 텍스트를 찾는 명령어이다.

            if (tempTr.name.Contains ("_body")) {                
                Target.GetComponent<Animated_Death_Combine>().Model_1 = tempTr.gameObject.GetComponent<Renderer>();
            } 
            else if (tempTr.name.Contains ("_head")|| tempTr.name.Contains ("face")) {
                Target.GetComponent<Animated_Death_Combine>().Model_2 = tempTr.gameObject.GetComponent<Renderer>();
            } 
            else if (tempTr.name.Contains ("_weap")) {                
                Target.GetComponent<Animated_Death_Combine>().Model_3 = tempTr.gameObject.GetComponent<Renderer>();
            }
            else if (tempTr.name.Contains ("_BODY")) {
                Target.GetComponent<Animated_Death_Combine>().Shadow_1 = tempTr.gameObject.GetComponent<Renderer>();
            } else if (tempTr.name.Contains ("_HEAD") || tempTr.name.Contains ("_FACE")) {                
                Target.GetComponent<Animated_Death_Combine>().Shadow_2 = tempTr.gameObject.GetComponent<Renderer>();
            }else if (tempTr.name.Contains ("_WEAP")) {                
                Target.GetComponent<Animated_Death_Combine>().Shadow_3 = tempTr.gameObject.GetComponent<Renderer>();
            }

            if (tempTr.childCount > 0)
            {
                SerchObjectsInChild(tempTr);
            }
        }
    }
}

반응형
반응형
GameObject.DestroyImmediate (Target.GetComponentInChildren<Animated_Death>());

 

위 구문은 선택된 타겟 오브젝트의 자식을 뒤져 Animated_Death 라는 컴포넌트를 제거해 주는 명령어 입니다.

GameObject.DestroyImmediate (Target.GetComponent<Animated_Death>());  라고 쓰면 자식이 아닌 본인의 컴포넌트를 제거해 주는 거겠죠~

 

이번에 죽기 관련 스크립트를 수정하면서 알게 된 내용이라 메모해둡니다~

반응형
반응형

유니티 스크립트에서는 메터리얼에 담겨 있는 텍스쳐의 다양한 정보를 편집해서 사용할 수 있다.

MainTexture는 maintexture / mainTextureOffset / mainTextureScale  등을 활용하여 크기나 좌표를 편집할 수 있고,

SubTexture들은 String 값을 참조하여 편집할 수 있다.

아래의 코드를 참고하자.

 

m_Renderer.material.mainTextureOffset    = new Vector2(offset.x - ((int)offset.x), offset.y - ((int)offset.y));

m_Renderer.material.SetTextureOffset("_Alpha", offset)//_Alpha 에 담겨 있는 텍스쳐의 좌표를 편집할 수 있다.

m_Renderer.material.mainTextureScale    = m_size;

m_Renderer.material.SetTextureScale("_Alpha", m_size);

반응형
반응형

유니티의 타임 스케일을 사용할 경우 파티클 애니메이션이 터지는 문제가 종종 발생한다.

이 문제를 해결하기 위해 Time.unscaledDeltaTime 함수를 사용하면 문제를 해결할 수 있다.

타임 스케일을 무시하고 출력되도록 하는 함수다.

 

 

public class UnscaledTimeParticle : MonoBehaviour

{

 // Update is called once per frame

    void Update()

{

if (Time.timeScale < 0.01f)

{

particleSystem.Simulate(Time.unscaledDeltaTime, true, false);

}

}

}

반응형
반응형

프로젝트를 진행하면서 파티클 빌트인 셰이더들이 문제를 일으켜 일괄 변환해 줘야 하는 상황이 발생했습니다.

일일이 바꾸는 번거러움을 덜고자 제작한 스크립트 입니다.

 

 

1. 모바일 엔진 셰이더(바뀌어야할 셰이더 선택)

2. 바뀔 셰이더 선택

3. 대상 메터리얼들 선택(폴더 / 파일)

4. 대상 메터리얼중 1번과 동일 셰이더를 찾아 2번으로 바꿔줌~ 일괄 교체~

 

 

 

 

using UnityEngine;
using UnityEditor;
using System.Collections;
using System.Collections.Generic;
using System.IO;

public class MaterialControl : EditorWindow
{  
    public Shader shader1 = null;
    public Shader shader2 = null;

    public Color defaultColor;
    public Color tintColor = new Color(0.5F, 0.5F, 0.5F, 0.5F);

    Shader defaultShader = null;
    Shader changeShader = null;

    static float UIWidth = 500;
    static string OutputPath = string.Empty;
    
    bool IsError = false;
    List<string> tempPathList = new List<string>();
    List<string> InputPaths = new List<string>();
    
    string SearchPattern = "*.mat;*.*";


//    ShaderImporter texFormatSelected = ShaderImporter.GetAtPath;
//    TextureImporterFormat texFormatToChange = TextureImporterFormat.ETC2_RGBA8;

    [MenuItem("RK Tools/Art/MaterialControl")]
    static void InitWindow()
    {
        MaterialControl window = EditorWindow.GetWindow<MaterialControl>();
        window.position = new Rect(100f, 100f, UIWidth, 400f); //  초기 사이즈
        
        window.title = "Save Bundle";
        window.Show();        
    }
    
    void OnGUI()
    {
        FunctionManual();
        ShaderSetting();
        SelectOutPath();
        ClearSelectFolder();
        OutputSelectedFile();
        ConvertAtlasImage();
    }
    
    void FunctionManual()
    {
        GUILayout.Space(16);
        GUI.contentColor = Color.cyan;
        EditorGUILayout.LabelField("폴더를 선택해서 해당 폴더 내의 Shader를 변경합니다.");
        GUI.contentColor = Color.white;
        GUILayout.Space(8);
    }

    void ShaderSetting()
    {
        defaultShader = (Shader)EditorGUILayout.ObjectField("Original Shader", shader1, typeof(Shader));
        changeShader = (Shader)EditorGUILayout.ObjectField("Change Shader", shader2, typeof(Shader));

        defaultColor = EditorGUILayout.ColorField("Tint Color", tintColor);

        if (defaultShader != null)
        {
            shader1 = defaultShader;
            shader2 = changeShader;
            tintColor = defaultColor;
        }


    }

    string prevPath = Application.dataPath;


    void SelectOutPath()
    {
        if (GUILayout.Button("폴더 or 파일 선택"))
        {            
            GetAssetPath_Project();            
            Repaint();
        }
    }
    
    void GetAssetPath_Project()
    {        
        SelectionProcess();
        
        foreach (string path in tempPathList)
        {
            if (InputPaths.Exists(assetPath => (assetPath == path)))
                continue;
            InputPaths.Add(path);
        }        
        IsError = false;
    }
    
    bool SelectionProcess()
    {
        tempPathList.Clear();
        string AssetPath = string.Empty;
        foreach (UnityEngine.Object obj in Selection.objects)
        {
            AssetPath = AssetDatabase.GetAssetPath(obj);
            if (Directory.Exists(AssetPath))
            {
                tempPathList.AddRange(
                    AssetDatabase.FindAssets("t:Material", new string[] { AssetPath }));
                
                for (int i = 0; i < tempPathList.Count; ++i)
                {
                    tempPathList[i] = AssetDatabase.GUIDToAssetPath(tempPathList[i]);                    
                }
            }
            else if (File.Exists(AssetPath))
            {
                char[] separate = new char[] { ';', '*', '.' };
                foreach (string pattern in SearchPattern.Split(separate))
                {
                    if (AssetPath.ToLower().Contains(pattern))
                    {
                        tempPathList.Add(AssetPath.Replace(Application.dataPath, "Assets"));
                        break;
                    }
                }
            }
        }
        return true;
    }
    
    void ClearSelectFolder()
    {
        if (GUILayout.Button("선택 폴더 or 파일 해제"))
        {
            tempPathList.Clear();
            InputPaths.Clear();
        }
    }
    
    Vector2 scrollPos = new Vector2();
    void OutputSelectedFile()
    {
        EditorGUILayout.LabelField("선택 폴더");
        
        EditorGUILayout.BeginVertical("box", GUILayout.Width(UIWidth));
        scrollPos = EditorGUILayout.BeginScrollView(scrollPos, GUILayout.Width(UIWidth));
        
        foreach (string path in InputPaths)
        {
            EditorGUILayout.LabelField(path);
        }
        EditorGUILayout.EndScrollView();
        EditorGUILayout.EndVertical();
    }
    
    
    void ConvertAtlasImage()
    {        
        EditorGUILayout.BeginHorizontal();

        EditorGUILayout.EndHorizontal();

        EditorGUILayout.BeginVertical("box", GUILayout.Width(UIWidth));

        if(GUILayout.Button("선택된 메터리얼을 Change Shader로 변환"))
        {
            ChangeShaderToTargetMaterial();
        }
        EditorGUILayout.EndVertical ();
    }


    void ChangeShaderToTargetMaterial()
    {
        foreach (string path in InputPaths)
        {
            AssetDatabase.ImportAsset(path);

            Material material = (Material)AssetDatabase.LoadMainAssetAtPath(path);


            if (material.shader == shader1)
            {
                material.shader = shader2;

                material.SetColor("_TintColor", tintColor);
            }         
        }
    }
}

반응형
반응형

게임을 제작할때 메인 전투씬이 있고 그 곳에 배경씬을 로드되는 식으로 제작될 때가 있습니다.

이때의 문제점이 각각의 배경마다의 포그값이나 후처리 값을 가지고 갈 수 없는 문제점이 있습니다.

 

 

 

 

그럴 때 로드될 배경씬에 포그값 정보에 대한 것을 스크리트로 처리하게 되면 사용이 가능해 집니다.

 

 

1. 스크립트 제작

 

using UnityEngine;
using System.Collections;

public class RK_Fog : MonoBehaviour {

    public bool isFogOnOff = false;

    public Color RkFogColor;

    public enum RkFogMode : int
    {
        Linear = 0,
        Expotential = 1,
        ExpotentialSquared = 2,
    }

    public RkFogMode RkFogModeEnum;

    public float LinearStart = 0.0f;
    public float LinearEnd = 10.0f;

    public float ExpotentialDensity = 0.01f;


    // Use this for initialization
    void Start () {

        if (isFogOnOff == true) {
            RenderSettings.fog = true;
            RenderSettings.fogColor = RkFogColor;

            if ((int)RkFogModeEnum == 0) {
                RenderSettings.fogMode = FogMode.Linear;
                RenderSettings.fogStartDistance = LinearStart;
                RenderSettings.fogEndDistance = LinearEnd;
            }
            if ((int)RkFogModeEnum == 1) {
                RenderSettings.fogMode = FogMode.Exponential;
                RenderSettings.fogDensity = ExpotentialDensity;
            }
            if ((int)RkFogModeEnum == 2) {
                RenderSettings.fogMode = FogMode.ExponentialSquared;
                RenderSettings.fogDensity = ExpotentialDensity;
            }

        } else {
            RenderSettings.fog = false;
        }    
    }
    
    // Update is called once per frame
    void Update () {
    
    }
}

 

 

2. 제작 된 스크립트를 씬에 오브젝트를 만들어 배치합니다.

 

 

 

 

반응형
반응형

카메라에 사용된 후처리(Post Processor / Image Effect)를 키로 제어할 수 있게 되면 다양하게 활용이 가능합니다.

 

캐릭터가 강력한 스킬을 쓸때에 블룸효과를 강하게 준다던지, 컷씬을 제작할때 순간적으로 화면을 어둡게 한다던지.

막타연출에서 모션블러나 라디알 효과등을 원하는 타이밍에 쓰기 위해서 애니메이션으로 후처리를 제어하는 방식이

가장 편하고 좋습니다.

 

또한 씬 전환 트랜지션상황에서도 유용하게 사용이 가능합니다.

 

 

1. 카메라에 다양한 후처리 적용하기

- 먼저 카메라에 사용될 다양한 후처리들을 적용하고, 비활성화 시킵니다.

- 라디알블러, 비네팅, 블룸등을 적용시켰습니다.

 

 

 

2. 스크립트 제작하기

- 카메라에 적용된 후처리들을 파싱하여 사용할 수 있도록 스크립트를 제작합니다.

- 사용된 카메라와 카메라에 사용된 후처리 스크립트를 제어하는 코드입니다.

- Animated_PostProcessor 란 이름으로 제작했습니다.

 

using UnityEngine;
using System.Collections;

public class Animated_PostProcessor : MonoBehaviour
{
    public GameObject PostProcessorCtr;  
    public RK_RadialBlur RadialBlurScript;  
    public bool isRadialBlurScript = false;
    [Range(0.0f, 5.0f)]
    public float RadialBlurStrength = 2.2f;
    [Range(0.0f, 3.0f)]
    public float RadialBlurWidth = 0.5f;


    public RK_ScreenOverlay ScreenOverlayScript
    public bool isScreenOverlayScript = false;
    [Range(0.0f, 1.0f)]
    public float ScreenOverlayintensity = 1.0f;
    public enum OverlayBlendMode2 : int
    {
        Additive = 0,
        ScreenBlend = 1,
        Multiply = 2,
        Overlay = 3,
        AlphaBlend = 4,
    }
    public OverlayBlendMode2 ScreenOverlayblendMode;

    public RK_Vignette VignetteScript;
    public bool isVignetteScript = false;
    [Range(-1.0f, 1.0f)]
    public float Vignetteintensity = 0.25f; 


    public Rk_Bloom BloomScript;
    public bool isBloomScript = false;
    [Range(0.0f, 1.5f)]
    public float BloomScriptthreshold = 0.5f;
    [Range(0.0f, 2.5f)]
    public float BloomScriptintensity = 1.0f;


    void OnEnable ()
    {
        PostProcessorCtr = GameObject.Find("PostProcessorCam");
        RadialBlurScript = PostProcessorCtr.GetComponent<RK_RadialBlur>();
        ScreenOverlayScript = PostProcessorCtr.GetComponent<RK_ScreenOverlay>();
        VignetteScript = PostProcessorCtr.GetComponent<RK_Vignette>();
        BloomScript = PostProcessorCtr.GetComponent<Rk_Bloom>();

        LateUpdate();
    }

    void LateUpdate()
    {
        //라디알 블러 처리
        if (isRadialBlurScript == true) {
            RadialBlurScript.enabled = true;
        } 
        else {
            RadialBlurScript.enabled = false;
        }
        RadialBlurScript.blurStrength = RadialBlurStrength;
        RadialBlurScript.blurWidth = RadialBlurWidth;


        //스크린 오버 레이
        if (isScreenOverlayScript == true) {
            ScreenOverlayScript.enabled = true;
        } 
        else {
            ScreenOverlayScript.enabled = false;
        }
        ScreenOverlayScript.intensity = ScreenOverlayintensity;

        if ((int)ScreenOverlayblendMode == 0) {
            ScreenOverlayScript.blendMode = UnityStandardAssets.ImageEffects.ScreenOverlay.OverlayBlendMode.Additive;
        }
        if ((int)ScreenOverlayblendMode == 1) {
            ScreenOverlayScript.blendMode = UnityStandardAssets.ImageEffects.ScreenOverlay.OverlayBlendMode.ScreenBlend;
        }
        if ((int)ScreenOverlayblendMode == 2) {
            ScreenOverlayScript.blendMode = UnityStandardAssets.ImageEffects.ScreenOverlay.OverlayBlendMode.Multiply;
        }
        if ((int)ScreenOverlayblendMode == 3) {
            ScreenOverlayScript.blendMode = UnityStandardAssets.ImageEffects.ScreenOverlay.OverlayBlendMode.Overlay;
        }
        if ((int)ScreenOverlayblendMode == 4) {
            ScreenOverlayScript.blendMode = UnityStandardAssets.ImageEffects.ScreenOverlay.OverlayBlendMode.AlphaBlend;
        }


        //비네팅 처리
        if (isVignetteScript == true) {
            VignetteScript.enabled = true;
        } 
        else {
            VignetteScript.enabled = false;
        }
        VignetteScript.intensity = Vignetteintensity;


        //블룸 처리
        if (isBloomScript == true) {
            BloomScript.enabled = true;
        } 
        else {
            BloomScript.enabled = false;
        }
        BloomScript.intensity = BloomScriptintensity;
        BloomScript.threshold = BloomScriptthreshold;

    }
}

 

 

 

3. 후처리를 제어할 오브젝트나 파티클에 스크립트와 애니메이션을 붙힙니다.

 

 

 

 

4. 애니메이션으로 후처리 효과들을 제어합니다.

 

 

 

 

5. 완료 테스트 영상

 

 

반응형
반응형

유니티 스크립트에서는 기본적으로 쿼터니언을 제공한다.

스크립트 에디터에서 Quternion을 치고 F12를 누르면~ 

 

쿼터니언에 관련된 다양한 함수를 확인할 수 있다.

 

 

이중 SetLookRotation을 활용하면 편리하게 룩앳 함수를 만들수 있다.

 

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {

 [SerializeField] private GameObject targetObj;

 void Start () { 
 }


 void Update () {
  lookTarget(); 
 }

 void lookTarget()
 {
  Quaternion lookAt = Quaternion.identity;    // Querternion 함수 선언
  Vector3 lookatVec = (targetObj.transform.position - transform.position).normalized; //타겟 위치 - 자신 위치 -> 노멀라이즈
  lookAt .SetLookRotation (lookatVec);  // 쿼터니언의 SetLookRotaion 함수 적용
  transform.rotation = lookAt ;   //최종적으로 Quternion  적용
 }
}

위와 같이 코딩을 하고 바라볼 타겟 오브젝트를 지정해 주면 아래처럼 작동하는 룩앳함수가 완성된다.

 

 

 

 

반응형
반응형

빌드 테스트 중에 씬에러 발생으로 빌드를 못하다가 여러가지 뒤져서 알아낸 결과

Bulid Setting - > Player Setting 으로 들어가서

Stripping Level을 Disabled로 변경하자 빌드가 되었다~

원인은 분석중..

 

참고 사이트 - http://westwoodforever.blogspot.kr/2014/02/unityexception-failed-assemblies.html

 

 

반응형
반응형

스크립트 공부중..

회사에서 캐릭터 선택창에서 사용될 카메라 회전 구조를 정리하면서 짠 초기 코드지만, 나름 유니티 스크립트 개념을 깨워준 코딩임..ㅋㅋ

 

using UnityEngine;
using System.Collections;

public class FollowCam : MonoBehaviour
{
 
 public Transform target;     //추적할 타겟 게임 오브젝트의 Transform 변수
 public float dist = 10.0f;     // 카메라와의 일정 거리
 public float height = 5.0f;     // 카메라의 높이 설정
 public float dampRotate = 5.0f;     // 부드러운 회전을 위한 변수
 public float TurnSpeed;
 public float camPos;

 Vector3 V3;
 
 private Transform tr;
 // Use this for initialization
 void Start ()
 {
  //카메라 자신의 transform 컴포넌트를 tr에 할당
  tr = GetComponent<Transform> (); 
  TurnSpeed = 2f;
  camPos = 2f;
 }

 void Update()
 {
  Vector3 PositionInfo = tr.position - target.position;     // 주인공과 카메라 사이의 백터정보
  PositionInfo = Vector3.Normalize (PositionInfo);     // 화면 확대가 너무 급격히 일어나지 않도록 정규화~

  target.transform.Rotate (0, Input.GetAxis ("Horizontal")* TurnSpeed, 0);      //마우스 입력이 감지되면 오브젝트 회전
  transform.RotateAround (target.position, Vector3.right, Input.GetAxis ("Mouse Y")* TurnSpeed);     //마우스 Y(Pitch) 움직임 인지하여 화면 회전
  tr.position = tr.position - (PositionInfo * Input.GetAxis ("Mouse ScrollWheel")* TurnSpeed);     // 마우스 휠로 화면확대 축고
 }
}

 

마우스 회전시 카메라 좌우 회전~

오브젝트에 왼쪽마우스를 찍고 돌리면 회전~

훨로 카메라 확대 축고~ 

 

 

아래는 똑같은 형식의 모바일 터치 구현 스크립트

using UnityEngine;
using System.Collections;

public class TouchCameraControl : MonoBehaviour
{
 public Transform charTarget;
 private Camera cameratarget;
 private Vector2 PrevPoint;

 public float moveSensitivityX = 1.0f;
 public float moveSensitivityY = 1.0f;
 public bool updateZoomSensitivity = true;
 public float orthoZoomSpeed = 0.05f;
 public float minZoom = 1.0f;
 public float maxZoom = 20.0f;
 public float perspectiveZoomSpeed = .5f;
 private Transform tr;

 void Start ()
 {
  //카메라 자신의 transform 컴포넌트를 tr에 할당
  tr = GetComponent<Transform> ();
  cameratarget = Camera.main; 
 }

 void Update()
 {
  Vector3 PositionInfo = tr.position - charTarget.position;
  PositionInfo = Vector3.Normalize (PositionInfo);

  if (updateZoomSensitivity)
  {
   moveSensitivityX = cameratarget.orthographicSize / 5.0f;
   moveSensitivityY = cameratarget.orthographicSize / 5.0f;
  }

  Touch[] touches = Input.touches;

  if (cameratarget)
  {
   if (Input.touchCount == 1) {
    if (Input.GetTouch (0).phase == TouchPhase.Moved) {
     PrevPoint = Input.GetTouch (0).position - Input.GetTouch (0).deltaPosition;

     charTarget.transform.Rotate (0, -(Input.GetTouch (0).position.x - PrevPoint.x), 0);
     cameratarget.transform.RotateAround (charTarget.position, Vector3.right, -(Input.GetTouch (0).position.y - PrevPoint.y)*0.5f);

     PrevPoint = Input.GetTouch (0).position;
    }
   }
  }
  if (cameratarget) {
   if (Input.touchCount == 2)
   {
    Touch touchZero = Input.GetTouch(0);
    Touch touchOne = Input.GetTouch(1);

    Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
    Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;

    float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
    float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;

    float deltaMagnitudediff = prevTouchDeltaMag - touchDeltaMag;

    tr.position = tr.position - -(PositionInfo * deltaMagnitudediff * orthoZoomSpeed);
   }
  }
 }
}

반응형
반응형

최근 마티네 작업중에 카메라밖에 있는 캐릭터일 경우 렌더링 되지 않는 문제가 발견됬습니다.

그것을 해결하기 위해서는 Always Tick PoseAlways Tick Pose And Refresh Bone 으로 설정을 변경해 주시면 해결 됩니다.

 

레벨-> 해당 스켈레탈 캐릭터의 디테일 옵션 -> Skeletal Mesh의 MeshComponent Updata Flag를 보시면

Always Tick Pose로 되어 있습니다.

해당 옵션의 설명을 보면 Always Tick, But Refresh Bone Transforms only when rendered 이라고 설명하고 있습니다.

번역  - 렌더링  경우에는 반드시 하지만 업데이트 에만 변환합니다....  한마디로 렌더링이 되어야 업데이트를 한다는 이야기입니다.

 

이 옵션을 Always Tick Pose And Refresh Bone으로 설정을 변경해 주시면 정상 출력이 되는데요, 이 설정은 렌더링 되든 안되든 항상 본의

트랜스폼을 감지하게 됩니다.

 

당연히 이 옵션이 Always Tick Pose보다 무겁기 때문에 모든 캐릭터에 켜주시면 안되고, 불가피하게 안나오는

캐릭터들에게만 체크해 주시기 바래요^^

반응형
반응형

 

FBX와 언리얼상에 애니메이션 속도가 다르게 재생되는 문제가 발생했습니다.

여러가지 확인해 본 결과 FBX의 샘플링 속도와 엔진에서의 샘플링 속도 차이로 생기는 문제였습니다.

매번 발생하지 않고, 간헐적으로 발생하는 문제인데요~

문제 발생시 Use Default Sample Rate 를 체크하고 임포트 해주면 문제가 해결됩니다.

 

반응형
반응형

시네마팀에서 마티네에서 애니메이션 레이어 기능이 필요하다고 하셔서 내용 연구하여 공유합니다.

 

1. 편집할 애니메이션 선택


 

 

2. 어셋 생성하여 시퀀스 추가

- 첫 포즈만 불러올지 애니메이션을 전체 불러올지 정할 수 있습니다.


 

 

3. 생성된 시퀀스 에서 모션 편집

- 정면을 보면서 걷는 모션을 왼쪽을 보면서 걷는 키를 만들어보고자 합니다.


 

4. 각 메뉴별 상세 설명

(1) 현재 편집 시퀀스 - 현재 모션이 저장될 시퀀스 입니다.

(2) 현재 편집 관절 - 키를 수정할 관절입니다.

(3) 뷰 상에서 로테이션, 포지션, 스케일 키등을 편집후 +키 버튼을 누르면 애니메이션 키가 적용됨

(4) 커브 에디터 - 맥스의 커브 에디터와 거의 흡사하며 그래프의 꺽임등을 수정할 수 있고, 키도 수정할수 있습니다.

(5) Roll , Pitch , Yaw의 각축의 락을 걸어 원하는 축에만 모션을 줄 수 있음

(6) 작업을 하다보면 그래프가 보이지 않는 곳으로 사라질 때가 있는데, 새로 고침이나 모두 표시 버튼을 눌러주면 보기 편하게 정리해 줍니다.

 

- 이미지를 클릭하시면 크게 보실 수 있습니다.

 

작업된 모션 영상

- 원래 정면을 보던 모션인데 위 기능을 활용하여 왼쪽을 바라보며 걷도록 수정해 봤습니다.

 

 

키 작업시 주의사항

- 맥스만큼 편하게 키를 제어하는 것은 어렵습니다.. 당연히 간단한 레이어 포즈 편집용으로만 사용해 주시는게 좋을거 같습니다.

1. 키 복사가 쉽지 않다. 맥스에서는 Shift 버튼으로 간편히 키 복사가 되지만, 언리얼에서는 그래프상에서 수치를 복사하여 사용합니다.

2. 키 편집 공정이 맥스와 매우 달라 숙지하는 시간이 필요함. - 틈틈히 연습이 필요해 보입니다.

3. 오토키(Autokey)가 아닌 셋키(Setkey) 방식입니다~  풀어서 설명하자면 모션을 움직인 후 반드시 키버튼을 눌러줘야 모션이 적용됩니다.

 

반응형
반응형

얼마전에 맥스의 프랍이 언리얼로 넘어가면서 y축으로 90도 돌아가는 문제가 있었습니다.

그 이유를 명확히 이야기 하자면 맥스와 언리얼의 좌표계가 다르기 때문입니다.

맥스 - 오른손 좌표게 , 언리얼 - 왼손 좌표계

 

 

여기서 오른손 좌표계와 왼손 좌표계에 대한 간단한 설명.

3차원 좌표계를 표현할 땐 보통 아래의 그림과 같은

왼손 좌표계(left-handed coordinate system)와 오른손 좌표계(right-handed coordinate system) 두 가지 방법을 이용합니다.

 

오른손 좌표계는 근 100여년 동안 표준적으로 사용되어온 방법입니다. 물리학계를 비롯해 OpenGL 등 대부분은 이 좌표계를 사용합니다.

왼손 좌표계를 사용하는 대표적인 사례로는 Direct3D가 있습니다.

 

이 두 좌표계를 외우는 방법은 다양하겠지만다음과 같은 방법으로 숙지하면 알기 편합니다.

 

  1. 박수 칠 때 처럼 양 손을 마주 보도록 펼친다. 이 때 엄지손가락은 나머지 손가락과 직각이 되도록 한다.

 

  2. 가운뎃손가락을 손바닥에 수직이 되도록 반 쯤 접고, 약손가락과 새끼손가락은 완전히 접는다.

 

  3. 마치 '쌍권총 자세'를 연상케하는 모양이 되었을 것이다.

 

     왼손은 엄지손가락부터 순서대로

 

     [엄지손가락-x, 집게손가락-y, 가운뎃손가락-z]

 

     오른손은 반대 순서로

 

     [엄지손가락-z, 집게손가락-y, 가운뎃손가락-x]

 

     이런 식으로 대응시킬 수 있다.

 

이 상태로 양 손의 집게손가락을 하늘을 향하게 해서 위의 그림에 나온 좌표계와 비교해보시면 똑같은 배치가 되어있음을 알 수 있습니다.

 


추가질문 - 다른 뼈들은 알아서 축정렬이 되서 잘 들어오는데 왜 소켓에 붙은 Prop만 그렇지?

그 이유도 간단합니다. 다른 모든 더미와 뼈들은 맥스에서 제작해서 넘어오기 때문에 알아서 부모축을 따라가면서

문제가 없지만, 무기에 붙은 소켓 더미는 언리얼에서 제작한 것으로 언리얼 원본 좌표를 따라갑니다.

거기서 프랍이 혼란을 갖게 됩니다.

프랍은 맥스에서 만든거라 오른손좌표계로 기억되어 넘어오지만, 언리얼에서 만든 소켓은 왼손 좌표계다 보니

축이 다르게 설정 되는 겁니다.

이 문제를 해결하기 위해서 무기 축 더미를 언리얼 소켓이 아닌 맥스에서 더미를 만들어 사용하게 되면

문제가 해결되는데, 현재 언리얼에서 쓰는 소켓 더미는 엔진상에서 많이 사용되고 있기 때문에 그 상태를 유지하도록 한겁니다.

 

반응형
반응형

*제작한 이펙트가 처음엔 잘 나오다가 시간이 지나면 알파값이 제대로 안빠지고 소팅도 안되는 문제가 발생하고 있습니다.  동시에 프레임도 떨어지고 있습니다. .(갤럭시s5, 넥서스5, G2 모두 발생)

 

 

원본 이펙트

 

 

빌드후 시간이 지나면서 문제가 발생하는 모습

 

 

여러가지 테스트를 거쳐 원인을 파악해 본 결과 파티클에 패너 함수가 적용되면서 값이 누적되면서 발생하는 문제로 추정됩니다.

파트클에도 움직임을 주고 쉐이더에도 움직임을 주면서 뭔가 문제를 발생하는거처럼 보입니다.

패너값을 0으로 놓고 파티클로만 움직임을 주고 빌드를 하면 문제가 발생하지 않습니다.




**이 문제의 해결방안~

이 문제점을 커스텀 UV를 사용하여 해결하였습니다.

기존에는 PANNER를 이미지에 바로 연결하여 UV애니메이션을 적용했는데, 모바일 빌드시에는 텍스처에 바로 연결하지 않고

커스텀 UV를 만들어 연결해줘야 느려지는 현상이 없어집니다.

반응형
반응형

모프타겟을 사용하여 마티네에서 표정애니를 추가할 수 있는 것을 확인했고, 모바일에서도 작동됨을 확인했씁니다.

 

이 작업이 들어가기전에 선행되어야할 중요한 사항.

- 모프타겟은 버텍스 애니라 연산부하가 있어 표정애니 넣을 부분만 파츠로 나눠서 적용한다.
- 얼굴만 따로 분리하니까 2400 poly 정도 됩니다. 몸 전체는 15000개 이상이라 그냥은 절대 넣을수 없음.PC에서도.. 퍼포먼스 테스트를 거친후 무리가 없다고 판단될때 게임에 적용.

 


작업 공정 가이드

1. Max 에서의 작업

맥스의 모퍼 기능을 활용해 필요한 표정들을 셋팅해 준다. 여기서는 테스트로 5가지만 넣었음.
모퍼의 장점은 모델러들에게 부탁해 원하는 표정을 버텍스를 이용하여 퀄리티 있게 마음껏 만들어 사용할 수 있다.

(1) 원본 모델 소스를 여러개로 복사해 원하는 표정의 모델링을 한다.

 

(2) 원본 모델 소스에 복사된 캐릭터들의 표정 데이터를 모퍼에 추가해 준다.

- 0~100의 수치를 조절하여 표정을 다양하게 꾸밀 수 있다.

 

(3) 맥스에서 익스포트시 체크사항

익스포트 스크립트를 사용하시면 안되고, 아래 사항을 체크하셔서 수동으로 익스포트 하셔야 합니다.

 

 

 

2. 언리얼에서 작업
 

(1) 임포트시 체크사항

- 모프타겟 체크


 

 

(2) 메쉬창에 모퍼 리스트가 뜨는지 확인

-맥스에서 지정한 모핑 리스트가 그대로 넘어왔는지 수치를 조절하며 테스트

 

 

(3) 페이셜을 추가할 시퀀스에 표정애니를 추가해줍니다.

- 모핑할 시퀀스를 추가 -> 변수 추가 -> 모퍼네임설정(수동으로 적으면 인덱스로 불러옴) -> 커브 추가로 애니 추가


 

- 커브 유형을 모프커브로 설정해 줍니다~ 설정안해주시면 애니가 안나와요~


 

(4) 마티네에서 표정애니가 추가된 시퀀스를 호출함

 

 

아래는 위 공정을 거쳐 마티네상에 표정애니를 추가해본 테스트 영상입니다.

 

반응형
반응형

1. 발사 모션을 상체에만 적용 시키기 위해 Fire라는 몽타주 애니메이션을 만들어 발사 Anim 파일을 적용시켜 줍니다.

 

 

제작된 몽타주 파일에 발사 모션을 연결해 준뒤 UpperSlot을 생성해 줍니다. - 상체모션만 넣기 위한 준비


 

 

 

2. 준비가 끝나면 Animation Blueprint로 와서 새롭게 코딩을 해줍니다.

- 애니메이션 블루프린트 제작할때 완료했던 모션을 캐시로 저장하여 메모리에 넣어줍니다.

 

 

-저장된 포즈를 불러와 사용합니다.

 

 

- 그 다음 '본 마다 레이어로 블렌드'를 불러옵니다.

 

- 블렌드의 세부 설정으로 가서 어느 본의 몇 번째 부터 블렌드가 시작될건지 정해 줍니다.

 

- 몽타주로 설정한 슬롯을 불러옵니다.

 

- 필요한 노드들이 전부 불러졌으면 최종적으로 연결해줍니다.

 

 

 

 

 

3. Animation Blueprint에서 제작된 것을 Character Blueprint에서 적용하도록 셋팅해 줍니다. 

위 그림처럼만 연결해주면 이제 발사버튼을 누를때 마다 볼사모션이 나옵니다.

이제 마지막으로 물리가 적용된 총알을 만들어 적용시켜 봅시다.

 

 

 

4. MyProjectile 이라는 물리가 적용된 총알을 만들어 줍니다.

물런 블루 프린트로~

 

 

 

5. 마지막으로 모바일 터치에 대한 부분과 총알 발사에 대한 부분을 캐릭터 블루프린트에서 코딩해 줍니다.

여기까지 마무리되면 모든 적용이 끝났습니다~!

언리얼은 계속 공부해 보고 싶은 엔진이긴 합니다..^^

 

적용이 완료되면 배경 오브젝트에 충돌이 일어나며 상하체 모션에 블렌드가 일어납니다~^^

 

 

반응형

+ Recent posts