반응형 박스 자막에서 모션 선택 기능 만들기
After Effect Blender expression Premiere 모션 그래픽 템플릿 애프터이펙트 익스프레션 프리미어
반응형 자막 템플릿에 모션을 추가하고 프리미어에서 선택할 수 있는 기능을 만들어보겠습니다.
재사용이 편하도록 코드 정리하기
먼저 지금까지 만들었던 코드를 재사용하기 편하게 정리해보겠습니다. 먼저 렌더 텍스트가 2개 이상이 될 수도 있기 때문에 레이어 이름부터 정리해보겠습니다.

레이어 이름을 이렇게 정리했습니다. 뒤에 숫자 01을 붙인 이유는 레이어를 ctrl+D로 복제하면 자동으로 숫자가 하나씩 커지면서 레이어가 생성되기 때문입니다. 이렇게 되면 익스프레션 코드만 잘 작성해 두면 그냥 레이어만 복제해서 디자인 작업만 추가하면 되기 때문에 유용합니다.
자바스크립트의 정규 표현식으로 텍스트에서 숫자 또는 문자만 골라낼 수 있는 방법이 있어서 그 방법을 사용하도록 하겠습니다. 다음과 같이 레이어 이름에서 문자와 숫자만 남길 수 있습니다.
// /[^0-9]/g : 정규 표현식의 예시입니다. // 정규 표현식은 '/'로 감싸 있는 대괄호[] 안의 패턴을 매칭합니다. // 정규 표현식에서 '[^]'는 []안의 문자가 아닌 것을 뜻합니다. '^문자열'은 특정 문자열의 시작을 뜻합니다. // 0-9 : 숫자가 0~9까지이므로 모든 숫자를 뜻합니다. // g : global 즉, 전체에 대해 모두 검사하라는 뜻입니다. const layerNum = thisLayer.name.replace(/[^0-9]/g,""); //해석해보면 0-9 모든 숫자가 아닌(^)것을 전체에서 찾아서(g) 바꿔라(replace)! 뭐로? 공백이 없는 빈 문자열로("") //즉, 이 레이어의 이름에서 모든 문자를 다 지운 것을 layerNum으로 정의합니다. const layerNum = thisLayer.name.replace(/\D/g,""); //이렇게도 표현할 수 있습니다. const layerName = thisLayer.name.replace(/[0-9]/g,""); //이 레이어의 이름에서 숫자만 전부 지워서 layerName으로 정의합니다. const layerName = thisLayer.name.replace(/\d/g,""); //이렇게 쓸 수도 있습니다.
그리고 자바스크립트에서 문자열 + 문자열은 연결된 하나의 문자로 바꿔줍니다.
RenderText_01의 소스텍스트의 익스프레션 코드를 이렇게 바꿔보세요.
//레이어 이름에서 숫자만 추출합니다.
const layerNum = thisLayer.name.replace(/\D/g,"");
//레이어 이름에서 문자만 추출합니다.
const layerName = thisLayer.name.replace(/\d/g,"");
//문자+문자는 하나의 연결된 문자가 됩니다.
//totalText는 이 컴포지션에서 이름이 Text_(이 레이어의 숫자)인 레이어에 입력된 텍스트입니다.
const totalText = thisComp.layer("Text_"+layerNum).text.sourceText;
const pointText = thisComp.layer("PointText_"+layerNum).text.sourceText;
const subPointText = thisComp.layer("SubPointText_"+layerNum).text.sourceText;
const pointTextArray = pointText.split("$$");
const subPointTextArray = subPointText.split("$$");
//여기에 속성들의 값을 정의해서 나열하겠습니다.
//pointColor 는 이 컴포지션에서 이름이 (이 레이어의 문자열)Control_(이 레이어의 숫자)인 레이어에 입력된 텍스트입니다. 이 레이어의 문자열이 RenderText_까지이므로 Control앞에 _는 붙이지 않았습니다.
const pointColor = thisComp.layer(layerName+"Control_"+layerNum).effect("PointColor")("Color");
const subPointColor = thisComp.layer(layerName+"Control_"+layerNum).effect("SubPointColor")("Color");
let renderText = text.sourceText.style;
let searchIndex = 0;
pointTextArray.forEach(text => {
const startIndex = totalText.indexOf(text, searchIndex);
if (startIndex !== -1) {
renderText = renderText.setFillColor(pointColor, startIndex, text.length);
searchIndex = startIndex + text.length;
}
});
searchIndex = 0;
subPointTextArray.forEach(text => {
const startIndex = totalText.indexOf(text, searchIndex);
if (startIndex !== -1 && text.length > 0) {
renderText = renderText.setFillColor(subPointColor, startIndex, text.length);
searchIndex = startIndex + text.length;
}
});
renderText.setText(totalText);
자 이렇게 코드를 리펙토링하면 RenderText_01과 RenderText_Control_01레이어를 그냥 복제만 해도 익스프레션 코드를 수정할 필요가 없어집니다.
마찬가지로 다른 코드도 layerNum을 사용해서 리펙토링 해주겠습니다.
//RenderText_01 레이어의 Transform의 Position 익스프레션 코드입니다.
const layerNum = thisLayer.name.replace(/\D/g,"");
x = thisComp.width/2;
y = thisComp.height*0.9 - thisLayer.sourceRectAtTime().height/2 - thisComp.layer("Box_Control_"+layerNum).effect("Heigth_Blank")("Slider")/2;
[x, y]
//Box_01 레이어의 Rectangle Size 익스프레션 코드입니다.
const layerNum = thisLayer.name.replace(/\D/g,"");
const layerName = thisLayer.name.replace(/\d/g,"");
const renderTextLayer = thisComp.layer("RenderText_"+layerNum);
const wb = thisComp.layer(layerName+"Control_"+layerNum).effect("Width_Blank")("Slider");
const hb = thisComp.layer(layerName+"Control_"+layerNum).effect("Heigth_Blank")("Slider");
const boxWidth = renderTextLayer.sourceRectAtTime().width + wb;
const boxHeight = renderTextLayer.sourceRectAtTime().height + hb;
[boxWidth, boxHeight]
//Box_01 레이어의 Transform의 Position 익스프레션 코드입니다.
const layerNum = thisLayer.name.replace(/\D/g,"");
thisComp.layer("RenderText_"+layerNum).transform.position
자 이렇게 코드를 리펙토링 했습니다. 그럼 이제 필요한 레이어를 그대로 복제만 하면 지금까지 적용한 익스프레션이 그대로 적용된 새로운 반응형 박스 자막이 생깁니다. _01이 있는 모든 레이어를 그냥 ctrl+D로 복제해보세요.


