/*:
* @target MZ
* @plugindesc Opening animé 60s — Persona 5 × Shōnen × Néon — Skippable après la première fois
* @author Bertran
* @help
* Placez vos images dans /img/pictures/ :
* - op_bg.png
* - op_slash.png
* - op_glitch.png
* - op_particle.png
* - op_kou.png
* - op_sakura.png
* - op_ken.png
* - op_asuka.png
* - op_villain.png
* - op_logo.png
*/
(() => {
// -------------------------------------------------------------
// CONFIG
// -------------------------------------------------------------
const OP = {
duration: 60 * 60, // 60 secondes × 60 FPS
skipAfterFirst: true,
storageKey: "TLF_OpeningSeen",
images: {
bg: "op_bg",
slash: "op_slash",
glitch: "op_glitch",
particle: "op_particle",
kou: "op_kou",
sakura: "op_sakura",
ken: "op_ken",
asuka: "op_asuka",
villain: "op_villain",
logo: "op_logo"
}
};
// -------------------------------------------------------------
// Override du boot : lancer l’opening avant le titre
// -------------------------------------------------------------
const _Scene_Boot_start = Scene_Boot.prototype.start;
Scene_Boot.prototype.start = function() {
if (!localStorage.getItem(OP.storageKey)) {
SceneManager.goto(Scene_Opening);
} else {
_Scene_Boot_start.call(this);
}
};
// -------------------------------------------------------------
// SCENE OPENING
// -------------------------------------------------------------
function Scene_Opening() {
this.initialize(...arguments);
}
Scene_Opening.prototype = Object.create(Scene_Base.prototype);
Scene_Opening.prototype.constructor = Scene_Opening;
Scene_Opening.prototype.initialize = function() {
Scene_Base.prototype.initialize.call(this);
this._timer = 0;
this._phase = 0;
this._sprites = {};
};
Scene_Opening.prototype.create = function() {
Scene_Base.prototype.create.call(this);
this.createBackground();
this.createParticles();
this.createSlash();
this.createGlitch();
this.createCharacters();
this.createLogo();
};
// -------------------------------------------------------------
// Création des éléments
// -------------------------------------------------------------
Scene_Opening.prototype.createBackground = function() {
const s = new Sprite(ImageManager.loadPicture(OP.images.bg));
s.anchor.x = 0.5;
s.anchor.y = 0.5;
s.x = Graphics.width / 2;
s.y = Graphics.height / 2;
s.opacity = 0;
this.addChild(s);
this._sprites.bg = s;
};
Scene_Opening.prototype.createParticles = function() {
this._sprites.particles = [];
for (let i = 0; i < 40; i++) {
const p = new Sprite(ImageManager.loadPicture(OP.images.particle));
p.anchor.x = 0.5;
p.anchor.y = 0.5;
p.x = Math.random() * Graphics.width;
p.y = Math.random() * Graphics.height;
p.opacity = 0;
p._speed = 0.5 + Math.random() * 1.5;
this.addChild(p);
this._sprites.particles.push(p);
}
};
Scene_Opening.prototype.createSlash = function() {
const s = new Sprite(ImageManager.loadPicture(OP.images.slash));
s.anchor.x = 0.5;
s.anchor.y = 0.5;
s.x = Graphics.width / 2;
s.y = Graphics.height / 2;
s.opacity = 0;
s.scale.x = 0;
this.addChild(s);
this._sprites.slash = s;
};
Scene_Opening.prototype.createGlitch = function() {
const s = new Sprite(ImageManager.loadPicture(OP.images.glitch));
s.opacity = 0;
this.addChild(s);
this._sprites.glitch = s;
};
Scene_Opening.prototype.createCharacters = function() {
const names = ["kou", "sakura", "ken", "asuka", "villain"];
this._sprites.chars = {};
for (const n of names) {
const s = new Sprite(ImageManager.loadPicture(OP.images[n]));
s.anchor.x = 0.5;
s.anchor.y = 0.5;
s.x = Graphics.width / 2;
s.y = Graphics.height / 2;
s.opacity = 0;
s.scale.x = 1.2;
s.scale.y = 1.2;
this.addChild(s);
this._sprites.chars[n] = s;
}
};
Scene_Opening.prototype.createLogo = function() {
const s = new Sprite(ImageManager.loadPicture(OP.images.logo));
s.anchor.x = 0.5;
s.anchor.y = 0.5;
s.x = Graphics.width / 2;
s.y = Graphics.height / 2;
s.opacity = 0;
this.addChild(s);
this._sprites.logo = s;
};
// -------------------------------------------------------------
// UPDATE
// -------------------------------------------------------------
Scene_Opening.prototype.update = function() {
Scene_Base.prototype.update.call(this);
this._timer++;
this.updateBackground();
this.updateParticles();
this.updateTimeline();
this.updateSkip();
};
// -------------------------------------------------------------
// Effets
// -------------------------------------------------------------
Scene_Opening.prototype.updateBackground = function() {
const bg = this._sprites.bg;
if (this._timer < 60) bg.opacity += 4;
bg.rotation = Math.sin(this._timer / 120) * 0.02;
};
Scene_Opening.prototype.updateParticles = function() {
for (const p of this._sprites.particles) {
if (this._timer > 30) p.opacity = Math.min(255, p.opacity + 3);
p.y -= p._speed;
if (p.y < -10) {
p.y = Graphics.height + 10;
p.x = Math.random() * Graphics.width;
}
}
};
// -------------------------------------------------------------
// TIMELINE 60 SECONDES
// -------------------------------------------------------------
Scene_Opening.prototype.updateTimeline = function() {
const t = this._timer;
// 0–60 : intro néon
if (t === 30) this.playSlash();
// 60–120 : KOU
if (t === 60) this.reveal("kou");
// 120–180 : Sakura
if (t === 120) this.reveal("sakura");
// 180–240 : Ken
if (t === 180) this.reveal("ken");
// 240–300 : Asuka
if (t === 240) this.reveal("asuka");
// 300–360 : Méchant (sourire)
if (t === 300) this.reveal("villain");
// 360–420 : glitch + tension
if (t > 360 && t < 420) this._sprites.glitch.opacity = Math.random() * 150;
// 420–480 : Logo final
if (t === 420) this.revealLogo();
// Fin → titre
if (t >= OP.duration) {
localStorage.setItem(OP.storageKey, "true");
SceneManager.goto(Scene_Title);
}
};
Scene_Opening.prototype.playSlash = function() {
const s = this._sprites.slash;
s.opacity = 255;
s.scale.x = 0;
s.scale.y = 1;
this._slashAnim = 20;
};
Scene_Opening.prototype.reveal = function(name) {
const s = this._sprites.chars[name];
s.opacity = 0;
s.scale.x = 1.3;
s.scale.y = 1.3;
this._revealTarget = s;
this._revealTimer = 0;
};
Scene_Opening.prototype.revealLogo = function() {
const s = this._sprites.logo;
s.opacity = 0;
s.scale.x = 1.4;
s.scale.y = 1.4;
this._logoTimer = 0;
};
Scene_Opening.prototype.updateSkip = function() {
if (!localStorage.getItem(OP.storageKey)) return;
if (Input.isTriggered("ok") || Input.isTriggered("cancel") || TouchInput.isTriggered()) {
SceneManager.goto(Scene_Title);
}
};
})();
To embed this project on your website, copy the following code and paste it into your website's HTML: