Skip to content

복잡한 퍼즐 인터랙션에 적절한 청각적 자막 제공하기

lybell edited this page Aug 27, 2024 · 2 revisions

배경상황

스크린샷 2024-08-26 오후 5 13 58

저희는 V2L을 홍보하는, 퍼즐 인터랙션을 가지고 있어요. 이 퍼즐은 3x3의 그리드로 된 퍼즐 조각을 클릭해서, 퍼즐에 그려진 선들을 이어서 시작 지점에서 종료 지점으로 이으면 되는 게임이에요. 기본적으로, 버튼 기반이기 때문에 시각장애인도 버튼을 누를 수는 있지만, 퍼즐을 풀 수는 없었어요. 시각장애인이 퍼즐을 풀 수 없었던 이유는 2가지에요.

  1. 기본적으로, 퍼즐이 시각적 요소에 의존하고 있어요. 퍼즐 전체가 텍스트 기반이 아니라, 시각적으로 이어져 있는지, 이어져 있지 않는지를 기반으로 이해하도록 구성되어 있어요. 시각이 멀쩡한 사람들은 위의 사진에서 첫 번째 퍼즐 조각이 두 번째 퍼즐 조각과 이어져 있음을 알 수 있지만, 만약에 시각적 정보가 없다면 첫 번째 퍼즐 조각이 두 번째 퍼즐 조각과 이어져 있는지, 어떻게 이어져 있는지를 알 수 없을 거에요.
  2. 청각적 피드백이 없었어요. 당초에는 해당 퍼즐 인터랙션에 aria-live="assertive"를 이용한 청각 자막이 없어서, 버튼을 눌러도 청각적으로는 아무 일이 일어나지 않았어요. (aria-live="assertive"가 무엇인지는 다음 글인 '드래그 인터랙션의 시각장애인 접근성 개선기' 아티클을 참조하세요.)

그래서, 저희는 어떻게 하면 복잡한 시각 정보에 의존하는 퍼즐 게임을 친절하게 청각 자막으로 제공할지에 대해 의논했어요. 그 결과, 다음의 결과를 얻을 수 있었어요.

  • 게임을 처음 접했을 때, 퍼즐 게임의 규칙을 상세하게 알려준다.
  • 퍼즐을 돌릴 때(버튼을 클릭할 때), 현재 퍼즐 게임의 상태를 알려준다.

퍼즐의 규칙 알려주기

저희는 퍼즐 조각 돌려서 길 맞히기 퍼즐의 본질을 고민했어요. 그리고, 이 퍼즐의 본질은 주어진 시각적 정보에 따라 퍼즐을 돌려서 퍼즐을 연결하는 것이에요. 퍼즐은 왼쪽 위부터 오른쪽 아래에 있는 퍼즐까지 이어져 있어야 하며, 이것이 게임의 승리 조건이에요. 이 때, 퍼즐이 어떻게 생겼는지는 알 필요는 없고, 퍼즐을 돌려서 "연결된 상태"로 만들면 되는 것이 핵심이에요.

그리고, 퍼즐을 우리는 3x3으로 구성되어 있고, 좌표로 각각의 퍼즐 조각을 구분한다는 건 알고 있지만, 각각의 퍼즐의 번호를 좌표로 전달하면 직관적으로 알아듣기 힘들 것이라고 생각했어요. 그래서, 저희는 퍼즐의 번호를 왼쪽 위부터 1번이라고 매겨서, 2차원 좌표를 제공하는 대신 번호 하나만 제공해서 명료하게 퍼즐을 구분하고자 했어요. 실제로도, 시각장애인은 탭으로 각각의 퍼즐을 일차원적으로 탐색하기 때문에, 버튼의 번호를 순차적으로 구성하는 것이 시각장애인이 생각하는 버튼의 상태와 같다고 생각했어요.

논의 결과, 작성된 규칙은 다음과 같아요. 여러분이 볼 때에는 명료하게 전달된 것처럼 들리시나요?

IONIQ 5의 브이투엘 기능을 홍보하는 길 맞추기 퍼즐 게임입니다. 방향키나 탭 키로 퍼즐 조각을 탐색할 수 있고, 스페이스바로 퍼즐 조각을 눌러서 퍼즐을 오른쪽으로 돌려보세요. 퍼즐은 가로 3칸, 세로 3칸으로 구성되어 있으며, 왼쪽 위부터 1번입니다. 1번 퍼즐이 9번 퍼즐까지 이어지면 게임에서 이길 수 있습니다.

