Large Display Size Middle Display Size Small Display Size
there is no canvas.

2017 年 12月 の日記



正社員のお父さんが一家を養う。
37歳くらいで独身の男性はとても珍しい。
昔はそんな感じでしたよね。
今は、40代の派遣社員の独身の男女がいっぱい。
あれもこれもクールにスマートに。
時代はだいぶ変わりましたね…。




2017/12/17(日)

fig.
▲必ず地面に着地

「サイドビューキャラ」について、地面の上に立たせるしくみの試作品を作りました。

今までは腰が必ず画面の中心になっていたので足の位置はかならずしも地面の位置ではありませんでしたが、今回は地面を用意し、キャラが地面の上に立つようにしてあります。

時間がなかったので、人間のキャラは用意できませんでしたが、プログラムとしては出来上がっています。

これにより、「サイドビューキャラ」は、地面を蹴ってジャンプするなど、場面の表現が可能になります。

左の画像リンクをクリックすると文書へ移動します。



今回の試作プログラムを構成しているファイルは data_test.js, javascript.html, class_Anmsys.js の3つです。

ファイル一式をダウンロード(12KB)

 

data_test.js


テスト用のキャラデータです。

このような画像を連ねて表示するオブジェクト(サイドビューキャラ クラス)を、この js ファイルで記述しています。

キャラクターなどゲームのプログラムの中でたくさんある場合、それを1つの配列だけで管理するよりも、別の配列でも管理する…というやり方を使うといろいろ便利だと思います。たとえば…

a, b, c, d, e のキャラクターがあるとして、1つの配列に

array = [ a, b, c, d, e ]

のように入っているわけです。しかしこのほかに、

enemy = [ a, b, e ]; friends = [ c, d ];

とか、別の配列があっても良いわけです。各配列は a, b, c .. への参照なので、メモリの無駄はありません。上記の例では、画面上の動作は array を使って順次処理する、全滅アイテムを拾ったらenemyを使って順次撃破の処理をする、という感じで使います。array 単一で同じことをする場合、a, b, c .. それぞれについて if 文を使って敵味方の判定をしなくてはなりません。それでもプログラムは作れますが、処理時間やプログラムをシンプルにするためにはあらかじめ別配列に分別してしまうのが良いんじゃないかと思います。

今回も、体の各パーツについて、「親子関係」という配列にしたり、「描画の前後関係」という配列にしたりと、複数の配列で管理しています。


このファイル: cgi-bin/prj/20170904-javascript/サイドビューキャラ/20171216-アニメ対応 - 着地対応(再)/data_test.js

var o = new Object();

//各インスタンス
o.actionbase
	
= new SVC( "アクションベース" );

o.svc1
			
= new SVC( "シート1" );

o.svc2
			
= new SVC( "シート2" );

o.svc3
			
= new SVC( "シート3" );


//親子関係
o.actionbase.children.push( o.svc1 );
o.svc1.children.push( o.svc2 );
o.svc2.children.push( o.svc3 );

//描画の前後関係
o.zindex = [
o.actionbase,
o.svc1,
o.svc2,
o.svc3,
];


//各インスタンスの詳細設定

with( o.actionbase ) {
varname
			
=
	
"actionbase";

pathImage
		
=
	
( function() {/*

beginPath();
moveTo( 0, 0 );
lineTo( 32, 0 );
lineTo( 32, 16 );
lineTo( -32, 16 );
lineTo( -32, 0 );
closePath();
fillStyle = "red";
		
fill();

strokeStyle = "white";
		
stroke();

*/} ).toString().match( HereDocument )[ 1 ];
x
				
=
	
window.innerWidth;

y
				
=
	
0;

imageOffsetX
	
=
	
-4;

imageOffsetY
	
=
	
-4;

theta
			
=
	
0;

thetaMin
		
=
	
-3.14;

thetaMax
		
=
	
3.14;

visibility
		
=
	
false;

}

with( o.svc1 ) {
varname
			
=
	
"svc1";

pictImage
		
=
	
new Image();

pictImage.src
	
=
	
"test/Sheet1.png";

x
				
=
	
0;

y
				
=
	
0;

imageOffsetX
	
=
	
-21;

imageOffsetY
	
=
	
-21;

theta
			
=
	
0;

thetaMin
		
=
	
-3.14;

thetaMax
		
=
	
3.14;

}

