Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
서버를 이용한 최적화
Godot와 같은 엔진은 높은 수준의 구성과 기능 덕분에 사용 편의성이 향상되었습니다. 대부분은 :ref:`씬 시스템 <doc_scene_tree>`을 통해 액세스하고 사용됩니다. 노드 및 리소스를 사용하면 복잡한 게임에서 프로젝트 구성 및 자산 관리가 단순화됩니다.
이렇게 하려는 이유가 몇 가지 있습니다:
추가적인 복잡성 계층이 있습니다.
단순 API를 직접 사용할 때보다 성능이 저하됩니다.
:ref:`여러 스레드를 사용 <doc_using_multiple_threads>`하여 제어하는 것은 불가능합니다.
더 많은 메모리가 필요합니다.
대부분의 경우 이는 실제로 문제가 되지 않습니다. Godot는 잘 최적화되어 있으며 대부분의 작업은 시그널로 처리됩니다. 이는 폴링이 필요하지 않음을 의미합니다. 하지만 때로는 다른 최적화 방법이 소진되었을 때 하드웨어에서 더 나은 성능을 추출하고 싶을 때도 있습니다. 예를 들어, 프레임마다 처리해야 하는 작업에 대해 수만 개의 인스턴스를 처리하면 병목 현상이 발생할 수 있습니다.
이러한 유형의 상황은 프로그래머가 게임 엔진을 사용하고 있다는 사실을 후회하게 만들고 더 손으로 만든 저수준 게임 코드 구현으로 돌아갈 수 있기를 바랍니다.
그럼에도 불구하고 Godot는 이 문제를 해결하도록 설계되었습니다.
더 보기
이 문서 외에 여러가지 Godot 데모 프로젝트들도 살펴보면 좋습니다.
Server
Godot의 가장 흥미로운 설계 결정 중 하나는 전체 씬 시스템이 *선택 사항*이라는 사실입니다. 이를 컴파일하는 것은 불가능하지만 완전히 우회할 수 있습니다.
핵심적으로 Godot는 서버 개념을 사용합니다. 렌더링, 물리, 사운드 등을 제어하는 저수준 API입니다. 씬 시스템은 그 위에 구축되어 직접 사용됩니다. 가장 일반적인 서버는 다음과 같습니다.
RenderingServer: 그래픽과 관련된 모든 것을 처리합니다.
PhysicsServer3D: 3D 물리학과 관련된 모든 것을 처리합니다.
PhysicsServer2D: 2D 물리학과 관련된 모든 것을 처리합니다.
AudioServer: 오디오와 관련된 모든 것을 처리합니다.
API를 탐색하면 제공된 모든 기능이 Godot가 노드를 사용하여 수행할 수 있는 모든 것에 대한 저수준 구현이라는 것을 알게 될 것입니다.
RID
서버 사용의 핵심은 리소스 ID(RID) 개체를 이해하는 것입니다. 이는 서버 구현에 대한 불투명 핸들입니다. 수동으로 할당 및 해제됩니다. 서버의 거의 모든 기능에는 실제 리소스에 액세스하기 위해 RID가 필요합니다.
대부분의 Godot 노드 및 리소스에는 내부적으로 서버의 이러한 RID가 포함되어 있으며 다양한 기능을 통해 얻을 수 있습니다. 실제로 :ref:`Resource <class_Resource>`을 상속하는 모든 항목은 RID로 직접 캐스팅될 수 있습니다. 그러나 모든 리소스에 RID가 포함되어 있는 것은 아닙니다. 이러한 경우 RID는 비어 있습니다. 그러면 리소스가 RID로 서버 API에 전달될 수 있습니다.
경고
리소스는 참조 횟수로 계산되며(RefCounted 참조) 리소스가 아직 사용 중인지 확인할 때 리소스의 RID에 대한 참조는 계산되지 않습니다. 서버 외부 리소스에 대한 **참조를 유지**하세요. 그렇지 않으면 리소스와 해당 RID가 모두 지워집니다.
노드의 경우 다음과 같은 다양한 기능을 사용할 수 있습니다.
CanvasItem의 경우 CanvasItem.get_canvas_item() 메서드는 서버에서 캔버스 항목 RID를 반환합니다.
CanvasLayer의 경우 CanvasLayer.get_canvas() 메서드는 서버에서 캔버스 RID를 반환합니다.
뷰포트의 경우 뷰포트.get_viewport_rid() 메서드는 서버에서 뷰포트 RID를 반환합니다.
2D의 경우 World2D 리소스(Viewport 및 CanvasItem 노드에서 얻을 수 있음)에는 RenderingServer Canvas 및 *PhysicsServer2D Space*를 가져오는 함수가 포함되어 있습니다. 이를 통해 서버 API로 직접 2D 객체를 생성하고 사용할 수 있습니다.
3D의 경우 World3D 리소스(Viewport 및 Node3D 노드에서 얻을 수 있음)에는 RenderingServer Scenario 및 *PhysicsServer Space*를 가져오는 기능이 포함되어 있습니다. 이를 통해 서버 API로 직접 3D 객체를 생성하고 사용할 수 있습니다.
VisualInstance3D 클래스를 사용하면 각각 VisualInstance3D.get_instance() 및 :ref:`VisualInstance3D.get_base() <class_VisualInstance3D_method_get_base>`를 통해 인스턴스 및 인스턴스 베이스 시나리오를 가져올 수 있습니다.
익숙한 노드 및 리소스를 탐색하고 서버 *RID*를 얻는 기능을 찾아보세요.
이미 노드가 연결된 개체에서 RID를 제어하는 것은 권장되지 않습니다. 대신, 새로운 기능을 생성 및 제어하고 기존 기능과 상호 작용하는 데 항상 서버 기능을 사용해야 합니다.
스프라이트 만들기
이는 코드에서 스프라이트를 생성하고 하위 수준 CanvasItem API를 사용하여 이동하는 방법의 예입니다.
참고
RenderingServer를 사용하여 캔버스 항목을 생성할 때 :ref:`RenderingServer.canvas_item_reset_physics_interpolation() <class_RenderingServer_method_canvas_item_reset_physics_interpolation>`을 사용하여 첫 번째 프레임에서 물리 보간을 재설정해야 합니다. 이렇게 하면 렌더링과 물리 시스템 간의 적절한 동기화가 보장됩니다.
그렇지 않으면 캔버스 항목이 의도한 위치에 직접 나타나지 않고 씬이 로드될 때 순간이동하는 것처럼 나타날 수 있습니다.
extends Node2D
# RenderingServer expects references to be kept around.
var texture
func _ready():
# Create a canvas item, child of this node.
var ci_rid = RenderingServer.canvas_item_create()
# Make this node the parent.
RenderingServer.canvas_item_set_parent(ci_rid, get_canvas_item())
# Draw a texture on it.
# Remember to keep this reference.
texture = load("res://my_texture.png")
# Add it, centered.
RenderingServer.canvas_item_add_texture_rect(ci_rid, Rect2(-texture.get_size() / 2, texture.get_size()), texture)
# Add the item, rotated 45 degrees and translated.
var xform = Transform2D().rotated(deg_to_rad(45)).translated(Vector2(20, 30))
RenderingServer.canvas_item_set_transform(ci_rid, xform)
# Reset physics interpolation for this item.
RenderingServer.canvas_item_reset_physics_interpolation(ci_rid)
public partial class MyNode2D : Node2D
{
// RenderingServer expects references to be kept around.
private Texture2D _texture;
public override void _Ready()
{
// Create a canvas item, child of this node.
Rid ciRid = RenderingServer.CanvasItemCreate();
// Make this node the parent.
RenderingServer.CanvasItemSetParent(ciRid, GetCanvasItem());
// Draw a texture on it.
// Remember to keep this reference.
_texture = ResourceLoader.Load<Texture2D>("res://my_texture.png");
// Add it, centered.
RenderingServer.CanvasItemAddTextureRect(ciRid, new Rect2(-_texture.GetSize() / 2, _texture.GetSize()), _texture.GetRid());
// Add the item, rotated 45 degrees and translated.
Transform2D xform = Transform2D.Identity.Rotated(Mathf.DegToRad(45)).Translated(new Vector2(20, 30));
RenderingServer.CanvasItemSetTransform(ciRid, xform);
// Reset physics interpolation for this item.
RenderingServer.CanvasItemResetPhysicsInterpolation(ciRid);
}
}
서버의 Canvas Item API를 사용하면 그리기 기본 요소를 추가할 수 있습니다. 추가한 후에는 수정할 수 없습니다. 항목을 지우고 기본 요소를 다시 추가해야 합니다. 원하는 만큼 여러 번 수행할 수 있는 변환 설정의 경우는 그렇지 않습니다.
기본 요소는 다음과 같이 삭제됩니다.
RenderingServer.canvas_item_clear(ci_rid)
RenderingServer.CanvasItemClear(ciRid);
메시를 3D 공간으로 인스턴스화
3D API는 2D API와 다르기 때문에 인스턴스화 API를 사용해야 합니다.
extends Node3D
# RenderingServer expects references to be kept around.
var mesh
func _ready():
# Create a visual instance (for 3D).
var instance = RenderingServer.instance_create()
# Set the scenario from the world. This ensures it
# appears with the same objects as the scene.
var scenario = get_world_3d().scenario
RenderingServer.instance_set_scenario(instance, scenario)
# Add a mesh to it.
# Remember to keep this reference.
mesh = load("res://my_mesh.obj")
RenderingServer.instance_set_base(instance, mesh)
# Move the mesh around.
var xform = Transform3D(Basis(), Vector3(2, 3, 0))
RenderingServer.instance_set_transform(instance, xform)
public partial class MyNode3D : Node3D
{
// RenderingServer expects references to be kept around.
private Mesh _mesh;
public override void _Ready()
{
// Create a visual instance (for 3D).
Rid instance = RenderingServer.InstanceCreate();
// Set the scenario from the world. This ensures it
// appears with the same objects as the scene.
Rid scenario = GetWorld3D().Scenario;
RenderingServer.InstanceSetScenario(instance, scenario);
// Add a mesh to it.
// Remember to keep this reference.
_mesh = ResourceLoader.Load<Mesh>("res://my_mesh.obj");
RenderingServer.InstanceSetBase(instance, _mesh.GetRid());
// Move the mesh around.
Transform3D xform = new Transform3D(Basis.Identity, new Vector3(2, 3, 0));
RenderingServer.InstanceSetTransform(instance, xform);
}
}
2D RigidBody 생성 및 스프라이트 이동
결과적인 공간 RID 및 Physics2DServer 에서 사용할 수 있습니다.
# PhysicsServer2D expects references to be kept around.
var body
var shape
func _body_moved(state, index):
# Created your own canvas item; use it here.
# `ci_rid` from the sprite example above needs to be moved to a
# member variable (instead of within `_ready()`) so it can be referenced here.
RenderingServer.canvas_item_set_transform(ci_rid, state.transform)
func _ready():
# Create the body.
body = PhysicsServer2D.body_create()
PhysicsServer2D.body_set_mode(body, PhysicsServer2D.BODY_MODE_RIGID)
# Add a shape.
shape = PhysicsServer2D.rectangle_shape_create()
# Set rectangle extents.
PhysicsServer2D.shape_set_data(shape, Vector2(10, 10))
# Make sure to keep the shape reference!
PhysicsServer2D.body_add_shape(body, shape)
# Set space, so it collides in the same space as current scene.
PhysicsServer2D.body_set_space(body, get_world_2d().space)
# Move initial position.
PhysicsServer2D.body_set_state(body, PhysicsServer2D.BODY_STATE_TRANSFORM, Transform2D(0, Vector2(10, 20)))
# Add the transform callback, when body moves
# The last parameter is optional, can be used as index
# if you have many bodies and a single callback.
PhysicsServer2D.body_set_force_integration_callback(body, self, "_body_moved", 0)
# Also create a sprite using RenderingServer here.
# See the section above on creating a sprite.
# ...
using Godot;
public partial class MyNode2D : Node2D
{
private Rid _canvasItem;
private void BodyMoved(PhysicsDirectBodyState2D state, int index)
{
// Created your own canvas item; use it here.
// `ciRid` from the sprite example above needs to be moved to a
// member variable (instead of within `_Ready()`) so it can be referenced here.
RenderingServer.CanvasItemSetTransform(_canvasItem, state.Transform);
}
public override void _Ready()
{
// Create the body.
var body = PhysicsServer2D.BodyCreate();
PhysicsServer2D.BodySetMode(body, PhysicsServer2D.BodyMode.Rigid);
// Add a shape.
var shape = PhysicsServer2D.RectangleShapeCreate();
// Set rectangle extents.
PhysicsServer2D.ShapeSetData(shape, new Vector2(10, 10));
// Make sure to keep the shape reference!
PhysicsServer2D.BodyAddShape(body, shape);
// Set space, so it collides in the same space as current scene.
PhysicsServer2D.BodySetSpace(body, GetWorld2D().Space);
// Move initial position.
PhysicsServer2D.BodySetState(body, PhysicsServer2D.BodyState.Transform, new Transform2D(0, new Vector2(10, 20)));
// Add the transform callback, when body moves
// The last parameter is optional, can be used as index
// if you have many bodies and a single callback.
PhysicsServer2D.BodySetForceIntegrationCallback(body, new Callable(this, MethodName.BodyMoved), 0);
// Also create a sprite using RenderingServer here.
// See the section above on creating a sprite.
// ...
}
}
2D 및 3D 물리 서버가 동일하므로(각각 RigidBody3D 및 PhysicsServer3D 사용) 3D 버전은 매우 유사해야 합니다.
서버에서 데이터 얻기
수행 중인 작업을 알지 못하는 경우 함수를 호출하여 RenderingServer, PhysicsServer2D 또는 :ref:`class_PhysicsServer3D`에 정보를 절대 요청하지 마세요. 이러한 서버는 성능을 위해 비동기적으로 실행되는 경우가 많으며 값을 반환하는 함수를 호출하면 서버가 정지되고 함수가 실제로 호출될 때까지 보류 중인 모든 항목을 강제로 처리하게 됩니다. 매 프레임마다 호출하면 성능이 심각하게 저하됩니다(이유는 분명하지 않습니다).
이로 인해 해당 서버의 대부분의 API는 저장할 수 있는 실제 데이터가 될 때까지 정보를 다시 요청할 수 없도록 설계되었습니다.