
뒷배경이 클릭이 안된다.. 크기도 조절이 안되고 .. 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>
'학원 TEAM 프로젝트 > 심화과정 Team Project(최종)' 카테고리의 다른 글
| 기록 10 . 환경설정관련 설치 해야할것 추가 (0) | 2025.11.14 |
|---|---|
| 기록 9 . 로그인기능을 구현해야한다.(네이버,구글,카카오) (0) | 2025.11.14 |
| 기록 7 . 바탕화면에 띄워야해... (0) | 2025.11.13 |
| 기록 6 . 일단 캐릭터를 띄워보자...(가져다 쓴다) (0) | 2025.11.13 |
| 기록 5-1 . 그래서 버츄얼 비서 프로젝트..잘할수있을까? (0) | 2025.11.12 |