화면 흔들림 (스크린 셰이크)

Screen Shake

피격·폭발 같은 큰 충격 순간에 화면 전체를 짧게 흔들어 물리적인 힘이 가해졌다는 감각을 주는 연출.

다른 이름: 카메라 셰이크Camera shake트라우마 셰이크
···
html
<div class="ssouter"><div class="ssworld" id="world"><div class="ssgrid"></div><div class="sshero"></div><div class="sstarget" id="tgt"></div></div><div class="ssflash" id="flash"></div></div>
css
.ssouter{position:relative;width:100%;height:100%;overflow:hidden;background:var(--bg);box-shadow:inset 0 0 clamp(20px,8vmin,60px) color-mix(in srgb,var(--fg) 12%,transparent)}
.ssworld{position:absolute;inset:-8%;display:grid;place-items:center}
.ssgrid{position:absolute;inset:0;background-image:linear-gradient(var(--line) 1px,transparent 1px),linear-gradient(90deg,var(--line) 1px,transparent 1px);background-size:13% 13%;opacity:.6}
.sshero{position:absolute;left:34%;top:50%;width:clamp(24px,13vmin,44px);height:clamp(24px,13vmin,44px);margin-top:-22px;border-radius:7px;background:var(--accent);box-shadow:0 6px 14px color-mix(in srgb,var(--accent) 40%,transparent)}
.sstarget{position:absolute;left:62%;top:50%;width:clamp(30px,16vmin,54px);height:clamp(30px,16vmin,54px);margin-top:-27px;border-radius:8px;background:var(--surface);border:3px solid var(--line)}
.ssflash{position:absolute;inset:0;background:var(--fg);opacity:0;pointer-events:none}
js
const world=document.getElementById('world');
const tgt=document.getElementById('tgt');
const flash=document.getElementById('flash');
let trauma=0, t=0;
function loop(){
  t+=1;
  if(trauma>0) trauma=Math.max(0, trauma-0.045);
  const s=trauma*trauma;
  const ox=(Math.sin(t*1.7)+Math.sin(t*3.1+1))*10*s;
  const oy=(Math.sin(t*2.3+2)+Math.sin(t*4.1))*7*s;
  const rot=(Math.sin(t*2.7))*6*s;
  world.style.transform='translate('+ox+'px,'+oy+'px) rotate('+rot+'deg)';
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
function hit(){
  trauma=1;
  flash.style.transition='none'; flash.style.opacity='0.5';
  requestAnimationFrame(function(){ flash.style.transition='opacity .3s ease-out'; flash.style.opacity='0'; });
  tgt.style.transition='transform .08s ease-out';
  tgt.style.transform='translateX(10px) scale(0.9)';
  setTimeout(function(){ tgt.style.transform='translateX(0) scale(1)'; }, 100);
}
setInterval(hit, 1300);
setTimeout(hit, 500);

단순히 카메라를 무작위로 흔드는 대신, "trauma"라는 0~1 값을 두고 흔들림 세기를 trauma의 제곱에 비례하게 만드는 방식이 널리 쓰인다. 충격이 오면 trauma를 올리고, 매 프레임 일정량씩 감소(decay)시키면서 그 값으로 오프셋과 회전을 계산한다 — 세게 맞을수록 더 격하게, 시간이 지날수록 빠르게 잦아든다.

제곱을 쓰는 이유는 약한 충격에서는 거의 안 흔들리다가 trauma가 쌓일수록 흔들림이 급격히 커지게 하기 위해서다(선형이면 작은 흔들림이 계속 거슬린다). 오프셋은 Perlin 노이즈나 sin 함수 여러 개를 합쳐서 만들면 무작위 떨림이 아니라 유기적으로 흔들리는 느낌이 난다.

화면을 너무 세게, 너무 자주 흔들면 UI 텍스트를 읽을 수 없고 멀미를 유발한다. 최대 오프셋에 상한을 걸고, 연속 타격 시 trauma가 무한히 쌓이지 않게 최댓값(보통 1)에서 clamp 해야 한다.

언제 쓰나

강한 피격, 폭발, 보스 등장 같은 드문 순간에만. 평범한 이동·기본 공격마다 흔들면 금세 피로해진다.