with( o.svc2 ) {
varname
			
=
	
"svc2";

pictImage
		
=
	
new Image();

pictImage.src
	
=
	
"test/Sheet2.png";

x
				
=
	
176;

y
				
=
	
0;

imageOffsetX
	
=
	
-21;

imageOffsetY
	
=
	
-21;

theta
			
=
	
0;

thetaMin
		
=
	
-3.14;

thetaMax
		
=
	
3.14;

}

with( o.svc3 ) {
varname
			
=
	
"svc3";

pictImage
		
=
	
new Image();

pictImage.src
	
=
	
"test/Sheet3.png";

x
				
=
	
176;

y
				
=
	
0;

imageOffsetX
	
=
	
-21;

imageOffsetY
	
=
	
-21;

theta
			
=
	
0;

thetaMin
		
=
	
-3.14;

thetaMax
		
=
	
3.14;

usefulX
			
=
	
170;

usefulY
			
=
	
0;

}


//ポーズ

o.poses = [
{
name
	
:
	
"ポーズ0",

data
	
:
	
[

//オブジェクト、そのメンバ、その値 の順に並ぶ
//その集合が1つのポーズを形作る
[
	
o.svc1,
	
"theta",
	
0
	
],

[
	
o.svc2,
	
"theta",
	
0
	
],

[
	
o.svc3,
	
"theta",
	
0
	
],

],
//地面への着地や、地面からの飛翔に関する値
actionbaseMeasureTarget
			
:
	
o.svc1,

actionbaseMeasureTargetHeight
	
:
	
0,

},

{
name
	
:
	
"ポーズ1",

data
	
:
	
[

[
	
o.svc1,
	
"theta",
	
3.14 / 4
	
],

[
	
o.svc2,
	
"theta",
	
3.14 / 4
	
],

[
	
o.svc3,
	
"theta",
	
3.14 / 4
	
],

],
actionbaseMeasureTarget
			
:
	
o.svc3,

actionbaseMeasureTargetHeight
	
:
	
0,

},

{
name
	
:
	
"ポーズ2",

data
	
:
	
[

[
	
o.svc1,
	
"theta",
	
0
			
],

[
	
o.svc2,
	
"theta",
	
3.14 / 2
	
],

[
	
o.svc3,
	
"theta",
	
-3.14 / 2
	
],

],
actionbaseMeasureTarget
			
:
	
o.svc3,

actionbaseMeasureTargetHeight
	
:
	
0,

},

{
name
	
:
	
"ポーズ3",

data
	
:
	
[

[
	
o.svc1,
	
"theta",
	
3.14 / 10
	
],

[
	
o.svc2,
	
"theta",
	
3.14 / 10
	
],

[
	
o.svc3,
	
"theta",
	
3.14 / 10
	
],

],
actionbaseMeasureTarget
			
:
	
o.svc3,

actionbaseMeasureTargetHeight
	
:
	
0,

},

];


masters[ "test" ] = o;


javascript.html


プログラム本体です。

