アニメーションとキャンバス

私の手はCanvasになりました。 私はずっと前に彼を見ましたが、彼はグラフのツールとして私をとても気に入っていました。 そして、HabréのCanvasウィークは関心を支持しました。

しかし、退屈なグラフィックスの代わりに、アニメーションの方向を掘り始めました。 毎回新しい方法でシーンを描画すると、現在のフレームに関するすべての情報がJSに保存されることがわかりました。 そして、ユーザーの要求に応じて、オブジェクトのアニメーション、状態、および選択を保存できる単純なアルゴリズムを作成することにしました。

キャンバス


ここではすべてがシンプルで標準的です。
<! DOCTYPE html >
< html >
< head >
< title > Animation test </ title >
< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
</ head >
< body >
< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >
< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >
< option value ="stop" > Stop </ option >
< option value ="jump" > Jump </ option >
< option value ="bum" > Bum </ option >
< option value ="dead" > Dead </ option >
</ select >
</ body >
</ html >

* This source code was highlighted with Source Code Highlighter .
<! DOCTYPE html >
< html >
< head >
< title > Animation test </ title >
< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
</ head >
< body >
< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >
< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >
< option value ="stop" > Stop </ option >
< option value ="jump" > Jump </ option >
< option value ="bum" > Bum </ option >
< option value ="dead" > Dead </ option >
</ select >
</ body >
</ html >

* This source code was highlighted with Source Code Highlighter .
<! DOCTYPE html >
< html >
< head >
< title > Animation test </ title >
< meta http-equiv ="Content-Type" content ="text/html; charset=UTF-8" >
</ head >
< body >
< canvas id ="cnv" width ="600" height ="200" > It's not working! </ canvas >
< select id ="animations" name ="animations" onchange ="changeAnimation(this.value)" >
< option value ="stop" > Stop </ option >
< option value ="jump" > Jump </ option >
< option value ="bum" > Bum </ option >
< option value ="dead" > Dead </ option >
</ select >
</ body >
</ html >

* This source code was highlighted with Source Code Highlighter .


選択がキャンバスの下に追加され、オブジェクトのアニメーションのリストが表示されます。 アルゴリズムには制限がありませんが、1つのオブジェクトがあります。

グローバル変数

では、JSに移りましょう。 ここでは、シーンオブジェクトと可能なアニメーションを格納するためにいくつかの変数が必要です。
//
function $(id) {
return document .getElementById(id);
}

var tx = $( 'cnv' ).getContext( '2d' ); //
var childs = {}; //
var animate = {}; //

* This source code was highlighted with Source Code Highlighter .
//
function $(id) {
return document .getElementById(id);
}

var tx = $( 'cnv' ).getContext( '2d' ); //
var childs = {}; //
var animate = {}; //

* This source code was highlighted with Source Code Highlighter .
//
function $(id) {
return document .getElementById(id);
}

var tx = $( 'cnv' ).getContext( '2d' ); //
var childs = {}; //
var animate = {}; //

* This source code was highlighted with Source Code Highlighter .



Childsは、すべてのシーンオブジェクトを基本的なパラメーターとともに保存します。 配列を調べて、1つずつ描画します。
アニメーションでは、アニメーションのオブジェクトを保存します。 ここでは別の方法で行うことができます。たとえば、アニメーションではアニメーションが必要なオブジェクトのみにアニメーションを保存し、その他の場合は子配列自体にレンダリングに関する情報を保存します。 私のバージョンでは、キャンバスに表示する場合、各オブジェクトには少なくとも1つのアニメーションが必要です。

配列を埋める

まず、オブジェクトを追加します。 私はパラメータを書きませんでした、なぜなら 簡潔にするために、これに進むことができ、名前はすべて直感的だと思います。

