Difference between revisions of "Tutorial:Animation (日本語)"

(Wok in progress. (現在完了しているところまで投稿 .. Line of 118 / 219))
 
m (Translate is done.)
Line 117: Line 117:
 
スプライトシートは幅 16 ピクセル、高さ 18 ピクセルの寸法によるスプライトから構成されています。 1 秒間にすべての画像を再生するよう意図しています。
 
スプライトシートは幅 16 ピクセル、高さ 18 ピクセルの寸法によるスプライトから構成されています。 1 秒間にすべての画像を再生するよう意図しています。
  
In love.update we simply add dt (delta time aka the time since the last frame) to our current time. We now count upwards continuously!
+
ただ、 <code>love.update</code> で現在時刻に [[dt (日本語)|dt]] (最後のフレームからの経過時間として知られているデルタ・タイム) を加算します。これで以降は絶えず計算が行われます!
  
But we will use the current time to determine which frame should be shown. As such we will want it to be between 0 and the value of "duration". The if simply checks if "currentTime" is more than "duration" in which case we subtract "duration". You could just set "currentTime" to 0 instead of subtracting "duration". However this will result in fractions of a second being lost every time the animation finishes and therefore slow down the animation playtime ever so slightly. Which can easily be avoided and should be avoided!
+
しかし、どのフレームを表示するか決定するために現在時間を使用します。それ自体は 0 "duration" (継続時間) の値の間に発生することを望むでしょう。 "currentTime" が  "duration" 以上かを確認して、そうならば  "duration" を減算します。単に  "duration" を減算するのではなく  "currentTime" 0 に設定することもできます。しかしながら、これでは秒の小数部がアニメーションの終了時に毎回失われてしまい、アニメーションの再生時間が少し崩れてしまいます。それは容易に避けることができますし、避けるべきです!
  
Now for the really interesting part!
+
さて、次は本当に興味深い部分ですよ!
  
 
== アニメーションの描画 ==  
 
== アニメーションの描画 ==  
  
How do we draw this?
+
これをどのように描画しますか?
  
Well. We have the duration and current time. With this info we can calculate a percentage! How much of the animation has passed so far?
+
よろしい。継続時間と現在時間があります。この情報で割合を計算することができます! これまでのアニメーションの経過時間はどのくらいですか?
  
If you've followed this tutorial correctly so far ''"currentTime / duration"'' will provide you with a number between 0 and 1. Which represents the percentage. 0.25 means 25% of the animation has passed.
+
これまで読者が適切に本チュートリアルを理解しているならば ''"currentTime / duration"'' では 0 1 の間にある数値を用意します。それは割合を表します。 0.25 はアニメーションが 25% 再生経過していることを意味します。
  
Considering this we can search for the correct image to use! Since we already have a number between 0 and 1 we can simply multiply this percentage with our total amount of images and get a number between 0 and 6!
+
このことを考慮すれば使用する画像を正確に探し出すことができます! 既に 0 1 の間にある数値はありますので、この割合を画像の総枚数と掛けることで 0 6 の間の数値を得ることができます!
  
 
''currentTime / duration * #quads''
 
''currentTime / duration * #quads''
  
However. If we try to get this from our table we will run into the issue that this is not a whole number. But our images are stored with whole numbers! So attempting to get the image at index "4.75" will give us nothing. Bummer!
+
しかし、これでテーブルで取得しようとすると、これが整数ではない問題に出くわします。 ですが、画像は整数にて記録されています! そして "4.75" インデックスの画像を取得しようとしても何も得られません。なんてっこった、がっかりだ!
  
Fear not. The solution is not too difficult.
+
でも、大丈夫です。 解決方法はあまり難しくはありません。
  
"currentTime" will be a number between 0 and just below "duration" (because we reduce "currentTime" if it is larger or ''equal'' "duration"). Which would result in a number between 0 and 5.
+
"currentTime" の数値が 0 の間は "duration" はそれ以下になります (大きいか ''等しい'' "duration" の場合は "currentTime" を減らすためです)。その結果は 0 5 の間の数値となります。
  
To transform this value from our decimal point value to a whole number between 1 and 6 we do the following:
+
この値を小数点の値から整数の 1 6 の間の数値へ変換するには次の処理を行います:
  
 
<source lang="lua">
 
<source lang="lua">
Line 147: Line 147:
 
</source>
 
</source>
  
"math.floor" provides us with the next lower number. Which means in our case a number between 0 and 5. We add one pushing it to a number between 1 and 6. All the sprites we have!
+
<code>math.floor</code> は下方の次にある数値を提供します。この場合は 0 から 5 の間にある数値を意味します。 1 から 6 の間にある数値を広げるために 1 を加算します。これですべてのスプライトを扱えるようになります!
  
Lövely!
+
素晴らしい!
  
Alright. So all that's left is to draw the appropriate quad!
+
大丈夫です。残りは全て適切な Quad として描画します!
  
This simply requires us to provide "love.graphics.draw" with the image reference (our spriteSheet) and the quad we want to use. Simple enough!
+
これは必要となる参照用の画像 (用意したスプライトシート) と使用したい Quad を <code>love.graphics.draw</code> へ渡すだけです。まあまあ簡単ですね。
  
 
<source lang="lua">
 
<source lang="lua">
Line 160: Line 160:
 
</source>
 
</source>
  
And we are done! You should have a walking dude in the top left corner of your window when you execute this code!
+
そして、これで完了です! このコードを実行したときに、ウィンドウの左上の隅っこを歩いているアイツがいるはずです!
  
You can change where and how it is drawn by providing more parameters to the [[love.graphics.draw|draw function]].
+
また、[[love.graphics.draw|描画関数]]へ更なる引数を指定することにより描画位置と描画方法を変更することができます。
  
'''Disclaimer: This code is not game ready! It is meant to explain the basic logic behind animations. If you are searching for game ready code take a look at the list of [[Libraries]] available!'''
+
'''免責次項: このコードはゲーム製作用に設計されていません。アニメーションの裏側にある基本的な論理に関しての説明用です。ゲーム制作用のコードを探しているのでしたら[[Libraries (日本語)|ライブラリ]]の一覧が利用できます!'''
  
 
== 課題 ==
 
== 課題 ==
  
To improve this code. Try to rewrite the update and draw function to be able to handle multiple animations.
+
このコードを改良してみましょう。更新と描画用の関数を書き直して複数のアニメーションを扱えるようにしてみましょう。
  
You can load the same animation multiple times and store them in a special table to get started right away!
+
同じアニメーションを複数回読み込んでから、即時再生するために特別なテーブルへ格納することができます。
  
As hint as to how it could be done: You could put the drawing logic in the animation table as well. And build it so you can call "animation:draw(x, y, r, sx, sy, [...])" to draw it on the screen.
+
どのような方法で行えるか示唆をしましょう: アニメーションのテーブルへ描画用のロジック (論理) を同じように挿入することができます。そして <code>animation:draw(x, y, r, sx, sy, [...])</code> を呼び出して画面上に描画できるようにするために、それ構築してください。
  
Good luck and have fun!
+
幸運をお祈りします。そして楽しい時間をお過ごしくださいませ!
  
 
== 完全なソースコード ==
 
== 完全なソースコード ==

Revision as of 22:59, 16 August 2017

はじめる前に。これは中上級者向けのチュートリアルです。読者はテーブル、ループおよび LOVE による描画の基本事項に関して理解しているものと想定されております。もちろん LOVE でゲームを起動する方法も含まれます。

わかりました。どうすれば理解できますか?

筆者はスプライト技術によるアニメーションの基本事項を取り扱います。これは連続で表示される一連の画像があることを意味します。スプライトシートを自作すると決めているならば各スプライトの間に純粋な透過色のピクセル (αチャンネルで 255 の値) を最低でも 1 ピクセル配置してあるか確認してください。そうしなければ、次または前にある画像に一切不要かつ不自然な結果が見えてしまう場合があります!

アニメーションの読み込み

このチュートリアルでは無名の老英雄を使用します。 GrafxKid 製作のものであり、OpenGameArt.org によりみなさんのために提供されています。下記で画像をダウンロードして、 main.lua のある作業用フォルダへ画像を配置してください。

oldHero.png