すこしトリッキー(変な考え方)なしくみをいくつか導入しているので、説明しないと理解しづらいと思います。トリッキーな個所を挙げると…

  1. 1つのキャラは いくつかの「サイドビューキャラ」オブジェクト(SVCクラス)で構成されていますが、別途「アクションベース」と名付けた「サイドビューキャラ」を追加しています。
    ネーミングはガンプラの「アクションベース」です。似たような用途なので…。地面からキャラへの高さを管理するのに用意しました。ガンプラのほうもプラモに高さを与えるものです。
     
  2. キャラを地面に着地させるために、決して変ではないのですが、見た目が変な計算をしています。関数 doPose() の後半と、関数 doAct() の後半です。オブジェクトの状態のバックアップを取るとか、なぜそんなことが必要なのか…という感じです。もっといい方法があるかもしれませんが、バックアップを取ってからオブジェクトを一時 別の状態にして必要な値を得たらバックアップを復帰する…という方法なのですが、苦労している中で編み出した1つの方法です。
     
  3. 同じく、着地させるために、SVC(サイドビューキャラ)クラスに measure() というメソッドを作りましたが、これも説明しないと分からないものだと思います。各パーツは親子関係になっていてその座標系は、canvas.save() や canvas.translate() を駆使したローカル座標の「入れ子」になっているので、実際の画面上の座標は簡単にはわかりません。回転も入っているので sin, cos など三角関数を使った計算も必要なります。しかし、地面からどれだけキャラが離れているのかを知るためには、この「実際の画面上の座標」を知る必要がありました。
    ところで、なじみのない人もいると思いますが、canvas は「画面の座標だけ」を使って描くほかに、「ローカル座標」というしくみを使って描くこともできます。ローカル座標を使うと複雑な座標計算がシンプルになり、とても便利です。( canvas の仕様としては「ローカル座標」という言葉は使われていませんが、まぁローカル座標だと思います。save(), restore(), translate() などを使ってローカル座標を実現できます)
     
    ここで、たくさん書いても文字ばかりで読んでもらえない気もするので、プログラム中に書いたメモを載せておきます。ちょっとユーモラスに描けました☆



    ローカル座標の話ですが、たとえば、通常、

    fillRect( 親のx座標+子のx座標, 親のy座標+子のy座標, w, h );
     
    と書くものは、ローカル座標を使うと、

    canvas.save();
    canvas.translate( 親のx座標, 親のy座標 );
    canvas.fillRect( 子のx座標, 子のy座標, w, h );
    canvas.restore();
     
    と書きます。もし、fillRect() のような描画が大量にあって座標計算も入り混じって複雑になっている場合は、親の座標を加算する通常の方法よりもローカル座標を使って書いたほうが頭の混乱がなくなります。また、canvas.rotate() を使えば、子の座標だけ回転させた状態で子を描画できる(方眼紙を目の前で傾けるような感覚で描画できる)ので、便利です。

以上、トリッキーがいくつかある、というお話でした。

※関数は色分けされますが、クラスのメソッドは色分けされません。

このファイル: cgi-bin/prj/20170904-javascript/サイドビューキャラ/20171216-アニメ対応 - 着地対応(再)/javascript.html

<html lang="ja">
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>サイドビューキャラ試作</title>
<script src="class_Anmsys.js"></script>
<script>
console.log( "=============== script ==============" );
/*
『名称未設定』

使用手順:
Hello World!:
言葉遣い:
言葉
	
語源
		
意味

EL
		
ELement
		
HTML要素


*/


//utilities.----------------------
var HereDocument = /\/\*\s*([^]*?)\s*\*\//;
//usage:
	
var s = ( function() {/* multi line */} ).toString().match( HereDocument )[ 1 ];

function $( id ) { return document.getElementById( id ); }

var masters = new Object();
/*
クラスとインスタンスのようなしくみを作ろうとしているが
しくみは現在機能していない。
*/

function onloadx() {
//canvas用意
canvasEL
	
= $( "canvasELID" );

canvas
		
= canvasEL.getContext( "2d" );

// --- crisp edge
canvasEL.style.imageRendering = "pixelated";
canvasEL.style.imageRendering = "optimizeSpeed";

//地面もサイドビューキャラの一種として作成
with( land
			
= new SVC( "地面" ) ) {

varname
			
= "actionbase";

pathImage
		
= ( function() {/*

beginPath();
moveTo( 0, 0 );
lineTo( 500, 0 );
lineTo( 500, 4 );
lineTo( -500, 4 );
lineTo( -500, 0 );
closePath();
fillStyle = "black";
		
fill();

strokeStyle = "white";
		
stroke();

*/} ).toString().match( HereDocument )[ 1 ];
x
				
= window.innerWidth;

y
				
= window.innerHeight;

imageOffsetX
	
= 0;

imageOffsetY
	
= 0;

theta
			
= 0;

thetaMin
		
= -3.14;

thetaMax
		
= 3.14;

}

sprite = masters[ "test" ];
		
//spriteは1つのキャラ
doPose( 1 );
					
//ポーズ

//ウィンドウリサイズイベントに対応
window.onresize = onresizex;
window.onresize( null );

//テスト実行
doTest();
}

