[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 34회차 미션 시작합니다.

04. 배틀로얄 - 13, 14 번을 진행합니다.




EffectTool Script 생성하여 진행합니다 ^^~


using UnityEngine;
using UnityEditor; // Editor Tool을 만들기 위해 필수
using System.Text;
using UnityObject = UnityEngine.Object;

// Clip Property를 수정하고, 읽고, 저장하는 기능.
public class EffectTool : EditorWindow
{
    //UI 그리는데 필요한 변수들.
    pubic int uiWidthXLarge = 450; // pixel 크기.
    pubic int uiWidthLarge = 300;
    pubic int uiWidthMiddle = 200;
    private int selection = 0; // 선택 index
    private Vector2 SP1 = Vector2.zero;
    private Vector2 SP2 = Vector2.zero;
}

여기까지가 기본적인 처리를 위한 준비 과정을 마친 상태이고, 이제 본격적으로 이팩트 툴 작성에 들어갑니다 ^^


 



public class EffectTool : EditorWindow
{
    // 위의 변수들..
    
    // 이팩트 클립
    private GameObject effectSource = null;
    // 이팩트 데이터
    private static EffectData effectData;
    
    [MenuItem("Tools/Effect Tool")]  // 유니티에 메뉴 생성
    static void Init() {
        effectData = ScriptableObject.CreateInstance<EffectData>();
        effectData.LoadData();
        
        EffectTool window = GetWindow<EffectTool>(false, "Effect Tool");
        window.Show();
    }
    
    private void OnGUI() {
        if (effectData == null) return;
        
        EditorGUILayout.BeginVertical();
        {
            // 상단 - add, remove, copy
            UnityObject source = effectSource;
            EditorHelper.EditorToolTopLayer(effectData, ref selection, ref source, this.uiWidthMiddle);
            effectSource = (GameObject)source;
            
            EditorGUILayout.BeginHorizontal();
            {
                // 중단 - data list
                EditorHelper.EditorToolListLayer(ref SP1, effectData, ref selection, ref source, this.uiWidthLarge);
                effectSource = (GameObject)source;
                
                // 설정 부분
                EditorGUILayout.BeginVertical();
                {
                    SP2 = EditorGUILayout.BeginScrollView(this.SP2);
                    {
                        if (effectData.GetDataCount() > 0) {
                            EditorGUILayout.BeginVertical();
                            {
                                EditorGUILayout.Separator();
                                EditorGUILayout.LabelField("ID", selection.ToString(), GUILayout.Width(uiWidthLarge));
                                effectData.names[selection] = EditorGUILayout.TextField("이름.", effectData.names[selection], GUILayout.Width(uiWidthXLarge));
                                effectData.effectClips[selection].effectType = (EffectType)EditorGUILayout.EnumPopup("이팩트 타입.", effectData.effectClips[selection].effectType, GUILayout.Width(uiWidthLarge));
                                EditorGUILayout.Separator();
                                if (effectSource == null && effectData.effectClips[selection].effectName != string.Empty) {
                                    effectData.effectClips[selection].PreLoad();
                                    effectSource = Resources.Load(effectData.effectClips[selection].effectPath + effectData.effectClips[selection].effectName) as GameObject;
                                }
                                effectSource = (GameObject)EditorGUILayout.ObjectField("이팩트", this.effectSource, typeof(GameObject), false, GUILayout.Width(uiWidthXLarge));
                                if (effectSource != null) {
                                    effectData.effectClips[selection].effectPath = EditorHelper.GetPath(this.effectSource);
                                    effectData.effectClips[selection].effectName = effectSource.name;
                                }
                                else {
                                    effectData.effectClips[selection].effectPath = string.Empty;
                                    effectData.effectClips[selection].effectName = string.Empty;
                                    effectSource = null;
                                }
                                EditorGUILayout.Separator();
                            }
                            EditorGUILayout.EndVertical();
                        }
                    }
                    EditorGUILayout.EndScrollView();
                }
                EditorGUILayout.EndVertical();
            }
            EditorGUILayout.EndHorizontal();
        }
        EditorGUILayout.EndVertical();
        
        EditorGUILayout.Separator();
       
        // 하단
        EditorGUILayout.BeginHorizontal();
        {
            if (GUILayout.Button("Reload Settings")) {
                effectData = CreateInstance<EffectData>();
                effectData.LoadData();
                selection = 0;
                this.effectSource = null;
            }
            if (GUILayout.Button("Save")) {
                EffectTool.effectData.SaveData();
                CreateEnumStructure();
                AssetDatabase.Refresh(ImportAssetOptions.ForceUpdate);
            }
        }
        EditorGUILayout.EndHorizontal();
    }
    
    public void CreateEnumStructure() {
        string enumName = "EffectList";
        StringBuilder builder = new StringBuilder();
        builder.AppendLine();
        for (int i = 0; i < effectData.names.Length; i++) {
            if (effectData.names[i] != string.Empty) {
                builder.AppendLine("   " + effectData.names[i] + " = " + i + ",");
            }
        }
        EditorHelper.CreateEnumStructure(enumName, builder);
    }
}


또 EffectData가 VisualStudio에서 오류를 발생하네요.. 전체 컴파일이라거나 namespace 등의 문제가 아닐까 싶긴합니다만..
관련 변수, 함수들 인텔리전스가 지원되지 않으니 강의하면서 답답하실 듯 하네요.. 미리 확인이 되지 않았던건가 하는 아쉬움은 남네요.
어여 해결이 되어서 뒤의 내용들에서는 수월하게 진행될 수 있기를 바랍니다.




드디어 위에까지 작업을 하면 위와 같이 Tools - Effect Tool 이라는 메뉴가 생성된 것을 볼 수가 있습니다..


 



그리고 클릭을 해보면 와우~ Vertical, Horizontal 함수를 열심히 사용하여 만든 "Effect Tool" 창이 나타나고, 지금까지 작업한대로 버튼들과 리스트컨트롤이 하나 붙어서 보이는 것을 확인할 수 있습니다.

Unity UI 제작을 일일이 Typing으로 하니 소스보기가 좀 복잡하네요 ㅎㅎ. 좀더 복잡한 UI를 가지는 사운드 툴은 확실히 정신이 없을 듯하네요..

Unity UI 제작을 지원하는 Addon 등이 없나 궁금해지네요.

 

궁금해서 검색해보니 Unity UI Addons를 누가 만들어 배포하고 있긴 하네요 ^^. 물론 안정성이나 검증되지 않아 사용하지 않으셨는지는 모르겠습니다. 나중에 시간이 되면 사용해 봐야겠습니다.

 


아직 버그들이 있기에 좀더 수정 작업을 진행하게 되고, 다음 시간에는 데이터 매니저와 이팩트 매니저를 만들어서 전체 EffectTool이 구동되는 것을 확인하고, 바로 이어서 사운드툴 제작으로 들어가겠습니다 ^^~





<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 33회차 미션 시작합니다.

04. 배틀로얄 - 11, 12 번을 진행합니다.


저번 시간에 이어 Save, Add, Remove, Copy 등의 구현을 시작합니다.




아래 진행하는 코드 중 ArrayHelper라는 클래스는 제공되는 프로젝트에 포함되어 있는 코드를 사용합니다.
Array 기능을 좀더 쉽게 사용할 수 있는 기능을 가진 함수 모음 클래스라고 생각하면 됩니다.
크게 중요하지 않은 내용이므로 제공된 소스를 사용만 하면 됩니다. Tool용 ArrayHelper 입니다.



public class EffectData : DataBase
{
    // 저번시간코드들..
    
    public void SaveData() {
        using (XmlTextWriter xml = new XmlTextWriter(xmlFilePath + xmlFileName, System.Text.Encoding.Unicode) {
            xml.WriteStartDocument();
            xml.WriteStartElement(EFFECT); // EFFECT Key
            xml.WriteElementString("length", GetDataCount().ToString());
            for (int i = 0; i < this.names.Length; i++) {
                EffectClip clip = this.effectClips[i];
                xml.WriteStartElement(CLIP);  // CLIP Key
                xml.WriteElementString("id", i.ToString());
                xml.WriteElementString("name", this.names[i]);
                xml.WriteElementString("effectType", clip.effectType.ToString());
                xml.WriteElementString("effectPath", clip.effectPath);
                xml.WriteElementString("effectName", clip.effectName);
                xml.WriteEndElement(); // CLIP End
            }
            xml.WriteEndElement(); // EFFECT End
            xml.WriteEndDocument();
        }
    }
   
    public override int AddData(string newName) {
        if (this.names == null) {
            this.names = new string[] { name };
            this.effectClips = new EffectClip[] { new EffectClip() };
        }
        else {
            this.names = ArrayHelper.Add(name, this.names);
            this.effectClips = ArrayHelper.Add(new EffectClip(), this.effectClips);
        }
        return GetDataCount();
    }
    
    public override void RemoveData(int index) {
        this.names = ArrayHelper.Remove(index, this.names);
        if (this.names.Length == 0) this.names = null;
        this.effectClips = ArrayHelper.Remove(index, this.effectClips);
    }
    
    public void ClearData() { // Bonus function
        foreach (EffectClip clip in this.effectClips) {
            clip.ReleaseEffect();
        }
        this.effectClips = null;
        this.names = null;
    }
    
    public EffectClip GetCopy(int index) {
        if (index < 0 || index >= this.effectClips.Length) return null;
        EffectClip original = this.effectClips[index];
        EffectClip clip = new EffectClip();
        clip.effectFullPath = original.effectFullPath;
        clip.effectName = original.effectName;
        clip.effectType = original.effectType;
        clip.effectPath = original.effectPath;
        clip.realId = this.effectClips.Length;
        return clip;
    }
    
    /// <summary>
    /// 원하는 인덱스를 프리로딩해서 찾아준다
    /// </summary>
    public EffectClip GetClip(int index) {
        if (index < 0 || index >= this.effectClips.Length) return null;
        effectClips[index].PreLoad();
        return effectClips[index];
    }
    
    public override void Copy(int index) {
        this.names = ArrayHelper.Add(this.names[index], this.names);
        this.effectClips = ArrayHelper.Add(GetCopy(index), this.effectClips);
    }
}


여기까지가 EffectData 에 대한 처리 마무리였고 이제부터 본격적인 Tool 개발에 들어갑니다 ^^~





공통 툴 레이어 작성입니다.

본격적인 EffectTool 제작 전에, 공통 툴을 만들어두면 다른 툴을 만들때에도 재사용 가능하기 때문에 제작해두는 것이 좋습니다.




EditorHelper 클래스입니다.

ArrayHelper와 비슷하게 Tool 개발을 용이하게 해주는 함수들의 모음이며 프로젝트에 포함되어 배포되어 있습니다.

다음의 2가지 함수는 구현되어 있고, 나머지 함수들을 추가 구현합니다.

+ GetPath() - Resource를 전달하면 Resource의 경로를 리턴해주는 함수
+ CreateEnumStructure() - Enum 객체를 만들어주는 함수

 


public class EditorHelper
{
    public static string GetPath(UnityEngine.Object p_clip) {}
    public static void CreateEnumStructure(string enumName, StringBuilder data) {}
    
    // Tool UI 상단
    public static void EditorToolTopLayer(BaseData data, ref int selection, ref UnityObject source, int uiWidth) {
        EditorGUILayout.BeginHorizontal();
        {
            if (GUILayout.Button("ADD", GUILayout.Width(uiWidth))) {
                data.AddData("New Data");
                selection = data.GetDataCount() - 1; // 최종 Item 선택
                source = null;
            }
            if (GUILayout.Button("COPY", GUILayout.Width(uiWidth))) {
                data.Copy(selection);
                source = null;
                selection = data.GetDataCount() - 1;
            }
            if (data.GetDataCount() > 1) {
                if (GUILayout.Button("REMOVE", GUILayout.Width(uiWidth))) {
                    source = null;
                    data.RemoveData(selection);
                }
            }
            
            if (selection > data.GetDataCount() - 1) { // out of range
                selection = data.GetDataCount() - 1;
            }
        }
        EditorGUILayout.EndHorizontal();
    }
    
    // Tool UI 리스트
    public static void EditorToolListLayer(ref Vector2 ScrollPosition, BaseData data, ref int selection, ref UnityObject source, int uiWidth) {
        EditorGUILayout.BeginVertical(GUILayout.Width(uiWidth);
        {
            EditorGUILayout.Separator();
            EditorGUILayout.BeginVertical("box");
            {
                ScrollPosition = EditorGUILayout.BeginScrollView(ScrollPosition);
                {
                    if (data.GetDataCount() > 0) {
                        int lastSelection = selection;
                        selection = GUILayout.SelectionGrid(selection, data.GetNameList(true), 1);
                        if (lastSelection != selection) { // 선택이 바뀌었으면
                            source = null;
                        }
                    }
                }
                EditorGUILayout.EndScrollView();
            }
            EditorGUILayout.EndVertical();
        }
        EditorGUILayout.EndVertical();
    }
}


다음시간에 공통 툴 레이어 완성 및 본격적인 EffectTool 만들기를 시작합니다 ^^~




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 32회차 미션 시작합니다.

04. 배틀로얄 - 09, 10 번을 진행합니다.


저번에 이어서 이번에는 EffectData 클래스를 구현합니다.




코드 내용중 clipPath는 위에서 보듯이 Unity Project에서 "9.ResourcesData/Resources/Prefabs/Effects" 폴더를 가리킵니다.




마찬가지로 코드 내용중 dataPath는 위에서 보이는 Unity Project에서 "9.ResourcesData/Resources/Data" 폴더를 가리킵니다.



using System.Xml; // XML 사용.
using System.IO; // 읽기 쓰기.
/// <summary>
/// 이팩트 클립 리스트와 이팩트 파일 이름과 경로를 가지고 있으며 파일을 읽고 쓰는 기능을 가지고 있다.
/// </summary>
public class EffectData : BaseData
{
    // Array는 실수로 무한정 커질 수 있는 문제를 가지고 있어서 한정된 자원이라는 의미와 크기 명확성을 위해 배열을 사용.
    public EffectClip[] effectClips = new EffectClip[0];
    
    public string clipPath = "Effects/";
    private string xmlFilePath = ""; // Path, File 분리이유는 경로만 바꾸어 파일관리, 버전관리가 용이하며, 특정 Asset 폴더로 관리하는 등 유연하기 때문.
    private string xmlFileName = "effectData.xml";
    private string dataPath = "Data/effectData";
    //XML 구분자.
    private const string EFFECT = "effect"; // 저장 KEY
    private const string CLIP = "clip"; // 저장 KEY
    
    private EffectData() { }
    
    // 읽어오고 저장하고, 데이터를 삭제하고, 특정 클립을 얻어오고, 복사하는 기능.
    public void LoadData() {
        Debug.Log($"xmlFilePath = {Application.dataPath} + {dataDirectory}");
        this.xmlFilePath = Application.dataPath + dataDirectory; // Application.dataPath = Unity Assets Folder
        TextAsset asset = (TextAsset)ResourceManager.Load(dataPath);
        if (asset == null || asset.text == null) { // 하나도 없는 경우
            this.AddData("new Effect");
            return;
        }
        
        using (XmlTextReader reader = new XmlTextReader(new StringReader(asset.text))) {
            int currentID = 0;
            while (reader.Read()) {
                if (reader.IsStartElement()) {
                    switch (reader.Name) {
                        case "length":
                            int length = int.Parse(reader.ReadString());
                            this.names = new string[length];
                            this.effectClips = new EffectClip[length];
                            break;
                        case "id":
                            currentID = int.Parse(reader.ReadString());
                            this.effectClips[currentID] = new EffectClip();
                            this.effectClips[currentID].realId = currentID;
                            break;
                        case "name":
                            this.names[currentID] = reader.ReadString();
                            break;
                        case "effectType":
                            this.effectClips[currentID].effectType = (EffectType)Enum.Parse(typeof(EffectType), reader.ReadString());
                            break;
                        case "effectName":
                            this.effectClips[currentID].effectName = reader.ReadString();
                            break;
                        case "effectPath":
                            this.effectClips[currentID].effectPath = reader.ReadString();
                            break;
                    }
                }
            }
        }
    }
}


초반 개발시에는 데이터 파일 저장 포맷이 지속적으로 바뀌게 될텐데 이때부터 json을 사용해도 되지만 json을 사용하게 되는 경우 무언가 잘못되어 꼬이게 되는 경우 저장 구조가 복잡하여 해당 문제점을 찾기가 어렵다는 단점이 있습니다.
그래서 초반에는 xml과 같은 명확하고 찾기 쉬운 데이터 파일 구조를 사용하다가 어느 정도 안정화되면 json으로 변경하는 것도 좋은 방법입니다.

사실 실무에서는 자체 개발된 데이터 파일 구조를 많이 사용한다고 합니다.




위와 같이 $를 붙여서 사용하면 좀더 편하게 문자열 구조를 사용할 수 있습니다.
지원되지 않았을 때는 아래 문장처럼 +를 마구마구 써서 연결해야 했던 불편함이 컸었지요.

C#은 정말 개발자 편의성을 정말 많이 고려한 것 같습니다.

 


그런데 왜 1.2x를 없앤거죠?? 1x 1.5x 2x만 나오는... 갑자기 오늘부터 지원을 안하네요.. 라고 하고 있는데 잠시 그랬던거 같네요 ^^;;;
다시 불러와서 재생하니 정상적으로 나오네요.. 서버 작업중이신건가 ㅎㅎ
헉.. 그런가보네요.. 갑자기 저화질로 나오고 서버 일시오류라고 뜨며 멈추고 튕기기도 하네요 ㅠ.,ㅜ;; 무어지.. 뜨합..
어여 안정화시켜주세요..




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts