my projects/2024

[프로젝트 회고] 삼성청년SW아카데미 2학기 평가 프로젝트(1), 앱 서비스 기웃기옷

지그농 2026. 2. 26. 01:29

기웃기옷 : AI가 분석하는 오늘의 옷 기억도 앱 서비스 회고

프로젝트 기간 : 2024.07 ~ 2024.08 (7주)
서비스 유형 : APP 
역할 : UX/UI, 프론트엔드 개발
팀 구성 : 백엔드 2명, 프론트엔드 2명, AI 2명 (총 6명)
기술 스택 : Dart, Flutter, Flame, Lottie
평가 : 삼성 청년 SW 아카데미 2학기 평가 프로젝트 - 공통 프로젝트

 

 

들어가며 

"오늘 뭐 입지"라는 고민은 누구나 매일 반복합니다. 기웃기옷은 그 고민을 조금 다른 각도에서 접근한 앱 서비스 프로젝트입니다. 

 

단순히 옷을 추천하는 것이 아니라, 사용자의 옷이 주변 사람들에게 얼마나 기억에 남아 있는지를 AI로 분석하고,

그것을 알기 쉽게 시각적으로 보여준다는 아이디어에서 출발했습니다.

 

메인 화면에서 지난 2주간 입은 옷 데이터를 확인

 

서비스 핵심은 실시간 데이터 수집이었습니다. 

 

걸음 수와 음성 대화 데이터를 기기에서 직접 수집하고, 이를 AI 모델과 연동해 옷의 기억도를 계산하는 구조였기 때문에, 사용자가 가장 자연스럽게 접근할 수 있는 플랫폼으로 앱을 선택했습니다. 

실시간 센서 데이터를 안정적으로 수집하면서도 독창적인 인터랙션을 구현할 수 있는 환경을 만드는 것이 이 프로젝트의 목표였습니다.

 

삼성 청년 SW 아카데미 2학기 첫 번째 팀 프로젝트였고, 각자 다른 전공의 팀원들이 함께 처음으로 모여 팀 단위 개발에 도전한 경험이기도 했습니다.


1. 프로젝트 개요

기웃기옷은 AI를 활용해 사용자의 옷 정보를 저장하고, 옷의 기억도를 분석하여 스타일 관리와 선택을 돕는 의류 관리 앱 서비스입니다.

 

투명한 모래 시계 컨셉의 메인 화면

 

서비스의 핵심 화면은 모래시계 형태의 메인 화면입니다. 각 옷은 하나의 구슬로 표현되며, 구슬의 크기는 기억도에 비례합니다.

기억도가 높은 옷은 큰 구슬로 모래시계 상단에 위치하고, 시간이 지나 기억도가 낮아질수록 구슬이 작아지며 하단으로 떨어지는 구조입니다.  화면만으로도 어떤 옷이 주변에 인상을 남기고 있는지, 어떤 옷이 잊혀져 가고 있는지를 파악할 수 있도록 설계했습니다.

 

옷의 기억도는 옷의 색상·패턴, 대화를 나눈 시간, 기억 곡선을 기반으로 평가됩니다.

사용자는 음성 AI와 대화하거나 메인 화면에서 옷 구슬을 클릭해 기억도를 확인하고, 이를 바탕으로 오늘 무엇을 입을지 효율적이면서도 재미있게 선택할 수 있습니다. 


2. 왜 이 서비스를 기획하게 되었나요?

“오늘 뭐 입지?”라는 일상적인 고민에서 출발했지만, 왜 옷을 고르는 일이 유독 어렵게 느껴지는지에 대한 질문에서 이 프로젝트를 본격적으로 기획하게 되었습니다.

 

팀원들과 논의 끝에, 옷 선택이 어려운 이유가 단순한 취향의 문제가 아니라 내가 입은 옷이 주변 사람들에게 어떻게 인식되고 기억되는지에 대한 부담 때문일 수 있다는 가설을 세웠습니다.

이를 검증하기 위해 교육생들과 회사원 지인들을 대상으로 설문 조사를 진행했고, 옷을 기억하게 되는 이유로는 (중복 선택 가능) 색상(55.3%), 패턴과 디자인(46.1%)이 가장 높은 비율을 차지했습니다.

 

설문 조사에서 수집한 키워드로 생성한 워드 크라우드

 

또한 설문에서 수집한 자유 응답 키워드를 워드 클라우드로 분석한 결과, ‘색상’, ‘땡땡이’, ‘패턴’, ‘디자인’ 등 시각적 요소가 반복적으로 등장하며, 사람들이 옷을 기억하는 기준이 명확히 존재한다는 인사이트를 도출할 수 있었습니다.

 

이러한 조사 결과를 바탕으로, 단순히 “오늘 입을 옷”을 추천하는 서비스가 아니라 내가 가진 옷이 실제로 얼마나 기억에 남는지를 데이터로 보여주는 서비스를 기획하게 되었습니다. 사용자는 음성 AI와의 대화나 메인 화면에서 옷 구슬을 클릭해 옷의 기억도를 확인할 수 있고, 이를 통해 옷장을 효율적으로 관리하며 새로운 스타일을 보다 전략적으로 선택할 수 있습니다.

 


3. 왜 이 기술 스택을 선택했나요?

기웃기옷은 단순히 정보를 나열하는 앱이 아니라, 기기에서 수집한 데이터를 실시간으로 처리하고 이를 감각적인 인터랙션으로 전달해야 하는 서비스였습니다.

 

이를 위해 다음 세 가지 기술적 요구사항을 핵심 기준으로 설정했습니다.

  1. 모바일 기기에서의 실시간 센서 데이터 수집 안전성
  2. AI 모델과의 연동을 고려한 구조적 유연성
  3. 데이터를 직관적으로 체감할 수 있는 시각적 표현력

3-1) Flutter - 실시간 센서 수집과 크로스 플랫폼 안전성

이 서비스의 핵심은 걸음 수, 음성 등 센서 데이터를 기기에서 직접 수집하는 것이었습니다. React Native 또한 검토했지만, 프로젝트 특성상 필요한 센서 관련 라이브러리의 안정성과 유지보수 사례가 충분하지 않다고 판단했습니다.

 

반면 Flutter는 Android와 iOS 모두에서 센서 연동이 비교적 일관되었고, 단일 코드베이스로 크로스 플랫폼을 지원하면서도 성능 저하가 적었습니다. 실시간 데이터 수집과 화면 반영이 동시에 필요한 서비스 특성상, Flutter가 가장 안정적인 선택이었습니다.


3-2) Flame + Forge2D - 데이터를 감각으로 전달하기 위한 선택

기억도 데이터를 수치나 차트로 표현하는 것은 기술적으로 어렵지 않았습니다. 그러나 이 서비스의 목표는 데이터를 ‘해석’하게 만드는 것이 아니라, 변화를 즉각적으로 체감하게 만드는 것이었습니다.

 

기억도가 낮아질수록 구슬이 작아지고 모래시계 하단으로 떨어지는 인터랙션은, 사용자가 별도의 설명 없이도 상태 변화를 인식하도록 설계된 핵심 UX였습니다. 이러한 표현을 구현하기 위해서는 단순 애니메이션이 아닌 중력, 충돌, 반발력 등 실제 물리 법칙을 기반으로 한 시뮬레이션이 필요하다고 판단했습니다.

 

이를 위해 Flutter 기반 게임 엔진인 Flame을 도입하고, Flame 생태계에서 제공하는 물리 엔진 연동 라이브러리인 flame_forge2d를 통해 Forge2D물리 엔진을 사용했습니다. Forge2DGame 구조를 통해 게임 루프와 물리 월드를 함께 제어할 수 있었고, 프레임 단위로 중력 벡터와 객체 상태를 직접 관리하는 구조를 설계할 수 있었습니다.

(당시 참고한 flame git : 링크)

 

GitHub - flame-engine/flame: A Flutter based game engine.

A Flutter based game engine. Contribute to flame-engine/flame development by creating an account on GitHub.

github.com

 

 

보여지는 최종 옷구슬 위젯을 위한 적층 구조화 (옷 -구슬)

 

시각화 측면에서는 외부 애니메이션 라이브러리를 사용하지 않고, Flame의 네이티브 Sprite 렌더링과 Forge2D 물리 계산을 결합해 구슬과 옷 이미지를 계층적으로 렌더링했습니다. 이를 통해 물리 엔진과 시각 요소 간의 싱크를 유지하면서도 가볍고 안정적인 구조를 유지할 수 있었습니다.

 

Flame은 React Native 대비 참고 자료가 많지는 않았지만, 공식 문서가 체계적으로 정리되어 있어 빠르게 학습할 수 있었습니다. 특히 메인 화면 구현에 앞서, 충돌 처리와 좌표 계산 구조를 정확히 이해하기 위해 간단한 미니 게임을 직접 구현하며 엔진의 동작 방식을 학습했고, 이 경험을 바탕으로 실제 서비스에 필요한 물리 로직을 안정적으로 적용할 수 있었습니다.