function onresizex( e ) {
//ウィンドウがリサイズされたら
console.log( "resized." );

canvasEL.style.width
	
= window.innerWidth;

canvasEL.style.height
	
= window.innerHeight - 24;

canvasEL.width
			
= window.innerWidth * 2;

canvasEL.height
		
= ( window.innerHeight - 24 ) * 2;


sprite.actionbase.x = window.innerWidth;
land.x = window.innerWidth;

draw();
}

function draw() {
canvas.clearRect( 0, 0, canvasEL.width, canvasEL.height );

land.draw( land );
	
//地面を描く

//キャラを描く
for( var i = 0; i < sprite.zindex.length; i++ ) {
var target = sprite.zindex[ i ];
sprite.actionbase.draw( target );
}
}

function doPose( posenum, doDraw ) {
//瞬時にポーズ変更
//usage:
	
doPose( 1 );
		
//値だけ変えて画面更新しない

//usage:
	
doPose( 1, true );
	
//画面更新も行う


pose = sprite.poses[ posenum ];
console.log( pose.name );

for( var i = 0; i < pose.data.length; i++ ) {

var data
	
= pose.data[ i ];

var object
	
= data[ 0 ];

var member
	
= data[ 1 ];

var value
	
= modifyType( data[ 2 ] );


object[ member ] = value;
}

//着地対応 ※着地関係(着地補正値)について

var base
				
= sprite.actionbase;

var measureTarget
		
= pose.actionbaseMeasureTarget;

var measureTargetHeight = pose.actionbaseMeasureTargetHeight;

//地面の画面上の高さ
var d1 = land.measure( land );

//ついでに、アクションベースは地面の高さに合わせる
base.y = d1.y;

//そのポーズの基準パーツの画面上の高さ
var d2 = base.measure( measureTarget );

var from
		
= d1.y - d2.y;
			
//現在の地面からの高さ
var next
		
= measureTargetHeight;
	
//目的の高さ
var diff
		
= next - from;
			
//その差
base.childrenY
	
= diff;
					
//差を埋める
/*
着地補正値childrenYの値により、キャラは地面に着地する
*/


if( doDraw ) draw( canvas );
}

function doAct( posenum ) {
//アニメでポーズ変更

var pose = sprite.poses[ posenum ];

for( var i = 0; i < pose.data.length; i++ ) {

var data
	
= pose.data[ i ];

var object
	
= data[ 0 ];

var memb
	
= data[ 1 ];

var value
	
= modifyType( data[ 2 ] );


switch( memb ) {
case "theta":
case "y":
//アニメ1件追加
anmsys.append_value( object, memb, object[ memb ], value, 700 );
break;
default:
object[ memb ] = value;
}
}

//着地対応 ※着地関係(着地補正値)について(も、アニメする)

var base
	
= sprite.actionbase;

var y1
		
= base.childrenY;
	
//現在の着地補正値 を得る

//アニメ後の着地補正値 を得る
base.backup();
					
//各現在値を退避
doPose( posenum );
				
//一時的にアニメ後にする
var y2
		
= base.childrenY;
	
//アニメ後の補正値を得る
base.backup_restore();
			
//各現在値を復帰

//アニメ1件追加
anmsys.append_value( base, "childrenY", y1, y2, 700 );
	

/*
着地補正値childrenYの値により、キャラは地面に着地する
*/

}

//---

var timerID2;

function doTest() {

ms
		
= 100;

anmsys
	
= new Anmsys( ms );


run1 = function() {
anmsys.run();

var len = sprite.poses.length;
if( anmsys.isUpdated )
draw( canvas );
else if( ! timerID2 )
if( 0 )
	
//またずに次のポーズへ
doact( Math.floor( Math.random() * len ) );
else
	
//少し待って次のポーズへ
timerID2 = setTimeout( "timerID2 = null; doAct( Math.floor( Math.random() * " + len + " ) )", 400 );
};

run2 = function() {
anmsys.run();

if( anmsys.isUpdated )
draw( canvas );
};

var len = sprite.poses.length;
switch( 3 ) {

case 1:
	
//ランダムポーズ
timerID = setInterval( run1, ms );
break;

case 2:
	
//キー入力
onkeydown = function( e ) {
if( e.which >= 65 && e.which < 65 + len ) {
	
//a - z
var num = e.which - 65;
doPose( num, true );
} else {
console.log( e.which );
				
}

};
break;

case 3:
	
//キー入力+アニメ
onkeydown = function( e ) {
if( e.which >= 65 && e.which < 65 + len ) {
	
//a - z
var num = e.which - 65;
doAct( num );
} else {
console.log( e.which );
				
}

};
timerID = setInterval( run2, ms );
break;
default:
}
}