RenderText_02의 포지션이 익스프레션으로 묶여 있으므로 다시 alt와 함께 스탑워치를 눌러 익스프레션을 지우고 적당한 위치로 옮겨 텍스트를 수정해서 테스트 해보세요.

이렇게 잘 동작하는 것을 확인할 수 있습니다. 이렇게 렌더 텍스트를 2개로 수정하면 이런 유형의 자막을 만들 수 있습니다.



이제 남은 건 디자인과 필요 없는 항목이나 기능을 제거하고 디자인적으로 추가할 부분을 추가하는 것 정도라고 볼 수 있겠습니다.
모션 선택 기능 구현하기
우리가 지금 만들고 있는 것은 ‘모션’ 그래픽 템플릿입니다. 어떤 자막이 나올 때 나오는 모션들이 필요할 때가 있죠? 그리고 같은 형태의 자막이지만 모션을 어떻게 주는지에 따라서 또 다른 느낌을 주기도 합니다.
그래서 이번에는 모션을 두 세개 만들어서 선택해서 사용할 수 있도록 익스프레션을 구성해보겠습니다.
먼저 새로운 컴포지션을 하나 만들고 그 안에 지금까지 작업했던 컴포지션을 넣어주세요. 저는 Box_Text라는 컴포지션을 새로 만들어서 그 안에 넣었습니다.

