AI 바탕화면 펫 프로젝트 개선기 - 좌우 반전부터 실행 최적화까지

2025. 12. 15. 15:53·5. [개인] 프로젝트 및 공모전/4-2 바탕화면 AI 펫 프로그램

개요

Electron + React + Matter.js로 만든 AI 바탕화면 펫 프로젝트의 주요 개선 사항을 정리했습니다. 좌우 반전 문제 해결부터 실행 스크립트 최적화까지, 실제 개발 과정에서 마주한 문제들과 해결 방법을 공유합니다.

 

 

동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 


주요 개선 사항

1. 좌우 반전 이미지 적용 문제 해결

문제점:

  • 고양이가 왼쪽으로 이동할 때 이미지가 좌우 반전되지 않는 문제
  • Matter.js의 기본 sprite 렌더러가 xScale 음수 값을 제대로 지원하지 않음

해결 방법:
커스텀 렌더링 함수를 구현하여 Canvas context의 scale() 메서드로 직접 이미지 반전 처리

// 커스텀 렌더링: 좌우 반전을 확실하게 처리
const originalRenderBodies = Render.bodies;
Render.bodies = function (render, bodies, context) {
  const state = interactionState.current;
  const cat = catRef.current;

  // 고양이를 제외한 나머지 바디 렌더링
  const otherBodies = bodies.filter((body) => body !== cat);
  if (otherBodies.length > 0) {
    originalRenderBodies(render, otherBodies, context);
  }

  // 고양이 스프라이트 커스텀 렌더링 (좌우 반전 적용)
  if (cat && cat.render && cat.render.sprite && cat.render.sprite.texture) {
    const sprite = cat.render.sprite;
    const texture = sprite.texture;
    const img = loadImage(texture);

    if (img && img.complete && img.naturalWidth > 0) {
      context.save();

      // 고양이 위치로 이동 및 회전
      context.translate(cat.position.x, cat.position.y);
      context.rotate(cat.angle);

      // 좌우 반전 적용
      const baseScale = 0.5;
      const scaleX = state.facingLeft ? -1 : 1;
      const scaleY = 1;

      // 스프라이트 크기 계산
      const imgWidth = img.naturalWidth * baseScale;
      const imgHeight = img.naturalHeight * baseScale;

      // 이미지 그리기 (반전 적용)
      context.scale(scaleX, scaleY);
      context.drawImage(
        img,
        -imgWidth / 2,
        -imgHeight / 2,
        imgWidth,
        imgHeight
      );

      context.restore();
    }
  }
};

핵심 포인트:

  • facingLeft 상태를 기반으로 context.scale(-1, 1) 또는 context.scale(1, 1) 적용
  • 이미지 캐싱을 통해 성능 최적화
  • 방향 감지를 AI 명령과 속도 기반으로 이중 체크

2. 방향 전환 빈도 개선 및 화면 끝 감지 시스템

문제점:

  • 한 방향으로 너무 오래 이동하여 화면 끝에 계속 부딪히는 현상
  • 이동 범위가 너무 커서 방향 전환이 드묾

해결 방법:

서버 측 개선 (Python)

@app.get("/ai-command")
def get_ai_command(cat_x: float, cat_y: float, screen_width: float = 1920, screen_height: float = 1080):
    # 화면 끝 감지 (여유 공간 50픽셀)
    MARGIN = 50
    is_at_left_edge = cat_x <= MARGIN
    is_at_right_edge = cat_x >= screen_width - MARGIN

    # 화면 끝에 닿으면 즉시 방향 전환
    if is_at_left_edge or is_at_right_edge:
        cat_state["wander_target"] = -1  # 목표 초기화
        if is_at_left_edge:
            cat_state["wander_target"] = cat_x + random.randint(80, 150)
        elif is_at_right_edge:
            cat_state["wander_target"] = cat_x - random.randint(80, 150)

    if cat_state["mode"] == "WANDER":
        # 목표 도달 판정을 30픽셀으로 줄임 (기존 50픽셀)
        if cat_state["wander_target"] == -1 or abs(cat_state["wander_target"] - cat_x) < 30:
            # 이동 범위를 50~150으로 대폭 줄임 (기존 100~300)
            move_range = random.randint(50, 150)
            # ... 나머지 로직

개선 사항:

  • 이동 범위: 100300픽셀 → 50150픽셀
  • 목표 도달 판정: 50픽셀 → 30픽셀
  • 화면 끝 감지 및 자동 방향 전환 시스템 추가

클라이언트 측 개선 (React)

// 화면 크기를 서버에 전달
axios.get("http://127.0.0.1:8000/ai-command", {
  params: {
    cat_x: cat.position.x,
    cat_y: cat.position.y,
    screen_width: window.innerWidth,
    screen_height: window.innerHeight,
  },
});