複数のアニメーションの作成することは可能ですので、アニメーションと関連のあるすべてのものに対してテーブルを渡すことで使用できる再利用可能な関数が必要です。

新しい関数を定義してから作業用の画像に対して変数への格納を行い、新しい局所変数のテーブルを作成することにより、この作業を開始します。 (そして返値はテーブルです。あるアニメーション用のテーブルを思い付きで大域変数として定義してしまうと最悪の場合は関係のないその他のデータを上書きして破壊してしまうため、そのようなことは望ましくはありません!)

次の引数が必要です:

  • image

love.graphics.newImage(ファイルのパス) で作成される画像オブジェクトです。

  • width

個々のスプライトごとの幅です。すべてのスプライトは同一寸法であるものと想定しています。

  • height

個々のスプライトごとの高さです。

  • duration

アニメーションが最初のフレームまで折り返し再生 (ループバック) されるまでの長さ (全体の再生速度)。

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;

    return animation
end

これは love.graphics.draw(animation.spriteSheet) だけで描画しようとすることができます。しかしながら、全ての画像が互いに隣りで描画されるだけです。この結果は確かに望むものではありません。望む結果を得るには Quad が非常に役に立ちます!

そのために画像全体の代わりに描画される画像の一部を定義します。これこそ、まさしく望むものです!

さて、これで各 Quad を個別に定義することができました。けれども、いくつかのアニメーションで何百枚ものスプライトがある場合は、非常に面倒な作業になります! それを行うのではなく、読み込み時にループ内で画像内のスプライト枚数を検出する処理を行います。この配置では隙間が存在していけませんし、画像は正確な形式のアニメーションを有していなければいけません。

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    return animation
end

for ループに対する引数は次の意味があります:

1. x または y の開始値。

2. 最大値 (この場合は参照する画像の全体の幅または高さ)

3. 1段階あたりの加算量。この場合のスプライトの寸法。

これにより y と x からスプライトシートの位置を取得できます!

なぜ、画像の高さを確認するのではなく image:getHeight() - height を行うのか疑問に思うでしょう。これは別のスプライトがスプライトシートへ常に適合するかどうかを確認したいからです。シート自体が必要とする寸法がない場合があるからです。1ピクセルあたりの寸法が多き過ぎる場合もあります。これでもう Quad の追加はしたくありませんので (何もレンダリングはしません)、なにもしないで無視します。

さて、テーブルのインデックス 1 ~ 6 の範囲から Quad は 6 個あります。すばらしい!

しかし、個別に描画できる6 個の画像には本質的な問題があります。けれども、それらを長い間次々描画する必要があります。 同様に、このアニメーションだけを再生したくありません。その再生速度を変更したいと思うかもしれません。

これを扱うためにアニメーションのテーブルへ二つ以上の変数を追加します。 継続時間である duration (引数として要求します) および現在時間である currentTime は経過時間を計測するために使用されます。

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    animation.duration = duration or 1
    animation.currentTime = 0

    return animation
end

これでアニメーション生成関数は完了です!

アニメーションの更新

さて、次の話題として必要なものはアニメーションのテーブルへの読み込み (新規に作成した関数の呼び出し)、および現在時間による更新です。

function love.load()
    animation = newAnimation(love.graphics.newImage("oldHero.png"), 16, 18, 1)
end

function love.update(dt)
    animation.currentTime = animation.currentTime + dt
    if animation.currentTime >= animation.duration then
        animation.currentTime = animation.currentTime - animation.duration
    end
end

スプライトシートは幅 16 ピクセル、高さ 18 ピクセルの寸法によるスプライトから構成されています。 1 秒間にすべての画像を再生するよう意図しています。

ただ、 love.update で現在時刻に dt (最後のフレームからの経過時間として知られているデルタ・タイム) を加算します。これで以降は絶えず計算が行われます!

しかし、どのフレームを表示するか決定するために現在時間を使用します。それ自体は 0 と "duration" (継続時間) の値の間に発生することを望むでしょう。 "currentTime" が "duration" 以上かを確認して、そうならば "duration" を減算します。単に "duration" を減算するのではなく "currentTime" を 0 に設定することもできます。しかしながら、これでは秒の小数部がアニメーションの終了時に毎回失われてしまい、アニメーションの再生時間が少し崩れてしまいます。それは容易に避けることができますし、避けるべきです!