그리고 원래 컴포지션으로 돌아가서 디자인을 잡아주세요.
저는 텍스트 2 부분에 이름을 적을 것으로 생각해서 텍스트 2가 항상 박스의 좌측에 붙도록 포지션 값을 익스프레션으로 계산하였습니다.
//RenderText_02 레이어의 포지션 값 계산
a = thisLayer.sourceRectAtTime();
x = thisComp.width/2 - thisComp.layer("Box_01").sourceRectAtTime().width/2 + a.width/2 + thisComp.layer("Box_Control_02").effect("Width_Blank")("Slider")/2;
y = thisComp.layer("RenderText_01").transform.position[1] - thisComp.layer("Box_01").sourceRectAtTime().height/2 - a.height/2 - thisComp.layer("Box_Control_02").effect("Heigth_Blank")("Slider")/2;
[x,y]
이렇게 만들면 아래 이미지와 같이 항상 박스 텍스트의 왼쪽에 붙어서 움직이게 됩니다.

이제 다시 모션 작업을 시작해보겠습니다.
Box_Text 컴포지션에서 조정레이어를 하나 추가하고 이름을 Motion이라고 바꾼 뒤 그 안에 Dropdown Menu Control과 Checkbox Control 효과를 넣어주세요. 이름은 적당히 바꿔주세요. 저는 Motion_Select와 IsMotion?으로 네이밍 했습니다.

그 다음 모션을 주고자 하는 레이어에 Transfrom 효과를 넣어주세요.

transform 효과에 적당히 모션을 준 뒤 효과의 이름을 바꿔주세요. 저는 간단히 왼쪽에서 오른쪽으로 나타나는 모션으로 만들기 위해 leftToRight로 이름을 바꾸겠습니다.
간단하게 오파시티와 포지션 값을 조절하겠습니다.


이렇게 transfrom이라는 Effect를 사용하는 이유가 있습니다. Effect의 속성들을 열어보면 마지막에 Compositing Options라는 속성이 있는데요. 말그대로 이 효과의 합성 정도를 결정하는 속성입니다.
예를 들어 두 개의 transform효과를 줬는데 하나는 왼쪽에서 오른쪽으로 움직이고, 다른 하나는 아래에서 위로 움직이는 효과를 줬다고 가정하겠습니다. 이때 Compositing Options의 Effect Opacity가 낮아지면 낮아질 수록 transform 효과가 덜 먹습니다.
지금 만든 leftToRight의 Compositing Options의 Effect Opacity를 조절해보세요. Effect Opacity가 0이 되면 leftToRight 모션은 동작하지 않습니다.
자 그럼 다들 눈치 채셨나요? 우리는 이 Compositing Options의 Effect Opacity 값에 익스프레션으로 작업을 해서 모션을 컨트롤할 것입니다.
모션 On/Off기능 구현하기
단순히 하나의 모션을 구현하고 그 모션을 On/Off로 구현해보겠습니다.
모션은 leftToRight만 사용할 예정이므로 bottomToTop은 Effect Opacity를 0으로 바꿔주세요.
자 이제 leftToRight의 Effect Opacity 속성에 익스프레션 코드 블록을 열고 이렇게 작성해보세요.

// 체크박스가 선택되어 있으면 a 값은 1, 선택되어 있지 않으면 a값은 0을 출력합니다.
a = thisComp.layer("Motion").effect("IsMotion?")("Checkbox");
// a가 0일 때(체크박스가 선택되어 있지 않을 때) 0을 출력하고, 그 외에는 100을 출력합니다.
(a == 0) ? [0] : [100]
이러면 체크박스가 선택이 되면 모션이 동작하게 됩니다.
이제 체크 박스 값을 Essential Graphics 패널에 올려서 모션그래픽 템플릿으로 빼면 프리미어에서 모션 On/Off 기능을 사용할 수 있습니다.