3. UI 정리 및 마우스 이벤트 버그 수정

문제점:

  • 왼쪽 위에 표시되는 상태 텍스트가 불필요
  • 투명 윈도우에서 마우스 이벤트가 가끔 작동하지 않는 버그

해결 방법:

상태 텍스트 제거

// 제거된 코드
// const [serverStatus, setServerStatus] = useState("Conn...");
// <div className="status-text">{serverStatus}</div>

마우스 이벤트 개선

// 초기 상태 설정: 처음에는 마우스 이벤트를 받도록 설정
ipcRenderer.send("set-ignore-mouse-events", false, {
  forward: true,
});

// 마우스 이동 이벤트 처리 (Throttling 적용)
let lastUpdateTime = 0;
const UPDATE_THROTTLE = 50; // 50ms마다 최대 1번 업데이트

Events.on(mouseConstraint, "mousemove", (e) => {
  const now = Date.now();
  if (now - lastUpdateTime < UPDATE_THROTTLE) {
    return;
  }
  lastUpdateTime = now;

  // 정확한 충돌 감지 (여유 공간 추가)
  const bounds = cat.bounds;
  const margin = 10;
  const isMouseOver =
    e.mouse.position.x >= bounds.min.x - margin &&
    e.mouse.position.x <= bounds.max.x + margin &&
    e.mouse.position.y >= bounds.min.y - margin &&
    e.mouse.position.y <= bounds.max.y + margin;

  if (isMouseOver !== wasMouseOver) {
    ipcRenderer.send("set-ignore-mouse-events", !isMouseOver, {
      forward: true,
    });
    wasMouseOver = isMouseOver;
  }
});

// 마우스가 캔버스 밖으로 나갔을 때 처리
render.canvas.addEventListener("mouseleave", () => {
  if (wasMouseOver) {
    ipcRenderer.send("set-ignore-mouse-events", true, {
      forward: true,
    });
    wasMouseOver = false;
  }
});

개선 포인트:

  • 초기 상태 명시적 설정
  • Throttling으로 성능 최적화
  • 여유 공간(margin) 추가로 감지 정확도 향상
  • mouseleave 이벤트 처리 추가

4. 화면 밖으로 나가는 문제 해결

문제점:

  • 마우스로 클릭했을 때 고양이가 화면 밖으로 날아가서 사라지는 현상
  • 경계 벽이 제대로 작동하지 않음

해결 방법:

클릭 시 힘 제한

Events.on(mouseConstraint, "mousedown", (event) => {
  if (Matter.Bounds.contains(cat.bounds, event.mouse.position)) {
    if (!interactionState.current.isHit) {
      interactionState.current.isHit = true;
      // 힘을 줄이고 화면 경계 내에서만 작동하도록 제한
      const safeY = Math.max(50, Math.min(cat.position.y - 3, height - 150));
      Body.setPosition(cat, { x: cat.position.x, y: safeY });
      Body.setVelocity(cat, { x: cat.velocity.x * 0.5, y: -3 });
    }
  }
});

화면 경계 자동 복귀 시스템

Events.on(engine, "beforeUpdate", (event) => {
  // 화면 경계 체크 및 자동 복귀
  const MARGIN = 50;
  const catX = cat.position.x;
  const catY = cat.position.y;
  const catWidth = cat.bounds.max.x - cat.bounds.min.x;
  const catHeight = cat.bounds.max.y - cat.bounds.min.y;

  let needsReposition = false;
  let newX = catX;
  let newY = catY;

  // 좌우 경계 체크
  if (catX - catWidth / 2 < MARGIN) {
    newX = MARGIN + catWidth / 2;
    needsReposition = true;
  } else if (catX + catWidth / 2 > width - MARGIN) {
    newX = width - MARGIN - catWidth / 2;
    needsReposition = true;
  }

  // 상하 경계 체크
  if (catY - catHeight / 2 < MARGIN) {
    newY = MARGIN + catHeight / 2;
    needsReposition = true;
  } else if (catY + catHeight / 2 > height - MARGIN) {
    newY = height - MARGIN - catHeight / 2;
    needsReposition = true;
  }

  // 화면 밖으로 나갔으면 강제로 복귀
  if (needsReposition) {
    Body.setPosition(cat, { x: newX, y: newY });
    // 속도도 제한
    const maxSpeed = 5;
    Body.setVelocity(cat, {
      x: Math.max(-maxSpeed, Math.min(maxSpeed, v.x)),
      y: Math.max(-maxSpeed, Math.min(maxSpeed, v.y)),
    });
  }
});

경계 벽 강화

