Widget:PtNLive2DJolyne: Difference between revisions
Jump to navigation
Jump to search
Paisley Park (talk | contribs) No edit summary |
Paisley Park (talk | contribs) No edit summary |
||
(75 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
<noinclude>Live2D Embed for ''[[Path to Nowhere/Jolyne Cujoh]]''. |
<noinclude>Live2D Embed for ''[[Path to Nowhere/Jolyne Cujoh]]''. Used by {{Tl|PtNLive2DJolyne}}. |
||
</noinclude><includeonly> |
[[Category:Path to Nowhere Templates]]</noinclude><includeonly> |
||
<div class="modal-template"><span class="mcbutton mw-ui-button modal-button white" style="width:auto; height:auto; line-height: 1; margin-left: auto; margin-right: auto;">Live2D</span><div class="modal" style="display:none"><div class="modal-content" style="width:max-content"><div class="modal-header"><span class="close">×</span></div><div class="modal-body |
<div class="modal-template"><span class="mcbutton mw-ui-button modal-button white" style="width:auto; height:auto; line-height: 1; margin-left: auto; margin-right: auto;">Live2D Animation</span><div class="modal" style="display:none"><div class="modal-content" style="width:max-content; background: none; background-image: none; padding: none; border: none; margin-top: 0;"><div class="modal-header"><span class="close">×</span></div><div class="modal-body" style="background-color:transparent; border: none; padding: 0; margin: 0;"><div id="ptn-canvas-container" style="margin: 0 auto;"><canvas id="ptn-canvas"></canvas></div><div id="ptn-canvas-caption" style="background:rgba(0, 0, 0, 0.8); display: none; z-index:1000; color: white; width: 50%; position: absolute; bottom: 20px; left: 0; right: 0; margin: 0 auto; text-align: center;"></div></div></div></div></div> |
||
<script type="text/javascript"> |
|||
<script src="https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js" crossorigin="anonymous"></script> |
|||
let resizeCanvasToModel; |
|||
<script src="https://cdn.jsdelivr.net/gh/dylanNew/live2d/webgl/Live2D/lib/live2d.min.js" crossorigin="anonymous"></script> |
|||
let captionDiv; |
|||
<script src="https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js" crossorigin="anonymous"></script> |
|||
<script src="https://jojowiki.com/customizations/Live2D/assets/cubism4.min.js"></script> |
|||
<script> |
|||
const live2d = PIXI.live2d; |
|||
document.querySelector(".modal-button").addEventListener("click", () => { |
|||
(async function main() { |
|||
// Check if the canvas exists; recreate it if necessary |
|||
const container = document.getElementById("ptn-canvas-container"); |
|||
let canvas = document.getElementById("ptn-canvas"); |
|||
autoStart: true, |
|||
backgroundAlpha: 0, |
|||
}); |
|||
app.ticker.maxFPS = 30; |
|||
app.renderer.resolution = Math.min(window.devicePixelRatio, 1); |
|||
if (!canvas) { |
|||
const jolyne = await live2d.Live2DModel.from('https://jojowiki.com/customizations/Live2D/resources/model/char2d_jolyne_2/char2d_jolyne_2.model3.json', { idleMotionGroup: 'clip_jolyne_background' }); |
|||
canvas = document.createElement("canvas"); |
|||
canvas.id = "ptn-canvas"; |
|||
container.appendChild(canvas); |
|||
} |
|||
// Check if Live2D scripts are already loaded |
|||
if (!window.PIXI || !window.live2d) { |
|||
const scripts = [ |
|||
"https://cubism.live2d.com/sdk-web/cubismcore/live2dcubismcore.min.js", |
|||
"https://cdn.jsdelivr.net/gh/dylanNew/live2d/webgl/Live2D/lib/live2d.min.js", |
|||
"https://cdn.jsdelivr.net/npm/pixi.js@7.x/dist/pixi.min.js", |
|||
"https://jojowiki.com/customizations/Live2D/assets/cubism4.min.js", |
|||
]; |
|||
function loadScriptsSequentially(scripts, callback) { |
|||
// Store the original model dimensions |
|||
if (scripts.length === 0) { |
|||
callback(); |
|||
return; |
|||
} |
|||
const script = document.createElement("script"); |
|||
script.src = scripts.shift(); |
|||
script.crossOrigin = "anonymous"; |
|||
script.onload = () => { |
|||
console.log(`${script.src} loaded.`); |
|||
loadScriptsSequentially(scripts, callback); |
|||
}; |
|||
script.onerror = () => { |
|||
console.error(`Failed to load ${script.src}`); |
|||
}; |
|||
document.body.appendChild(script); |
|||
} |
|||
loadScriptsSequentially(scripts, initializeLive2D); |
|||
const container = document.getElementById('ptn-canvas-container'); |
|||
} else { |
|||
const canvas = document.getElementById('ptn-canvas'); |
|||
initializeLive2D(); |
|||
canvas.style.touchAction = "auto"; |
|||
} |
|||
function initializeLive2D() { |
|||
// Function to resize the canvas and center the container |
|||
const live2d = PIXI.live2d; |
|||
const isMobile = window.innerWidth < 1280; // Define a mobile breakpoint |
|||
const scaleFactor = isMobile ? 2 : 1.0; // Scale larger on mobile |
|||
(async function () { |
|||
// Calculate the scale to fit the model within the window dimensions |
|||
const |
const canvas = document.getElementById("ptn-canvas"); |
||
captionDiv = document.getElementById("ptn-canvas-caption"); |
|||
const scaleY = (window.innerHeight * scaleFactor) / jolyne.originalHeight; |
|||
if (!canvas) { |
|||
console.error("Canvas element is missing. Cannot initialize Live2D."); |
|||
return; |
|||
} |
|||
const app = new PIXI.Application({ |
|||
view: canvas, |
|||
autoStart: true, |
|||
backgroundAlpha: 0, |
|||
}); |
|||
window.live2DApp = app; |
|||
canvas.width = jolyne.width; |
|||
canvas.height = jolyne.height; |
|||
app.ticker.maxFPS = 30; |
|||
app.renderer.resolution = Math.min(window.devicePixelRatio, 1); |
|||
container.style.height = `${canvas.height}px`; |
|||
const jolyne = await live2d.Live2DModel.from( |
|||
'https://jojowiki.com/customizations/Live2D/resources/model/char2d_jolyne_2/char2d_jolyne_2.model3.json', |
|||
app.renderer.resize(canvas.width, canvas.height); |
|||
{ idleMotionGroup: 'clip_jolyne_background' } |
|||
} |
|||
); |
|||
console.log("Live2D model initialized: Jolyne Cujoh"); |
|||
app.stage.addChild(jolyne); |
|||
window.live2DModel = jolyne; |
|||
// Store the original model dimensions for resizing logic |
|||
jolyne.originalWidth = jolyne.width; |
|||
jolyne.originalHeight = jolyne.height; |
|||
resizeCanvasToModel = function () { |
|||
window. |
const isMobile = window.innerWidth<1280; |
||
const scaleFactor = isMobile ? 1.5 : 1.0; |
|||
document.querySelector(".modal").addEventListener("show", () => { |
|||
app.renderer.resize(canvas.width, canvas.height); |
|||
}); |
|||
const |
const scaleX = (window.innerWidth * scaleFactor) / jolyne.originalWidth; |
||
const scaleY = (window.innerHeight * scaleFactor) / jolyne.originalHeight; |
|||
const headMotions = ["clip_jolyne_click_head", "clip_jolyne_irritated", "clip_jolyne_special"]; |
|||
const |
const scale = Math.min(scaleX, scaleY); |
||
const legMotions = ["clip_jolyne_click_leg", "clip_jolyne_fail"]; |
|||
jolyne.scale.set(scale); |
|||
function playRandomMotion(model, motionGroup) { |
|||
const randomIndex = Math.floor(Math.random() * motionGroup.length); |
|||
const randomMotion = motionGroup[randomIndex]; |
|||
const motionData = jolyne.internalModel.settings.motions[randomMotion]; |
|||
if (!motionData || motionData.length === 0) return; |
|||
canvas.width = jolyne.width; |
|||
canvas.height = jolyne.height; |
|||
const container = document.getElementById("ptn-canvas-container"); |
|||
container.style.width = `${canvas.width}px`; |
|||
{ group: 'clip_jolyne_background', index: 0, priority: 1 }, |
|||
container.style.height = `${canvas.height}px`; |
|||
]); |
|||
app.renderer.resize(canvas.width, canvas.height); |
|||
}; |
|||
console.log(`Playing motion: ${randomMotion}, Sound: ${motionSound}`); |
|||
jolyne.speak(motionSound); |
|||
resizeCanvasToModel(); |
|||
window.addEventListener("resize", resizeCanvasToModel); |
|||
} |
|||
// Define motions |
|||
const bodyMotions = ["clip_jolyne_click_body", "clip_jolyne_greet", "clip_jolyne_boring"]; |
|||
const headMotions = ["clip_jolyne_click_head", "clip_jolyne_irritated", "clip_jolyne_special"]; |
|||
const handMotions = ["clip_jolyne_click_hand", "clip_jolyne_level"]; |
|||
const legMotions = ["clip_jolyne_click_leg", "clip_jolyne_fail"]; |
|||
function playRandomMotion(model, motionGroup) { |
|||
const randomIndex = Math.floor(Math.random() * motionGroup.length); |
|||
const randomMotion = motionGroup[randomIndex]; |
|||
const motionData = jolyne.internalModel.settings.motions[randomMotion]; |
|||
if (!motionData || motionData.length === 0) return; |
|||
const motionSound = motionData[0].Sound; |
|||
const motionCaption = motionData[0].Caption; |
|||
function loopIdleMotion() { |
|||
jolyne.parallelMotion([ |
jolyne.parallelMotion([ |
||
{ group: 'clip_jolyne_background', index: 0, priority: 1 }, |
{ group: 'clip_jolyne_background', index: 0, priority: 1 }, |
||
{ group: |
{ group: randomMotion, index: 0, priority: 2 }, |
||
]); |
]); |
||
if (motionSound) { |
|||
jolyne.speak(motionSound, { |
|||
onFinish: function () { |
|||
removeCaptions(); |
|||
}, |
|||
}); |
|||
// Set the caption text and make it visible |
|||
if (motionCaption && jolyne.internalModel.parallelMotionManager[1].manager.currentAudio) { |
|||
if (jolyne.internalModel.parallelMotionManager[1].manager.currentAudio.src == motionSound) { |
|||
captionDiv.innerText = motionCaption; |
|||
captionDiv.style.display = "block"; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
removeCaptions(); |
|||
} |
|||
} |
} |
||
setTimeout(loopIdleMotion, 1000); |
|||
} |
|||
loopIdleMotion() |
function loopIdleMotion() { |
||
if (!jolyne.internalModel || !jolyne.internalModel.motionManager) { |
|||
console.log("Idle motion stopped: Live2D model not available."); |
|||
return; |
|||
} |
|||
if (!jolyne.internalModel.motionManager.isPlaying) { |
|||
// Handle tapping |
|||
jolyne.parallelMotion([ |
|||
jolyne.on("hit", (hitAreas) => { |
|||
{ group: 'clip_jolyne_background', index: 0, priority: 1 }, |
|||
if (hitAreas.includes("Body")) { |
|||
{ group: 'clip_jolyne_idle', index: 0, priority: 1 }, |
|||
]); |
|||
} |
|||
setTimeout(loopIdleMotion, 1000); |
|||
} |
} |
||
if (hitAreas.includes("Head")) { |
|||
loopIdleMotion(); |
|||
jolyne.on("hit", (hitAreas) => { |
|||
if (hitAreas.includes("Body")) { |
|||
playRandomMotion(jolyne, bodyMotions); |
|||
} |
|||
if (hitAreas.includes("Head")) { |
|||
playRandomMotion(jolyne, headMotions); |
|||
} |
|||
if (hitAreas.includes("HandRight") || hitAreas.includes("HandLeft")) { |
|||
playRandomMotion(jolyne, handMotions); |
|||
} |
|||
if (hitAreas.includes("LegRight") || hitAreas.includes("LegLeft")) { |
|||
playRandomMotion(jolyne, legMotions); |
|||
} |
|||
}); |
|||
document.addEventListener("visibilitychange", () => { |
|||
app.ticker.stop(); |
|||
if (!document.hidden) app.ticker.start(); |
|||
}); |
|||
})(); |
|||
} |
|||
function removeCaptions() { |
|||
if (captionDiv) { |
|||
captionDiv.innerText = ""; |
|||
captionDiv.style.display = "none"; |
|||
} |
|||
} |
|||
function destroyLive2D() { |
|||
if (window.live2DApp) { |
|||
window.live2DApp.ticker.stop(); |
|||
if (window.live2DModel) { |
|||
window.live2DModel.destroy({ children: true, texture: true, baseTexture: true }); |
|||
window.live2DModel = null; |
|||
} |
} |
||
if (hitAreas.includes("HandRight") || hitAreas.includes("HandLeft")) { |
|||
window.live2DApp.destroy(true, { children: true, texture: true, baseTexture: true }); |
|||
window.live2DApp = null; |
|||
removeCaptions(); |
|||
if (resizeCanvasToModel) { |
|||
window.removeEventListener("resize", resizeCanvasToModel); |
|||
} |
} |
||
if (hitAreas.includes("LegRight") || hitAreas.includes("LegLeft")) { |
|||
playRandomMotion(jolyne, legMotions); |
|||
} |
|||
}); |
|||
document.addEventListener("visibilitychange", () => { |
|||
app.ticker.stop(); // Pause rendering when the tab is inactive |
|||
if (!document.hidden) app.ticker.start(); // Resume rendering when active |
|||
}); |
|||
console.log("Live2D resources destroyed."); |
|||
})(); |
|||
} |
|||
} |
|||
const modal = document.querySelector(".modal"); |
|||
modal.addEventListener("click", (event) => { |
|||
if (event.target === modal) { |
|||
destroyLive2D(); |
|||
modal.style.display = "none"; |
|||
} |
|||
}); |
|||
document.querySelector(".close").addEventListener("click", () => { |
|||
destroyLive2D(); |
|||
}); |
|||
}); |
|||
</script> |
|||
</includeonly> |
</includeonly> |
Latest revision as of 21:05, 10 December 2024
Live2D Embed for Path to Nowhere/Jolyne Cujoh. Used by PtNLive2DJolyne.