본문 바로가기

로블록스 개발 중급

CFrame 이해하기

Coordinate Frame의 줄임말인 CFrame은 파트나 모델의 위치와 방향을 알려주는 데이타형이다. 위치(Position)는 x축, y축, z축의 넘버, 그리고 방향(rotation)은 x축, y축, z축, 각축의 회전값을 포함한다. 게임내에서 CFrame 데이터형은 단순히 위치와 방향의 데이터값만 제공하는 것이 아니라 여러 유용한 프로퍼티와 함수를 제공한다. 2D게임이라면 위치가 X축, Y축, 그리고 방향이 Z축으로 하나라서 단순하지만, 로블록스에서는 3D가 기본이라 로블록스에서 게임을 만든다면 아마 반드시 이해하고 넘어가야 하는 내용이 아닐까? 라고 생각한다.

CFrame의 기본

CFrame으로 위치 지정

CFrame의 new()함수를 사용하면 생성되는 파트나 모델의 위치를 직접 지정할 수 있다. 그냥 CFrame.new()라고 하면 0,0,0의 위치를 가진다. 특정 위치에 만들고 싶다면 CFrame.new(x, y, z)의 x축, y축, z축의 값을 지정하면 된다. 이미 생성되어 있는 파트나 모델이라면, 아래와 같이 파트나 모델의 CFrame을 덮어쓰는 방식으로 지정할 수 있다.

local newCFrame = CFrame.new(-2, 2, 4)
redBlock.CFrame = newCFrame

CFrame의 x, y, z는 Vector3로 표현될 수 있다.

local redBlock = game.Workspace.RedBlock
 local newVector3 = Vector3.new(-2, 2, 4)
local newCFrame = CFrame.new(newVector3)
redBlock.CFrame = newCFrame

CFrame으로 방향 정하기

CFrame의 new()생성자처럼 Angle()생성자를 사용하면 CFrame의 방향을 생성자에서 지정할 수 있다. x축, y축, z축 각축의 회전을 radian값으로 표현한다. degree 값인 "도"로 표현한다면 math.rad()함수를 사용하면 된다.

local redBlock = game.Workspace.RedBlock 
-- Angle()생성자를 사용하기. 여기서는 y축에 45도인 CFrame생성
local newCFrame = CFrame.Angles(0, math.rad(45), 0)
redBlock.CFrame = newCFrame

여기서 Angle()생성자는 각도가 아니라 radian값을 넣어야 한다. 그래서 math.rad()함수로 각도에서 radian을 계산하고 삽입한다.

포인트를 향하게 하기

CFrame의 new()생성자의 가장 파워풀한 사용법은 파트를 특정 위치에 위치시키고 난 후, 특정 포인트를 향하게 만드는 것이다. 예를 들어 RedBlock 파트를 (0, 3, 0)에 위치시키고 BlueBlock 파트를 바라보게 만들고 싶다고 가정하자. 그러면 아래의 소스 코드처럼 하면 된다.

local redBlock = game.Workspace.RedBlock
local blueCube = game.Workspace.BlueCube
 
-- startPosition은 RedBlock 이 위치하게 될 포인트이다.
local startPosition = Vector3.new(0, 3, 0)
-- targetPosition은 RedBlock이 향하게 될 포인트.(BlueBlock을 바라보게 된다)
local targetPosition = blueCube.Position
 
-- RedBlock의 CFrame()에 처음 인자로 startPosition, 두번째 인자로 targetPosition
redBlock.CFrame = CFrame.new(startPosition, targetPosition)

CFrame의 오프셋에 위치시키기

어떤 상황에서는 이미 정해진 포인트에서 오프셋(정해진 위치에서 어느정도 떨어져 있냐를 알려주는 정보) 만큼 이동시켜야 할 때도 있다. 이럴때는 단순하게 Vector3를 더하거나 빼서 위치를 정해줄 수 있다.

local redBlock = game.Workspace.RedBlock
-- RedBlock의 포인트에서 (0, 1, 0)만큼 이동시킨다.
redBlock.CFrame = CFrame.new(redBlock.Position) + Vector3.new(0, 1, 0)

위의 소스코드는 해당 파트의 위치에서 오프셋만큼 이동시킨 것이다. 비슷한 방식으로 다른 파트위 위치에서 오프셋만큼 이동 시킬 수 도 있다. RedBlock 을 BlueBlock의 위에 위치시키고 싶다면 아래의 코드를 살펴보자.

local redBlock = game.Workspace.RedBlock
local blueCube = game.Workspace.BlueCube
 
redBlock.CFrame = CFrame.new(blueCube.Position) + Vector3.new(0, 2, 0)

동적으로 CFrame 정하기

CFrame.new()생성자와 CFrame.Angles()생성자는 매우 활용성이 좋지만, 특정 값을 인자로 줘야 한다는 단점이 있다. 실제 프로그래밍에서 다양한 상황에 대처하기 힘든 조건이다. 예를 들어, 계속 움직이는 캐릭터의 머리 위에 어떤 파트를 놓는다고 가정해보자. 캐릭터는 어떻게 움직일 지 알 수 없기 때문에 생성자로는 대응할 수 없을 것이다.