さて、次は本当に興味深い部分ですよ!

アニメーションの描画

これをどのように描画しますか?

よろしい。継続時間と現在時間があります。この情報で割合を計算することができます! これまでのアニメーションの経過時間はどのくらいですか?

これまで読者が適切に本チュートリアルを理解しているならば "currentTime / duration" では 0 と 1 の間にある数値を用意します。それは割合を表します。 0.25 はアニメーションが 25% 再生経過していることを意味します。

このことを考慮すれば使用する画像を正確に探し出すことができます! 既に 0 と 1 の間にある数値はありますので、この割合を画像の総枚数と掛けることで 0 と 6 の間の数値を得ることができます!

currentTime / duration * #quads

しかし、これでテーブルで取得しようとすると、これが整数ではない問題に出くわします。 ですが、画像は整数にて記録されています! そして "4.75" インデックスの画像を取得しようとしても何も得られません。なんてっこった、がっかりだ!

でも、大丈夫です。 解決方法はあまり難しくはありません。

"currentTime" の数値が 0 の間は "duration" はそれ以下になります (大きいか 等しい "duration" の場合は "currentTime" を減らすためです)。その結果は 0 と 5 の間の数値となります。

この値を小数点の値から整数の 1 と 6 の間の数値へ変換するには次の処理を行います:

math.floor(currentTime / duration * #quads) + 1

math.floor は下方の次にある数値を提供します。この場合は 0 から 5 の間にある数値を意味します。 1 から 6 の間にある数値を広げるために 1 を加算します。これですべてのスプライトを扱えるようになります!

素晴らしい!

大丈夫です。残りは全て適切な Quad として描画します!

これは必要となる参照用の画像 (用意したスプライトシート) と使用したい Quad を love.graphics.draw へ渡すだけです。まあまあ簡単ですね。

    local spriteNum = math.floor(animation.currentTime / animation.duration * #animation.quads) + 1
    love.graphics.draw(animation.spriteSheet, animation.quads[spriteNum])

そして、これで完了です! このコードを実行したときに、ウィンドウの左上の隅っこを歩いているアイツがいるはずです!

また、描画関数へ更なる引数を指定することにより描画位置と描画方法を変更することができます。

免責次項: このコードはゲーム製作用に設計されていません。アニメーションの裏側にある基本的な論理に関しての説明用です。ゲーム制作用のコードを探しているのでしたらライブラリの一覧が利用できます!

課題

このコードを改良してみましょう。更新と描画用の関数を書き直して複数のアニメーションを扱えるようにしてみましょう。

同じアニメーションを複数回読み込んでから、即時再生するために特別なテーブルへ格納することができます。

どのような方法で行えるか示唆をしましょう: アニメーションのテーブルへ描画用のロジック (論理) を同じように挿入することができます。そして animation:draw(x, y, r, sx, sy, [...]) を呼び出して画面上に描画できるようにするために、それ構築してください。

幸運をお祈りします。そして楽しい時間をお過ごしくださいませ!

完全なソースコード

function love.load()
    animation = newAnimation(love.graphics.newImage("oldHero.png"), 16, 18, 1)
end

function love.update(dt)
    animation.currentTime = animation.currentTime + dt
    if animation.currentTime >= animation.duration then
        animation.currentTime = animation.currentTime - animation.duration
    end
end

function love.draw()
    local spriteNum = math.floor(animation.currentTime / animation.duration * #animation.quads) + 1
    love.graphics.draw(animation.spriteSheet, animation.quads[spriteNum], 0, 0, 0, 4)
end

function newAnimation(image, width, height, duration)
    local animation = {}
    animation.spriteSheet = image;
    animation.quads = {};

    for y = 0, image:getHeight() - height, height do
        for x = 0, image:getWidth() - width, width do
            table.insert(animation.quads, love.graphics.newQuad(x, y, width, height, image:getDimensions()))
        end
    end

    animation.duration = duration or 1
    animation.currentTime = 0

    return animation
end


そのほかの言語