// 경계 벽을 더 두껍고 확실하게 설정
const wallOpts = { isStatic: true, render: { visible: false } };
const wallThickness = 100; // 벽 두께 증가 (기존 50)
const ground = Bodies.rectangle(
  width / 2,
  height + wallThickness / 2,
  width,
  wallThickness,
  wallOpts
);
const left = Bodies.rectangle(
  -wallThickness / 2,
  height / 2,
  wallThickness,
  height * 2,
  wallOpts
);
const right = Bodies.rectangle(
  width + wallThickness / 2,
  height / 2,
  wallThickness,
  height * 2,
  wallOpts
);
const top = Bodies.rectangle(
  width / 2,
  -wallThickness / 2,
  width,
  wallThickness,
  wallOpts
);

5. 실행 스크립트 최적화

문제점:

  • 여러 개의 검은 CMD 창이 동시에 뜨어서 화면이 지저분함
  • 실행 후 수동으로 창을 닫아야 함

해결 방법:

@echo off
chcp 65001 > nul
echo ====================================
echo   AI Desktop Pet - 개발 모드 실행
echo ====================================
echo.

set "ROOT_DIR=%~dp0"

REM [1/3] Python 서버 실행 (최소화)
echo [1/3] Python 서버 시작 중...
start /min "Python Server" cmd /k "cd /d "%ROOT_DIR%server" && python main.py"
timeout /t 2 /nobreak > nul

REM [2/3] Vite 개발 서버 실행 (최소화)
echo [2/3] Vite 개발 서버 시작 중...
start /min "Vite Dev Server" cmd /k "cd /d "%ROOT_DIR%client" && npm run dev"
timeout /t 3 /nobreak > nul

REM Vite 서버 준비 대기
echo Vite 서버 준비 대기 중...
set "WAIT_COUNT=0"
:WAIT_LOOP
timeout /t 1 /nobreak > nul
set /a WAIT_COUNT+=1

curl -s http://localhost:5173 > nul 2>&1
if %ERRORLEVEL% == 0 (
    echo Vite 서버 준비 완료!
    goto SERVER_READY
)

if %WAIT_COUNT% geq 15 (
    echo 경고: Vite 서버 응답이 없지만 계속 진행합니다...
    goto SERVER_READY
)

echo 대기 중... (%WAIT_COUNT%/15초)
goto WAIT_LOOP

:SERVER_READY
REM [3/3] Electron 앱 실행
echo [3/3] Electron 앱 시작 중...
cd /d "%ROOT_DIR%client"
start "Electron App" cmd /k "npm run electron"

echo.
echo ====================================
echo   실행 완료!
echo   - Python 서버: http://localhost:8000 (최소화됨)
echo   - Vite 개발 서버: http://localhost:5173 (최소화됨)
echo   - Electron 창이 곧 열립니다
echo.
echo   Python 서버와 Vite 서버는 작업 표시줄에 최소화되어 실행 중입니다.
echo   필요시 작업 표시줄에서 창을 열어 로그를 확인할 수 있습니다.
echo ====================================
timeout /t 2 /nobreak > nul

개선 포인트:

  • start /min으로 서버 창을 최소화하여 작업 표시줄에만 표시
  • Vite 서버 준비 상태를 확인하는 로직 추가
  • 메인 bat 파일 자동 종료 (pause → timeout)

배운 점

1. Matter.js 커스텀 렌더링

  • 기본 렌더러의 한계를 커스텀 렌더링으로 해결
  • Canvas API를 직접 사용하여 더 세밀한 제어 가능

2. 물리 엔진 경계 처리

  • 경계 벽만으로는 부족할 수 있음
  • 매 프레임 경계 체크 + 자동 복귀 시스템이 더 안정적

3. 투명 윈도우 마우스 이벤트

  • Electron의 setIgnoreMouseEvents는 초기 상태 설정이 중요
  • Throttling과 정확한 충돌 감지가 필수

4. 실행 스크립트 최적화

  • 사용자 경험을 고려한 UI/UX 개선
  • 백그라운드 프로세스 관리의 중요성

코드 변경 요약

주요 파일 수정 내역

  1. client/src/App.jsx
    • 커스텀 렌더링 함수 추가
    • 화면 경계 체크 및 자동 복귀 로직
    • 마우스 이벤트 개선
    • 상태 텍스트 제거
  2. server/main.py
    • 화면 끝 감지 로직 추가
    • 이동 범위 및 목표 도달 판정 조정
    • 화면 크기 파라미터 추가
  3. start-dev.bat / start-dev-advanced.bat
    • 서버 창 최소화
    • Vite 서버 준비 상태 확인
    • 자동 종료 기능

실행 방법

# 프로젝트 루트에서
start-dev-advanced.bat

또는 수동 실행:

# 터미널 1: Python 서버
cd server
python main.py

# 터미널 2: Vite 개발 서버
cd client
npm run dev

