오늘은 게임에 흔히 사용되는 조이스틱을 구현해보려한다.
어쩌면 간단한 게임의 완성까지 블로그를 작성하려한다.
조이스틱 내부의 원형 컨트롤러는 흔히 게임 내에서 캐릭터 및 커서의 이동을 가능하게 한다.
터치스크린에서 원형 조이스틱의 특징은 포인터의 위치(원 내부 / 외부
)에 따라 다르게 동작한다는 것이다.
외부
에 포인터가 위치해 있을 경우, 해당 방향의 최대 값을 적용시킨다.먼저 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,
),
),
이제 기초적인 게임 틀 구현을 끝냈다.
어디로든 갈 수 있게 해주는 조이스틱과 어디든 갈 수 있는 캐릭터가 있으니, 활용은 방향성에 따라서 무궁무진하다 생각된다.