//---SVCクラス

function SVC( title ) {
/*
サイドビューキャラ クラス
*/
//check.
if( ! title ) title = "名称未設定";

this.title
			
= title;

this.varname
		
= "noname";


this.x
				
= 0;
			
//親の原点からの相対座標
this.y
				
= 0;


this.imageOffsetX
	
= 0;
			
//画像ソースを描画する位置の補正値
this.imageOffsetY
	
= 0;


this.theta
			
= 0;
			
//回転
this.thetaMin
		
= 0;

this.thetaMax
		
= Math.floor( Math.PI * 2 * 1000 ) / 1000;


this.pictImage
		
= null;
			
//画像
this.pathImage
		
= null;


this.children
		
= new Array();
	
//子

//すべての子の位置を抜本的に補正する(actionbaseの高さで使用)
this.childrenX
		
= 0;

this.childrenY
		
= 0;


this.usefulX
		
= 0;
			
//便利なXY情報。
this.usefulY
		
= 0;

//(物体が地面と接触する位置だったり、手の平の中心だったり)

this.visibility
		
= true;

}

SVC.prototype.draw = function( target ) {

/*
メインのdraw()から呼ばれている。
SVCクラスのこのdraw()は再帰呼び出しを行うことで親子関係を走査し、targetを見つけたら描画して終了する。
その際、各親の座標系(translate(),rotate())を経由するので、正しい位置と回転でtargetの描画を行える。
たとえば、肩から先の「手の平」の描画は、肩の回転、腕の回転を踏まえてから描画される。
*/
//(再帰)


var drewFLG = false;

//親の座標系を保存
canvas.save();

//座標系(いわゆるローカル座標系)作成
canvas.translate( this.x, this.y );
canvas.rotate( this.theta );

if( this == target && this.visibility ) {
var drawX = this.imageOffsetX;
var drawY = this.imageOffsetY;
//画像ソース描画(画像)
if( this.pictImage ) {
canvas.drawImage( this.pictImage, drawX, drawY );
}
//画像ソース描画(パス)
if( this.pathImage ) {
canvas.save();
canvas.translate( drawX, drawY );
with( canvas ) {
eval( this.pathImage );
}
canvas.restore();
}

//ガイド
if( 0 ) {
//回転の中心 しるし
canvas.beginPath();
canvas.arc(0, 0, 4, -3.14 / 4, 3.14 / 4 * 7, false);

canvas.lineTo(16, -16);
canvas.lineTo(64, -16);

canvas.stroke();
canvas.fillStyle = "orange";
canvas.font = "16px 'MS ゴシック'";
canvas.fillText(target.title, 16, -19);
}

drewFLG = true;

} else {
canvas.save();
canvas.translate( this.childrenX, -this.childrenY );
//親子関係の子たちを走査する
for( var i = 0; i < this.children.length; i++ ) {
var child = this.children[ i ];
drewFLG = child.draw( target );
if( drewFLG ) break;
}
canvas.restore();
}


//親の座標系へ戻す
canvas.restore();
return drewFLG;
};

