diff --git a/Css/main.css b/Css/main.css index 6d88cbd..de7c29e 100644 --- a/Css/main.css +++ b/Css/main.css @@ -4,36 +4,63 @@ } @font-face { - font-family: FOT-RodinNTLGPro-DB; - src: url(../Fonts/FOT-RodinNTLGPro-DB.woff2) format('woff'); -} - -@font-face { - font-family: FOT-RodinNTLGPro-M; + font-family: FOT-RodinNTLGPro; src: url(../Fonts/FOT-RodinNTLGPro-M.woff2) format('woff'); } @font-face { - font-family: Orbitron Black; + font-family: FOT-RodinNTLGPro; + src: url(../Fonts/FOT-RodinNTLGPro-DB.woff2) format('woff'); + font-weight: bold; +} + +@font-face { + font-family: Orbitron-Black; src: url(../Fonts/Orbitron Black.woff2) format('woff'); } @font-face { - font-family: Orbitron Bold; + font-family: Orbitron-Bold; src: url(../Fonts/Orbitron Bold.woff2) format('woff'); } @font-face { - font-family: Orbitron Light; + font-family: Orbitron-Light; src: url(../Fonts/Orbitron Light.woff2) format('woff'); } @font-face { - font-family: Orbitron Medium; + font-family: Orbitron-Medium; src: url(../Fonts/Orbitron Medium.woff2) format('woff'); } @font-face { font-family: SourceCodePro-Regular; src: url(../Fonts/SourceCodePro-Regular.woff2) format('woff'); -} \ No newline at end of file +} + +.centered { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; } + +#app-container { position: relative; width: 1334; height: 750; } + +#parent-container { display: flex; flex-direction: column; align-items: center; } + +#text-container { position: absolute; height: 100%; width: 100%; font-family: 'FOT-RodinNTLGPro'; } + +#text-container #title { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 20px; } + +#text-container #diva { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 34px; } + +#text-container #dialog-box { box-sizing: border-box; position: absolute; bottom: 0; text-align: left; width: 100%; height: 200px; } + +#text-container #main-ui-img { width: 100%; } + +#dialog-box { color: white; font-weight: bold; text-shadow: 1px 1px 6px black; } + +#dialog-box #character { position: absolute; top: -5px; left: 70px; font-size: 30px; } + +#dialog-box #dialog { position: absolute; padding: 60px 30px 0px 70px; box-sizing: border-box; height: 100%; width: 100%; font-size: 30px; } + +#dialog-box #ui-buttons { right: 20px; top: -50px; position: absolute; } + +#dialog-box .ui-button { position: relative; height: 85px; } \ No newline at end of file diff --git a/Images/hide_button.png b/Images/hide_button.png new file mode 100644 index 0000000..e308170 Binary files /dev/null and b/Images/hide_button.png differ diff --git a/Images/newui_main.png b/Images/newui_main.png new file mode 100644 index 0000000..7e25dc6 Binary files /dev/null and b/Images/newui_main.png differ diff --git a/Images/white.png b/Images/white.png new file mode 100644 index 0000000..818c71d Binary files /dev/null and b/Images/white.png differ diff --git a/Js/Common.js b/Js/Common.js index 5e37bda..5b9fdf1 100644 --- a/Js/Common.js +++ b/Js/Common.js @@ -50,4 +50,15 @@ class commonFunctions { static lerp(start, end, t) { return (1 - t) * start + t * end; } + + static getColorFromName(name) { + if(!name) { return 0xFFFFFF } + + switch(name.toLowerCase()) { + case 'black': + return 0x000000; + case 'white': + return 0xFFFFFF; + } + } } \ No newline at end of file diff --git a/Js/Main.js b/Js/Main.js index 531aec7..719d92e 100644 --- a/Js/Main.js +++ b/Js/Main.js @@ -1,28 +1,56 @@ 'use strict'; const pixiApp = { - app: new PIXI.Application({width: 1334, height: 750}), + app: new PIXI.Application(baseDimensions), loader: PIXI.loader }; const utage = new UtageInfo(); -const player = new Player(pixiApp, utage); +const textFunc = new TextFunctions(); +const player = new Player(pixiApp, utage, textFunc); const context = new (window.AudioContext || window.webkitAudioContext)(); -const onBodyLoaded = () => { +let bodyLoaded = false; +let utageLoaded = false; +function onBodyLoaded() { + bodyLoaded = true; +} + +(function startLoad() { var promises = [ utage.loadUtageSettings() ]; Promise.all(promises) .then((success) => { - onParsed(success); + utageLoaded = true; }, (failure) => { console.log(failure); }); -}; +})(); -function onParsed (success) { +(function checkIsLoaded() { + if(bodyLoaded) { + document.getElementById('loading-font').style.cssText = "display: none;"; + } + if(utageLoaded) { + document.getElementById('loading-utage').style.cssText = "display: none;"; + } + if(bodyLoaded && utageLoaded) { + document.getElementById('loading-container').style.cssText = "opacity: 0;"; + onAllLoaded(); + } else { + setTimeout(checkIsLoaded, 300); + } +})(); + +function onAllLoaded(success) { + textFunc.findTextElements(); buildMissionSelectList(); - document.getElementById('app-container').appendChild(pixiApp.app.view); + let appContainer = document.getElementById('app-container'); + appContainer.appendChild(pixiApp.app.view); + //appContainer.style.cssText = `width: ${baseDimensions.width}; height: ${baseDimensions.height};`; + setTimeout(() => { + document.getElementById('parent-container').style.cssText = "opacity: 1;"; + }); } function buildMissionSelectList() { @@ -46,10 +74,16 @@ function missionChanged(event) { if(!event || !event.currentTarget || !event.currentTarget.value || event.currentTarget.value === '{Select}') { return; } let newMission = utage.availableMissions[event.currentTarget.value.split('|')[0]]; - utage.parseMissionFile(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '')}`) + var promises = [ + utage.parseMissionFile(`${utage.rootDirectory}XDUData/${newMission.Path.replace('Asset/', '').replace('.utage', '')}`), + player.resetAll() + ]; + + Promise.all(promises) .then((success) => { - player.playFile() + var res = player.playFile() .then((success) => { + player.resetAll(); debugger; }, (failure) => { debugger; @@ -58,4 +92,10 @@ function missionChanged(event) { }, (failure) => { console.log(failure); }); +} + +function onTextClicked(event) { + event.preventDefault(); + event.stopPropagation(); + } \ No newline at end of file diff --git a/Js/Player.js b/Js/Player.js index 6c901df..27ebf3c 100644 --- a/Js/Player.js +++ b/Js/Player.js @@ -1,10 +1,28 @@ +'use strict'; + +const baseDimensions = {width: 1334, height: 750}; class Player { - constructor(pixi, utage) { + constructor(pixi, utage, text) { this.pixi = pixi; this.loader = pixi.loader; - this.resources = pixi.loader.resources; this.utage = utage; + this.text = text; + this.resolutionScale = 1; + this.baseFps = 60; //I am assuming that PIXI is going to stay as keeping 60fps = delta1. + this.blackBackSp = undefined; this.currentCharacters = []; + this.layers = {}; + this.sprites = {}; + this.currentCommand = undefined; + this.runEvent = false; + this.secondTicker = 1000; + this.waitTime = 0; + this.lerpTargets = []; + this.bgLayerName = "背景"; + this.titleWaitTime = 1; + this.manualNext = false; + this.hasMoreText = false; + this.center = {x: ((baseDimensions.width / 2) * this.resolutionScale), y: ((baseDimensions.height / 2) * this.resolutionScale) }; } playFile() { @@ -14,10 +32,7 @@ class Player { }); this.preCheckFilesToGet() .then((success) => { - this.pixi.app.ticker.add((delta) => { - this.onPixiTick(delta); - }); - this.getNextCommand(); + this.pixi.app.ticker.add(this.onPixiTick, this); }, (failure) => { console.log(failure); }); @@ -26,16 +41,16 @@ class Player { preCheckFilesToGet() { return new Promise((resolve, reject) => { - for(var i = 0; i < utage.currentPlayingFile.length; ++i) { + for(let i = 0; i < utage.currentPlayingFile.length; ++i) { try { - var c = utage.currentPlayingFile[i]; + let c = utage.currentPlayingFile[i]; if(c.comment) { continue; } - var command = c.Command ? c.Command.toLowerCase() : ''; + let command = c.Command ? c.Command.toLowerCase() : ''; switch(command) { //BG images case "bg": if(this.utage.textureInfo[c.Arg1]) { - if(!this.resources[`bg|${c.Arg1}`]) { + if(!this.loader.resources[`bg|${c.Arg1}`]) { this.loader.add(`bg|${c.Arg1}`, this.utage.textureInfo[c.Arg1].FileName); } } else if(!this.utage.textureInfo[c.Arg1]) { @@ -46,12 +61,12 @@ class Player { case "": //Character Text if(c.Arg1 && c.Arg2 && c.Arg2 != "" && c.Text && - this.utage.characterData[c.Arg1] && this.utage.characterData[c.Arg1][c.Arg2]) { - if(!this.resources[`char|${c.Arg1}|${c.Arg2}`]) { - this.loader.add(`char|${c.Arg1}|${c.Arg2}`, this.utage.characterData[c.Arg1][c.Arg2].FileName); + this.utage.characterInfo[c.Arg1] && this.utage.characterInfo[c.Arg1][c.Arg2]) { + if(!this.loader.resources[`char|${c.Arg1}|${c.Arg2}`]) { + this.loader.add(`char|${c.Arg1}|${c.Arg2}`, this.utage.characterInfo[c.Arg1][c.Arg2].FileName); } } else if(c.Arg1 && c.Arg2 && c.Arg2 != "" && - (!this.utage.characterData[c.Arg1] || !this.utage.characterData[c.Arg1][c.Arg2])) { + (!this.utage.characterInfo[c.Arg1] || !this.utage.characterInfo[c.Arg1][c.Arg2])) { console.log(`Failed to get Character: ${c.Arg1}|${c.Arg2}`); } break; @@ -61,6 +76,8 @@ class Player { continue; } } + //Manually load white bg for fading. Can be tinted to change color. + this.loader.add('bg|whiteFade', `${this.utage.rootDirectory}Images/white.png`); this.loader .on("progress", (loader, resource) => { this.onPixiProgress(loader, resource) @@ -71,16 +88,241 @@ class Player { }); } - onPixiProgress(loader, resource) { + //note containers render in the order they are added, eg. the last added is always on top. + buildLayerContainers() { + let layersToAdd = []; + for(let l of Object.keys(this.utage.layerInfo)) { + layersToAdd.push(this.utage.layerInfo[l]); + } + layersToAdd.sort(compare); + let parentContainer = new PIXI.Container(); + parentContainer.position.set(this.center.x, this.center.y); + parentContainer.pivot.set(this.center.x, this.center.y); + this.pixi.app.stage.addChild(parentContainer); + this.layers["bg|mainparent"] = { container: parentContainer }; + for(let l of layersToAdd) { + this.layers[l.LayerName] = { info: l }; + let cont = new PIXI.Container(); + this.layers[l.LayerName].container = cont; + parentContainer.addChild(cont); + //center the position of the container then offset it by the value in the layer info + let x = (((baseDimensions.width / 2) + Number(l.X)) * this.resolutionScale); + let y = (((baseDimensions.height / 2) + Number(l.Y)) * this.resolutionScale); + //let x = (Number(l.X) * this.resolutionScale); + //let y = (Number(l.Y) * this.resolutionScale); + cont.position.set(x, y); + cont.visible = false; + } + let fadeBackCon = new PIXI.Container(); + let fadeBackSp = new PIXI.Sprite(this.loader.resources["bg|whiteFade"].texture); + fadeBackSp.height = baseDimensions.height * this.resolutionScale; + fadeBackSp.width = baseDimensions.width * this.resolutionScale; + this.layers["bg|whiteFade"] = { info: undefined, sprite: fadeBackSp, container: fadeBackCon }; + fadeBackSp.alpha = 0; + fadeBackCon.addChild(fadeBackSp); + this.pixi.app.stage.addChild(fadeBackCon); + function compare(a, b) { + return a.Order - b.Order; + } + } + + onPixiProgress(loader, resource) { + if(loader.progress < 100) { + this.text.titleText(true, `Loading Assets... ${loader.progress}%`); + } else { + this.text.titleText(false, ''); + } } onPixiLoad(resolve, reject) { + this.runEvent = true; + this.buildLayerContainers(); resolve(); } onPixiTick(delta) { - + try + { + if(!this.runEvent) { return; } + let deltaTime = (1000/this.baseFps)*delta; + this.secondTicker -= deltaTime; + if(this.waitTime >= 0) { + this.waitTime -= deltaTime; + } + //Lerp items + this.loopHandleLerps(deltaTime); + //If wait time has expired and we arent waiting for user input end the current command + if(this.currentCommand && this.waitTime <= 0 && !this.manualNext) { + this.processEndCommand(delta); + } + //If we cleared the command get the next + if(!this.currentCommand) { + this.getNextCommand(); + } + //If we have a new command set up the info for it + if(this.currentCommand && !this.manualNext && this.waitTime <= 0) { + this.processCommand(delta); + } + + if(this.secondTicker < 0) { + this.secondTicker = 1000; + } + } catch (error) { + console.log(error); + } + } + + loopHandleLerps(deltaTime) { + //Loop through the lerp targets, modify the needed objects. If a object is at its 1 time state remove it from the list. + let toRemove = []; + for(let i = 0; i < this.lerpTargets.length; ++i) { + let l = this.lerpTargets[i]; + l.curTime += deltaTime; + let pos = l.curTime / l.time; + if(pos >= 1) { + pos = 1; + toRemove.push(i); + } + var newValue = commonFunctions.lerp(l.initV, l.finalV, pos); + var split = l.type.split("."); + switch(split.length) { + case 1: + l.object[split[0]] = newValue; + break; + case 2: + l.object[split[0]][split[1]] = newValue; + break; + default: + continue; + } + } + for(let i = toRemove.length - 1; i > -1; --i) { + this.lerpTargets.splice(toRemove[i], 1); + } + } + + processCommand(delta) { + try { + let cur = this.currentCommand; + switch((cur.Command || "").toLowerCase()) { + case "scenetitle01": + this.waitTime = this.titleWaitTime * 1000; + this.text.titleText(true, cur.Text); + break; + case "divaeffect": + this.waitTime = Number(cur.Arg5) * 1000; + this.text.divaText(true, cur.Text); + break; + //FadeTo + case "fadeout": + this.waitTime = Number(cur.Arg6) * 1000; + this.layers["bg|whiteFade"].sprite.tint = commonFunctions.getColorFromName(cur.Arg1); + this.lerpTargets.push({type: 'alpha', object: this.layers["bg|whiteFade"].sprite, curTime: 0, time: this.waitTime, finalV: 1, initV: 0}); + break; + //FadeFrom + case "fadein": + this.waitTime = Number(cur.Arg6) * 1000; + this.layers["bg|whiteFade"].sprite.tint = commonFunctions.getColorFromName(cur.Arg1); + this.lerpTargets.push({type: 'alpha', object: this.layers["bg|whiteFade"].sprite, curTime: 0, time: this.waitTime, finalV: 0, initV: 1}); + break; + case "bg": + let bgInfo = this.utage.textureInfo[cur.Arg1]; + let container = this.layers[this.bgLayerName].container; + //clear the sprite for the bg currently in use. + for(var i = 0; i < container.children.length; ++i) { + let s = container.children[i]; + container.children[i].destroy(); + } + container.visible = true; + let sprite = new PIXI.Sprite(this.loader.resources[`bg|${cur.Arg1}`].texture); + sprite.scale.set(Number(bgInfo.Scale), Number(bgInfo.Scale)); + //center the bg + sprite.anchor.set(0.5, 0.5); + container.addChild(sprite); + break; + case "wait": + this.waitTime = Number(cur.Arg6) * 1000; + break; + case "waitinput": { + let time = Number(cur.Arg6) + if(time) { + this.waitTime = time * 1000; + } else { + this.manualNext = true; + } + break; + } + case "movecamera": { + let time = Number(cur.Arg4); + let scale = 1 + (1 - Number(cur.Arg3)); + var cont = this.layers["bg|mainparent"].container; + let x = this.center.x + -(Number(cur.Arg1)); + let y = this.center.y + -(Number(cur.Arg2)); + if(time) { + this.waitTime = time * 1000; + if(cont.scale.x !== scale) { + this.lerpTargets.push({type: 'scale.x', object: cont, curTime: 0, + time: this.waitTime, finalV: scale, initV: cont.x }); + } + if(cont.scale.y !== scale) { + this.lerpTargets.push({type: 'scale.y', object: cont, curTime: 0, + time: this.waitTime, finalV: scale, initV: cont.y }); + } + + if(cont.position.x !== x) { + this.lerpTargets.push({type: 'position.x', object: cont, curTime: 0, + time: this.waitTime, finalV: x, initV: cont.x }); + } + + if(cont.position.y !== y) { + this.lerpTargets.push({type: 'position.y', object: cont, curTime: 0, + time: this.waitTime, finalV: y, initV: cont.y }); + } + if(cur.Arg6 && cur.Arg6.toLowerCase() === "nowait") { + this.waitTime = 0; + } + } else { + cont.scale.set(scale, scale); + cont.position.set(x, y); + } + break; + } + default: + this.processCommandOther(delta); + break; + } + } catch(error) { + console.log(error); + } + } + + //This should mostly be handling things like text + processCommandOther(delta) { + if(this.currentCommand) + } + + processEndCommand(delta) { + let cur = this.currentCommand; + switch(cur.Command) { + case "SceneTitle01": + this.text.titleText(false, ''); + break; + case "DivaEffect": + this.text.divaText(false, ''); + break; + } + this.currentCommand = undefined; + } + + onMainClick() { + if(this.runEvent && this.manualNext) { + if (!this.hasMoreText) { + this.getNextCommand(); + } else { + + } + } } onEndFile() { @@ -88,20 +330,38 @@ class Player { } getNextCommand() { - let running = true; - do - { - var command = utage.currentPlayingFile.pop(); - if(!utage.currentPlayingFile || utage.currentPlayingFile.length === 0) { - this.onEndFile(); - running = false; - return; + let command = utage.currentPlayingFile.pop(); + if(!utage.currentPlayingFile || utage.currentPlayingFile.length === 0) { + this.onEndFile(); + return; + } + if(!command || command.comment || (!command.Command && !command.Arg1 && !command.Arg2 && !command.Arg3 && !command.Arg4 && !command.Arg5 && !command.Arg6)) { + this.getNextCommand(); + return; + } + this.currentCommand = command; + } + + resetAll() { + return new Promise((resolve, reject) => { + try { + this.pixi.app.ticker.remove(this.onPixiTick, this); + this.pixi.app.stage.children.forEach(function(child) { child.destroy(true, true, true); }); + Object.keys(PIXI.utils.TextureCache).forEach(function(texture) { + if(PIXI.utils.TextureCache[texture]) { + PIXI.utils.TextureCache[texture].destroy(true); + } + }); + this.loader.reset(); + this.currentCharacters = []; + this.currentCommand = undefined; + this.runEvent = false; + this.secondTicker = 1000; + this.text.resetAll(); + resolve(); + } catch (error) { + reject(error); } - if(!command || command.comment) { - continue; - } - //do things - continue; - } while (running); + }); } } \ No newline at end of file diff --git a/Js/TextFunctions.js b/Js/TextFunctions.js new file mode 100644 index 0000000..c061b88 --- /dev/null +++ b/Js/TextFunctions.js @@ -0,0 +1,63 @@ +'use strict'; + +class TextFunctions { + constructor() { + this.mainUi = undefined; + this.title = undefined; + this.diva = undefined; + this.dialogBox = undefined; + this.character = undefined; + this.dialog = undefined; + } + + findTextElements() { + this.mainUi = document.getElementById('main-ui-img'); + this.title = document.getElementById('title'); + this.diva = document.getElementById('diva'); + this.dialogBox = document.getElementById('dialog-box'); + this.character = document.getElementById('character'); + this.dialog = document.getElementById('dialog'); + } + + titleText(show, text) { + if(text != undefined) { + this.title.innerHTML = text; + } + this.title.style = show ? "opacity: 1;" : "opacity: 0;"; + } + + divaText(show, text) { + if(text != undefined) { + this.diva.innerHTML = text; + } + this.diva.style = show ? "opacity: 1;" : "opacity: 0;"; + } + + characterName(show, text) { + if(text != undefined) { + this.character.innerHTML = text; + } + this.mainUi.style = show ? "opacity: 1;" : "opacity: 0;"; + this.character.style = show ? "opacity: 1;" : "opacity: 0;"; + } + + dialog(show, text) { + if(text != undefined) { + this.dialog.innerHTML = text; + } + this.mainUi.style = show ? "opacity: 1;" : "opacity: 0;"; + this.dialogBox.style = show ? "opacity: 1;" : "opacity: 0;"; + } + + resetAll() { + this.title.innerHTML = ''; + this.diva.innerHTML = ''; + this.character.innerHTML = ''; + this.dialog.innerHTML = ''; + this.title.style = "opacity: 0;"; + this.diva.style = "opacity: 0;"; + this.mainUi.style = "opacity: 0;"; + this.character.style = "opacity: 0;"; + this.dialogBox.style = "opacity: 0;"; + } +} \ No newline at end of file diff --git a/Js/UtageParse.js b/Js/UtageParse.js index ae937d7..ff736d0 100644 --- a/Js/UtageParse.js +++ b/Js/UtageParse.js @@ -7,8 +7,8 @@ class UtageInfo { this.rootDirectory = `${rootUrl}XDUPlayer/`; this.availableMissions = {}; this.missionsList = []; - this.characterData = {}; - this.layerData = {}; + this.characterInfo = {}; + this.layerInfo = {}; this.localizeInfo = {}; this.paramInfo = {}; this.soundInfo = {}; @@ -100,10 +100,10 @@ class UtageInfo { if(read.FileName && !read.FileName.startsWith('file://')) { read.FileName = `${this.rootDirectory}XDUData/Character/${read.FileName}`; } - if(!this.characterData[lastCharName]) { - this.characterData[lastCharName] = {}; + if(!this.characterInfo[lastCharName]) { + this.characterInfo[lastCharName] = {}; } - this.characterData[lastCharName][read.Pattern || "none"] = read; + this.characterInfo[lastCharName][read.Pattern || "none"] = read; } } } @@ -120,7 +120,7 @@ class UtageInfo { } else { var read = commonFunctions.readLine(line, headers); if(read && read.LayerName) { - this.layerData[read.LayerName] = read; + this.layerInfo[read.LayerName] = read; } } } diff --git a/Player.html b/Player.html index 794756e..0557267 100644 --- a/Player.html +++ b/Player.html @@ -1,16 +1,54 @@ - - - - - - - + + + + + + + + + + + + -
- +
+

Loading Utage Data...

+

Loading Fonts...

-
+
+
+ +
+ +
+
+ +
Title Text
+
Diva Text
+
+
+ +
+
女教師
+
+ 「はい今日は前回の続きからはじめます。
 抑揚をつけることで、歌の表現は深まりますが――」 +
+
+
+
+
+ + test + test + +
\ No newline at end of file