공부/Game Bootcamp

[멋쟁이사자처럼 부트캠프 TIL] 유니티 게임 개발 3기 : 자료구조(Stack)

Ail_ 2024. 12. 4. 17:45

 

 

Stack

후입선출

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

public class StackNode<T>
{
    public T data;
    public StackNode<T> prev;
}

public class StackCustom<T> where T : new()
{
    public StackNode<T> top;

    public void Push(T data)
    {
        var stackNode = new StackNode<T>();
        stackNode.data = data; // 노드에 data 넣어주고
        stackNode.prev = top; // 이전 top을 새 노드의 prev에 연결 = 이전 top이 노드의 밑(Prev)으로 내려감
        top = stackNode; // top에는 새로운 data(새 노드)가 들어감
    }

    public T Pop() // stack에서 데이터를 제거하는 메서드 -> 제일 위의 것부터 나감(후입선출)
    {
        if (top == null) // top이 비어있으면 stack이 다 비어있는 것
        {
            return new (); // T 타입의 기본 생성자 호출
        }

        var result = top.data; // 제거될 현재 top.data를 result에 담아주고
        top = top.prev; // 새로운 top은 top의 prev 데이터와 이어줌 = 현재 top의 이전 노드를 새로운 top으로 설정

        return result; // 제거된 데이터 return
    }

    public T Peek() // stack의 top 데이터 확인하는 메서드
    {
        if (top == null)
        {
            return new();
        }

        return top.data; // 현재 top의 데이터 반환
    }
}

public class DataStructure : MonoBehaviour
{
    void Start()
    {
        StackCustom<int> stack = new StackCustom<int>();
        stack.Push(1);
        stack.Push(2);
        stack.Push(3);

        stack.Pop();

        Debug.Log(stack.Pop());
        Debug.Log(stack.Peek());
    }
    
}

Pop할 때 2, Peek할 때 1이 찍혔음

 

움직이는 코드 짜기

    [NonSerialized] // public이지만 인스펙터에 노출 안됨, c# class에서는 접근 가능
    public float Speed = 3.0f;
    
    [SerializeField] // private이지만 인스펙터에 노출 됨, c# class에서는 접근 불가
    private float Speed2 = 3.0f;
    

	void Update()
    {
        Vector3 movePos = Vector3.zero;

        if (Input.GetKey(KeyCode.W))
        {
            movePos += transform.forward;
        }
        if (Input.GetKey(KeyCode.S))
        {
            movePos -= transform.forward;
        }
        if (Input.GetKey(KeyCode.A))
        {
            movePos -= transform.right;
        }
        if (Input.GetKey(KeyCode.D))
        {
            movePos += transform.right;
        }
        
        transform.position += movePos.normalized * Speed2 * Time.deltaTime;
    }

 

Stack을 이용해 움직임 취소까지 짜기

ex. 오버워치 트레이서의 시간 돌리는 스킬

    [NonSerialized] // public이지만 인스펙터에 노출 안됨, c# class에서는 접근 가능
    public float Speed = 3.0f;
    
    [SerializeField] // private이지만 인스펙터에 노출 됨, c# class에서는 접근 불가
    private float Speed2 = 3.0f;
    
    private Stack<Vector3> position_stack = new Stack<Vector3>();
    

    void Update()
    {
        // 캐릭터의 이동 방향 저장
        Vector3 movePos = Vector3.zero;

        if (Input.GetKey(KeyCode.W))
        {
            movePos += transform.forward;
        }
        if (Input.GetKey(KeyCode.S))
        {
            movePos -= transform.forward;
        }
        if (Input.GetKey(KeyCode.A))
        {
            movePos -= transform.right;
        }
        if (Input.GetKey(KeyCode.D))
        {
            movePos += transform.right;
        }

        // 이동했던 위치의 기록을 위해 키에서 손을 뗄 때마다 위치 저장
        if (Input.GetKeyDown(KeyCode.W) ||
            Input.GetKeyDown(KeyCode.S) ||
            Input.GetKeyDown(KeyCode.A) ||
            Input.GetKeyDown(KeyCode.D))
        {
            movePos = Vector3.zero; // 이동 위치 기록을 새로 시작
            position_stack.Push(transform.position); // 현재 캐릭터의 위치를 스택에 저장
        }
        
        // 스페이스바 누르면 이전 위치로 되돌아가는 코드
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (position_stack.Count > 0) // 스택에 아무것도 없는데 스페이스바 누르면 오류 -> 버그 방지
            {
                transform.position = position_stack.Pop(); // 스택의 top(가장 마지막에 저장된) 위치 가져오고 삭제
            }
        }
        
        transform.position += movePos.normalized * Speed2 * Time.deltaTime; // 캐릭터 이동 업데이트
    }

 

 

