[Flutter] 조이스틱 구현하기

LOCKED·2021년 10월 19일
13
post-thumbnail

오늘은 게임에 흔히 사용되는 조이스틱을 구현해보려한다.

어쩌면 간단한 게임의 완성까지 블로그를 작성하려한다.

조이스틱 내부의 원형 컨트롤러는 흔히 게임 내에서 캐릭터 및 커서의 이동을 가능하게 한다.

🌕 . 원형 조이스틱?

터치스크린에서 원형 조이스틱의 특징은 포인터의 위치(원 내부 / 외부)에 따라 다르게 동작한다는 것이다.

  • 터치스크린에서 조이스틱은 보통 2개의 원으로 구현되어 있다
  • 내부의 원은 외부의 원을 침범할 수 없다.
  • 외부에 포인터가 위치해 있을 경우, 해당 방향의 최대 값을 적용시킨다.

🌗 . 개발

레이아웃

먼저 Stack 위젯으로 조이스틱의 기본이 될 원 두개를 생성한다.

Stack(
  children: [
    CircleAvatar(
      child: CircleAvatar(),
    ),
  ],
)

Positioned 위젯으로 감싸 좌측 하단에 위치하도록 했다

...
Positioned(
  left: 40,
  bottom: 100,
  child: CircleAvatar(
    child: CircleAvatar(),
  ),
),
...

조이스틱 인터렉션

조이스틱의 내부 원에 인터렉션 효과를 주는 방법은 다음과 같다.

late Animation<Offset> animation;
Offset currentPoint = Offset(0, 0);
///
GestureDetector(
  onPanUpdate: (_){  
    setState((){
      currentPoint.translate(_.delta.dx, _.delta.dy);
    });
  },
  ...
  child: CircleAvatar( // 외부
    child: AnimatedBuilder(
      child: CircleAvatar(), // 내부
        builder: (context, child) {
          return Transform.translate(
            offset: animation.value,
            child: child,
          );
        },
        animation:animation,
      ),
    ),
)

원을 이동시키는 애니메이션은 1차적으로 끝났다.

그렇다면 조이스틱 내부에 있는 원이 원 밖으로 나가지 않기 하기 위해서는 어떻게 해야할까?

평소 모바일 게임에서 얻은 경험에 따르면, 위를 해결한 UX가 많았다.

사각형에 맞추기

쉬운 난이도를 자랑하며 얼추 되는 것 같은 느낌을 주는 방법이다.
간단한 이론은 다음과 같다.

  • 작은 원의 이동거리는 큰 원의 반지름보다 작아야 한다.
...
  onPanUpdate: (_){  
    setState((){
      currentPoint.translate(_.delta.dx, _.delta.dy);
      if (currentPoint.dx >= radius) currentPoint = Offset(radius, currentPoint.dy);
      if (currentPoint.dy >= radius) currentPoint = Offset(currentPoint.dx, radius);
      if (currentPoint.dx <= -radius) currentPoint = Offset(-radius, currentPoint.dy);
      if (currentPoint.dy <= -radius) currentPoint = Offset(currentPoint.dx, -radius);
    });
    ...
  },
...

외부 원에 맞추기

위의 방법을 사용하면 간단하게 해결할 수 있지만 좋아보이진 않는다.
그래서 외부 원에 맞추는 방법을 사용하기로 한다.

...
  onPanUpdate: (_){  
    setState((){
      currentPoint.translate(_.delta.dx, _.delta.dy);
      bool isOut = sqrt(currentPoint.dx * currentPoint.dx + currentPoint.dy * currentPoint.dy) <= radius;
      if (isOut) {
        double r = radius;
        double dx = currentPoint.dx;
        double dy = currentPoint.dy;
        double dr = sqrt(dx * dx + dy * dy);
        double x = dx * r / dr;
        double y = dy * r / dr;
        currentPoint = Offset(x, y);
      }    
    });
    ...
  },
...

여러가지 공식이 있을 수 있으나 비례식을 세워 (x, y)의 좌표를 구하였다.

사각형에 맞추는 방법에 비해 한결부드럽고 자연스러워졌다.

캐릭터

조이스틱이 구현되었으니, 조이스틱의 움직임에 따라서 같이 움직이는 캐릭터도 구현한다.

Align(
  alignment: Alignment.center,
  child: AnimatedBuilder(
    child: CircleAvatar(), // 캐릭터
    builder: (context, child) {
      return Transform.translate(
        offset: charactorAnimation.value,
        child: child,
      );
    },
    animation: charactorAnimation,
  ),
),

🌑 . 결과

이제 기초적인 게임 틀 구현을 끝냈다.
어디로든 갈 수 있게 해주는 조이스틱과 어디든 갈 수 있는 캐릭터가 있으니, 활용은 방향성에 따라서 무궁무진하다 생각된다.

profile
Flutter 개발자 :'>

0개의 댓글