(당시 참고한 Google 문서 : 링크)

 

Flutter를 사용한 Flame 소개  |  Google Codelabs

이 Codelab에서는 Flutter를 기반으로 빌드된 게임 엔진인 Flame을 사용하는 방법을 알아봅니다. Flame의 구성요소 및 효과에 관해 알아보고 Flame을 Flutter의 상태 관리와 통합하는 방법도 알아봅니다.

codelabs.developers.google.com


4. UX/UI 설계

Figma를 활용해 시각적 요소와 기능 흐름을 함께 설계했습니다. 프로토타입을 통해 동선과 인터랙션의 타당성을 점검하며 기획 단계에서 서비스 주제를 구체화했습니다.

 

 

메인 화면의 핵심 컨셉은 모래시계였습니다. 시간이 지남에 따라 기억도가 낮아지는 옷이 모래시계 하단으로 떨어지는 시각적 은유를 통해, 사용자가 데이터를 자연스럽게 이해할 수 있도록 설계했습니다.

 

또한 필요한 정적 에셋 파일들은 모두 Photoshop Illustrator를 활용해 배경 이미지, 구슬 아이콘, 옷 이미지 등 메인 화면에 필요한 모든 에셋을 직접 제작했습니다. 옷 이미지는 생성형 AI를 활용해 서비스 컨셉에 맞는 스타일로 제작했습니다.


5. 기술 구현 과정

5-1) 센서 기반 인터랙션 설계 - 물리 엔진 관점에서의 해석

구슬의 움직임을 단순 애니메이션으로 처리하지 않고, 기기의 실제 움직임과 연동된 물리 반응으로 구현하고자 했습니다.

 

기기의 움직임을 단순히 좌표 변화로 처리하지 않고, Forge2D의 world.gravity를 실시간으로 제어하는 방식을 선택했습니다.

class MyGame extends Forge2DGame {
  @override
  void update(double dt) {
    super.update(dt);

    if (sensorData.isEmpty) return;

    final acc = sensorData.last.sublist(0, 3);
    final gyro = sensorData.last.sublist(3, 6);
      
    // 가속도 값으로 기본 중력 벡터 설정
    Vector2 gravity = Vector2(-acc[0] * 30, acc[1] * 30);

    gravity.x -= gyro[0] * 0.1;
    gravity.y += gyro[1] * 0.1;

    world.gravity = gravity;
  }
}
 
  • 가속도 센서 : 기울기 기반 중력 방향
  • 자이로 센서 : 회전에 대한 보정값

이 구조를 통해 단순히 화면 기울기에 맞춰 좌표를 이동시키는 애니메이션이 아니라, 기기의 기울기를 중력 벡터로 해석해 실제 물리 월드의 중력 방향을 변경하는 방식을 통해 사용자가 휴대폰을 기울일 때 구슬이 자연스럽게 흐르는 물성을 느낄 수 있도록 구현했습니다.


5-2) 구슬을 하나의 물리 단위로 관리 - Forge2D BodyComponent 설계

 

각 옷구슬은 하나의 BodyComponent로 정의해 물리 상태와 시각 요소를 분리하지 않고 함께 관리했습니다.

@override
Body createBody() {
  final shape = CircleShape()..radius = radius - collisionMargin;

  final fixture = FixtureDef(shape)
    ..density = 2.0
    ..friction = 1.0
    ..restitution = 0.8;

  return world.createBody(BodyDef(type: BodyType.dynamic, position: position))
    ..createFixture(fixture);
}

 

여러 값들을 직접 조정하며 테스트한 결과, 과장되지 않으면서도 ‘살아있는 느낌’을 주는 물성을 찾을 수 있었습니다.
이 과정에서 “보기 좋은 값”보다 사용자가 느끼기에 자연스러운 값을 기준으로 판단했습니다.


5-3) Flame Sprite 기반 레이어링 - 시각과 물리의 싱크 유지

외부 애니메이션 라이브러리를 사용하지 않고, Flame의 네이티브 Sprite 렌더링만으로 시각화를 구성했습니다.

 

전략으로는 (1) 유리 구슬 Sprite 위에 옷 이미지를 계층적으로 렌더링하며, (2) Forge2D가 계산한 Body 위치를 그대로 반영하는 것이었습니다.

