뒷배경이 클릭이 안된다.. 크기도 조절이 안되고 .. main.js를 조금 수정해야했다.

const { app, BrowserWindow, globalShortcut, screen, ipcMain } = require('electron');

let win;
function createWindow(){
  const { width, height } = screen.getPrimaryDisplay().workAreaSize;

  // 전체 화면 투명 창 (클릭-스루 가능)
  win = new BrowserWindow({
    width: width,
    height: height,
    x: 0,
    y: 0,
    frame: false,
    transparent: true,
    alwaysOnTop: true,
    resizable: false,
    hasShadow: false,
    skipTaskbar: true,
    backgroundColor: '#00000000',
    webPreferences: { contextIsolation:false, nodeIntegration:true }
  });

  win.loadFile('index.html');

  // 기본은 클릭-스루 (투명 배경은 뒤 클릭 가능)
  // forward: true로 설정하면 마우스 이벤트를 받으면서도 뒤로 전달
  win.setIgnoreMouseEvents(true, { forward: true });
  
  // 개발자 도구 단축키 (디버깅용)
  win.webContents.on('before-input-event', (event, input) => {
    if (input.key === 'F12' || (input.control && input.shift && input.key === 'I')) {
      win.webContents.toggleDevTools();
    }
  });
}

// 렌더러에서 클릭-스루 영역 정보 받기 (마우스가 캐릭터 위에 있는지)
ipcMain.on('va:set-ignore-mouse', (_e, ignore)=>{
  win?.setIgnoreMouseEvents(ignore, { forward: true });
});

app.whenReady().then(createWindow);
app.on('window-all-closed', ()=>{ if (process.platform!=='darwin') app.quit(); });
app.on('activate', ()=>{ if (BrowserWindow.getAllWindows().length===0) createWindow(); });

 

 

 

 

하지만 난 이제 왠지 만족스러워져 버렸다..
그냥 화면에 뽀작뽀작한게 띄워진것 만으로도..
프로젝트... 멈..

 

 

크흠. . index.html 도 수정이 필요하다. 이 아래로는 코드만 있다. 

 

이제  + , - 버튼으로 크키를 키우고 줄일수있고, 마우스 드래그 드롭으로 이동이 가능하다 .

위젯형태로 만들순 없었고...

투명한 전체창을 띄워서 캐릭터를 이동하는 모습이되는데...뒤에 아이콘들도 조작이 가능하도록 작용만했다.

 

그런데..아쉽게도 많이 어려웠다.. 일단 ai로 작업했고..공부해서 이부분을 채워나가야할듯

<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <title>Virtual Desk Assistant - Hiyori</title>

   <!-- 배경 완전 투명 + 캐릭터만 클릭 가능 -->
   <style>
     html, body {
       margin: 0;
       height: 100%;
       overflow: hidden;
       background: transparent;
       /* 배경은 클릭-스루 */
       pointer-events: none;
     }
     #app {
       width: 100%;
       height: 100%;
       position: relative;
       pointer-events: none;
     }
     #stage {
       position: fixed;
       inset: 0;
       pointer-events: none;
     }
     /* canvas만 클릭 가능 (Live2D 캐릭터) */
     canvas {
       pointer-events: auto !important;
       cursor: move; /* 드래그 가능 표시 */
     }
   </style>
</head>