SVC.prototype.measure = function( target, parentTheta, parentX, parentY ) {
//画面上の実際の座標(絶対座標)を返す
//(再帰)

//check.
if( parentTheta == null ) {
parentTheta
	
= 0;

parentX
	
= 0;

parentY
	
= 0;

}
var res = kaiten( this.x, this.y, parentTheta );

var globalX
	
= parentX + res.x;

var globalY
	
= parentY + res.y;

var globalTheta
	
= parentTheta + this.theta;


if( this == target && this.visibility ) {
var res = kaiten( this.usefulX, this.usefulY, globalTheta );
return { x : globalX + res.x, y : globalY + res.y, theta : globalTheta };

} else {
//親子関係の子たちを走査する
for( var i = 0; i < this.children.length; i++ ) {
var child
	
= this.children[ i ];

var d
		
= child.measure( target, globalTheta, globalX, globalY );

//check.
if( d != null ) return d;
}
}//if

return null;
}

SVC.prototype.backup = function() {
//メンバを退避する

this.backupBuf = new Object();
for( var name in this ) {
var v = this[ name ];
switch( name ) {
case "x":
case "y":
case "theta":
this.backupBuf[ name ] = v;
break;
}
}
};

SVC.prototype.backup_restore = function() {
//メンバを復帰する

//check.
if( this.backupBuf == null ) {
alert( "backup_restore(): NG." );
return;
}

for( var name in this.backupBuf ) {
this[ name ] = this.backupBuf[ name ];
}
}

//---common.

function kaiten( x, y, theta2 ) {
//数学回転

var theta1
	
= Math.atan2( y, x );

var hankei
	
= Math.sqrt( x * x + y * y );

x
			
= Math.cos( theta1 + theta2 ) * hankei;

y
			
= Math.sin( theta1 + theta2 ) * hankei;


return { x : x, y : y };
}

function modifyType( value ) {
//数値なのに文字列になっている場合は数値にする(適切な型に修正)

res = Number( value );
//check.
if( res.toString() == "NaN" ) {
if( value == "true" || value == "false" ) {
res = value == "true";
	

} else {
res = value;
}
}
return res;
}

function preload( arg ) {
/*
画像プリロードツール
使用法:
1
この関数をページのSCRIPTタグ内に貼り付ける。
2
下記//ここから、//ここまで
の部分にプリロードしたい画像のURLを
相対アドレスで記入する。
3
BODYタグのonload属性を下記のように変更する
変更前:
<body onload="test();test2();">
変更後:
<body onload="preload( 'test();test2();' );">
注:
preload(), _preloaderという名前の関数とオブジェクトを作成している。
名前の競合に注意。

以上

*/
if( typeof( arg ) == "string" ) {
console.log( "preloader: page onload." );

_preloader = new Object();
_preloader.callback = arg;

_preloader.files = [
//ここから
"test/Sheet1.png",
"test/Sheet2.png",
"test/Sheet3.png",
//ここまで
];

//ローディング中..表示
_preloader.messageEL = document.createElement( "div" );
_preloader.messageEL.innerHTML = "Just a moment...";
document.body.appendChild( _preloader.messageEL );
with( _preloader.messageEL.style ) {
position
	
=
	
"absolute";

left
		
=
	
window.innerWidth / 2 - _preloader.messageEL.getBoundingClientRect().width / 2;

top
			
=
	
window.innerHeight / 2;

}

_preloader.buff = new Array();
for( var i = 0; i < _preloader.files.length; i++ ) {
var image = new Image();
image.src = _preloader.files[ i ];
image.onload = preload;
_preloader.buff.push( image );
}
} else {
//check.
if( _preloader.allOK ) return;
console.log( "preloader: image onload." );
_preloader.allOK = true;
for( var i = 0; i < _preloader.files.length; i++ ) {
var image = _preloader.buff[ i ];
if( ! image.complete ) {
_preloader.allOK = false;
console.log( image.src );
break;
}
}
if( _preloader.allOK ) {
document.body.removeChild( _preloader.messageEL );
console.log( "preloader: callback." );
eval( _preloader.callback );
}
}
}


</script>
<script src="data_test.js"></script>
</head>
<body id="bodyID" onload="preload( 'onloadx();' );" style="
background-color
	
:
	
lightgray;

">
a,b,c,d キーで各ポーズへアニメします。いずれも青い部品が地面に着地。
<canvas id="canvasELID" width="640" height="640" style="
position
	
:
	
absolute;

left
		
:
	
0px;

top
			
:
	
24px;

background-color
	
:
	
white;

">
there is no canvas.
</canvas>
</body>
</html>