Box_Text 컴포지션을 모션 그래픽 템플릿으로 만들어야 합니다. 그리고 Essential Graphics 패널에는 Box_Text 컴포지션 안에 있는 자녀 컴포지션안의 속성들도 뺄 수 있습니다.

모션 그래픽 템플릿으로 저장하기 전에 여기를 살펴보시면 이것은 PointText 컴포지션 자체에서 Essential Graphics 패널에 올려 놓은 속성들입니다. 지금 각 속성들 소용돌이 모양에 pull과 push 버튼이 비활성화 되어 있는 것은 현재 Box_Text의 Essential Graphics 패널에도 같은 속성이 올라가 있다는 뜻이고, 활성화 된 부분은 현재 Essential Graphics 패널에 없다는 것입니다.

이렇게 PointText 에 TEXT라는 속성이 올라가 있는 상태고, 이 속성은

RenderText_01에 입력된 텍스트입니다. 그런데 우리는 이 속성이 필요가 없으므로 PointText의 에센셜 그래픽 패널에서 삭제해 줍니다. 이 것을 삭제해 주는 이유는 우리가 출력하고자 하는 Box_Text의 에센셜 그래픽 패널에 올라간 속성과 충돌이 생길 수 있기 때문입니다.
가장 깔끔한건 PointText 컴포지션은 별도로 빼지 않을 것이므로 그냥 에센셜 그래픽 패널을 비워주는 것이 깔끔하겠죠?

이제 프리미어 프로에서 테스트 해보겠습니다.
의도한대로 잘 동작하는 것을 확인할 수 있습니다.
드롭다운 메뉴로 모션 셀렉터 구성하기
이번에는 모션이 2개 이상일 때를 드롭다운 메뉴로 구성해서 만들겠습니다.
아까 Motion 레이어에 추가한 드롭다운 메뉴(Motion_Select)를 사용할 건데요.


이렇게 드롭다운 메뉴의 Edit 버튼을 눌러 드롭다운 메뉴의 이름을 바꿉니다. +, – 버튼으로 드롭다운 메뉴의 개수를 추가하거나 삭제할 수도 있습니다.
드롭다운 메뉴의 이름 앞에 있는 숫자에 주목해주세요.
a = thisComp.layer(“Motion”).effect(“Motion_Select”)(“Menu”); 이렇게 정의했을 때 드롭다운 메뉴로 선택한 메뉴에 해당하는 숫자가 a의 값으로 출력됩니다.
드롭다운 메뉴로 leftToRight를 선택하면 a의 값은 2, bottomToTop를 선택하면 a의 값은 3이 되겠죠? 그리고 none을 선택하면 a의 값은 1을 출력할 것입니다.
leftToRight의 Compositing Options의 Effect Opacity의 익스프레션 블록을 열고 다음과 같이 작성합니다.
//none : 1, leftToRight: 2, bottomToTop: 3을 출력합니다.
a = thisComp.layer("Motion").effect("Motion_Select")("Menu");
//a가 2인 경우에만 Effect Opacity 값을 100으로 출력하고 그 외에는 0으로 출력합니다.
a == 2 ? [100] : [0]
bottomToTop의 Compositing Options의 Effect Opacity의 익스프레션 블록을 열고 다음과 같이 작성합니다.
a = thisComp.layer("Motion").effect("Motion_Select")("Menu");
a == 3 ? [100] : [0]
이러면 드롭다운 메뉴가 none일 때는 Effect Opacity 값이 둘 다 0이 되고, 자신이 선택된 경우에만 100이 출력됩니다.
이제 드롭다운 메뉴를 에센셜 그래픽 패널에 넣고 프리미어로 얹어서 확인해보겠습니다. 체크박스는 사용하지 않았으므로 제외하고 출력하겠습니다. Name을 다른 것으로 바꿔서 출력하겠습니다.