퍼즐의 상태 알려주기

사용자가 퍼즐을 돌리면, 퍼즐의 상태가 변경되어요. 이는, 퍼즐의 연결된 방식과, 현재 퍼즐이 정답인지가 바뀌는 것을 의미해요. 시각 정보를 인지하는 사람들은 시각적으로 퍼즐의 상태를 파악해서, 다음에 어떤 퍼즐을 돌리면 될지를 예측할 수 있지만, 시각장애인은 퍼즐이 어떻게 생겼는지, 다음에 어떤 퍼즐을 돌려야 할지를 알지 못해요. 그래서, 저희는 퍼즐의 현재 상태와 다음 퍼즐의 힌트를 알려주기로 했어요.

퍼즐의 현재 상태 중 가장 근본적인 것은, 어떠한 번호의 퍼즐 조각이 무엇과 이어져 있는지에요. 그래서, 퍼즐 조각이 이어진 번호의 배열을 알려주기 위해, 퍼즐이 현재 어떤 퍼즐과 이어져 있는지를 알려주는 알고리즘이 필요했어요. 그 알고리즘은 다음과 같이 구현했어요.

function getLinkedPuzzleState(pieces, width, height) {
  const linked = [];
  let [x, y] = [0, 0];
  let prev = LEFT;
  while (x >= 0 && x < width && y >= 0 && y < height) {
    // 직전 연결된 상태와 현재 커서가 연결되어 있지 않으면 return
    const cursor = y * width + x;
    const connectData = pieces[cursor].getConnectData();
    if ((prev & connectData) === 0) return [linked, cursor];
    // 연결되어 있다면 인덱스를 배열에 넣음
    linked.push(cursor);
    // 다음 연결된 상태를 가져옴
    prev = connectData ^ prev;
    // 다음 연결 상태를 기반으로 커서를 옮기고, 다음 연결 상태를 반전시킴 (이전 커서에서 오른쪽 = 다음 커서에서 왼쪽)
    switch (prev) {
      case LEFT:
        x--;
        prev = RIGHT;
        break;
      case RIGHT:
        x++;
        prev = LEFT;
        break;
      case UP:
        y--;
        prev = DOWN;
        break;
      case DOWN:
        y++;
        prev = UP;
        break;
      default:
        return [linked, -1];
    }
  }
  return [linked, -1];
}

각 퍼즐 조각은 2개의 방향을 연결해요. 그리고 맨 처음 1번(0,0) 퍼즐 조각은 왼쪽과 연결되도록, 9번(2,2) 퍼즐 조각은 오른쪽과 이어지도록 해야 해요. 1번 퍼즐 조각이 왼쪽과 이어져 있지 않다면, 즉시 리턴해요. 1번 퍼즐 조각이 왼쪽과 이어져 있다면, 왼쪽이 아닌 다른 방향 쪽으로 커서를 이동시켜요. 이해를 쉽게 하기 위해, 그 다른 방향을 아래쪽이라고 가정할게요. 1번 퍼즐 조각의 아래쪽에 있는 퍼즐은 4번 퍼즐 조각이에요. 1번 퍼즐 조각이 4번 퍼즐 조각과 이어진다는 것은, 1번 퍼즐 조각이 아래쪽과 이어져 있고, 4번 퍼즐 조각이 위쪽과 이어져 있어야 해요. 이미 1번 퍼즐 조각을 체크해서 4번 퍼즐 조각으로 커서를 바꿨으니, 1번 퍼즐 조각이 아래쪽과 이어져 있다는 것은 자명해요. 4번 퍼즐 조각이 위쪽에 있다는 것을 체크한 뒤, 위쪽과 이어져 있으면 끊어져 있다고 판단해서, 즉시 리턴해요. 만약 위쪽에 있으면 4번 퍼즐 조각의, 위쪽이 아닌 다른 방향으로 커서를 이동시켜요. 만약, 커서를 이동시킨 방향이 퍼즐의 바깥쪽이라면, 이 역시 즉시 리턴해요.

