[Flutter] 조이스틱 만들어보기

GONG·2023년 7월 27일

Flutter

목록 보기
4/5
post-thumbnail

플러터로 조이스틱을 구현해보겠습니다

참고: Getx 사용햇습니다

구현 순서

  1. 조이스틱 생성
  2. 조이스틱 움직이기
    • 조이스틱 드래그 범위 지정하기
  3. 조이스틱으로 캐릭터(원) 움직여보기


1. 조이스틱 생성

  • 연한 회색 배경 원과 조이스틱으로 사용될 진한 회색 원으로 구성


2. 조이스틱 움직이기

  • joystick_controller 생성
    • 조이스틱 드래그 범위 지정 (진한 회색 원이 연한 회색 밖으로 나갈 수 없도록)

    • 조이스틱의 중심과 현재 드래그 위치의 거리를 계산해서 드래그 위치가 조이스틱 배경 원의 반지름을 초과하면 드래그 위치를 조이스틱 배경 원 둘레에 맞게 조정하는 방식

      class JoystickController extends GetxController {
        // 조이스틱 중심점 위치
        var center = Offset(0, 0).obs;
      
        // 조이스틱 움직임 위치
        var movement = Offset(0, 0).obs;
      
        // 배경 원 반지름
        double radius = 50;
      
        updatePosition(DragUpdateDetails position) {
          // 현재 드래그 위치와 초기 드래그 위치의 차이 계산해서 movement 변수에 저장
          movement.value = position.localPosition - center.value;
      
          // 현재 드래그 위치와 원점(조이스틱 중심) 간의 거리 계산
          double distance = movement.value.distance;
      
          // 만약 드래그 위치가 조이스틱의 원의 반지름을 초과하면
          if (distance > radius) {
            // 드래그 위치를 조이스틱의 원 둘레에 맞게 조정
            movement.value = movement.value * (radius / distance);
          }
        }
      }
  • 조이스틱 GestureDetector 추가, Transform.translate

    • onPanStart : 사용자가 드래그 동작을 시작할 때 호출되는 콜백 함수
    • onPanUpdate : 사용자가 드래그 동작을 진행하는 동안 호출되는 콜백 함수
    • onPanEnd : 사용자가 드래그 동작을 마치고 손가락을 뗄 때 호출되는 콜백 함수
    • Transform.translate : 하나의 위젯을 다른 위치로 이동시키는 데 사용되는 위젯
      // Obx 추가해야함
      GestureDetector(
        onPanStart: (details) {
          controller.center.value = details.localPosition;
          controller.movement.value = Offset(0, 0);
        },
        onPanUpdate: (details) {
          controller.updatePosition(details);
        },
        onPanEnd: (details) {
          controller.movement.value = Offset(0, 0);
        },
        child: CircleAvatar(
          radius: controller.radius,
          backgroundColor: Colors.grey[300],
          child: Transform.translate(
            offset: controller.movement.value,
            child: CircleAvatar(
              backgroundColor: Colors.grey[700],
            ),
          ),
        ),
      )

    GestureDetector 드래그


    • onPanStart : 사용자가 드래그 동작을 시작할 때 호출되는 콜백 함수
      • DragStartDetails 객체를 인자로 받음
        • globalPosition: 드래그 시작 시점에서의 전체 화면 기준 위치를 나타내는 Offset 객체
        • localPosition: 드래그 시작 시점에서의 이벤트가 발생한 위젯 내에서의 상대적인 위치를 나타내는 ****Offset 객체
    • onPanUpdate : 사용자가 드래그 동작을 진행하는 동안 호출되는 콜백 함수
      • DragUpdateDetails 객체를 인자로 받음
        • globalPosition: 드래그 중인 현재 시점에서의 전체 화면 기준 위치를 나타내는 Offset 객체
        • localPosition: 드래그 중인 현재 시점에서의 이벤트가 발생한 위젯 내에서의 상대적인 위치를 나타내는 Offset 객체
        • delta: 드래그 중인 이전 위치(previousGlobalPosition)와 현재 위치(globalPosition)의 차이를 나타내는 Offset 객체
    • onPanEnd : 사용자가 드래그 동작을 마치고 손가락을 뗄 때 호출되는 콜백 함수
      • DragEndDetails 객체를 인자로 받음
        • velocity: 드래그 종료 시점의 속도를 나타내는 Velocity 객체, x축과 y축으로의 속도가 포함되어 있음

    Transform.translate


    하나의 위젯을 다른 위치로 이동시키는 데 사용되는 위젯

    자식 위젯을 현재 위치에서 지정한 X축과 Y축의 픽셀만큼 이동시킬 수 있음

    화면에서 자식 위젯의 위치를 재조정하고자 할 때 유용하게 사용됨

    주요 속성

    • offset: 이동할 거리를 지정하는 속성 Offset 객체를 사용 / Offset(dx, dy) 형식으로 X축과 Y축으로 이동할 거리를 지정할 수 있음
    • child: 이동시킬 대상이 되는 자식 위젯



3. 조이스틱으로 캐릭터(원) 움직여보기

  • joystick_controller에 원의 위치 정보를 저장할 변수와 updatePosition 메소드 내에 위치 이동하는 코드 추가
    class JoystickController extends GetxController {
      ...
      // 조이스틱으로 움직이는 원(캐릭터) 위치
      var circle = Offset(0, 0).obs;
      ...
      updatePosition(DragUpdateDetails position) {
        ...
        circle.value += movement.value * 0.1; // 0.1 곱해서 원 이동 속도 조절
      }
    }
  • 조이스틱 Stack 위젯 안으로 이동, 캐릭터(원) 추가
    Obx(
      () => Stack(
        children: [
          Align(
            alignment: Alignment.center,
            child: Transform.translate(
              offset: controller.circle.value,
              child: CircleAvatar(backgroundColor: Colors.green)
            ),
          ),
          Positioned(
            left: 40,
            bottom: 50,
            child: GestureDetector(
              onPanStart: (details) {
                controller.center.value = details.localPosition;
                controller.movement.value = Offset(0, 0);
              },
              onPanUpdate: (details) {
                controller.updatePosition(details);
              },
              onPanEnd: (details) {
                controller.movement.value = Offset(0, 0);
              },
              child: CircleAvatar(
                radius: controller.radius,
                backgroundColor: Colors.grey[300],
                child: Transform.translate(
                  offset: controller.movement.value,
                  child: CircleAvatar(
                    backgroundColor: Colors.grey[700],
                  ),
                ),
              ),
            ),
          ),
        ],
      )
    )



전체 코드

  • main.dart
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import 'joystick_controller.dart';
    import 'main_page.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      
      Widget build(BuildContext context) {
        return GetMaterialApp(
          initialBinding: BindingsBuilder(() {
            Get.lazyPut(() => JoystickController(), fenix: true);
          }),
          home: MainPage(),
        );
      }
    }
  • main_page.dart
    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    
    import 'joystick_controller.dart';
    
    class MainPage extends GetView<JoystickController> {
      const MainPage({Key? key}) : super(key: key);
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('ㅋㅋ')),
          body: Center(
            child: Obx(() {
              return Column(
                children: [
                  Expanded(
                    child: Stack(
                      alignment: Alignment.center,
                      children: [
                        Align(
                          alignment: Alignment.center,
                          child: Transform.translate(
                              offset: controller.circle.value,
                              child: CircleAvatar(backgroundColor: Colors.green)
                          ),
                        ),
                        Positioned(
                          left: 40,
                          bottom: 50,
                          child: GestureDetector(
                            onPanStart: (details) {
                              controller.center.value = details.localPosition;
                              controller.movement.value = Offset(0, 0);
                            },
                            onPanUpdate: (details) {
                              controller.updatePosition(details);
                            },
                            onPanEnd: (details) {
                              controller.movement.value = Offset(0, 0);
                            },
                            child: CircleAvatar(
                              radius: controller.radius,
                              backgroundColor: Colors.grey[300],
                              child: Transform.translate(
                                offset: controller.movement.value,
                                child: CircleAvatar(
                                  backgroundColor: Colors.grey[700],
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              );
            }),
          ),
        );
      }
    }
  • joystick_controller.dart
    import 'package:flutter/material.dart';
    
    import 'package:get/get.dart';
    
    class JoystickController extends GetxController {
      // 조이스틱 중심점 위치
      var center = Offset(0, 0).obs;
    
      // 조이스틱 움직임 위치
      var movement = Offset(0, 0).obs;
    
      // 조이스틱으로 움직이는 원 위치
      var circle = Offset(0, 0).obs;
    
      // 배경 원 반지름
      double radius = 50;
    
      updatePosition(DragUpdateDetails position) {
        // 현재 드래그 위치와 초기 드래그 위치의 차이 계산해서 movement 변수에 저장
        movement.value = position.localPosition - center.value;
    
        // 현재 드래그 위치와 원점(조이스틱 중심) 간의 거리 계산
        double distance = movement.value.distance;
    
        // 만약 드래그 위치가 조이스틱의 원의 반지름을 초과하면
        if (distance > radius) {
          // 드래그 위치를 조이스틱의 원 둘레에 맞게 조정
          movement.value = movement.value * (radius / distance);
        }
    
        circle.value += movement.value * 0.1;
      }
    }

profile
우와재밋다

0개의 댓글