今更ながらES6を勉強したので、Live2D WebGL版をES6にかき直してみた。
開発環境
Live2D WebGL SDK2.1のSimpleプロジェクト
今回使ったものは以下の通り。まだ勉強中なので色々と間違ってるかも。
それにしてもソースがすっきりして素晴らしいっ!
・const/let
・class構文
・Promise
・arrow関数
・Fetch API
HTML部分
simple.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Live2D Simple</title>
</meta>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=4.0">
</meta>
</head>
<body>
<canvas id="glcanvas" style="border:dashed 1px #CCC"></canvas>
<!-- Live2D Library -->
<script src="../../lib/live2d.min.js"></script>
<!-- User's Script -->
<script src="src/Simple.js"></script>
</body>
</html>
javascript部分
Simple.js
'use strict';
const CANVAS_SIZE = 512; // canvasサイズ
const CANVAS_ID = 'glcanvas'; // canvasID
// Live2D モデル設定
const MODEL_DEF = {
"type":"Live2D Model Setting",
"name":"haru",
"model":"assets/haru/haru_02.moc",
"textures":[
"assets/haru/haru_02.1024/texture_00.png",
"assets/haru/haru_02.1024/texture_01.png",
"assets/haru/haru_02.1024/texture_02.png",
]
};
window.onload = () => {
let Live2DModel = new Simple(CANVAS_SIZE, CANVAS_ID, MODEL_DEF);
};
class Simple{
/*
* コンストラクタ
* @param {number} canSize
* @param {string} canId
*/
constructor(canSize, canId, modelDef){
// Live2Dモデルのインスタンス
this.live2DModel = null;
// アニメーションを停止するためのID
this.requestID = null;
// モデルの初期化が完了したら true
this.initLive2DCompleted = false;
// WebGL Image型オブジェクトの配列
this.loadedImages = [];
// Live2D モデル設定
this.modelDef = modelDef;
// Live2Dの初期化
Live2D.init();
// canvasオブジェクトを取得
this.canvas = document.getElementById(canId);
this.canvas.width = this.canvas.height = canSize;
// コンテキストを失ったとき
this.canvas.addEventListener("webglcontextlost", (e) => {
console.error("context lost");
this.initLive2DCompleted = false;
// アニメーションを停止
let cancelAnimationFrame =
window.cancelAnimationFrame ||
window.mozCancelAnimationFrame;
cancelAnimationFrame(this.requestID);
e.preventDefault();
}, false);
// コンテキストが復元されたとき
this.canvas.addEventListener("webglcontextrestored", (e) => {
console.error("webglcontext restored");
this.initLoop(this.canvas);
}, false);
// Init and start Loop
this.initLoop(this.canvas);
}
/***
* WebGLコンテキストを取得・初期化。
* Live2Dの初期化、描画ループを開始。
* @param {canvas} canvas
*/
initLoop(canvas){
// WebGLのコンテキストを取得する
let para = {
premultipliedAlpha : true,
// alpha : false
};
this.gl = this.getWebGLContext(canvas, para);
if (!this.gl) {
console.error("Failed to create WebGL context.");
return;
}
// OpenGLのコンテキストをセット
Live2D.setGL(this.gl);
// mocファイルからLive2Dモデルのインスタンスを生成
const modelBuffer = fetch(this.modelDef.model).then(res => {
return res.arrayBuffer();
}).then(arrayBuffer => {
this.live2DModel = Live2DModelWebGL.loadModel(arrayBuffer);
});
// テクスチャの読み込み
let promises = [];
for(let i = 0; i < this.modelDef.textures.length; i++){
promises[i] = this.loadTextureImage(i);
}
// 全部テクスチャロードしたら次の処理へ
Promise.all(promises).then(() => {
this.tick();
});
}
/***
* テクスチャを読み込みPromiseを返します。
* @param {Number} i
* @returns {Promise}
*/
loadTextureImage(i) {
this.loadedImages[i] = new Image();
return new Promise((resolve, reject) => {
this.loadedImages[i].addEventListener('load', (e) => {
resolve(this.loadedImages[i]);
});
this.loadedImages[i].src = this.modelDef.textures[i];
});
}
/***
* ループ処理
*/
tick(){
this.draw(); // 1回分描画
let requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
this.requestID = requestAnimationFrame(this.tick.bind(this));
}
/***
* 描画処理
*/
draw(){
// 描画エリアをクリア
this.gl.clearColor( 0.0 , 0.0 , 0.0 , 0.0 );
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Live2D初期化
if(!this.live2DModel)
return; //ロードが完了していないので何もしないで返る
// ロード完了後に初回のみ初期化する
if(!this.initLive2DCompleted){
this.initLive2DCompleted = true;
// 画像からWebGLテクスチャを生成し、モデルに登録
for(let i = 0; i < this.loadedImages.length; i++){
//Image型オブジェクトからテクスチャを生成
let texName = this.createTexture(this.gl, this.loadedImages[i]);
this.live2DModel.setTexture(i, texName); //モデルにテクスチャをセット
}
// テクスチャの元画像の参照をクリア
this.loadedImages = null;
// 表示位置を指定するための行列を定義する
let s = 2.0 / this.live2DModel.getCanvasWidth(); //canvasの横幅を-1..1区間に収める
let matrix4x4 = [
s, 0, 0, 0,
0,-s, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
this.live2DModel.setMatrix(matrix4x4);
}
// キャラクターのパラメータを適当に更新
let t = UtSystem.getUserTimeMSec() * 0.001 * 2 * Math.PI; //1秒ごとに2π(1周期)増える
let cycle = 3.0; //パラメータが一周する時間(秒)
// PARAM_ANGLE_Xのパラメータが[cycle]秒ごとに-30から30まで変化する
this.live2DModel.setParamFloat("PARAM_ANGLE_X", 30 * Math.sin(t/cycle));
this.live2DModel.setParamFloat("PARAM_EYE_R_OPEN", 1 * Math.sin(t/cycle));
this.live2DModel.setParamFloat("PARAM_EYE_L_OPEN", 1 * Math.sin(t/cycle));
// Live2Dモデルを更新して描画
this.live2DModel.update(); // 現在のパラメータに合わせて頂点等を計算
this.live2DModel.draw(); // 描画
}
/***
* WebGLのコンテキストを取得する
* @param {canvas} canvas
*/
getWebGLContext(canvas){
let NAMES = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
let param = {
alpha : true,
premultipliedAlpha : true
};
for(let i = 0; i < NAMES.length; i++){
try{
let ctx = canvas.getContext(NAMES[i], param);
if(ctx) return ctx;
}
catch(e){}
}
return null;
}
/***
* Image型オブジェクトからテクスチャを生成
* @param {gl} gl
* @param {Image} image
* @returns {Texture}
*/
createTexture(gl, image){
let texture = gl.createTexture(); //テクスチャオブジェクトを作成する
if (!texture){
console.error("Failed to generate gl texture name.");
return -1;
}
if(this.live2DModel.isPremultipliedAlpha() == false){
// 乗算済アルファテクスチャ以外の場合
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
}
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); //imageを上下反転
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D , texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
};
参考URL
・お疲れさまXMLHttpRequest、こんにちはfetch
モーション再生版も修正
==== 2017/02/26追記 ====
ちなみに以下記事のモーション切り替え版も修正してみた。
モーションファイル読み込みもfetchさせたら上手くいきました。
Live2D WebGL版のモーション切替する
simple.js
'use strict';
const CANVAS_SIZE = 512; // canvasサイズ
const CANVAS_ID = 'glcanvas'; // canvasID
var MODEL_PATH = "assets/haru/";
var MODEL_DEFINE = {
"type":"Live2D Model Setting",
"name":"haru",
"model": MODEL_PATH + "haru_01.moc",
"textures":[
MODEL_PATH + "haru_01.1024/texture_00.png",
MODEL_PATH + "haru_01.1024/texture_01.png",
MODEL_PATH + "haru_01.1024/texture_02.png",
],
"motions":[
MODEL_PATH + "motions/idle_00.mtn",
MODEL_PATH + "motions/shake_00.mtn",
MODEL_PATH + "motions/tapBody_05.mtn",
],
};
window.onload = () => {
let Live2DModel = new Simple();
};
class Simple{
/*
* コンストラクタ
*/
constructor(){
// Live2Dモデルのインスタンス
this.live2DModel = null;
// アニメーションを停止するためのID
this.requestID = null;
// モデルの初期化が完了したら true
this.initLive2DCompleted = false;
// WebGL Image型オブジェクトの配列
this.loadedImages = [];
// モーション
this.motions = [];
// モーション管理マネジャー
this.motionMgr = null;
// モーション番号
this.motionnm = 0;
// モーションチェンジ
this.motionchange = false;
// Live2D モデル設定
this.modelDef = MODEL_DEFINE;
// Live2Dの初期化
Live2D.init();
// canvasオブジェクトを取得
this.canvas = document.getElementById(CANVAS_ID);
this.canvas.width = this.canvas.height = CANVAS_SIZE;
// コンテキストを失ったとき
this.canvas.addEventListener("webglcontextlost", (e) => {
console.error("context lost");
this.initLive2DCompleted = false;
// アニメーションを停止
let cancelAnimationFrame =
window.cancelAnimationFrame ||
window.mozCancelAnimationFrame;
cancelAnimationFrame(this.requestID);
e.preventDefault();
}, false);
// コンテキストが復元されたとき
this.canvas.addEventListener("webglcontextrestored", (e) => {
console.error("webglcontext restored");
this.initLoop(this.canvas);
}, false);
// Init and start Loop
this.initLoop(this.canvas);
}
/***
* WebGLコンテキストを取得・初期化。
* Live2Dの初期化、描画ループを開始。
* @param {canvas} canvas
*/
initLoop(canvas){
// WebGLのコンテキストを取得する
let para = {
premultipliedAlpha : true,
// alpha : false
};
this.gl = this.getWebGLContext(canvas, para);
if (!this.gl) {
console.error("Failed to create WebGL context.");
return;
}
// OpenGLのコンテキストをセット
Live2D.setGL(this.gl);
// mocファイルからLive2Dモデルのインスタンスを生成
const modelBuffer = fetch(this.modelDef.model).then(res => {
return res.arrayBuffer();
}).then(arrayBuffer => {
this.live2DModel = Live2DModelWebGL.loadModel(arrayBuffer);
});
// テクスチャの読み込み
let promises = [];
for(let i = 0; i < this.modelDef.textures.length; i++){
promises[i] = this.loadTextureImage(i);
}
// 全部テクスチャロードしたら次の処理へ
Promise.all(promises).then(() => {
this.loadMotionFile();
this.tick();
});
}
/***
* テクスチャを読み込みPromiseを返します。
* @param {Number} i
* @returns {Promise}
*/
loadTextureImage(i) {
this.loadedImages[i] = new Image();
return new Promise((resolve, reject) => {
this.loadedImages[i].addEventListener('load', (e) => {
resolve(this.loadedImages[i]);
});
this.loadedImages[i].src = this.modelDef.textures[i];
});
}
/***
* モーションを読み込みます
*/
loadMotionFile(){
// モーションのロード
for(var i = 0; i < this.modelDef.motions.length; i++){
fetch(this.modelDef.motions[i]).then(res => {
return res.arrayBuffer();
}).then(arrayBuffer => {
this.motions.push(Live2DMotion.loadMotion(arrayBuffer));
});
}
// モーションマネジャーのインスタンス化
this.motionMgr = new L2DMotionManager();
// マウスクリックイベント
this.canvas.addEventListener("click", e => {
this.motionchange = true;
if(this.motions.length - 1 > this.motionnm){
this.motionnm++;
}else{
this.motionnm = 0;
}
}, false);
}
/***
* ループ処理
*/
tick(){
this.draw(); // 1回分描画
let requestAnimationFrame =
window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
this.requestID = requestAnimationFrame(this.tick.bind(this));
}
/***
* 描画処理
*/
draw(){
// 描画エリアをクリア
this.gl.clearColor( 0.0 , 0.0 , 0.0 , 0.0 );
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// Live2D初期化
if(!this.live2DModel)
return; //ロードが完了していないので何もしないで返る
// ロード完了後に初回のみ初期化する
if(!this.initLive2DCompleted){
this.initLive2DCompleted = true;
// 画像からWebGLテクスチャを生成し、モデルに登録
for(let i = 0; i < this.loadedImages.length; i++){
//Image型オブジェクトからテクスチャを生成
let texName = this.createTexture(this.gl, this.loadedImages[i]);
this.live2DModel.setTexture(i, texName); //モデルにテクスチャをセット
}
// テクスチャの元画像の参照をクリア
this.loadedImages = null;
// 表示位置を指定するための行列を定義する
let s = 2.0 / this.live2DModel.getCanvasWidth(); //canvasの横幅を-1..1区間に収める
let matrix4x4 = [
s, 0, 0, 0,
0,-s, 0, 0,
0, 0, 1, 0,
-1, 1, 0, 1
];
this.live2DModel.setMatrix(matrix4x4);
}
// モーションが終了していたら再生する
if(this.motionMgr.isFinished() || this.motionchange == true ){
this.motionMgr.startMotion(this.motions[this.motionnm], 0);
this.motionchange = false;
console.info("motion:" + this.motionnm);
}
// モーション指定されていない場合は何も再生しない
if(this.motionnm != null){
// モーションパラメータの更新
this.motionMgr.updateParam(this.live2DModel);
}
// Live2Dモデルを更新して描画
this.live2DModel.update(); // 現在のパラメータに合わせて頂点等を計算
this.live2DModel.draw(); // 描画
}
/***
* WebGLのコンテキストを取得する
* @param {canvas} canvas
*/
getWebGLContext(canvas){
let NAMES = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
let param = {
alpha : true,
premultipliedAlpha : true
};
for(let i = 0; i < NAMES.length; i++){
try{
let ctx = canvas.getContext(NAMES[i], param);
if(ctx) return ctx;
}
catch(e){}
}
return null;
}
/***
* Image型オブジェクトからテクスチャを生成
* @param {gl} gl
* @param {Image} image
* @returns {Texture}
*/
createTexture(gl, image){
let texture = gl.createTexture(); //テクスチャオブジェクトを作成する
if (!texture){
console.error("Failed to generate gl texture name.");
return -1;
}
if(this.live2DModel.isPremultipliedAlpha() == false){
// 乗算済アルファテクスチャ以外の場合
gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
}
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1); //imageを上下反転
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D , texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
gl.generateMipmap(gl.TEXTURE_2D);
gl.bindTexture(gl.TEXTURE_2D, null);
return texture;
}
};