전에 한 번 말씀드렸듯이 에센셜 그래픽의 Name에 입력한 이름이 프리미어 프로에서 불러오는 이름이 됩니다. 한 번 확인해보겠습니다.
의도한 대로 잘 동작합니다.
마커와 Region 설정하기
그런데 여기서 한 가지 문제점이 있습니다. 프리미어 프로에서 자막을 만들면 자막이 유지되는 길이를 마음대로 늘리고 줄일 수 있지만 애프터 이펙트로 만든 모션그래픽 템플릿은 자막의 길이가 고정됩니다. 줄일 수는 있지만 늘릴 수는 없습니다.

이 길이는 애프터 이펙트에서 처음 컴포지션 세팅을 할 때 만드는 컴포지션의 duration으로 고정되는데요. 자막이 들어가는 시간이 얼마인지 정해져 있지 않는 이상 작업을 하다가 불편한 점이 발생하게 됩니다. 자막이 떠 있어야 하는 시간보다 컴포지션의 길이가 짧아지게 되는 경우도 생기게 되죠.
해결 방법은 간단합니다.



타임라인에서(레이어 선택 하지 않은 상태) *키로 마커를 찍고 Region을 세팅해주면 됩니다. 마커를 더블클릭하면 Region duration을 세팅할 수 있습니다. 0프레임에서 듀레이션을 모션이 끝나고 난 뒤인 2초보다 뒤로 설정해주세요. 2초로 설정해도 무방합니다.
이렇게 설정하고 모션 그래픽 템플릿을 익스포트 하면 프리미어 프로에서 자막의 길이를 늘리고 줄일 수 있습니다.
여기서 Region의 동작 원리를 잠깐 짚고 넘어가겠습니다.
이 Region의 역할은 모션 그래픽 템플릿의 길이를 늘리고 줄일 때 고정하려는 부분을 지정하는 것입니다. 그런데 Region은 애프터 이펙트의 타임 리맵핑을 적용시키는 역할을 합니다.
애프터 이펙트는 애초에 컴포지션을 세팅할 때 duration을 세팅하게 되어 있습니다. 그런에 Region을 적용하면 duration을 프리미어에서 조절할 수 있게 해줍니다. 그렇다면 어떤 형태로 적용하는 것일까요?

애프터 이펙트의 Box_Text 컴포지션의 duration을 1분 30초로 늘렸습니다. 그런데 우리가 제작했던 PointText 컴포지션의 duration은 30초입니다. 당연히 컴포지션이 타임라인에 다 채울 수 없습니다.

그런데 crtl+alt+T로 타임 리맵핑을 하게 되면 duration을 마음대로 길게 늘릴 수 있습니다.
Region이 프리미어에서 이 역할을 해줍니다. Region에서 고정한 구간은 원래 세팅대로 시간이 흘러가도록 고정해주고, 그 외의 구간은 타임 리맵핑으로 늘리고 줄여줍니다. 그런데 타임 리맵핑은 그냥 duration을 늘려주는 것이 아니라 duration을 늘려주는 대신 컴포지션의 플레이 속도를 조절해주는 형태로 동작을 하게 됩니다.
즉, 우리가 모션을 2초로 만들었고, Region을 2초로 고정시켜 두었기 때문에 모션의 속도에는 영향을 미치지 않고 자막이 들어갈 수 있는 duration을 늘리고 줄일 수 있는 것입니다. 따라서 만약 이 Region 영역을 모션이 포함되어 있지 않는 구간으로 세팅한다면 프리미어에서 자막의 길이를 늘리고 줄일 때 모션의 속도가 변하게 되는 상황이 발생합니다. 다음은 Region을 1초로 설정했을 때와 비교한 영상입니다.
만약 자막의 in모션과 out모션이 있다면 마커의 Region을 두 군데로 지정하면 됩니다. 그럼 모션은 유지 되고 자막 시간만 길게 늘려서 작업할 수 있습니다.
Region 설정 시 주의점
Region은 타임 리맵핑이 적용된다고 앞에서 말씀드렸습니다. 따라서 Region 설정을 할 수 없는 경우가 있습니다. 바로 wiggle 처럼 duration 내내 모션이 유지되는 효과를 사용할 경우입니다. 이때는 프리미어에서 자막 duration을 늘리면 늘릴수록 이펙트가 느려지게 되겠죠.
따라서 이런 모션 효과를 주는 자막이라면 Region 세팅을 하지 않는 것이 좋습니다.
posterizeTime()
포스터라이즈 타임 익스프레션 코드는 보통 스탑 모션 에니메이션을 만들 때 사용하는 코드 중에 하나입니다. posterizeTime()는 기존에 세팅한 컴포지션의 프레임수와 상관 없이 프레임 수를 줄일 때 사용하는 익스프레션 코드인데 이 코드를 사용하면 속성의 프레임 속도를 컴포지션의 프레임 속도보다 낮게 설정할 수 있습니다.
예를 들어 posterizeTime(1); 이렇게 코드를 작성하면 초당 1프레임으로 만들어 줍니다.
모션 그래픽 템플릿을 사용할 때 템플릿이 많이 쌓이거나 하면 프리미어 프로에서 버벅거림이 심해지는 경우가 생깁니다. 아무래도 애프터 이펙트의 특성상 매 프레임 마다 모든 레이어의 위치, 앵커, 사이즈 등등 다 체크해야 하잖아요.
디자인된 요소가 많을 수록, 들어간 효과가 많을 수록 점점 느려질 수 밖에 없습니다. 그래서 저는 모션 그래픽 템플릿 작업을 할 때 타임 리맵핑과 posterizeTime을 사용하는 편입니다.