@override
void render(Canvas canvas) {
  // 구슬 이미지 렌더링
  marbleSprite.render(
    canvas,
    size: Vector2.all(radius * 2),
    position: Vector2(-radius, -radius),
  );

  // 옷 이미지를 구슬 위에 렌더링
  double clothSizeFactor = 1.0;
  clothSprite.render(
    canvas,
    size: Vector2.all(radius * clothSizeFactor),
    position: Vector2.all(-radius * clothSizeFactor / 2),
  );
}

 

이 구조를 통해 물리 엔진이 계산한 위치가 곧 시각적 위치가 되어 별도의 애니메이션 상태 관리나 보정 로직 없이 싱크를 유지할 수 있어, 렌더링과 상태 동기화 비용을 최소화할 수 있었습니다. 


5-4) 모래시계 물리 경계 설정 - Forge2D Boundary 활용

구슬들이 실제로 모래시계 안에서 흔들리기 위해서는, 실제 이미지와 일치하는 영역에 물리적인 경계와 성질이 있어야 합니다.

 

 

모래시계 내부 경계를 단순 사각형이 아닌, 실제 이미지와 일치하는 곡선 형태로 구현하기 위해
Flutter의 Path.cubicTo()로 좌우 내벽을 정의하고, 이를 Forge2D의 ChainShape로 변환해 정적 물리 경계로 등록했습니다.

 

@override
Future<void> onLoad() async {
  await super.onLoad();
  await add(ScreenHitbox());

  // 모래시계 좌측 벽
  final path1 = Path()
    ..moveTo(0, screenSize.y * 0.34)
    ..cubicTo(screenSize.x * 0.13, screenSize.y * 0.48,
        screenSize.x * 0.68, screenSize.y * 0.575,
        0, screenSize.y * 0.735)
    ..close();

  // 모래시계 우측 벽
  final path2 = Path()
    ..moveTo(screenSize.x, screenSize.y * 0.34)
    ..cubicTo(screenSize.x * 0.87, screenSize.y * 0.47,
        screenSize.x * 0.32, screenSize.y * 0.575,
        screenSize.x, screenSize.y * 0.735)
    ..close();

  await add(Boundary(path1));
  await add(Boundary(path2));
}

class Boundary extends BodyComponent {
  final Path path;
  Boundary(this.path);

  @override
  Body createBody() {
    final shape = ChainShape();
    final vertices = _convertPathToVertices(path);
    shape.createChain(vertices);
    final bodyDef = BodyDef()
      ..type = BodyType.static
      ..position = Vector2.zero();
    final fixtureDef = FixtureDef(shape);
    return world.createBody(bodyDef)..createFixture(fixtureDef);
  }
}

 

모든 디바이스에서 물리 반응이 왜곡되지 않도록, 고정 좌표가 아닌 screenSize 기반 비율 계산 방식으로 모래시계 경계 좌표를 정의했습니다. 이를 통해 기기 해상도나 화면 비율이 달라져도 시각적 모래시계 이미지와 실제 물리 충돌 영역 간의 불일치를 최소화할 수 있었습니다.

 

이 경계 설계를 기준으로 유리 구슬의 물성, 자이로·가속도 센서 기반 중력 제어 로직까지 이전에 설계해 둔 모든 물리 로직을 하나의 일관된 물리 경험으로 완성할 수 있었습니다


5-5) 트러블 슈팅 - 초기화 충돌로 인한 블랙 스크린 해결

개발 중 메인 화면 진입 전 블랙 스크린이 출력되는 문제가 발생했습니다.

서비스 진입시 발생한 블랙 스크린 이슈

 

 

원인을 파악해보니, 기억도 분석을 위한 걸음 수·음성 수집 센서 패키지와 AI 모델 로딩, Flame 물리 엔진이 동시에 초기화되면서 충돌이 발생한 것이었습니다. 초기화 순서가 명확히 설정되지 않아 실행 타이밍이 겹쳤고, 이로 인해 화면이 정상적으로 출력되지 않았습니다.

 

해결 방법으로 초기화 프로세스를 비동기적으로 분리했습니다. 센서 초기화가 완료된 이후 AI 모델과 Flame 엔진을 순차적으로 실행하도록 순서를 조정했고, 초기화 진행 중에는 랜딩 화면을 출력하여 사용자 경험이 저하되지 않도록 개선했습니다.