이때는 보다 높은 활용성을 가지는 CFrame의 함수를 사용해보자.

상대적 위치 정하기

CFrame:ToWorldSpace()함수는 인자로 오프셋를 주면 월드 위치를 계산해서 반환해주는 함수이다. 자신의 머리위에 파트를 위치시키고 싶다? 그러면 자신 캐릭터의 CFrame:ToWorldSpace()함수에 머리위(아마 0, 2, 0) 오프셋을 인자로 보내면 자신의 머리위 월드 위치를 계산해서 알려준다. 월드 위치(World Position)이라는 용어가 생소할 수 있겠다. 월드 위치는 지금껏 우리가 사용해 온 위치와 같다. 어느 파트가 어느 포인트에 있다고 할때 그 포인트가 월드 포인트이다. 그럼 왜 이제와서 월드 포인트라는 용어를 사용하는냐? 그것은 로컬 포인트라는 개념이 나오기 때문이다. 오프셋은 사실 로컬 포인트이다. 월드에서 계산된 위치가 아닌, 특정 파트를 원점 기준을 잡고 표현하기 때문에 로컬 포인트라고 한다. 우리가 CFrame을 통해 파트의 위치를 정해 줄 때는 월드 포인트값이 필요하다. 그래서 CFrame:ToWorldSpace()함수가 유용하다. 아래의 코드는 RedBlock의 위치를 BlueBlock의 (0, 2, 0) 만큼 떨어진 곳에 정하기 위한 코드이다.

local redBlock = game.Workspace.RedBlock
local blueCube = game.Workspace.BlueCube
 
local offsetCFrame = CFrame.new(0, 2, 0)
redBlock.CFrame = blueCube.CFrame:ToWorldSpace(offsetCFrame)

상대적으로 회전시키기

CFrame:ToWorldSpace() 는 위치뿐만이 아니라 방향을 정하는 때에도 사용된다. CFrame.new()대신 CFrame.Angles()를 사용하면 로컬 방향을 월드 방향으로 교환할 수 있다. 아래는 RedBlock의 현재 방향에서 Y축으로 70도, Z축으로 20도를 회전시키기 위한 코드이다. 

local redBlock = game.Workspace.RedBlock
 
local rotatedCFrame = CFrame.Angles(0, math.rad(70), math.rad(20))
redBlock.CFrame = redBlock.CFrame:ToWorldSpace(rotatedCFrame)

특정 방향 바라보게 하기

물론 CFrame을 통해서 회전을 시킬 수 있지만, 파트를 원하는 방향을 바라보게 회전값을 각 축에 정하는 것은 생각보다 까다롭다. 그래서 CFrame의 생성자인 new()함수에서는 생성시에 특정 포인트를 바라보게 하는 CFrame을 생성할 수 있는 기능이 있다. new(포지션, 타겟 포인트)로 사용이 가능하다.  포인트는 Vector3 형으로 알려줘야 한다. 사용방식은 new() 생성자로 파트의 Front면을 원하는 방향으로 향하게 만든 다음, 실제로 원하는 면이 타겟포인트를 바라보게 바꾼다.

아래의 소스코드를 살펴보자.

-- 움직일 파트
local redBlock = game.Workspace.RedBlock
-- 타겟 포인트가 될 파트
local blueCube = game.Workspace.BlueCube
 
-- 타겟 포인트를 구함
local targetPosition = blueCube.Position
 
-- redBlock의 Front면이 타겟 포인트를 향하게 함.
redBlock.CFrame = CFrame.new(redBlock.Position, targetPosition)
 
-- redBlock을 회전하여 Top면이 타겟포인트를 향하게 함.
local rotatedCFrame = CFrame.Angles(math.rad(-90), 0, 0)
redBlock.CFrame = redBlock.CFrame:ToWorldSpace(rotatedCFrame)

두 포인트 사이의 포인트 구하기

선형 보정(Linear Interpolation)이라는 방법을 사용하여 2개의 포인트 사이의 위치를 구할 수 있다. 이때 CFrame의 Lerp라는 함수를 사용한다. 예를 들어 greenCube와 CyanCube의 위치 중에서 7:3 부근에 redBlock 위치를 정하고 싶다고 할 때, greenCube의 CFrame:Lerp() 함수를 사용하고 그 인자로 cyanCube의 CFrame과 0.7 을 넣으면 된다.

local redBlock = game.Workspace.RedBlock
local greenCube = game.Workspace.GreenCube
local cyanCube = game.Workspace.CyanCube
 
redBlock.CFrame = greenCube.CFrame:Lerp(cyanCube.CFrame, 0.7)

이제까지 파트 CFrame의 생성자와 함수를 사용하면 객체의 위치와 방향을 정해줄 수 있다는 것을 알아 보았다.