괄호 검사

public bool AreBracketBalanced(string expression)
    {
        Stack<char> stack = new Stack<char>();

        foreach (char c in expression)
        {
            if (c == '(' || c == '[' || c == '{')
            {
                stack.Push(c);
            }
            else if (c == ')' || c == ']' || c == '}')
            {
                if (stack.Count == 0)
                    return false;

                char top = stack.Pop();
                if ((c == ')' && top != '(') ||
                    (c == ']' && top != '[') ||
                    (c == '}' && top != '{'))
                {
                    return false;
                }
            }
        }

        return stack.Count == 0;
    }

 

 

Unity Editor 메뉴 아이템 만들기

using UnityEditor;

public class ScopeChecker : EditorWindow
{
    [MenuItem("Window/Scope Checker")]
    public static void ShowWindow()
    {
        GetWindow<ScopeChecker>("Scope Checker");
    }
}

 

텍스트 input창 추가하기

using UnityEditor;

public class ScopeChecker : EditorWindow
{
    private string _text;
    
    [MenuItem("Window/Scope Checker")]
    public static void ShowWindow()
    {
        GetWindow<ScopeChecker>("Scope Checker");
    }

	// input text 입력란 추가
    private void OnGUI()
    {
        _text = EditorGUILayout.TextField("Text", _text);
    }
}

 

아래처럼 수정할 수도 있음

pixel값을 width랑 height에 준 것

        _text = EditorGUILayout.TextField("Text", _text, GUILayout.Width(300), GUILayout.Height(300));

 

만약 직접적인 값이 없다면 반응형임(창 사이즈에 따라 사이즈 변환)

        _text = EditorGUILayout.TextField("Text", _text, GUILayout.Height(300));

 

아까 작성한 괄호 검사 코드를 넣고 검사 결과에 따라 알림창 띄우기

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

public class ScopeChecker : EditorWindow
{
    private string _text;
    
    [MenuItem("Window/Scope Checker")]
    public static void ShowWindow()
    {
        GetWindow<ScopeChecker>("Scope Checker");
    }

    private void OnGUI()
    {
        _text = EditorGUILayout.TextField("Text", _text, GUILayout.Height(300));
        // _text = EditorGUILayout.TextField("Text", _text);

        // 에디터 상에서 버튼을 누르면 true 반환되어 해당 조건문에 들어감
        if (GUILayout.Button("Check Scope"))
        {
            if (AreBracketBalanced(_text))
            {
                // 알림창 띄우기
                EditorUtility.DisplayDialog("Scope Check", "Scope Check Success", "OK");
            }
            else
            {
                EditorUtility.DisplayDialog("Scope Check", "Scope Check Fail", "OK");
            }
        }
    }
    
    public bool AreBracketBalanced(string expression)
    {
        Stack<char> stack = new Stack<char>();

        foreach (char c in expression)
        {
            if (c == '(' || c == '[' || c == '{')
            {
                stack.Push(c);
            }
            else if (c == ')' || c == ']' || c == '}')
            {
                if (stack.Count == 0)
                    return false;

                char top = stack.Pop();
                if ((c == ')' && top != '(') ||
                    (c == ']' && top != '[') ||
                    (c == '}' && top != '{'))
                {
                    return false;
                }
            }
        }

        return stack.Count == 0;
    }
}

 

잘 뜬다

 

input창 형식을 TextArea로 바꿔주기

        _text = EditorGUILayout.TextArea(_text, GUILayout.Height(300));

 

그럼 줄바꿈도 들어가는 textarea로 되어 보기 더 편함

 

isNullOrEmpty로 타입 검사 가능

if (GUILayout.Button("Check Scope") && string.IsNullOrEmpty(_text))

 

Debug.Assert로 예외 처리

Debug.Assert(false);

 

 

GUI 내용물을 가로(or세로)로 정렬하는 법

    private void OnGUI()
    {
        // 여기부터 GUI 내용물을 가로로 정렬
        EditorGUILayout.BeginHorizontal();
        
        _text = EditorGUILayout.TextArea(_text, GUILayout.Height(300));
        // _text = EditorGUILayout.TextField("Text", _text);
        
        // 에디터 상에서 버튼을 누르면 true 반환되어 해당 조건문에 들어감
        if (GUILayout.Button("Check Scope") && string.IsNullOrEmpty(_text))
        {
            if (AreBracketBalanced(_text))
            {
                // 알림창 띄우기
                EditorUtility.DisplayDialog("Scope Check", "Scope Check Success", "OK");
            }
            else
            {
                EditorUtility.DisplayDialog("Scope Check", "Scope Check Fail", "OK");
            }
        }
        
        // 이 위까지 들어간 GUI 내용물을 가로로 정렬
        EditorGUILayout.EndHorizontal();
    }

 