function initAnimation() {

//
childs[ 'ball' ] = {
'at' : 'jump' , //
'w' : 30, //
'h' : 30, //
'fw' : 30, //
'x' : 100, //
'y' : 100 //
}

//
animate[ 'ball' ] = {
'jump' : { //
'el' : null , // Image
'src' : 'images/ball.png' , //
'step' : 0, //
'speed' : 3, //
'curr' : 0, //
'steps' : 3, // , 0
'onend' : null //
},
'bum' : {
'el' : null ,
'src' : 'images/ball_m.png' ,
'step' : 0,
'speed' : 3,
'curr' : 0,
'steps' : 7,
'onend' : 'onBumEnd'
},
'stop' : {
'el' : null ,
'src' : 'images/ball_s.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
},
'dead' : {
'el' : null ,
'src' : 'images/ball_d.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
}
}

//
for ( var o in childs) {

//
for ( var a in animate[o]) {

//
var img = new Image();
img.src = animate[o][a].src;
//
animate[o][a].el = img;

}

}

}

* This source code was highlighted with Source Code Highlighter .
function initAnimation() {

//
childs[ 'ball' ] = {
'at' : 'jump' , //
'w' : 30, //
'h' : 30, //
'fw' : 30, //
'x' : 100, //
'y' : 100 //
}

//
animate[ 'ball' ] = {
'jump' : { //
'el' : null , // Image
'src' : 'images/ball.png' , //
'step' : 0, //
'speed' : 3, //
'curr' : 0, //
'steps' : 3, // , 0
'onend' : null //
},
'bum' : {
'el' : null ,
'src' : 'images/ball_m.png' ,
'step' : 0,
'speed' : 3,
'curr' : 0,
'steps' : 7,
'onend' : 'onBumEnd'
},
'stop' : {
'el' : null ,
'src' : 'images/ball_s.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
},
'dead' : {
'el' : null ,
'src' : 'images/ball_d.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
}
}

//
for ( var o in childs) {

//
for ( var a in animate[o]) {

//
var img = new Image();
img.src = animate[o][a].src;
//
animate[o][a].el = img;

}

}

}

* This source code was highlighted with Source Code Highlighter .
function initAnimation() {

//
childs[ 'ball' ] = {
'at' : 'jump' , //
'w' : 30, //
'h' : 30, //
'fw' : 30, //
'x' : 100, //
'y' : 100 //
}

//
animate[ 'ball' ] = {
'jump' : { //
'el' : null , // Image
'src' : 'images/ball.png' , //
'step' : 0, //
'speed' : 3, //
'curr' : 0, //
'steps' : 3, // , 0
'onend' : null //
},
'bum' : {
'el' : null ,
'src' : 'images/ball_m.png' ,
'step' : 0,
'speed' : 3,
'curr' : 0,
'steps' : 7,
'onend' : 'onBumEnd'
},
'stop' : {
'el' : null ,
'src' : 'images/ball_s.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
},
'dead' : {
'el' : null ,
'src' : 'images/ball_d.png' ,
'step' : 0,
'speed' : 10,
'curr' : 0,
'steps' : 0,
'onend' : null
}
}

//
for ( var o in childs) {

//
for ( var a in animate[o]) {

//
var img = new Image();
img.src = animate[o][a].src;
//
animate[o][a].el = img;

}

}

}

* This source code was highlighted with Source Code Highlighter .



少し説明します。 各アニメーションは、すべてのフレームを含むファイルです。 キャンバスには、現在のフレームに対応する画像の部分のみを表示します。 このために、ステップとステップ(フレーム数)を保存します。

画像は次のとおりです。
画像
これは、最長8フレームのBumアニメーションを担当する最大の画像です。

速度と通貨は、フレームの切り替え速度に影響します。 16msごとにキャンバスを再描画し、必要な速度でフレームが変更されるように、カリーを考慮し、速度に達した後、フレームを変更します。

