public void AddTwoNumbersResult(int x, int y, int result) { Debug.Log("C# - Debug.Log : " + x + " + " + y + " = " + result); // Visual Script에서 값을 받아서 출력함. } }
C#에서 10, 20 숫자 두개를 받아서 로그로 남긴다.
두 숫자를 더해서 C# 함수로 값을 넘겨주면서 함수 AddTwoNumbersResult()를 실행한다.
실행해서 Console을 보면 C#과 비주얼 스크립트에서 남긴 Debug.Log를 각각 볼 수 있다.
Formula(공식)은 수식과 인수의 목록을 이용하여 작성된 논리식와 수학식을 평가하는 강력한 유닛이다.
참고: 이진 트리를 순회해야 하는 과부담 때문에(캐슁을 사용함에도 불구하고), Formular 유닛을 사용할 경우 성능이 일반 연산자 유닛들을 개별적으로 사용하는 것보다 성능이 상당히 떨어지게 된다. 따라서 매 프레임에서 이 유닛들이 사용되는 것은 피하는 것이 바람직하다.
일반적으로 Formula는 참/거짓의 논리 값이나 수학의 숫자를 반환하지만, 실제로는 어떤 타입의 값도 반환할 수 있다.
해더의 첫 번째 텍스트 필드에는 Formula 자신이 들어가 있다.
두 번째 텍스트 필드는 인수의 수이다.기본적(default)으로 2가 설정되어 있는데, 이는 입력으로 A와 B를 제공한다는 뜻이다. Formula은 최대 10개의 인수를 가질 수 있으며, 항상 알파벳 순으로 정렬된다. 즉, B, C, D, E 등으로 호출된다.
예를 들어, 아래 Formula는 다음의 두 가지 내용이 맞는지(참인지)에 대한 Boolean 값을 반환한다.
시간이 10초를 경과하였는지 여부 그리고
현재의 게임 오브젝트 이름이 "Player"인지 여부
Arguments(인수들)
변수 이름(Variable Names)
변수 이름들은 Formula에 직접적으로 사용할 수 있다.예를 들어, 'health"라는 이름의 graph 변수는 'health > 50'이라는 공식으로 작성해서 불리언(참/거짓) 값을 리턴 받을 수 있다. 인수 이름은 다음과 같은 우선 순위로 평가된다.
알파벳 인수 이름(a - z)
Graph 변수 이름
Object 변수 이름
Scene 변수 이름
Application 변수 이름
Saved 변수 이름
속성(Properties)과 메서드(Methods)
[arg.prop] 표기법을 사용하여 인수 또는 변수에 대한 속성 값을 확인한다.예를 들어 만약, Position이 Vector3 Object 변수인데 그 값이 0인지 체크하고자 한다면 [position.x] = 0라고 하면 된다. [arg.Method()] 표기법을 사용하여 매개 변수가 없는 메서드의 반환값을 얻을 수도 있다.
참고: AOT 사전 빌드(pre-build)에서는 이름으로만 액세스되는 멤버에 대해 스텁(stubs)을 생성할 수 없기 때문에 속성 및 메서드에 액세스하는 것이 AOT 플랫폼과 호환된다고 확신할 수는 없다.
Literals(리터럴스)
고정 값(fixed value)을 할당하려면 다음 리터럴(literal)을 사용하십시오.
리터럴설명예
Literal
Description
Example
Number
integer 또는 float
3.5
String
어포스트로피로 감싸진 텍스트
"Hello World!!"
Boolean
부울 값
true, false
Null
null 상수
a != null
Delta time
Unity 프레임 델타 시간
30 * dt
Invert Delta Time
델타 시간의 역.
30/second
Operators(연산자)
모든 논리 연산자와 수학 연산자는 Formula에 사용가능하다. 스크립트에서 사용자 정의 연산자(custom operator)를 통해 정의된 연산자도 사용할 수 있다.
변수(variable) 유닛은 6종류가 있다.이들 변수 유닛 각각은 세 가지 오브젝트 유닛이 존재한다..
Get : 변수 값 인출하기(얻기)
Set : 변수에 새로운 값 할당하기(저장하기)
Is Defined : 변수가 정의되어 있는지 체크하기
이것들은 퍼지 파인더의 변수 분류(Variables category)에 위치한다.
변수 유닛들은 청녹색을 띄고 있다.
동적 형변환(Dynamic Typing)
Get / Set 유닛들에 대해 변수들은 정적으로 입력되지 않는다. 즉 변수 타입이 실시간으로 변경될 수 있음을 의미한다. 그 타입이 Blackboard 창에 정의되면 오브젝트 처럼 표시된다.
Get Variable - 변수에서 값 얻기
get 변수 유닛은 입력으로서 변수의 이름이 필요하며, 출력으로Value를 반환한다.
Set Variable - 변수에 값 할당(저장) 하기
set 변수 유닛은 변수의 이름과 변수에 할당될 새로운 값이 필요하다.레이아웃은 편의를 위해 출력으로 동일한 값을 반환한다.
참고: 변수에 값이 할당되는 타임을 잡는 제어 입력 포트는 연결이 반드시 있어야 한다. 선택적으로 제어 출력 포트는 저장(set) 이후에 어떤 작업을 할 것인가에 따라 연결할 수 도 있다.
아직 생성되지 않은 변수 이름으로 set 할 수도 있다. 그러면 자동으로 생성된다.
Is Variable Defined - 변수가 정의되어 있는지 체크하기
is variable defined 유닛은 변수의 이름을 입력으로 받아서 그 이름의 변수가 있는지 없는지를 판단 후 Is Defined boolean 으로 반환(출력)한다. 변수가 생성되었는지 확인하는 데 유용하며, 생성되지 않은 경우에는 fallback 값을 제공하는 경우가 많다.(역자 주 - 아래의 경우 score 변수가 있다면 score 값을 출력하고 없다면 0 값을 출력한다.)
참고: Get Variable 유닛을 선택 하고 그래프 인스펙터에서 Fallback 체크 박스에 체크를 하면 더 쉽게 같은 작업을 수행할 수 있다. 이렇게 하면 변수가 정의되지 않은 경우 반환되는 유닛에 fallback 입력이 추가된다.
동적 변수(Dyanmic Variables)
변수의 이름은 표준 값 입력 포트이기 때문에 문자열(string)을 반환하는 다른 포트에 연결할 수도 있다."동적 변수"이기 때문에 플레이 모드가 실행중에는 변수가 변경된다.(역자주 - int 변수를 string 변수에 연결하면 string 변수가 플레이 모드일 때 int 변수로 변경된다.)
오브젝트 변수(Object Variables)
오브젝트 변수 유닛은 추가적인 입력 포트가 있다. 그 포트에는 참조하고 있는 변수가 정의된 게임 오브젝트를 연결해야 한다. 기본적으로는 현재 오브젝트(self, This)를 바라보도록 설정되어져 있다.
예를 들어 Get Variable 유닛은 플레이어2 오브젝트에 있는 health 변수의 값을 얻는다.
드롭다운(Dropdowns)
변수의 종류와 이름은 드롭다운 메뉴를 통해 빠르게 세팅할 수 있다. 이름의 우선 표시는 맥락상황에 따라 달라지는데 해당 종류의 변수가 존재하는 상황인가 그리고 현재 그래프에 다른 변수 유닛이 있는 상황인가에 기반한다.
드래그 앤 드랍(Drag and Drop)
동일한 유닛을 그래프에 직접 만들려면 블랙보드 창의 변수 항목을 그래프 창에 직접 끌어다 놓으면 된다.
기본적(default)으로 Get 유닛이 생성된다.
Alt 키를 누른 경우 Set 유닛이 생성된다.
Shift 키를 누른 경우 Is Defined 유닛이 생성된다.
변수 API(Variables API)
비주얼 스크립팅은 변수를 쉽게 다룰 수 있는 API를 제공하고 있다. 즉, get, set 그리고 is variable defined를 쉽게 처리할 수 있다. 이러한 모든 처리는 변수 클래스(Variables class)에서 가능하다.
예를 들면 다음과 같다.
Variables.Application.Set("score", 100);
Usings
API 액세스하기 위해서는 C# 스크립트에 다은의 using을 추가해야 한다.
using Unity.VisualScripting;
Scope
Graph(그래프) - Graph Variable
그래프 상에서만 액세스되는 변수에 액세스하려면, Graph 변수를 만들면 된다. 이것은 기본적으로 루트 머신에서 Graph 영역으로 한정된(중첩된) 경로만 지칭하게 된다.
며신 상에서 루트 Graph를 가져오려면:
var graphReference = GraphReference.New(flowMachine, true);
중접 Graph에 액세스하려면 추가 매개변수들로써 부모 노드를 전달한다;
var graphReference = GraphReference.New(flowMachine, new IGraphParentElement[] { superUnit }, true);
Graph 참조를 전달하는 방법;
Variables.Graph(graphReference)
Object(오브젝트) - Object Variable
Object에 존재하는 변수에 엑세스하려면:
Variables.Object(gameObject)
Scene(씬) - Scene Variable
Scene 변수에 액세스하려면 다음 중 하나를 수행한다.
Variables.Scene(scene)
또는:
Variables.Scene(gameObjectInScene)
또는:
Variables.ActiveScene
Application(어플리케이션) - Application Variable
Application 변수에 액세스하려면:
Variables.Application
Saved(저장) - Save Variable
저장된 변수에 액세스하려면:
Variables.Saved
활용(Operations)
아래 예에서 소문자 scope는 이전 scope 중 하나를 가리킨다.
Get
변수 값을 가져오려면 변수이름과 Get 메서드를 사용한다.
scope.Get("name");
변수는 엄격한 타입으로 사용되고 있지 않음에 주목하자. 즉, 아래 예시처럼 수동으로 캐스팅될 필요가 있다.
int health = (int)Variables.Object(player).Get("health")
Set
변수의 값을 설정하려면 변수 이름, Set 메서드 그리고 값 매개 변수를 사용한다.
scope.Set("name", value);
예를 들면 다음과 같다.
Variables.Object(player).Set("health", 100);
변수는 엄격한 타입이 아니기 때문에 2번째 매개 변수로는 어떤 값도 전달할 수 있다. 심지어 현재 변수와 타입이 같지 않다도 된다.
참고: 현재 존재하지 않는 변수 이름에 Set 메서드를 사용하면 새로운 변수가 자동으로 정의된다.
Is Defined
변수가 정의되었는지 확인하려면 변수이름과 IsDefined 메서드를 사용한다.
scope.IsDefined("name");
예를 들면 다음과 같다.
if (Variables.Application.IsDefined("score")) { // ... }
이벤트 유닛은 프로그램이 실행되는 진입점으로 OnStart, OnUpdate, OnButton과 같은 것들이 있다.다른 비주얼 스크립트 그래프가 이벤트를 전송할 수 있도록 하려면Event Sender 유닛을 생성해야 한다.
사용자 정의 비주얼 이벤트(custom visual event)를 생성하는 프로세스는 다음과 같다.
단순한 custom event sender 유닛을 만든다.
프로그래밍을 통해(C# 스크립트 내에) 간단한 custom event를 트리거하는 스크립트를 생성한다.
custom event를 수신하는 스크립트를 생성한다.
단순한 Custom Event 유닛 만들기
빈 이벤트 단위를 만들려면:
프로젝트에서 마우스 오른쪽 버튼을 클릭하고Create>C# Script로 새로운 C# File 생성한다. 리네임하여 이벤트 유닛 이름으로 변경한다(예, MyEventNode.cs).
다음 코드를 스크립트에 복사, 붙여넣하고 저장한다.
usingUnity.VisualScripting; usingUnityEngine;
//Registering a string name for your custom event to hook it to an event. You can save this class in a separated file and add multiple events to it as public static strings. publicstaticclassEventNames { publicstaticstringMyCustomEvent ="MyCustomEvent"; }
[UnitTitle("On my Custom Event")]//Custom EventUnit to receive the event. Adding On to the unit title as an event naming convention. [UnitCategory("Events\\MyEvents")]//Setting the path to find the unit in the fuzzy finder in Events > My Events. publicclassMyCustomEvent : EventUnit<int> { [DoNotSerialize]// No need to serialize ports. publicValueOutput result { get;privateset; }// The event output data to return when the event is triggered. protectedoverrideboolregister=>true;
// Adding an EventHook with the name of the event to the list of visual scripting events. publicoverride EventHookGetHook(GraphReference reference) { returnnewEventHook(EventNames.MyCustomEvent); } protectedoverridevoidDefinition() { base.Definition(); // Setting the value on our port. result = ValueOutput<int>(nameof(result)); } // Setting the value on our port. protectedoverridevoidAssignArguments(Flow flow,intdata) { flow.SetValue(result, data); } }
Edit>Project Settings을 선택한다. Project Settings 창이 나타난다.
그래프에 새 이벤트 유닛을 추가하려면 스크립트 그래프의 백그라운드에 마우스 오른쪽 버튼을 클릭한다. 퍼지 파인더가 나타난다.
Events>My Events>On my custom Event를 선택한다. Script Graph 창이 나타난다.그래프가 아래 처럼 보여지게 된다. .
이벤트를 수신하였는지 확인하기 위해 event 값을 콘솔에 출력하도록 하는 Debug log 메시지를 출력하는 예는 아래와 같다.
Event Sender 유닛 만들기
사용자가 다른 그래프에서 이벤트 유닛을 트리거할 수 있도록 하려면 Event Sender 유닛을을 생성해야 한다.이 유닛은 다른 그래프에서 이벤트를 방출하고 다른 그래프에 있는 이벤트에 값을 전달할 수 있다.
C# 스크립트에서 이벤트를 트리거할수도 있다.
프로젝트에서 마우스 오른쪽 버튼을 클릭하고Create>C# Script로 새로운 C# File 생성한다. 리네임하여 이벤트 유닛 이름으로 변경한다(예, SendMyEventNode.cs).
다음 코드를 스크립트에 복사, 붙여넣하고 저장한다.
using Unity.VisualScripting;
//Custom Unit to send the event [UnitTitle("Send My custom Event")] [UnitCategory("Events\\MyEvents")]//Setting the path to find the unit in the fuzzy finder in Events > My Events. publicclass SendMyEvent : Unit { [DoNotSerialize]// Mandatory attribute, to make sure we don’t serialize data that should never be serialized. [PortLabelHidden]// Hiding the port label as we normally hide the label for default Input and Output triggers. public ControlInput inputTrigger { get; private set; } [DoNotSerialize] public ValueInput myValue; [DoNotSerialize] [PortLabelHidden]// Hiding the port label as we normally hide the label for default Input and Output triggers. public ControlOutput outputTrigger { get; private set; }
protected override void Definition() { inputTrigger = ControlInput(nameof(inputTrigger), Trigger); myValue = ValueInput<int>(nameof(myValue),1); outputTrigger = ControlOutput(nameof(outputTrigger)); Succession(inputTrigger, outputTrigger); } //Sending the Event MyCustomEvent with the integer value from the ValueInput port myValueA. private ControlOutput Trigger(Flow flow) { EventBus.Trigger(EventNames.MyCustomEvent, flow.GetValue<int>(myValue)); return outputTrigger; } }
Edit > Project Settings을 선택한다. Project Settings 창이 나타난다.
그래프에 새로운 이벤트 유닛을 추가하려면 스크립트 그래프의 백그라운드에서 마우스 오른쪽 버튼을 클릭하고 Events> My Enents>Send My Custom Event를 선택한다.
Space 바를 눌렀다가 놓을 때 SendMyCustomEvent를 보내는 예는 아래와 같다.
참고:Send My Custom Event유닛과On My CustomEvent유닛을 테스트하려면 다음 단계를 모두 수행한다.
custom evnet unit을 생성한다.
event sender 유닛을 생성한다.
Key up시 Event를 보내는 GameObject 추가
Key up시 Event를 보내기 위한 GameObject를 추가하려면;
Hierarchy 윈도우에서 빈GameObject를 새로 만들고 이름을 EventSender로 변경한다.
Script Machine 컴포넌트를 추가한다.
스크립트 머신 컴포넌트에 새로운 스크립트 그래프를 추가한다.
그래프를 연다.
마우스 오른쪽 버튼을 클릭해서 퍼지 파인더를 연다.
퍼지 파인더에 "On Keyboard Input"를 검색하여 추가한다.
마우스 오른쪽 버튼을 클릭해서 퍼지 파인더를 연다.
퍼지 파인더에 "Send My custom Event"를 검색하여 추가한다.
두 노드를 연결한다.
키보드 입력(keyboard input)과 연결한 예는 아래와 같다.
이벤트를 수신하고 코드를 실행하는 GameObject 추가
이벤트를 수신하고 코드를 실행할 GameObject를 추가하려면:
Hierarchy 윈도우에서 빈GameObject를 새로 만들고 이름을 EventReceiver로 변경한다.
Script Machine 컴포넌트를 추가한다.
스크립트 머신 컴포넌트에 새로운 스크립트 그래프를 추가한다.
그래프를 연다.
마우스 오른쪽 버튼을 클릭해서 퍼지 파인더를 연다.
퍼지 파인더에 "On my Custom Event"를 검색하여 추가한다.
마우스 오른쪽 버튼을 클릭해서 퍼지 파인더를 연다.
퍼지 파인더에 "Debug Log"를 검색하여 추가한다.
두 노드를 연결하십시오.
이벤트를 수신하고 이벤트 값을 콘솔에 출력하는 예는 아래와 같다.
한 번은 다음 단계를 완료해야 한다.
setup을 테스트한다. 게임 스타트를 위해 "play button"을 누른다.
스페이스 바를 눌렀다 놓는다.
Unity 콘솔에서 로그 메시지가 출력됐어야 한다.
코드에서 이벤트 트리거하기
C# 코드에서 이벤트 전송이 필요할 수도 있다. 다음 예는 컴포넌트 스크립트가 CodeTriggerCustomEvent를 호출하는 것으로 어떤 키가 눌렸을 때 이벤트를 트리거하는 클래스이다.
using Unity.VisualScripting; using UnityEngine;
publicclass CodeTriggerCustomEvent : MonoBehaviour { void Update() { if (Input.anyKeyDown) { //Triggering the previously created custom event MyCustomEvent with the integer value 2. EventBus.Trigger(EventNames.MyCustomEvent, 2); } } }
씬(scene)의 어느 GameObject에 작성한 스크립트를 추가한다.
Play 버튼을 누르고 아무 키를 누른다. 그 이벤트는 앞서 작성한 이벤트가 있는 씬의 어느 스크립트 그래프내에서 트리거되어야 한다.
C# 코드에서 이벤트 수신하기
C# 스크립트가 비주얼 스크립트에서 트리거된 이벤트를 수신하려면:
프로젝트에서 마우스 오른쪽 버튼을 클릭하고Create>C# Script로 새로운 C# File 생성한다. 리네임하여 이벤트 유닛 이름으로 변경한다(예, EventReceiver.cs).
다음 코드를 스크립트에 복사, 붙여넣기, 저장한다.publicclassEventReceiver : MonoBehaviour { voidStart() { EventBus.Register<int>(EventNames.MyCrazyCustomEvent, i => { Debug.Log("RECEIVED "+ i); }); } }
Custom Event 유닛은 사용자가 지정한 인수를 통해 트리된다. Custom Event를 다른 그래프에서 트리거 하려면 Trigger Custom Event 유닛을 이용해야 한다. 즉, Trigger Custom Event (On Damage) => Custom Event (On Damage)
Custom Event를 만드는 방법
스크립트 그래프에서:
빈 곳을 마우스 오른쪽 버튼을 클릭한다. 퍼지 파인더가 나타난다.
Events>Custom Event를 선택한다. Unity는 그래프에 Custom Event 유닛을 생성한다. 참고: 첫 번째 인수는 Arg 0인 인덱스이다.인수는 이벤트가 처리할 수 있는 값이다. 만약, 추가 데이터 포트가 필요하다면 인수 필드의 숫자를 필요한 수량의 숫자로 늘려주면 된다(예: 4개의 데이터 포트는 Arguments = 4).
참고: 송신자(sender)와 수신자(receiver) 유닛들은 동일한 갯 수의 인수(arguments)를 가져야 한다.
Custom Event를 트리거 하는 방법
역자주) 앞 단락에서 생성한 Custom Event를 발동시키는 방법이다.
스크립트 그래프에서:
빈 곳을 마우스 오른쪽 버튼을 클릭한다. 퍼지 파인더가 나타난다.
Events>Trigger Custom Event를 선택한다. 참고: 이벤트의 이름은 Custom Event(이름은 대/소문자 및 공백 구분함)와 같아야 한다. 트리거될 Custom Event가 있는 스크립트 머신(Script Machine) 컴포넌트를 소유한 게임 오브젝트가 게임 오브젝트 데이터 포트에 연결되어야 한다. Unity는 그래프에 Trigger Custom Event 유닛을 생성한다. 첫 번째 인수는 Arg 0인 인덱스이다. 인수는 이벤트가 처리할 수 있는 값이다. 만약, 추가 데이터 포트가 필요하다면 인수 필드의 숫자를 필요한 수량의 숫자로 늘려주면 된다(예: 4개의 데이터 포트는 Arguments = 4).
참고: 송신자(sender)와 수신자(receiver) 유닛은 동일한 수의 인수를 가져야 한다.또한 이벤트를 트리거할 때는 값을 사용하지 않더라도 인수와 연관된 값이 있어야 한다.데이터 포트를 통한 모든 인수는 다른 유닛에 연결되어야 한다. 그렇지 않으면 오류 메시지를 수신하게 된다.
스크립팅 유닛은 Events를 수신한다. Events 유닛은 모든 스크립트의 시작점이며 그래프에서 특별히 녹색 유닛으로 제공되고 있다.
매우 다양한 Events 유닛이 있는데, 이는 최상위의 Event 카테고리를 선택하면 하위 카테고리들이 그룹핑되어져 있다(퍼지 파인더>Events):
가장 기본이 되는 두 개의 Events로는 Start Event와 Update Event가 있다. 둘 다 Lifecycle에 들어 있다.
Start : 그래프(graph)나 이벤트 핸들러(evnet handler)가 최초 생성될 때 한 번 호출된다.
Update : 그래프(graph)나 이벤트 핸들러(event handler)가 실행중일 때 매 프레임마다 된다.
새로운 스크립트 머신(Script Machine)은 기본적으로 이러한 두 이벤트를 가지고 생성된다.
Inputs & Outpus
모든 Events에는 자신이 트리거 됐을 때 시작할 스크립트가 연결되는 1개의 트리거 제어 출력 포트가 있다.
값(Value) Input은 이벤트가 트리거될 때 영향을 줄 수 있는데 이는 옵션 사항이다.예를 들어, 어떤 이벤트는 어떤 오브젝트가 그 이벤트를 수신할 지를 결정하는 타겟 세팅(Target setting)을 갖는 경우가 있다. 대부분은 기본값으로 This가 설정되어져 있다.
Events에서 출력되는 값들은 그 Events를 통과하는 인자들이다. 즉, 실제로 발생된 무엇인가 대한 더 많은 정보를 제공한다. 예를 들면, On Trigger Enter Event는 그 충돌에 관여된 다른 Collider 정보를 제공한다.
Custom Events(사용자 지정 이벤트)
특수한 타입의 Event로 Custom Evnet가 있다. 이는 다른 그래프에서 Custom Event를 발동시킬 수 있도록 하는 커스텀 인자를 가지고 있다.
예를 들어, On Damage이라는 Custom Event를 만들어서 캐릭터의 HP를 깎는 기능을 구현하고자 한다. 따라서 그 Event는 손상의 정도를 나타내는 정수(integer) 인자(argument)를 가지고 있어야 한다. 이는 Custom Event 유닛을 생성하고 On Damage라는 이름을 부여하면 된다. 그리고 인자 수량(argument count, 아래 이미지에는 0이라고 되어 있음)을 1로 바꾸면 된다.
참고: Index는 zero-based이기(0부터 시작하기) 때문에, 첫 번째 인자(1을 입력하면)는 Arg. 0이라고 명칭이 붙는다.
다른 곳에서 이 Event를 트리거하려면 퍼지 파인더의 Trigger Custom Event 유닛을 사용하면 된다. 그 Event 이름은 대/소문자와 띄어 쓰기까지 정확히 똑같게 입력해야 한다.
예를 들어 Player에게 물리적인 충돌을 가하는 바위 기능을 하는 스크립트 머신(Script Machine)인 아래 그래프는 그 충력의 힘을 Damage 값으로 사용하고 있다.
참고: 바위와 부딪힌 Collider(충돌체)는 trigger의 대상이 된다. 즉, On Damage evnet는 그 Collider에 연계된 모든 머신(Machines)을 발동시킨다. Damage 손상은 해당 이벤트를 수신한 오브젝트의 HP에서 그 Damage 값을 빼면 된다.
사용자 정의 이벤트는 수신기(receiver)를 요구하지 않으며, 이를 다루는 수신자(Listener)가 없어도 에러가 생기지 않는다.
Animation Events(애니메이션 이벤트)
애니메이션 내에서 어떤 포인트에 도달했을 때 비주얼 스크립트를 발동시키고자 한다면 Animation Events를 이용하면 된다. 머신(machine)과 애니메이터(animator)가 있는 오브젝트를 선택한다. 그리고 그 애니메이션 윈도우에서 Animation Event를 추가한다.
해당 이벤트가 선택된 상태에서 인스펙터(Inspector)에 있는 함수 중 TriggerAnimationEvent를 선택한다.
Wait 유닛은 스크립트 실행을 지연(delay)시킨다.지연(delay)은 정해진 시간(초)이 될 수도 있고 계속 진행하기 전에 이행해야 하는 조건이 될 수도 있다.
Unity의 비동기성(실행이 지연되는 기능 또는 동작)은 코루틴(coroutine)으로 처리하고 있다(멀티쓰레드 방식이 아니다). Wait 유닛을 사용하려면 코루틴처럼 스크립트가 동작해야 한다고 비주얼 스크립팅에 알려야 한다. 그 방법은 스크립트를 시작시키는 초기 이벤트의 Coroutine 체크박스에 체크해 주면 된다.그래프 인스펙트에는 다음과 같이 보인다.
이벤트 유닛에 작은 이중 화살표 아이콘이 나타나 코루틴으로 실행되고 있음을 알려준다.
coroutine 확인란이 활성화되지 않은 경우, 런타임시에 발생하는 에러는 포트가 Wait 유닛에 도달할 때만 '코루틴에서 트리거될 수 있음'을 나타내는 것이다.
모든 Wait 유닛은 루프(loops)와 시퀀스(sequences) 내부에서도 사용가능하다.
Wait For Seconds
Wait For Seconds 유닛은 가장 단순하고 가장 자주 사용되는 Wait 유닛이다. 이 유닛은 실행을 일정 시간(초) 단위로 지연시킨다. (역자주-아래 예시는 마우스 클릭시 5초후에 "Waited!"라는 메시지가 콘솔에 출력된다.)
Wait Until
Wait Until 유닛은 주어진 조건이 충족될 때까지 실행을 멈추게 한다. 아래 예시는 A < 1 조건이 될 때까지 "Close enough"는 콘솔에 출력되지 않는다. 즉, A < 1 인 상태가 되는 순간 출력이 된다.
Wait While
Wait While 유닛은 Wait Until 유닛과 정반대이다. 즉, 주어진 조건이 충족되는 한 실행을 멈추고 있다. 아래 예시는 A > 1인 조건인 동안 콘솔에 "Close enough"이 출력되지 않는다. 즉, A > 1이 아닌 상태가 되는 순간 출력이 된다.
Wait For Frame
이름이 의미하듯이 Wait For End Of Frame 유닛과 Wait For Next Frame 유닛은 Unity의 업데이트 루프의 특정 지점이 충족될 때까지 실행을 지연시킨다.자세한 내용은Execution Order of Events를 참조하면 된다.
Wait For Flow
Wait For Flow 유닛은 모든 입력 스크립트들이 최소한 한 번은 모두 처리 될 때까지 실행을 지연시킨다.여러 Events 또는 Frames에 걸쳐 발생하는 조건들을 그룹화하는데 매우 유용한 방법이다.다른 프로그래밍 언어에서는 이 개념을 "Promises(약속들)"이라고 부르기도 한다.
Cooldown
Cooldown 유닛은 입력 스크립트가 제한된 횟수만 트리거될 수 있는 경우 시간 제한을 줄 수 있다.
Cooldown이 완료되면 입력 스크립트가 Ready 포트로 전송되고 그렇지 않으면 Not Ready 포트로 전송된다.
Duration 포트는 Cooldown이 다시 완료될 때까지 걸리는 시간을 의미한다.Unscaled가 체크되면 시간 척도(time scale)를 무시하게 된다.
Tick 포트는 Cooldown이 활성화된 상태일 때 매 프레임마다 호출된다. 어떤 액션(Action)이 다시 호출될 수 있을 때까지 남은 시간을 업데이트해서 표시하는 GUI 코드를 작성할 때 유용하다. 이를 위한 두 가지 옵션이 있다.
Remaining : Ready가 될때까지의 시간(초)을 반환한다.
Remaining % : Ready에서 Not Ready까지의 구간에 대응되는 0과 1사이의 값을 반환한다.
Cooldown이 Ready가 되자마다 Completed 포트가 작동된다.이 포트가 트리거되도록 입력 스크립트를 지속적으로 전달할 필요는 없다.
끝으로 Cooldown을 강제로 Ready 시킬 수도 있고, Reset port를 트리거하여 내부 타이머를 재설정할 수도 있다.
아래의 예는 Cooldown이 있는 단순한 사격 시스템으로 다음 사격 가능 시간 까지 남은 시간을 비율로 표시해주는 이미지 바와 시간 값을 표시해주는 텍스트가 있는 UI 스크립트 그래프이다.
Timer
타이머 유닛은 일시정지가 가능한 프로세스를 구현하거나 모니터하는 유닛이다.
Duration 포트는 Cooldown이 다시 완료될 때까지 걸리는 시간을 의미한다. Unscaled가 체크되면 시간 척도(time scale)를 무시하게 된다.
Timer는 Start 입력이 트리거 될 때 시작되며, 이후에 Started 출력을 트리거한다.
Pause 입력으로 일시 중지 시킬 수 있고, Resume 입력으로 재시작 시킬 수 있다. 또는 Toggle 입력으로 이러한 상태간 전환을 할 수도 있다.
Tick 포트는 타이머가 활성화되어 있는 동안 매 프레임마다 호출된다. 시간 측정을 얻는 방법에는 두 가지 옵션이 있다.
Elapsed : 타이머가 시작된 이후의 시간을 반환한다(경과 시간)
Remaining : 타이머가 완료될 때까지의 시간을 반환한다(남은 시간).
측정값은 절대 시간(초)값을 얻을 수 있고 또는 0과 1 사이의 비율 값(%)으로 얻을 수도 있다. 이는 Lerp를 작성할 때 유용하다.
Timer가 종료되자 마자 Completed 포트가 트리거된다.
아래 예시는 단순한 자동파괴 시스템인데, 파괴되기 전까지 점진적으로 스프라이트가 붉게 변해간다.
If 유닛은 부울(Boolean) 조건을 사용한다. 그 조건이 "True"일 때 어떤 작업을 진행하고, "False"일 때 다른 작업을 진행하게 된다.
Swith
enum(열거형), string(문자열) 또는 integer(정수) 값에 따라 분기된다. 이들 유닛들을 Switch unit이라고 한다.
emnu(열거형)을 switch로 사용하려면 열거형 타입을 선택해야 한다. 그러면 분기 출력 포트가 나타난다.
string(문자열) 또는 숫자(number)를 switch로 사용하려면, 그래프 인스펙터에 분기 옵션을 추가해야한다.
유닛의 출력 포트에 분기 옵션에 추가한 사항이 반영된다.
string(문자열)은 선택자(selector)의 대소문자를 무시하도록 선택할 수도 있다.
참고: Default 포트는 기본적으로 추가되어 있다. 만약, 일치하는 입력 선택자가 없을 경우 제어가 Default로 흘러가게 된다. (역자주-프로그래밍의 switch 문에 default와 동일함)
Select
Select unit은 Switch unit과 반대다.선택자의 값에 따라 여러개의 옵션 집합에서 단일 값을 선택하게 된다.
예를 들어 playerNo에 따라 색상을 선택하는 "Select On Integer" 유닛이 있다.
참고: 위의 예에서 예측 디버깅(주황색으로 표시되는 경고)은 Default 포트가 연결되어 있지 않기 때문에 playerNo가 1, 2, 3, 4가 아닐 경우 오류가 생기게 됨을 알려주고 있다.
Looping : While Loop, For Each loop, For Loop, Break Loop
루프(Loop)는 계속 진행하기 전에 일정 횟수만큼 로직(logic)을 반복하는 것이다.
반복되는 로직을 루프의 Body라고 한다. 루프가(반복이) 종료되면 Exit 포트가 호출된다.
참고: 모든 루프의 Body는 순차적으로 여러 프레임에 걸쳐 호출되는 것이 아니라 한번에 동시에 호출한다. Coroutine(코루틴)은 업데이트 이벤트를 수동적으로 수신해서 처리하는 방식으로 동작하고 있다.
While Loop
while loop은 가장 단순한 형태의 루프다. while의 조건이 true인 동안 Body를 반복한다. while조건이 false가 될때 루프가 종료된다.
예를 들어, 다음 그래프는 루프의 결과가 names 변수에 포함되지 않을 때까지(즉, 기존 목록에 없는 새로운 이름이 선택될 때 까지) 새로운 랜덤 name을 생성한다.
경고: 무한 루프(infinite loop)에 빠지지 않도록 주의 해야 한다.조건이 항상 True가 되면 무한 루프에 빠지게 된다. 루프 Body가 병렬적(parallel)이지 않고 동시적(synchronous)이기 때문에 비주얼 스크립팅에서는 while loop를 많이 이용하게 되지는 않는다.
For Each Loop
For Each 문은 컬렉션(collection)의 모든 요소(element)를 반복하게 된다.현재 루핑 중인 인덱스와 항목을 출력한다.
예를 들어 다음 그래프는 콘솔에 세 개의 메시지를 출력한다.
I like cats
I like dogs
I like birds
루프에서 Dictionary의 key와 value로 접근하고자 한다면, Dictionary 체크박스에 체크하면 된다.
For Loop
For 반복문은 숫자로 구성된 반복문이다. start index, end index 그리고 step 세 개의 정수(integer)가 필요하다.루프의 시작은 첫 번째 index에서 시작한 다음 step의 크기로 증가하면서 마지막 index까지 반복한다. 출력으로 현재 index를 내보낼 수 있다.
예를 들어, 다음 그래프는 step이 2씩 증가하기 때문에 홀수를 건너뛰며 10까지 진행한다.즉, 출력은 0, 2, 4, 6, 8이다.
For 문은 Get List Item, Count Items 유닛과 결합할 때 매우 유용하다.
예를 들어, 콘솔에 "I like {animal}s"라고 출력한다는 면에서 For Each Loop의 예시와 매우 유사하다.
Item을 일일이 하나씩 출력하는 대신 List Index를 기준으로 Item을 출력한다. 따라서 증가 값 Setp(여기서는 2)을 지정함으로써 일부 Item은 제외 시킬 수도 있다. 아래의 그래프는 두 개의 메시지를 출력한다.
I like cats
I like birds
Break Loop
루프는 Break Loop 유닛을 사용하여 반복 중간에 종료 시킬 수 있다.이 유닛이 실행되자마자 루프는 Exit 포트로 이동한다.
예를 들어, 아래 루프는 10까지 세도록 되어 있지만, Index가 5가 되는 순간 Break 유닛이 실행된다. 따라서 출력은 0, 1, 2, 3, 4만 된다.
예외 처리(Exception Handling) : Try Catch, Throw
Try Catch
Try Catch 유닛은 발생한 예외(Exceptions)를 처리한다.일부 코드에 오류기 의심될 경우 게임이 크래시(중단)되는 것을 방지할 수 있다.
Try 분기에서 실행되는 모든 작업은 "Safe(안전함)"으로 간주된다. 그리고 스크립트가 실패할 경우 Catch 분기를 통해 오류를 처리하고 계속 실행된다. Exception 포트는 실패에 대한 정보를 전달하는데, 이 정보를 경고 로그로 출력하는 것이 가장 일반적이다.
참고: 기본적으로 Try Catch 유닛은 모든 예외를 파악(캐치)할 수 있다.드롭다운에서 처리할 예외(exception) 유형을 변경하면 된다.
Finally 분기는 선택 사항이다. 연산(혹은 처리)가 성공하든 실패하든 상관 없이 항상 Try 또는 Catch 다음에 호출된다. 이는 보통 쓸모 없어진 리소스를 처분하거나 파괴하는데 이용된다. 리소스를 파괴할 필요가 없다면 이 포트는 사용할 필요가 없다.
Throw
Throw 유닛은 흐름을 멈추게 하는 예외(Exception)를 발생 시킨다.이 예외를 Try Catch로 처리한다.
예상치 못한 일이 발생하면 바로 던짐(throwing)으로써 미리 실패를 확인하는 것은 좋은 습관이다. 이는 버그들이 혼재되고 디버그 하기 어려운 예상치 못한 사이드 이펙트를 발생시키는 대신 초기에 버그를 잡을 수 있도록 하는데 도움이 된다.
예를 들어 damage가 양수인지를 보장해야 하는데, 음수가 들어 올경우 Throw로 처리하도록 하는 것이다.
Custom 체크박스를 선택을 하면, 단순한 메시지가 아닌 많은 데이터가 포함된 Custom Exception Object를 전달하게 된다. 일반적으로 사용할 일은 거의 없다. 던져진 예외(thrown exception)의 타입은System.Exception 이다.
Toggles(토글) : Toggle Flow, Toggle Value
Toggle 유닛은 전등 스위치와 원리적으로 유사하다. 즉, 스크립트나 값에 따라서 켜지거나 꺼질 수 있다. 문을 비유로 생각한다면 문이 열리거나 닫힌다고 생각하면 된다.
Toggle Flow
Toggle Flow 유닛은 제어 흐름의 관문과 같다. 켜지(On)면 흐름이 진행되지만, 꺼지(Off)면 흐름이 멈추게 된다. 예를들어 Space 바를 한 번 누르면 움직이기 시작하고 다시 두르면 멈추는 것이다.
로직을 제어하는 입/출력은 여러 가지가 있다. 앞의 Space 바 예시에서 Toggle on/Toggle off를 하는데 같은 이벤트(키 누르기)를 이용하였다. 하지만 On/Off를 대신하는 다른 두 개의 이벤트를 통해서도 Toggle이 가능하다.
출력 포트쪽에 있는 "Is On" 불리언 포트는 현재 toggle 상태가 켜져 있는지, 꺼져 있는지를 전달한다. 제어 출력은 아래 테이블에 따라서 전달된다(triggered).
Port
Triggered When
On
On인 상태일 때, 어떤 입력에 대한 제어 처리
Off
Off인 상태일 때, 어떤 입력에 대한 제어 처리
Turned On
On이 된 경우에 대한 제어 처리, 그게 On인데 On이 되었든 Toggle Input이 On이 들어 왔든지 간에
Turned Off
Off가 된 경우에 대한 제어 처리, 그게 Off인데 Off가 되었든 Toggle Input이 Off가 들어 왔든지 간에
Toggle Value
Toggle Value 유닛은 ON/OFF 여부에 따라 두 개의 입력 값(On Value 또는 Off Value) 중에 어느 하나의 값을 선택한다.포트의 작동은 Toggle Flow 유닛과 똑같이 동작한다.
앞의 예시를 다른 방법식으로 구현한다면: Space bar를 눌러스 토글을 통해 오브젝트를 움직이거나 멈추게 한다고 할 때, 이번에는 이동 속도(veloctiy)를 On 일 대는 1, Off 일 때는 0의 값을 전달해서 구현하는 것이다.
참고: 토글 포트 간의 흐름을 확인하는 방법은 toolbar에서 relations를 켜면 된다.
Once
Once 유닛은 그 이후의 로직을 오직 한 번만 실행한다. (아래 예시는 First Fram이 1회 출력되고 이후 부터는 Following Frames만 반복 출력 된다.)
Reset 포트를 이용해 재설정할 수도 있다.
Cache
Cache 유닛은 값 비싼 연산(계산)을 하고 그 결과를 저장한다. 그리고 이후 다시 필요할 때 재실행하지 않고 바로 재사용할 수 있도록 해준다.
예를 들어, 아래와 같이 그래프를 작성하면, Debug.Log가 두 개인데 이를 위해 Formula를 두번 계산하게 된다.
Cache 유닛을 사용함으로써 한 번만 계산하고 결과를 저장한 다음 두 번째 Debug.log에서는 계산 없이 바로 사용하게 된다. 이는 성능 최적화를 해준다.
참고: 중요한 것은 Cache는 현재 흐름의 범위에서만 사용가능하다는 것이다. Cache 값은 다른 이벤트와는 공유되지 않으며 접근할 수도 없다.
비주얼 스크립팅을 처음 공부할 때 그리고 매뉴얼을 한글로 작성하면서 매우 헷갈리게 만드는 단어가 그래프, Graph이다. 비주얼 스크립팅이라는 말속에는 그래프라는 이미지의 의미가 담겨져 있는데, 처음 시작할 때 단순히 그 그래프 라는 이미지의 의미만 떠올리며 메뉴얼이나 설명서를 읽다 보면 무슨 말인지 이해하기 어렵게 되는 경우들이 있다. 따라서 기술적 개념이나 메뉴의 명칭에 익숙하지 않은 완전 초보자들을 위해 정리해 본다.
1. 코딩을 하다, 작업을 하다. 그래프를 그리다.
비주얼 스크립팅은 C#, PHP, Python, JAVA, LISP 같이 프로그래밍 언어 코드를 사용하지 않는다. 여기서 코딩을 한다는 것은 그래픽컬한 유닛(혹은 노드)들을 배치하고 논리적으로 연결하여 마치 그래프를 그려나가듯 작업해서 어떤 기능을 동작하도록 하는 것을 의미한다. 즉, "그래프(Graph)를 작성한다", "그래프 작업을 한다"라고 말하면 코딩을 한다는 의미가 된다.
2. 작업한 결과물을 지칭한 그래프, Graph
앞의 맥락에서 그래프 작업을 통해 그려진 작업 결과물 또한 그래프(Graph)라고 지칭하고 있다. "그래프 내부를 보면" 혹은 "그래프가 동작한다." 같은 말이 뜻하는 의미가 된다.
3. Script Graph / State Graph -- 스크립트의 형태에 따른 구분을 위한 그래프
게임 오브젝트에 Add Component를 해서 Machine을 검색하면 위와 같이 두 개의 Machine이 있다.
Script Graph는 Script Machine에서 작성한 그래프(Graph)를 의미하고, State Graph는 State Machine에서 작성한 그래프(Graph)를 의미한다. 참고로 여기서 알아야 할 것은 State Machine안에 있는 State Graph는 많은 Script Graph들로 작성이 된다는 것이다. 즉, State Graph는 상태(State)의 의미나 기능을 정의한 그래프(Graph)들인데 그 그래프들 내부는 Script Graph들로 채워져 있다는 것이다. 따라서 우리가 작업하는 Graph는 99%가 Script Graph인 것이다. 하지만 State Graph의 활용 역시 매우 중요하다.
4. Embed / Graph -- Graph가 존재하는 방식에서 Graph 에셋 파일을 참조한다는 의미로 활용
Embed는 작업한 그래프들이 Machine 컴포넌트 안에 내장이 되어진다는 의미이다. 즉, Transform에 좌표나 스케일이 있는 것 처럼 Machine 컴포넌트 안에 작업한 그래프들이 들어 있게 된다. 따라서 Machine 컴포넌트를 지우면 모두 날아간다.
Graph는 작업한 그래프들이 그래프 에셋 파일로 저장이 되어 있고 그 저장된 파일을 링크하여 참조해서 사용한다는 의미이다. Machine 컴포넌트가 삭제되어도 그래프 에셋 파일은 삭제되지 않는다.
이런 의미를 생각하면 Embed Graph 즉, 내장된 그래프, Graph Graph 그래프 에셋파일을 참조하는 그래프라는 말이 만들어져야 하는데 매우 어색해서 Embed 그리고 Graph 라고만 사용한다. 그래서 헷갈린다. 설명 맥락에 따라 그래프라는 단어를 이해해야 한다.
5. Graph Variable -- 지역 변수 개념으로써 그래프
그래프 편집창의 블랙보드(Blackboard)를 보면 변수탭들이 있다. 제일 왼쪽(앞쪽)에 Graph 탭이 있다. 그래프 변수(Graph Variable)를 의미한다. 현재 그래프(Graph)에만 그 변수가 생존하기 때문에 명칭을 그렇게 붙인 듯 하다. 즉, 다른 그래프에서는 사용할 수 없다.
6. 유니티의 메뉴 명칭
1> Edit Graph : 링크된(혹은 선택된) 그래프를 편집할 수 있도록 오픈한다.
2> Graph Inspector : 선택한 유닛들에 대한 정보와 편집할 수 있는 요소들이 나타난다.
3> Script Graph : 그래프 편집창이다. 작업 그래프가 Script Graph라는 것을 알 수 있다. State Graph라면 State Graph라고 표시된다.
4> Graph : Graph Variabe 탭
5> Visual Scripting Graph : 위의 이미지에 보이는 편집창을 띄운다. Machine 컴포넌트가 있는 경우 대부분 자동으로 오픈된다. 혹은 Edit Graph를 클릭하면 된다. 따라서 위의 메뉴를 찾아갈 필요는 없다.
6> Fuzzy Finde > Graph : 퍼지 파인더 안에 Graph 그룹은 작업자가 생성해 놓은 Graph 에셋 파일들이 자동으로 들어가 있다. Super Unit 같은 경우 퍼지 파인더에서 찾아 넣어도 편리하다.
유닛(Unit)은 비주얼 스크립팅에서 계산을 하는 가장 기본적인 요소다. 유닛은 텍스트로 필요한 정보를 표시하기도 하지만, 인스펙터에서 편집이 될 수도 있다. 유닛을 편집하려면 유닛을 선택하고 인스펙터에서 유닛의 속성(property)을 편집하면 된다.
This unit
"This unit"은 그래프가 있는 머신(Machine) 컴포넌트를 소유한 게임 오브젝트(Game Object)를 리턴한다.
Control units
"Control unit"은 If, Loop 혹은 흐름 합체같은 유닛들이다. (역자주 - 제어 포트를 가지고 있다)
Time units
Time unit은 timer, cooldown 그리고 wait 유닛 등이 있다.
Events(이벤트들)
스크립팅 유닛(Scripting units)은 Evnets를 수신한다. Evnets는 모든 스크립트의 시작점이며 그래프에서 특별히 초록색 유닛으로 표시된다. (역자주 - Start Event, Update Event, On Button Input Event, On Destroy Event 등등)
트랜지션(Transitions)은 비주얼 스크립팅이 활성 상태를 다른 상태로 전환하는 조건을 갖고 있는 상태간 연결점을 말한다.
트랜지션 편집하기
Script State와 마찬가지로 트랜지션은 Script Graph로 이루어져 있다.
위의 예에서는 새로운 상태로 전환할 수 없는 문제점을 가지고 있다. Event가 언제 발동해야 하는지를 지정하지 않았기 때문에 결고 상태가 전환할 수 없는 것이다. 따라서 목적 상태와 트랜지션 둘 다 흐리게 표시되고 있다. 트랜지션 유닛을 더블 클릭하거나 Edit Graph 버튼을 클릭하면 트랜지션 그래프가 나타난다.기본적으로 다음과 같이 구성된다.
트리거 상태 전환 유닛(Trigger State Transition Unit)은 부모의 상태가 이 트랜지션 조건에 따라 상태가 변경되어야 한다는 것을 지시하는 특별한 유닛이다. 이벤트(events)나 조건(if)과 같은 유닛을 상태 전환 그래프에서 사용하면 된다.
예를 들어, "Player" tag가 붙은 게임 오브젝트가 적(Enemy)에게 감지(trigger)된다면 그 적은 추척 상태(chase state)로 전환된다고 할 때, 그 그래프는 다음과 같이 그려 질 수 있다.
끝으로 상위 상태 그래프에서 트랜지션의 라벨을 변경하고자 한다면, 해당 트랜지션을 선택하고 그래프 인스펙터에서 타이틀을 작성하면 된다.
상위 상태로 돌아가면 트랜지션은 다음과 같이 나타난다.(역자주-트랜지션 내부에 조건 스크립트를 작성하고 나오면 아이콘이 변경 된다.)
트랜지션에 직접 제목을 작성하지 않을 경우, 이벤트의 이름과 설명이 자동으로 지정된다.
트랜지션 라벨은 항상 보이는 것이 디폴트이다.이것이 그래프에서 너무 많이 차지하여 변경하고 싶다면, Unity>Preferences>Visual Scripting>State Graphs>Transition Display에서 디스플레이 트리거를 변경한다.
셀프 트랜지션
자기 자신의 상태로 되돌아 오는 트랜지션은 매우 유용한 경우가 있다. State 노드 위에 마우스 오른쪽 버튼을 클릭하고 Make Self Transition을 선택한다.
예를 들어, 적 캐릭터가 3초 마다 랜덤하게 방향을 바꾸게 함으로써 순찰(patrol) 기능을 구현할 수 있다.
순찰 상태의 스크립트 그래프는 다음과 같다.(역자주 - 이동할 타겟의 x, z 좌표가 -30 ~ 30 사이에서 각각 랜덤하게 정해진다.)
셀프 트렌지션은 다음과 같다.(역자주 - 3마다 상태가 전환된다.)
상위 상태 그래프에서 보면 다음과 같이 나타난다.(역자주 - 3초마다 랜덤 x, y 좌료로 이동하게 된다)
다중 트랜지션 추가하기
상태 전환을 위해 추가할 수 있는 트랜지션의 수에는 제한이 없다.그러나 트랜지션 사이에 우선순위 개념이 없기 때문에 적절한 트랜지션을 사용해야 한다.
상태(States)는 오브젝트가 특정 상태에 있을 때 어떻게 행동해야 하는지를 규정하는 것이다.상태(State)는 주로 인공지능(AI) 행동, 씬이나 레벨 구조 또는 상태 전환을 위해 요구되는 씬의 어떤 측면에 사용된다.
예를 들어, 적이면서 비 비플레이어 캐릭터의 상태 머신(state machine)은 "순찰(Patrolling)", "추적(Chasing)", "공격(Attacking)", "도피(Fleeing)" 네 개의 상태를 가질 수 있고, 문의 상태 기계은 "잠김(Locked)", "잠금 해제(Unlocked)", "열림(Open)" 세 개의 상태를 가질 수 있다.
그래프에 추가할 수 있는 상태 타입은 스크립트 상태(Script State)와 수퍼 상태(Super State) 두 가지가 있다. 스크립트 상태(Script State)는 중첩된(nested) 스크립트 그래프(script graph)로 구성되며, 수퍼 상태(Super State)에는 중첩된(nested) 상태 그래프(state graph)로 구성된다.
(역자주-이후 부터는 상태라는 말 대신 State(스테이트)라고 사용하겠다. 텍스트가 번역된 것이 아니기 때문에 스테이트를 머릿속에서 상태라고 번역해 가며 읽지 않기 때문에 스테이트라고 바로 읽는 것이 더 자연스럽다.)
참고: State는 트랜지션(Transitions)을 통해 다른 State로 연결(전환)된다.
Script State와 Super State 두 가지 모두 "nesters" 형태인데 이는 머신(Machine)과 똑같은 원리로 동작한다는 것을 의미한다. 즉, 해당 하위 그래프(Sub-graph)는 내장시키는 임배드(embed)로 작업하거나 참조형으로 연결시키는 그래프(graph)로 작업할 수 있다. Super State의 인스펙터(Inspector)도 같은 방식으로 보이고 동작한다.
State Machine(스테이트 머신)
State 그래프는 State Machine에 저장된다.Add Component를 하고State Machine을 선택하면 State Machine이 생성된다. 그래프를 편집하려면 State Machine을 저장해야한다.
State(상태) 생성하기
State를 생성하는 메뉴를 표시하려면 State Graph의 빈 곳에 마우스 오른쪽 버튼을 클릭한다.
Script Graph의 유닛(Unit)들 처럼 새로운 State가 생성되는데 입력 값이 없기 때문에 흐리게 표시되어져 있다. 새로운 State가 흐리게 표시되지 않게 하려면 도구 모음(toolbar)에서 Dim을 비활성화하면 된다.
Start State(s)
하나 또는 두 개 이상의 Start State를 만들어서 그래프가 시작 되는 싯점의 상태를 규정할 수 있다.이렇게 하려면 State 유닛 위에 마우스 오른쪽 버튼을 누르고Toggle Start을 선택한다. Start State 유닛의 상단이 녹색으로 강조 표시된다.
일반적인 유한 상태 기계(FSM) 도구와 달리 비주얼 스크립팅은 다중 Start State를 지원한다. 이는 같은 그래프에서 병렬적으로 FSM을 실행하거나, 어떤 포인트에서 병렬적인 상태진입이 가능하다는 것이다. 하지만 대부분은 한 개의 Start State면 충분하다.
Any State
현재 어떤 상태에 놓여 있는 것과는 상관없이 무조건 다른 상태로 전환을 하려면Any State를 생성한다.
참고: 이 상태는 어떤 트랜지션(transition)을 받을 수 없으며, 어떤 실행도 할 수 없다.
State 제목과 요약 작성하기
State의 제일 상단에 헤더(header)가 있다. 이는 그래프의 제목(Title)과 요약(Summary)을 표시하며 기능에는 영향을 미치지 않는다. 즉, 헤더는 State를 구분할 수 있는 제목이나 간략 설명을 작성할 수 있는 곳이다. Script Graph 아이콘은 Script State 아이콘과 동일하게 보인다.
State 인스펙터(Inspector)는 머신(Machine)과 동일한 형태와 방식으로 동작한다. State Graph의 내용을 확인하거나 편집하기 위해서는 Edit Graph버튼을 클릭하거나 State 노드를 더블클릭하면 된다.
Script State
Script State의 본체는 중첩된 스크립트 그래프내부에서 사용 중인 모든 Event 목록을 표시한다. 디폴트로, 새로운 Script State를 생성하면 On Enter State, Update 그리고 On Exit State Event가 자동 생성된다.
On Enter State는 상위(부모) 상태에 전환이 발생했을 때 즉 그 상태가 시작될 때 호출된다.
On Exit State는 다른 상태로 전환되어 일어날 때, 즉 그 상태가 종료될 때 호출된다.
Update는 그 상태가 활성화되어 있을 때 매 프레임마다 호출된다.
스크립트 그래프에 추가된 이벤트는 상위 State가 활성 상태인 경우에만 수신할 수 있다.
예:왼쪽 상단에 있는 탐색바(breadcrumbs)는 현재의 뷰가 게임 오브젝트의 State 그래프 내부에 있는 Start State에 있다는 것을 표시하고 있다.
언제든지 상위 그래프로 돌아가려면 도구 모음의 해당 부분을 클릭하면 된다.노드를 선택하지 않은 경우 그래프 인스펙터 상단에서 현재 State의 제목 및 요약을 작성할 수 있다.
수퍼 유닛(Super Unit)을 포함하여 사용할 수 있는 유닛에는 제한이 없다.
Super State
Super State는 Script State처럼 생성되고 편집된다.가장 큰 차이점은 그래프가 Script Graph가 아니라 다른 State Graph라는 것이다. Super State에 진입하게 되면, 그 Super State 내부에 있는 그래프들의 모든 Start State가 발동된다. Super State가 종료되면 그 Super State 내에 있는 모든 그래프의 상태나 전환이 비활성화 된다.
Transitions(전환)
트랜지션은 활성 상태를 전환시키는 상태들 간의 연결이다.
State Units
State Unit은 Super Unit과 매우 유사하다. 하지만, Script Graph가 아닌 State Graph에서 이용된다. State Unit은 Script Graph 내부에서 State Graph를 감사는 역할을 한다.
input system(input manager)에서 유닛을 사용하려면, 그 input 유닛의 출력 데이터 포트(output data port) 또는 입력 트리거(input trigger)가 다른 유닛과 연결되어 있어야만 한다. 이전 버전의 input system을 사용한다면Edit>ProjectSettings >Player>Active Input Handling을 Input Manager(Old) 또는 Both로 설정해야 한다.
참고: Input Manager(Edit>ProjectSettings > Input Manager)는 모든 형태의 입력 유형을 가지고 있다.
시스템 입력 받기
1) Event 유닛(예, Update Event)을 가지고 있는 스크립트 그래프(script graph)는 빈 공간에 마우스 오른쪽 버튼을 클릭한다. 명령 목록이 나타난다.
2) Add Unit을 선택한다.퍼지 파인더(fuzzy finder)가 나타난다.
3) 검색 필드에 "get axis"를 입력한다.
4) Input: Get Axis를 선택한다. Get Axis 유닛이 그래프에 나타난다.
5) axisName필드에 유닛 라벨을 입력한다.예: Horizontal). 경고: 유닛 라벨은 Input Manager에 정의된 데로 동일한 철자를 입력해야 한다. 그렇지 않으면 유니티는 인식하지 못한다. 팁: input 유닛 이름을 복사 & 붙여넣기하면 오타의 실수를 없앨 수 있다.
6) Event 유닛의 출력 포트에서 입력 받는 유닛의 입력 포트로 드래그한다. 삼각형 위에서 마우스를 놓으면 두 유닛이 연결된다. 참고: 모든 프레임에서 데이터 포트가 사용된다면 input 유닛은 신호를 받게 된다.
7) Get Axis유닛의 출력 트리거 포트(output trigger port)에서 다른 유닛(예: Transform 유닛)의 입력 포트로 드래그 한다. 사용자가 Get Axis 유닛의 키(예: 좌/우 화살표)를 누를 때마다 다운스트림 유닛(downstream unit)이 증가한다.
이 방법(3번 검색하는 방식)을 사용하여 input 유닛을 만들면 현재 event와 호환되는 유닛이 연결되었다고 확신할 수는 없다. event 출력 포트에서 드래그하여 퍼지 파인더를 사용하면 그 event와 호환되는 유닛들이 퍼지 파인터에 나타나고 그 중에 선택하면 확실하다.
비주얼 스크립팅은 프로젝트의 사용자 지정 스크립트에서 메소드(methods), 필드(fields) 및 속성(properties)을 자동으로 호출한다.예를 들어 TakeDamage 메소드(Method)가 있는 사용자 지정 플레이어 클래스(Class)를 유닛으로 생성할 수 있다.
다시 컴파일을 완료한 후에도 해당 속성(Attribute)은 소스에 남겨두는 것이 좋다. 비주얼 스크립팅이 Unity가 모든 그래프를 정상적으로 작성된 이름으로 Reserialize(재직렬화)를 보장하지는 않는다. 비주얼 스크립팅의[RenamedFrom]속성(Attribute)은 Unity 자신의 [FormerlySerializedAs] 속성(Attribute)처럼 작동한다.
타입 리네임하기
[RenamedFrom] 속성을 사용하여 타입(클래스, 구조체 및 열거형 포함)의 이름을 변경할 수 있다.
비주얼 스크립팅의 실시간 편집은 실시간으로 단순히 값을 조정한다는 것을 뛰어넘는다.실시간 편집에는 유닛간의 연결선뿐 아니라 유닛들을 추가하고 제거하는 것도 가능하다. 게임이 진행되는 동안 코딩을 할 수 있다는 것이며, 즉시 게임 플레이에서 그 변화를 확인할 수 있다는 것이다.
다음 사항은 기억하자.
임배드(Embed)로 작업된 경우 플레이 모드를 종료하면 모든 변경 내용이되돌아간다(reverted), 해당 변경 내용은 컴포넌트 내부에서만 의미가 있다.
그래프(Graph)로 작업된 경우 플레이 모드를 종료하면 그 변경내용은 저장된다. 즉, 변경 내용은 스크립트 그래프 에셋 파일에 저장된다.
그래프 변수(Graph Variables)들은 임배드 그래프(Embed Graph)가 아닐 때 저장된다.
다음 변수는 저장되지 않는다. (역자주-플레이 모드에서 변수를 추가, 삭제하지 않는게 좋을 듯 하다)
Object
Scene
App
Saved
팁: 임배드 그래프(Embed Graph)를 사용하였고 수정 내용을 버리고 싶지 않다면 플레이 모드를 종료하기 전에 변경한 모든 내용을 복사(Copy)하면 된다. 그리고 플레이 모드 종료 후 붙여 넣기 해서 복원하면 된다. 단, 변경된 변수는 이 방법을 사용할 수 없다.
시각적 표현을 목적으로 하기에 라이브 모드에서 연결선들(Connectors)에는 실행 방향에 맞게 애니메이션 되는 물방울같은 흐름이 표시된다. 물방울의 속도와 수는 실행 빈도나 속도를 나타내는 것은 않는다.
런타임 중에 그래프 변경하기
그래프를 연 상태에서 다음 중 하나 또는 모두를 수행한다.
인스펙터(Inspector)에서 컴포넌트의 값을 변경하고자 하는 필드를 클릭한다. 그 값은 플레이 모드를 종료하면 지속되지 않고 저장되지도 않는다.
그래프의 빈 곳을 마우스 오른쪽 버튼으로 누르고 유닛을 추가한다.
유닛 연결한다.
모든 연결(connectors)을 제거한다.
확장(extension)을 추가한다.
디버그 유닛을 추가하거나 연결한다.
유닛의 값을 직접 변경한다.
팁: 런타임 중에 선택한 GameObject의 값을 보고 작업하고자 한다면 스크립트 그래프가 포함된 GameObject를 선택한 상태여야 한다.
참고: 런타임 중에 Start Event와 관련된 변경 사항들은 해당 세션 동안 GameObject에 대한 업데이트 상태를 받을 수 없다. Start Event와 관련된 새로운 로직을 실행하려면 Unity 세션을 다시 시작해야 한다.(역자주 - 당연히 Start Event는 실행됐을 때 1번만 제일먼저 실행되기 때문이다)
그래프(Graph)로 작업된 경우 변경 사항들은 저장된 그래프 에셋의 모든 인스턴스들(Instances)에 즉시 공유된다.
비주얼 스크립팅은 실시간 편집을 지원한다.실시간 편집은 플레이 모드일 때 그래프를 추가하고 편집할 수 있으며 또한 즉시 그 결과가 반영(업데이트)된다는 것이다.이것은 프로젝트의 변경이 있을 때 마다 다시 컴파일 할 필요 없이 효율적인 반복 작업과 아이디어를 즉시 검토해 볼 수 있는 빠른 방법을 제공한다.
실시간 편집 작업하기
실시간 편집은 단순히 값을 수정하는 데 국한되지 않는다. 즉, 유닛이나 유닛간의 연결을 추가하거나 제거할 수도 있다. 기본적인 스크립트 편집 작업에서 할 수 있는 모든 것은 실시간 편집으로도 할 수 있다.
유니티의 기본 규칙에 따라:
플레이 모드를 종료하면 임배드(Embed)로 작업된 경우 모든 변경은 원상태로 되돌아간다(변경은 컴포넌트 내부에만 반영된다).
플레이 모드를 종료하면 그래프(Graph)로 작업된 경우 모든 변경은 그래프 파일에 저장된다(변경은 에셋-스크립트 파일- 내부에 반영된다.)
팁: 컴포넌트 그래프(임배드 그래프)의 변경 내용을 보존하려면 플레이 모드를 종료하기 전에 수정된 유닛을 복사해 놓았다가편집 모드에서 붙여넣기하여 사용하면 된다.
플레이 모드에서 비주얼 스크립팅은 연결선 상에 물방울의 흐름 같은 것이 표시한다.
값 연결(Value Connection), 제어 연결(Control Connection) 또는 둘 모두 이러한 애니메이션을 비활성화하려면 편집기 기본 설정(editor preferences) 창(Unity > Preferences > Visual Scripting > Script Graphs)에서 Animate Control Connections또는Animate Value Connections의 선택을 해제하면된다.
참고> 최신 버전에서는 "Super Units"이라는 명칭이 "Subgraph"로 변경되어졌다. 의미와 기능은 동일하다.
수퍼 유닛은 부모 스크립트 그래프에 단일 유닛으로 중첩되(nested)는 스크립트 그래프다.
빈 수퍼 유닛을 생성하려면:
그래프의 빈 공간을 마우스 오른쪽 버튼으로 누르고(퍼지 파인더를 오픈)Nesting>Super Unit을 선택한다 수퍼 유닛 인스펙터는 머신(Machine)의 그것과 정확히 똑같이 작동한다; 그래프의 소스를 임베드(embed)와 그래프(graph) 사이에서 전환하고 필요에 따라 변환한다.
그래프 인스펙터(Graph Inspector)에 수퍼 유닛에 대한 제목(Title)과 요약(Summary)을 입력한다.
그래프 편집(Edit Graph) 버튼을 클릭하거나 유닛을 두 번 클릭하여 중첩된 그래프를 연다.그래프 창의 상단 좌측에 있는 경로를 통해 상위 스크립트로 이동한다.
참고> 최신 버전에서는 "Super Units"이라는 명칭이 "Subgraph"로 변경되어졌다. 의미와 기능은 동일하다.
수퍼 유닛(Super Units)은 부모 스크립트 그래프내에 단일 유닛으로 중첩된(nested) 스크립트 그래프를 말한다. 수퍼 유닛은 재사용성과 그래프를 구조화시킬 수있도록 하는 강력한 기능을 제공한다.
Input Output 유닛
기본적으로 수퍼 유닛의 내장 그래프는 Input 유닛과 Output 유닛을 사용하여 생성된다.
이 두 단위는 부모 스크립트 그래프로 흐름과 값을 전달한다.
Input 유닛은 부모 그래프에서 수퍼 유닛으로 전달되는 어떤 타입의 흐름 진입점과 패러미터(수퍼 유닛 내부로 전달되는 값)를 정의한다.
Output 유닛은 수퍼 유닛이 부모 그래프에 반환할 어떤 타입의 흐름 출구점과 결과(수퍼 유닛이 처리한 결과 값)를 정의한다.
다음은 Input과 Out을 정의하는 위한 기본조건이다.
Key는 null이거나 빈 상태가 되어선 안된다.
각 포트의 Key는 전체 그래프에 걸쳐 유일해야 한다.같은 키를 갖는 입력과 출력은 있을 수 없다. 심지어 다른 종류나 타입이 다르더라도 안된다.
키를 변경하면 그 포트의 모든 연결이 제거된다. 비주얼 스크립팅은 키를 사용하여 포트를 식별하므로, 포트가 변경되면 연결이 더 이상 무의미해 진다.모든 연결을 유지하면서 포트 이름을 변경하고자 한다면, 기능에 어떤 영향을 미치지 않는 레이블 속성(Label Property)을 이용하면 된다.
각 Input value와 Output value는 데이터 타입을 가져야 한다.
인스펙터는 위 기준을 충족하지 못할 경우 경고를 한다.
수퍼 그래프 사용하기
스크립트 그래프를 수퍼 단위로 사용하려면 프로젝트 창에서 스크립트 그래프를 그래프 편집창으로 끌어다 놓거나 퍼지 파인더에서 그래프(Graph) 카테고리에 있는 수퍼 유닛을 추가한다.
비주얼 스크립팅은 플레이 모드가 실행되기전 오류를 발생시킬 수 있는 유닛을 예측하여 가리킬 수 있다.또한 빠진 컴포넌트나 null 참조를 예측하기 위해 그래프를 분석한다.런타임시에 오류가 발생하면 오류 유닛을 스크립팅 그래프에서 강조 표시로 집어낸다.
예측 디버깅
노드가 제대로 구성되지 않았거나 오류를 일으킬 수 있는 경우 노드는 노란색으로 표시되고오류를 일으킬 것이 확실할 때는 주황색으로 표시된다두 경우 모두 정상 상태가 될 때까지 노드를 검사하고 오류 색상을 유지한다.
예시: Debug.Log가 콘솔로 출력할 메시지(Message)가 누락되어 있기 때문에 로그 노드가 주황색으로 표시됨.
A + B의 결과를 메시지(Message) 포트에 연결하면 Debug.Log 노드가 정상으로 돌아간다.그러나, Add 노드는 첫 번째 피연산자 A의 값이 없기 때문에 주황색으로 변한다.
두 피연산자(A, B)에 값이 제공되면 모든 색상은 정상으로 돌아온다.
참고: B 입력 포트는 기본 인라인 값(Inline Value)을 가지므로 연결할 필요가 없다.
Null 참조(Null References)
null 참조 예외(Null Reference Exceptions)는 아주 흔히 발생하는 경우이다. 패러미터(Parameter)가 어떤 값을 받아야 하는데, 아무런 값도 받지 못하게 되면(프로그래밍 용어로 "null"일 때) 발생한다.
비주얼 스크립팅은 "Predict Potential Null References 옵션(Unity >Preferences>Visual Scripting>Flow Graph)" 이 선택된 경우 null 참조(null reference)를 예측하려고 시도한다.
예시: GameObject.Destroy 유닛은 인라인 값을 가지지만, 「None」(null)로 설정되어 있어, 오렌지색으로 나타나고 있다.
Null 파라미터를 허용하는 희귀한 유닛이 있긴 하지만, 불행히도 코드베이스 분석에서 비주얼 스크립팅이 이를 알 수 있는 방법이 없기 때문에 주황색으로 나타난다.이 문제가 반복되는 경우 Predict Potential Null References 옵션을 체크아웃하면 된다.
컴포넌트가 누락된 경우
유닛이 필요한 컴포넌트에 이용되면서 특정 컴포넌트가 없는 게임 오브젝트나 컴포넌트를 패스하는 경우 노드는 노란색으로 경고를 표시한다.예를 들어, 아래 Add Force 유닛의 각 값은 기본값이 있음에도 불구하고, 비주얼 스크립팅은 소유 게임 오브젝트에 Rigidbody가 없음을 감지하고 경고를 한다. (역자주-rigidbody 컴포넌트가 추가되지 않은 게임 오브젝트에 rigidbody 기능을 하는 Add Force 유닛을 사용하고 있기에 경고를 나타내고 있다.)
참고: 비주얼 스크립팅은 게임 오브젝트에 컴포넌트를 런타임중에 추가할 수도 있기 때문에 주황색 경고로 유닛을 표시하지 않고 있다. 따라서 해당 유닛이 호출되기 전에 필요한 컴포넌트가 추가된다면 크래시(Crash)의 원인을 명확히 찾을 수 없다. 이런 경우가 프로젝트에서 자주 발생한다면 Predict Potential Missing Components debugging (Unity>Preferences>VisualScripting>FlowGraphs)을 사용해제(disable)하면 된다.
라이브 디버깅
플레이 모드에서는 현재 활성화된 유닛이 푸른색으로 밝게 표시된다.오류가 발생하면 이를 일으킨 유닛은 빨간색으로 밝게 표시된다.
예:다음은 결함이 있는 그래프의 예다.결과는 "Space Bar"를 누를 때 콘솔에 "My 3rd favorite fruit is "나타난다.
플레이를 선택하고 객체를 클릭했을 때 발생하는 동작은 다음과 같다.
모든 유닛이 클릭하자마타 푸른색으로 밝게 표시되지만, 콘솔에 오류가 나타난다.
참고: 비주얼 스크립팅은 결함이 있는 노드를 빨간색으로 밝게 표시한다.
흔한 실수로 배열의 인덱스가 1에서 시작한다고 착각하는 것인데 실제로는 0에서 시작한다는 것이다.스크립팅에서 인덱스(Index)는 항상 제로 베이스(zero-based) 즉, 0부터 시작한다는 것이다. 1은 두번째 인덱스, 2는 세번째 인덱스인 것이다. 세 번째 항목을 얻으려면 필드에 2를 썼써야 한다.