결과적으로, 이를 일반화하면 다음과 같아요.

  • 최초의 prev(이전의 퍼즐과 이어진 방향) 상태를 LEFT로 정의하고, 커서를 0,0으로 정의해요.
  • 현재 커서의 퍼즐 조각에 대해, prev 상태와 이어져 있는지 판별해요.
    • 만약 이어져 있지 않다면 함수를 즉시 종료해요.
  • 이어져 있다면, 현재 커서의 인덱스를 배열에 추가한 뒤, prev 상태가 아닌 이어진 방향을 별도의 변수에 저장한 뒤, 이 변수에 대해...
    • 이어진 방향으로 커서를 이동시켜요. 만약, 커서가 밖으로 나갔다면 함수를 즉시 종료해요.
    • 이어진 방향의 반대 방향을 prev 상태에 대입해요. 이것을 커서가 밖으로 나가거나, 퍼즐이 끊어질 때까지 계속 반복해요. 퍼즐은 2개의 방향만 이을 수 있으며, 최초의 퍼즐은 시작 지점과 이어져 있지 않거나, 왼쪽으로 이어져 있음이 보장되므로, 루프가 발생할 가능성은 없어요.

이를 기반으로, 현재 퍼즐 조각의 이어짐 정보를 나타낼 수 있어요.

저희는 여기서 멈추지 않고, 다음에 이어질 퍼즐의 힌트를 주면 더 수월하게 퍼즐을 풀 수 있지 않을까? 라고 생각했어요. 일반적으로, 시각적으로 문제가 없는 사용자는 현재 이어진 퍼즐의 다음에 있는 퍼즐을 눈으로 찾고, 그 퍼즐 조각을 클릭해요. 하지만, 시각장애인은 퍼즐 조각의 모양을 배제한 채로 다음에 어떤 퍼즐을 눌러야 하는지 알아낼 수 없어요. 그래서, 저희는 다음에 이어진 퍼즐의 힌트를 줘서 사용자가 최소한의 퍼즐의 힌트를 제공해서 무리 없이 퍼즐을 풀 수 있도록 구성했어요.

우선, 다음에 이어질 퍼즐의 인덱스는 위의 함수를 이용해서 구할 수 있어요.

  • 퍼즐 조각이 이전의 퍼즐 조각과 이어져 있지 않다면, '다음에 이어질 퍼즐의 인덱스'는 현재 퍼즐 조각의 인덱스이다.
  • 퍼즐 조각이 밖으로 나갔다면, '다음에 이어질 퍼즐의 인덱스'는 -1(없음)이다. 만약 사용자가 퍼즐이 밖으로 이어져 있다는 것을 알게 되면, 그 퍼즐을 돌리면서 탐색할 것이다.

단, 퍼즐의 정답을 맞혔으면, 퍼즐을 끝까지 이었으면 다음에 이어질 퍼즐의 인덱스를 굳이 말해줄 필요가 없으므로, "퍼즐이 전부 이어졌습니다!"라는 메시지를 읽어주도록 구성했어요.

이걸 기반으로, 적절한 자막을 구성했어요.

  • 현재 퍼즐 조각 (n번, m번, o번)이 이어져 있습니다.
  • 다음 퍼즐 조각은...
    • 퍼즐이 전부 이어졌습니다! (퍼즐이 정답일 때)
    • 퍼즐이 끊어졌습니다. (이전에 퍼즐이 정답이었지만, 퍼즐의 조작으로 퍼즐이 정답이 아니게 되었을 때)
    • 다음 퍼즐 조각은 n번입니다. (다음에 이어질 퍼즐이 안에 있을 때)
    • 퍼즐이 밖으로 이어졌습니다. (다음에 이어질 퍼즐이 바깥에 있을 때)

결론

이렇게 자막을 제공한 뒤, 저희끼리 내부 테스트를 진행했고, 외부인 1명을 불러서 눈을 감고 테스트를 진행했어요. 그 결과, 테스트에 참여한 모든 인원이 눈을 감아도 복잡한 퍼즐 게임을 클리어할 수 있었어요!

이를 통해, 실제 시각장애인이 퍼즐 인터랙션을 수행하더라도, 시각적으로 문제가 없는 사용자처럼 퍼즐 인터랙션을 적절히 누릴 수 있게 접근성을 끌어올리는 결과를 얻을 수 있었어요.

Clone this wiki locally