아래처럼 TextArea랑 Button이 가로로 정렬됨

 

 

HeaderAttribute를 이용해 Inspector 꾸미기

using UnityEngine;

public class LayoutComponent : MonoBehaviour
{
    [Header("Life")]
    public string data1;
    public string data2;
    public string data3;
    
    [Header("Love")]
    public string data4;
    public string data5;
    public string data6;
    
    [Header("Power")]
    public string data7;

}

    [Header("Life"), Tooltip("인생에 관한 변수입니다")]
    public string data1;
    [Tooltip("인생에 관한 변수2입니다")]
    public string data2;
    public string data3;

 

 

Range로 슬라이더 넣기

int나 float 가능

    [Header("Power"), Range(0.1f, 5f)]
    public string data7;

 

LayoutComponent를 수정하기

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

[CustomEditor(typeof(LayoutComponent))] // LayoutComponent.cs에 선언한 LayoutComponent class를 수정
public class LayoutCompEditor : Editor
{
    private SerializedProperty data1Property;
    
    // start()랑 유사하게 Editor가 켜졌을 때 실행됨
    private void OnEnable()
    {
        data1Property = serializedObject.FindProperty("data1"); // 직렬화 : LayoutComponent의 data1 변수를 가져옴
    }
    
    // override : 부모에 있는 함수를 재정의
    public override void OnInspectorGUI() // 기존에 있던 OnIspectorGUI를 override로 재정의
    {
        // base.OnInspectorGUI(); // 부모 object에 있는 OnInspectorGUI를 그대로 사용 = LayoutComponent 그대로 뜸
        
        serializedObject.Update(); // data1을 업데이트 해줌
        
        EditorGUILayout.PropertyField(data1Property);
    }
}

 

기타 다양한 Methods

BeginVertical, BeginToggleGroup, EndScrollView 등을 자주 사용

Foldout : 접기 기능 추가

indentLevel++ : depth 추가

    public override void OnInspectorGUI() // 기존에 있던 OnIspectorGUI를 override로 재정의
    {
        // base.OnInspectorGUI(); // 부모 object에 있는 OnInspectorGUI를 그대로 사용 = LayoutComponent 그대로 뜸
        
        serializedObject.Update(); // data1을 업데이트 해줌
        
        foldState = EditorGUILayout.Foldout(foldState, "Layout")

        if (foldState)
        {
            EditorGUILayout.LabelField("Label"); // label 붙이기

            EditorGUI.indentLevel++; // depth 주기
            EditorGUILayout.PropertyField(data1Property); // 그대로 가져옴
            
            // 이렇게도 가져올 수 있음
            data2Property.stringValue = EditorGUILayout.TextField("Data 2", data2Property.stringValue);
            
            EditorGUI.indentLevel++;
            EditorGUILayout.PropertyField(data3Property); // 그대로 가져옴
            EditorGUI.indentLevel--;
            
            EditorGUILayout.PropertyField(data4Property); // 그대로 가져옴
            EditorGUILayout.PropertyField(data5Property); // 그대로 가져옴
            EditorGUILayout.PropertyField(data6Property); // 그대로 가져옴
            
            EditorGUI.indentLevel--;
        }
    }

 

코테

두 수의 나눗셈

이거 하면서 c# 형변환 방법을 알게됨

using System;

public class Solution {
    public int solution(int num1, int num2) {
        float answer = (num1 / (float)num2) * 1000;
        return (int)answer;
    }
}

 

숫자 비교하기

삼항 쓰는건 똑같네 굿

using System;

public class Solution {
    public int solution(int num1, int num2) {
        return num1 == num2 ? 1 : -1;
    }
}

 

 

는 분수 덧셈에서 막혀서 정답률 높은 순으로 다시 진행;

이미 이전에 푼 사칙연산까지 8개 풀었길래 각도기 추가로 풀어봄

 

각도기

각도기 문제도 조건이 정해져 있어서 삼항으로 가능하길래 삼항으로 풀었음

using System;

public class Solution {
    public int solution(int angle) {
        
        return angle < 90 ? 1 : angle == 90 ? 2 : angle < 180 ? 3 : 4;
    }
}

 

삼항 처음 쓸 땐 진짜 헷갈렸는데 이제 익숙해지니 안헷갈림

얼른 C#과 Unity에도 익숙해져야만...