const motionDuration = 2;
if (time > motionDuration) {
posterizeTime(0);
[motionDuration]
} else {
[timeRemap]
}
이렇게 작성하면 motionDuration이 2이므로 2초가 초과되는 순간 posterizeTime으로 프레임을 없애버립니다. 그리고 타임 리맵핑의 시간을 2초로 고정해버리구요.
즉, 모션이 있는 구간을 제외하고는 그냥 0프레임으로 만들어버리는거죠.
const motionDuration = 2;
if (time > motionDuration) {
[motionDuration]
} else {
[timeRemap]
}
사실 posterizeTime 없이 이렇게 타임 리맵핑만 작업해놔도 2초 이후부터는 계속 2초로 고정시켜서 화면에 렌더링해주는 역할을 하는데 이것도 결국 애프터 이펙트는 프레임이 지나갈 때마다 3초일 때도 타임 리맵핑을 체크하면서 2초를 뿌려주고, 4초일 때도 2초의 화면을 뿌려주는 식으로 계산하는 것 같아서 posterizeTime로 그냥 프레임 자체를 0으로 만들어서 작업하는 편입니다.
이 부분에 대한 정확한 동작 원리는 아직도 잘 모르겠습니다. 애프터 이펙트 고수 분들께서 혹시 알고 계시다면 저에게도 알려주시면 감사하겠습니다.
아무튼 이렇게 posterizeTime까지 적용해서 작업하면서 한결 가벼워진 것을 느낄 수 있었습니다.
이렇게 해서 가장 기본적인 자막 형태를 만드는 기본형을 모두 만들어봤습니다. 다음 부터는 이 기본형을 가지고 여러 가지 디자인 된 자막을 만들어 보도록 하겠습니다.
지금 만든 자막 템플릿을 첨부합니다. 필요하신 분들은 다운 받아서 사용해보세요~ 버전은 애프터 이펙트 2025이므로 프리미어 프로 2025 버전에서 사용이 가능합니다.
긴 글 읽어주셔서 감사합니다. 그럼 이만~
P.S 포스팅을 하면서 템플릿들을 하나씩 정리하며 업로드 하는 사이트를 만들고 있습니다. 다운 받으시는 분들이 늘어나고 반응이 괜찮으면 회원 가입을 받고 서로의 자료를 공유하는 식으로 발전시키면 어떨까 생각됩니다. 편하게 구경해보시고 템플릿 다운받아 가세요.
답글 남기기