アニメーションの最後にOnendを呼び出します。 ここで、アニメーションのタイプを爆発から変更して、遺物を表示したり、配列からオブジェクトを削除したりできます。 これには、もう少し高い機能があります。
function onBumEnd() {

//
childs[ 'ball' ].at = 'dead' ;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .
function onBumEnd() {

//
childs[ 'ball' ].at = 'dead' ;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .
function onBumEnd() {

//
childs[ 'ball' ].at = 'dead' ;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .



アニメーション

オブジェクトには4つのアニメーションがあります。


アニメーションを変更する機能

アニメーションを簡単に変更するために、Selectとその機能があります。
function changeAnimation(value) {

//
childs[ 'ball' ].at = value;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .
function changeAnimation(value) {

//
childs[ 'ball' ].at = value;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .
function changeAnimation(value) {

//
childs[ 'ball' ].at = value;
//
animate[ 'ball' ][childs[ 'ball' ].at].curr = 0;

}


* This source code was highlighted with Source Code Highlighter .


打ち上げ

これで、すべての画像と配列の準備が整い、アニメーションを開始します。
function startAnimation() {

//
setInterval( function () {

//
ctx.save();
ctx.fillStyle = '#FFFFFF' ;
ctx.fillRect(0, 0, 600, 200);
ctx.restore();

//
for ( var o in childs) {

//
if (animate[o]) {

//
var step = animate[o][childs[o].at].step;

//
ctx.drawImage(
animate[o][childs[o].at].el, // Image
Math.round(childs[o].fw * step), // , *
0, // , 0
childs[o].w, //
childs[o].h, //
childs[o].x, //
childs[o].y, //
childs[o].w, //
childs[o].h //
);

// speed,
if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {

//,
if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {

animate[o][childs[o].at].step = 0;

// , ,
if (animate[o][childs[o].at].onend)
window[animate[o][childs[o].at].onend]();

}
else animate[o][childs[o].at].step++;

//
animate[o][childs[o].at].curr = 0;

}

//
animate[o][childs[o].at].curr++;

}

}

}, 1000/16);

}


* This source code was highlighted with Source Code Highlighter .
function startAnimation() {

//
setInterval( function () {

//
ctx.save();
ctx.fillStyle = '#FFFFFF' ;
ctx.fillRect(0, 0, 600, 200);
ctx.restore();

//
for ( var o in childs) {

//
if (animate[o]) {

//
var step = animate[o][childs[o].at].step;

//
ctx.drawImage(
animate[o][childs[o].at].el, // Image
Math.round(childs[o].fw * step), // , *
0, // , 0
childs[o].w, //
childs[o].h, //
childs[o].x, //
childs[o].y, //
childs[o].w, //
childs[o].h //
);

// speed,
if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {

//,
if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {

animate[o][childs[o].at].step = 0;

// , ,
if (animate[o][childs[o].at].onend)
window[animate[o][childs[o].at].onend]();

}
else animate[o][childs[o].at].step++;

//
animate[o][childs[o].at].curr = 0;

}

//
animate[o][childs[o].at].curr++;

}

}

}, 1000/16);

}


* This source code was highlighted with Source Code Highlighter .
function startAnimation() {

//
setInterval( function () {

//
ctx.save();
ctx.fillStyle = '#FFFFFF' ;
ctx.fillRect(0, 0, 600, 200);
ctx.restore();

//
for ( var o in childs) {

//
if (animate[o]) {

//
var step = animate[o][childs[o].at].step;

//
ctx.drawImage(
animate[o][childs[o].at].el, // Image
Math.round(childs[o].fw * step), // , *
0, // , 0
childs[o].w, //
childs[o].h, //
childs[o].x, //
childs[o].y, //
childs[o].w, //
childs[o].h //
);

// speed,
if (animate[o][childs[o].at].curr >= animate[o][childs[o].at].speed) {

//,
if (animate[o][childs[o].at].step >= animate[o][childs[o].at].steps) {

animate[o][childs[o].at].step = 0;

// , ,
if (animate[o][childs[o].at].onend)
window[animate[o][childs[o].at].onend]();

}
else animate[o][childs[o].at].step++;

//
animate[o][childs[o].at].curr = 0;

}

//
animate[o][childs[o].at].curr++;

}

}

}, 1000/16);

}


* This source code was highlighted with Source Code Highlighter .



原則として、私はコメントから、何が来て、それがどのように機能するかは明らかだと思います。 その結果、シーンオブジェクトを保存し、ダイナミックにアニメーションを設定し、その終了に応答する機会を得ました。
アニメーション内のオブジェクトは頻繁に繰り返されるため、ライブラリの原則に従って、子をオブジェクトの配列として使用することは理にかなっています。
もちろん改善すべき点はありますが、現時点ではアニメーションのトピックに関する良い記事を見つけていないので、自分のバージョンを提案します。

コード全体

こちらで見れます
こちらからダウンロードできます。

Source: https://habr.com/ru/post/J119585/


All Articles