Future<void> initializeApp() async {
  await setupSensorManagement();      // 센서 먼저
  await initializeAIModel();           // 이후 AI
  setState(() => isInitialized = true);
}
 

 

단순한 버그가 아니라, 역할과 기능이 분리된 상태로 진행되며 사전에 조율되지 못해 발생한 문제였습니다.

 

센서 수집, AI 모델 로딩, 물리 엔진 초기화가 서로 다른 책임 영역에서 동시에 실행되며 충돌이 발생했고, 이를 계기로 초기화 순서를 명확히 정의하고 의존 관계를 정리하는 구조로 재설계했습니다.


6. 기술적 성장

6-1) Flutter & 크로스 플랫폼 앱 개발

Flutter로 앱 개발을 진행하며 위젯 트리 구조와 상태 관리 등 기본적인 코드 작성 로직을 이해하고, 단일 코드베이스로 Android, iOS에서 일관된 UI와 인터랙션을 구현하는 역량을 키울 수 있었습니다.

 

특히 화면 상태 변화와 렌더링 타이밍을 고려한 구조 설계를 통해, UI가 단순히 보이는 영역이 아니라 데이터 흐름의 결과물이라는 관점을 가질 수 있었습니다.


6-2) Flame & Forge2D 물리 엔진

게임 물리 엔진을 UI 개발에 응용하며 물리 기반 인터랙션 설계의 이해도를 높였습니다.

 

Forge2DGame 구조를 통해 물리 월드와 게임 루프를 직접 제어하고, 중력 벡터, 충돌 처리, 물리적 속성(마찰력-반발력-밀도)을 프레임 단위로 관리하는 경험을 쌓았습니다.

 

또한 외부 애니베이션 라이브러리에 의존하지 않고, Flame의 네이티브 Sprite 렌더링과 Forge2D 물리 계산을 결합해 시각화를 구현함으로써 렌더링 흐름과 물리 계산이 자연스럽게 동기화되는 구조를 직접 설계하고 운영하는 경험을 쌓을 수 있었습니다.


7. KPT 회고 

Keep

첫 앱 개발에서 Flame과 Forge2D를 처음 다루는 상황에서도, 구슬의 물리적 움직임과 기억도에 따른 크기 변화, 자이로센서 기반 중력 벡터 제어까지 물리 기반 인터랙션을 직접 설계하고 구현해본 경험이 가장 큰 성과였습니다.

 

데이터를 수치로 설명하는 대신, 사용자가 변화를 감각적으로 인지할 수 있도록 시각화 방식 자체를 설계했고, 물리 엔진-직접 제작한 에셋-햅틱 피드백을 결합해 서비스의 핵심 콘셉트를 화면으로 구현해냈습니다.

 

Problem

기획 단계에서 완성도를 높이려는 논의가 길어지면서, 실제 개발 기간이 약 1주일로 제한되었습니다.

 

첫 6인 팀 프로젝트였던 만큼 역할별 개발 범위와 실행 흐름을 충분히 맞추지 못한 채 구현에 들어갔고, 그로 인해 백엔드와의 데이터 요청 구조나 상태 관리 전략을 사전에 명확히 설계하지 못한 점이 아쉬움으로 남았습니다.

 

Try

다시 진행한다면 기획 확정 시점을 명확히 설정하고, 개발 전에 API 연동 구조와 상태 관리 전략을 먼저 설계한 뒤 구현을 시작할 것입니다.

이 프로젝트를 통해, 빠른 결정과 구조 설계가 결과물의 완성도와 팀 전체 생산성에 직접적인 영향을 준다는 것을 체감했습니다.


마치며

기웃기옷은 개발자 직무 전환 이후 처음으로 한 6인 팀 프로젝트였습니다.

 

개발 기간이 매우 짧고 기술적으로도 낯선 환경이었지만, 게임 물리 엔진을 UI에 응용하고 실시간 데이터를 안정적으로 연동하는 경험은 짧은 기간 동안 진행한 프로젝트 중 가장 도전적인 시도였습니다.

 

결과적으로 수상이라는 성과로 이어지지는 않았지만, 제한된 시간 안에서 문제를 정의하고 해결하기 위해 팀원들과 밤새 고민하고 시도했던 과정은 제 개발자로서의 사고 방식에 큰 영향을 주었습니다. 이 프로젝트를 통해 저는 낯선 기술과 짧은 일정 속에서도 문제를 회피하지 않고, 필요한 부분을 스스로 학습하며 끝까지 구현해내는 방식으로 성장할 수 있었습니다.