바탕화면에 띄워서 일거수 일투족 감시하는녀석이 목표이기에 작업을 해야한다.

 

Electron 이라는걸 설치해서 작업해야하니깐 , 바탕화면에 위젯으로 만들기를 진행해보자.

 

" package.json 생성: 프로젝트의 메타데이터/스크립트/의존성 버전을 기록하는 파일."

요걸하기위해서

index.html 있는 위치에서(요게 일단 테스트용 실행파일)
npm init -y

 

아래와같은 Json 파일이 만들어짐.

{
  "name": "virtual-assistant",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/TangledUpTeam/Virtual-Assistant.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/TangledUpTeam/Virtual-Assistant/issues"
  },
  "homepage": "https://github.com/TangledUpTeam/Virtual-Assistant#readme"
}

--------

추후 main.js 를 만들고나서 아래와 같이 수정했다. 

{
  "name": "virtual-assistant",
  "version": "1.0.0",
  "private": true,
  "description": "Desktop Live2D assistant (Electron, transparent window)",
  "main": "main.js",                      
  "scripts": {
    "start": "electron .",               
    "start:dev": "electron .",
    "postinstall": "echo Done"            
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/TangledUpTeam/Virtual-Assistant.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/TangledUpTeam/Virtual-Assistant/issues"
  },
  "homepage": "https://github.com/TangledUpTeam/Virtual-Assistant#readme",
  "devDependencies": {
    "electron": "^39.1.2"                 
  }
}

 

 

이제 아래 일렉트론을 설치하자 . -D는 개발의존성으로  설치하란뜻

npm i -D electron

 

 

그리고 main.js 파일을 추가해준다. 

 

// main.js (프로젝트 루트)
const { app, BrowserWindow, globalShortcut, screen } = require("electron");

let win;

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

  win = new BrowserWindow({
    width: 480,
    height: 800,
    x: width - 520,                      // 우하단 근처
    y: height - 840,
    frame: false,                        // 프레임 제거
    transparent: true,                   // 창 배경 투명
    alwaysOnTop: true,                   // 항상 위
    resizable: true,
    hasShadow: false,
    skipTaskbar: true,
    backgroundColor: "#00000000",
    webPreferences: {
      contextIsolation: true
    }
  });

  // index.html (Live2D 띄우는 페이지)
  win.loadFile("index.html");

  // 클릭-스루 토글 (Alt+D): 캐릭터를 통과해서 뒤 창 클릭 가능
  globalShortcut.register("Alt+D", () => {
    const ignoring = win.isIgnoringMouseEvents();
    win.setIgnoreMouseEvents(!ignoring, { 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();
});

 

그리고 pakage.json 파일을 수정하고 .. (위에 수정 내용까지 참고)

 

마지막으로 index.html 을 수정한다. 

 

<!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;
      /* 브라우저에서도, Electron에서도 투명 배경 유지 */
      background: transparent;
    }
    /* 창을 끌 수 있는 핸들(투명 막대) */
    .drag {
      -webkit-app-region: drag;   /* Electron에서 창 드래그 활성화 */
      position: fixed;
      top: 0; left: 0; right: 0;
      height: 32px;
      pointer-events: auto;
    }
    /* 상호작용(클릭 등)이 필요한 영역은 반드시 no-drag */
    .no-drag {
      -webkit-app-region: no-drag;
      pointer-events: auto;
    }
    /* 캔버스 전체를 우하단에 두고 싶다면 아래 컨테이너를 사용 */
    #app {
      width: 100%;
      height: 100%;
      position: relative;
    }
    /* (선택) 우하단 고정 레이아웃 예시 */
    #stage {
      position: fixed;
      right: 0;
      bottom: 0;
      left: 0;
      top: 0;
    }
  </style>
</head>

<body>
  <!-- 드래그 핸들(투명) : 일렉트론일 때 창 이동 가능 -->
  <div class="drag"></div>

  <!-- 캔버스 컨테이너는 no-drag로 상호작용 보장 -->
  <div id="app" class="no-drag">
    <div id="stage"></div>
  </div>

  <script>
    /****************************************************************
     * 순차 로더
     * - 의존성 → 코어 → 플러그인 순서 보장
     * - 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);
      });
    }

    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 모델 로드 ********/
        // Electron/브라우저 공통: 전체 창 사이즈에 맞춤
        const app = new PIXI.Application({ backgroundAlpha: 0, resizeTo: window });
        document.getElementById('stage').appendChild(app.view);

        // 모델 경로: 브라우저(http 서버) / Electron(file 스킴) 모두에서 동작하는 상대 경로 권장
        const MODEL_URL = 'public/models/hiyori_free_ko/runtime/hiyori_free_t08.model3.json';

        console.log('📦 모델 로딩:', MODEL_URL);
        const model = await PIXI.live2d.Live2DModel.from(MODEL_URL);

        // 배치: 화면 하단 중앙, 크기 적당히
        model.anchor.set(0.5, 1);
        model.scale.set(0.2); // 너비/해상도에 맞게 조절 (0.15~0.5 사이 시도)
        model.position.set(window.innerWidth / 2, window.innerHeight);
        app.stage.addChild(model);

        // 상호작용: 탭 모션
        model.interactive = true;
        model.on('pointertap', () => model.motion?.('Tap')?.start?.());

        // 리사이즈 대응(하단 정렬 유지)
        window.addEventListener('resize', () => {
          model.position.set(window.innerWidth / 2, window.innerHeight);
        });

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

      } 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>`;
      }
    }

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

 

 

실행해보면 뭔가 뒷배경을 날렸지만 .. 투명한 창이 있어서 ..뒤에 보이는 유투브나 넷플릭스를 실행할 수 없다.. 

 

진짜 ai 가 다 알려줘서 그렇지... 프론트엔드 공부해야겠따..

+ Recent posts