<body>
  <div id="app">
    <div id="stage"></div>
  </div>

  <script>
    /****************************************************************
     * Electron ipcRenderer 준비
     * - 브라우저로 열었을 때는 require가 없으니 try/catch
     ****************************************************************/
    let ipcRenderer = null;
    try {
      // electron 환경
      ipcRenderer = require('electron').ipcRenderer;
    } catch (e) {
      console.warn('Electron 환경이 아니어서 ipcRenderer 없음:', e);
    }

    /****************************************************************
     * 순차 로더
     * - 의존성 → 코어 → 플러그인 순서 보장
     * - CDN 실패 시 다음 후보 자동 시도
     ****************************************************************/
    function loadScript(url) {
      return new Promise((resolve, reject) => {
        const s = document.createElement('script');
        s.src = url;
        s.async = false;               // 로드 순서 보장(중요)
        s.onload = () => { console.log('✅ 로드 성공:', url); resolve(); };
        s.onerror = () => { console.error('❌ 로드 실패:', url); reject(new Error(url)); };
        document.head.appendChild(s);
      });
    }

    // 전역에서 쓸 참조용 (스케일 조절을 위해)
    let app = null;
    let model = null;

    async function init() {
      try {
        /******** 1) Pixi.js (v6.x) : 플러그인 0.4.x와 호환 ********/
        const pixiCdn = [
          'https://cdn.jsdelivr.net/npm/pixi.js@6.5.10/dist/browser/pixi.min.js',
          'https://unpkg.com/pixi.js@6.5.10/dist/browser/pixi.min.js'
        ];
        let ok = false;
        for (const url of pixiCdn) {
          try { await loadScript(url); ok = true; break; } catch {}
        }
        if (!ok) throw new Error('Pixi.js 로드 실패');
        console.log('PIXI.VERSION =', window.PIXI && PIXI.VERSION);

        /******** 2) Live2D Cubism Core : 플러그인이 참조하는 런타임 ********/
        const coreCdn = [
          'https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js',
          'https://cubism.live2d.com/sdk-web/bin/live2dcubismcore.min.js'
        ];
        ok = false;
        for (const url of coreCdn) {
          try { await loadScript(url); ok = true; break; } catch {}
        }
        if (!ok) throw new Error('Live2D Cubism Core 로드 실패');
        console.log('Live2DCubismCore =', typeof window.Live2DCubismCore); // "object" 기대

        /******** 3) pixi-live2d-display (Cubism4 빌드, 0.4.x) ********/
        const pluginCdn = [
          'https://cdn.jsdelivr.net/npm/pixi-live2d-display@0.4.0/dist/cubism4.js',
          'https://unpkg.com/pixi-live2d-display@0.4.0/dist/cubism4.js',
          'https://cdn.jsdelivr.net/gh/guansss/pixi-live2d-display@v0.4.0/dist/cubism4.js'
        ];
        ok = false;
        for (const url of pluginCdn) {
          try {
            await loadScript(url);
            if (window.PIXI?.live2d?.Live2DModel) { ok = true; break; }
          } catch {}
        }
        if (!ok) throw new Error('pixi-live2d-display (cubism4) 로드 실패');

        console.log('PIXI.live2d keys:', Object.keys(PIXI.live2d || {}));

        /******** 4) Pixi 앱 & Live2D 모델 로드 ********/
        app = new PIXI.Application({ backgroundAlpha: 0, resizeTo: window });
        document.getElementById('stage').appendChild(app.view);

        const MODEL_URL = 'public/models/hiyori_free_ko/runtime/hiyori_free_t08.model3.json';
        console.log('📦 모델 로딩:', MODEL_URL);

        model = await PIXI.live2d.Live2DModel.from(MODEL_URL);

         // 배치: 화면 우측 하단
         model.anchor.set(0.5, 1);
         model.scale.set(0.2); // 초기 크기 20% 줄임 (0.25 → 0.2)
         model.position.set(window.innerWidth - 150, window.innerHeight - 50);
         app.stage.addChild(model);

         // 상호작용: 탭 모션 (드래그가 아닐 때만)
         model.interactive = true;
         let clickStartPos = null;
         model.on('pointerdown', (e) => {
           clickStartPos = { x: e.data.global.x, y: e.data.global.y };
         });
         model.on('pointerup', (e) => {
           if (!clickStartPos) return;
           const dx = Math.abs(e.data.global.x - clickStartPos.x);
           const dy = Math.abs(e.data.global.y - clickStartPos.y);
           // 이동 거리가 5px 이하면 클릭으로 간주
           if (dx < 5 && dy < 5) {
             console.log('👆 캐릭터 클릭!');
             model.motion?.('Tap');
           }
           clickStartPos = null;
         });

        console.log('🎉 준비 완료!');

        // 모델 준비된 후 인터랙션 제어 로직 세팅
        setupInteractionControls();

      } catch (err) {
        console.error('❌ 초기화 실패:', err);
        document.getElementById('app').innerHTML =
          `<div style="color:white;padding:20px;font-family:monospace;">
            <h2>❌ 로드 실패</h2>
            <p>${err.message}</p>
            <p>DevTools → Network/Console 확인</p>
          </div>`;
      }
    }

    /****************************************************************
     * 인터랙션 제어
     * - 캐릭터 드래그: 캐릭터 위치 이동
     * - +/- 키: 크기 조절
     * - 캐릭터 바운딩 박스만 클릭 가능 (동적 조절)
     ****************************************************************/
    function setupInteractionControls() {
      let isDragging = false;
      let lastClientX = 0;
      let lastClientY = 0;

      // 크기 조절 함수
      const adjustScale = (delta) => {
        if (!model) return;
        const step = 0.05;
        let newScale = model.scale.x + delta * step;
        newScale = Math.max(0.1, Math.min(1.0, newScale));
        model.scale.set(newScale);
        console.log('🔍 크기:', newScale.toFixed(2));
      };

      // 키보드로 크기 조절: +/- 키만 사용
      window.addEventListener('keydown', (e) => {
        if (e.key === '+' || e.key === '=') {
          e.preventDefault();
          adjustScale(1);
        } else if (e.key === '-' || e.key === '_') {
          e.preventDefault();
          adjustScale(-1);
        }
      });

      // 캐릭터 드래그로 위치 이동
      document.addEventListener('mousedown', (e) => {
        if (e.target.tagName !== 'CANVAS') return;
        isDragging = true;
        lastClientX = e.clientX;
        lastClientY = e.clientY;
        e.preventDefault();
      });

      document.addEventListener('mousemove', (e) => {
        if (isDragging && model) {
          const dx = e.clientX - lastClientX;
          const dy = e.clientY - lastClientY;
          lastClientX = e.clientX;
          lastClientY = e.clientY;
          
          // 캐릭터 위치 이동
          model.position.x += dx;
          model.position.y += dy;
        }
      });

      document.addEventListener('mouseup', () => {
        isDragging = false;
      });

      // 캐릭터의 실제 바운딩 박스만 클릭 가능하도록 동적 체크
      if (ipcRenderer && model) {
        let isOverCharacter = false;
        
        const checkIfOverCharacter = (clientX, clientY) => {
          if (!model) return false;
          
          // 캐릭터의 실제 바운딩 박스 가져오기
          const bounds = model.getBounds();
          
          // Live2D 바운딩 박스가 실제보다 넓으므로 좌우를 대폭 축소
          const actualWidth = bounds.width * 0.4;  // 실제 너비의 40%만 사용 (50%에서 20% 추가 감소)
          const widthOffset = (bounds.width - actualWidth) / 2;  // 중앙 정렬
          
          // 세로는 그대로 유지
          const verticalMargin = bounds.height * 0.05;
          
          return clientX >= bounds.x + widthOffset && 
                 clientX <= bounds.x + widthOffset + actualWidth &&
                 clientY >= bounds.y - verticalMargin && 
                 clientY <= bounds.y + bounds.height + verticalMargin;
        };
        
        document.addEventListener('mousemove', (e) => {
          const wasOver = isOverCharacter;
          isOverCharacter = checkIfOverCharacter(e.clientX, e.clientY);
          
          // 상태가 변경되었을 때만 IPC 전송 (성능 최적화)
          if (wasOver !== isOverCharacter) {
            ipcRenderer.send('va:set-ignore-mouse', !isOverCharacter);
            // console.log('클릭 가능:', isOverCharacter); // 디버깅용
          }
        });
      }
    }

    window.addEventListener('DOMContentLoaded', init);
  </script>
</body>
</html>

 

+ Recent posts