# 터미널 3: Electron 앱
cd client
npm run electron

🔗 기술 스택

  • Frontend: React 19, Vite 7
  • Desktop: Electron 39
  • Physics: Matter.js 0.20
  • Backend: Python FastAPI, Uvicorn
  • Build: Electron Builder

향후 개선 가능한 부분

  1. 이미지 프리로딩 최적화
  2. 물리 엔진 성능 튜닝
  3. 에러 핸들링 강화
  4. 설정 파일을 통한 커스터마이징

작성일: 2025년 12월
프로젝트: AI Desktop Pet
목적: 학습 및 포트폴리오

'5. [개인] 프로젝트 및 공모전 > 4-2 바탕화면 AI 펫 프로그램' 카테고리의 다른 글

살아있는 AI 고양이 프로젝트 만들어보기(3) - 배포(.exe)  (0) 2025.12.08
살아있는 AI 고양이 프로젝트 만들어보기(2)  (0) 2025.12.07
살아있는 AI 고양이 프로젝트 만들어보기(1)  (0) 2025.12.06
'5. [개인] 프로젝트 및 공모전/4-2 바탕화면 AI 펫 프로그램' 카테고리의 다른 글
  • 살아있는 AI 고양이 프로젝트 만들어보기(3) - 배포(.exe)
  • 살아있는 AI 고양이 프로젝트 만들어보기(2)
  • 살아있는 AI 고양이 프로젝트 만들어보기(1)
고니3000원
고니3000원
프로젝트의 구현 과정과 기술적 노하우를 담았습니다. AI 모델 연구와 매일의 학습 기록을 차곡차곡 공유하고 있습니다.
  • 고니3000원
    곤이의 공부 블로그
    고니3000원
  • 전체
    오늘
    어제
    • 분류 전체보기 (231)
      • 1. AI 논문 + 모델 분석 (22)
        • AI 논문 분석 (13)
        • AI 모델 분석 (9)
      • 2. 자료구조와 알고리즘 (16)
        • 2-1 자료구조와 알고리즘 (13)
        • 2-2 강화학습 알고리즘 (3)
      • 3. 자습 & 메모(실전, 실습, 프로젝트) (27)
        • 3-1 문제 해석 (4)
        • 3-2 메모(실전, 프로젝트) (14)
        • 3-3 배포 실전 공부 (7)
        • 3-4 최신 기술 분석 (2)
      • 4. [팀] 프로젝트 및 공모전 (31)
        • 4-1 팀 프로젝트(메모, 공부) (1)
        • 4-2 Meat-A-Eye (6)
        • 4-3 RL-Tycoon-Agent (3)
        • 4-4 구조물 안정성 물리 추론 AI 경진대회(D.. (4)
        • 4-5 AgentShield(보안 플랫폼) (17)
      • 5. [개인] 프로젝트 및 공모전 (25)
        • 4-1 귀멸의칼날디펜스(자바스크립트 활용) (5)
        • 4-2 바탕화면 AI 펫 프로그램 (4)
        • 4-3 개인 프로젝트(기타) (3)
        • 4-4 공모전 (9)
        • 4-5 나만의 로컬 LLM 멀티 에이전트 구축 (4)
      • 6. 경진 대회 (kaggle, dacon 등) (12)
        • 6-1 연구 및 개선 기록 (11)
        • 6-2 공유 코드 분석하기 (1)
      • 개념 정리 step1 (32)
        • Python 기초 (7)
        • DBMS (1)
        • HTML | CSS (3)
        • Git | GitHub (1)
        • JavaScript (5)
        • Node.js (5)
        • React (1)
        • 데이터 분석 (6)
        • Python Engineering (3)
      • 개념 정리 step2 (60)
        • Machine | Deep Learning (15)
        • 멀티모달(Multi-modal) (23)
        • 강화 학습 (10)
        • AI Agent (9)
        • 메디컬 이미지 (3)
      • 개인 공부 - 내가 공부하고 싶은 모든 것 (3)
        • 1. 인프라 (2)
        • 2. 자율주행 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • Notion-포트폴리오
    • Github
  • 공지사항

    • 나의 핵심 가치
  • 인기 글

  • 태그

    논문 리뷰
    Agent
    인공지능
    파인튜닝
    github
    알고리즘
    강화학습
    전처리
    파이썬
    Ai
    연구
    Vision
    데이터분석
    paddleocr
    자료구조
    ViT
    RAG
    llm
    transformer
    pandas
    API
    Python
    공모전
    구현
    프로젝트
    자바스크립트
    학습
    OCR
    Ollama
    Lora
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
고니3000원
AI 바탕화면 펫 프로젝트 개선기 - 좌우 반전부터 실행 최적화까지
상단으로

티스토리툴바