class_Anmsys.js


アニメのシステムは前回からほとんどいじっていません。

このファイル: cgi-bin/prj/20170904-javascript/サイドビューキャラ/20171216-アニメ対応 - 着地対応(再)/class_Anmsys.js

//---class Anmsys

function Anmsys( timerMS ) {
/*

アニメーションシステム クラス

usage:
(注:以下は未検証の説明です)
anmsys = new Anmsys();
anmsys.append_value( $( "test" ).style, "left", 0, 300, 700, "px" );
	
//アニメ1件追加(スタイル数値)
//”あるHTML要素のスタイルのleftプロパティについて、値0pxから値300pxへ、0.7秒かけてアニメーションする”
anmsys.append_value( object, "value", 1, 0, 1000 );
						
//アニメ1件追加
//”objectのvalueメンバ変数について、値1から値0へ、1秒かけてアニメーションする”
run = function() {
anmsys.run();
						
//アニメ全件実行
if( anmsys.isUpdated ) {
			

console.log( object.value );
}
};
timerID = setInterval( run, 50 );

*/

this.timerMS
	
= timerMS;

this.list
		
= new Array();

this.isUpdated
	
= false;

}

Anmsys.prototype.run = function() {
this.isUpdated = false;

for( var i = this.list.length - 1; i >= 0; i-- ) {
var anm = this.list[ i ];
if( ! anm.add() ) {
this.list.splice( i, 1 );
}
anm.isUpdated
	
= true;

this.isUpdated
	
= true;

}
}

Anmsys.prototype.append_value = function( object, membname, startvalue, endvalue, limitMS, unit ) {
var anm
			
= new Anm( object, membname );

anm.value
		
= startvalue;

anm.startvalue
	
= startvalue;

anm.endvalue
	
= endvalue;

anm.addvalue
	
= ( endvalue - startvalue ) / ( limitMS / this.timerMS );

anm.unit
		
= unit ? unit : 0;

anm.add
			
= anm.add_value;


this.list.push( anm );
}

function Anm( object, membname ) {
this.object
	
= object;

this.membname
	
= membname;

this.isUpdated
	
= false;

}

Anm.prototype.add_value = function() {
var thisIsNotEnd = true;

this.value += this.addvalue;
//check.
if(
	
this.addvalue >= 0 && this.value >= this.endvalue

||
	
this.addvalue < 0 && this.value <= this.endvalue ) {

this.value
		
= this.endvalue;

thisIsNotEnd
	
= false;

}

this.object[ this.membname ]
	
= this.value + this.unit;

this.isUpdated
					
= true;


return thisIsNotEnd;
}

以上です。

平日は趣味のPC起動を禁止して平和になったけど、金土の夜は 夜更かしするの何とかしないとな…(~~;


2017/12/10(日)

今月の右上画像は…、「左上」になってますね。

これは以前からこのページで披露している「サイドビューキャラ(JavaScriptプログラム)」をアニメ化したものです。


ポーズとポーズのあいだがアニメで埋められています。

身体のそれぞれのパーツの回転角度を「ポーズ1の角度」から「ポーズ2の角度」へ、setInterval() 関数を使って少しずつ回転させる…、というような方法でアニメしています。

身体のそれぞれのパーツは、腰を一番上の「親」として、ツリー状に連なっています。

      腰
      │ 
      ├─スカートパーツ 中央
      ├─スカートパーツ 右側
      ├─スカートパーツ 左側
      ├─スカートパーツ 裏地
      ├─右もも
      │ └─右すね
      │   └─右足
      ├─左もも
      │ └─左すね
      │   └─左足
      └─胸
        ├─右上腕
        │ └─右腕
        │   └─右手
        ├─左上腕
        │ └─左腕
        │   └─左手
        │     └─杖
        └─頭
                            

それぞれのパーツが親パーツとの接続点(関節)を 0, 0 の原点(中心)として回転するので、体全体がうまいぐあいにポーズを取ってくれます。

ポーズさえ作ってしまえば、ポーズからポーズへのアニメーションは自動で補完されるので、人間が作成する手間が省けて楽です。


All Rights Reserved (C) 2017 d_kawakawa