差分
このページの2つのバージョン間の差分を表示します。
両方とも前のリビジョン 前のリビジョン 次のリビジョン | 前のリビジョン最新のリビジョン両方とも次のリビジョン | ||
lecture:design_with_prototyping:p5.js編:13.動きの再構成 [2021/03/11 03:03] – baba | lecture:design_with_prototyping:p5.js編:13.動きの再構成 [2021/05/25 13:23] – [パーティクル] baba | ||
---|---|---|---|
行 12: | 行 12: | ||
特段面白いものではないと思いますが、次のようなコードはどうでしょうか?p5jsのエディタごとこのページにはります。短いプログラムですが、じっくり読んでみましょう。単純にマウス位置に丸を描くのではなく、マウスカーソルの位置に徐々に丸が近づいていくプログラムにしています。 | 特段面白いものではないと思いますが、次のようなコードはどうでしょうか?p5jsのエディタごとこのページにはります。短いプログラムですが、じっくり読んでみましょう。単純にマウス位置に丸を描くのではなく、マウスカーソルの位置に徐々に丸が近づいていくプログラムにしています。 | ||
- | <html> | + | |
- | <iframe src="https://editor.p5js.org/tetsuakibaba/sketches/lq6TI8Sis" | + | <WRAP group> |
- | </html> | + | <WRAP half column> |
+ | < | ||
+ | function setup() { | ||
+ | createCanvas(displayWidth, | ||
+ | } | ||
+ | |||
+ | var position | ||
+ | x: 0, | ||
+ | y: 0 | ||
+ | }; | ||
+ | |||
+ | function draw() { | ||
+ | background(100); | ||
+ | circle(position.x, | ||
+ | |||
+ | position.x = position.x + (mouseX - position.x) | ||
+ | position.y = position.y + (mouseY - position.y) | ||
+ | } | ||
+ | </file> | ||
+ | </WRAP> | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </WRAP> | ||
+ | </WRAP> | ||
これらプログラムはどちらもマウスカーソルにくっつくプログラムであり、目的は達成しています。一方で操作して「気持ちいい」、「楽しい」と感じるのはどちらでしょうか? 状況にもよるかもしれませんが、おそらく後者の方が気持ちいい、楽しいと思う人が多いのではないでしょうか?ただ動く、といってもその動きは様々です。まっすぐ移動する円であっても、徐々に早くなって、最後はゆっくり止まるといったある種の無駄な動作が、私達人間にとっては体験の楽しさや興味、注意を引く大きな仕掛けになっていることがあります。これは映像制作に限らずインタラクションでも同じことが言えます。 | これらプログラムはどちらもマウスカーソルにくっつくプログラムであり、目的は達成しています。一方で操作して「気持ちいい」、「楽しい」と感じるのはどちらでしょうか? 状況にもよるかもしれませんが、おそらく後者の方が気持ちいい、楽しいと思う人が多いのではないでしょうか?ただ動く、といってもその動きは様々です。まっすぐ移動する円であっても、徐々に早くなって、最後はゆっくり止まるといったある種の無駄な動作が、私達人間にとっては体験の楽しさや興味、注意を引く大きな仕掛けになっていることがあります。これは映像制作に限らずインタラクションでも同じことが言えます。 | ||
行 21: | 行 46: | ||
* ボールが10m先に落ちた | * ボールが10m先に落ちた | ||
ということでしかありませんが、その間はボールが1, | ということでしかありませんが、その間はボールが1, | ||
- | * 直径50の円をx=100の位置からx=300の位置へ移動してください | + | * 直径50の円をx=100[px]の位置からx=300[px]の位置へ移動してください |
- | という命題があった場合、ボールのx座標には最初に100をいれ、次にxに300を入れればおしまいです。デジタル上では非常に簡単ですが、これを現実世界に置き換えた場合、円(この場合ボールで考えてください)は必ず指定座標にいくまでも移動しなければなりません。私達の生きるこの世界はこのような連続性で成り立っており、デジタルにおけるデザインではこの関連性を常に意識しておく必要があります。 | + | という命題があった場合、ボールのx座標には最初に100をいれ、次にxに300を入れればおしまいです。デジタル上では非常に簡単ですが、これを現実世界に置き換えた場合、円(この場合ボールで考えてください)は必ず指定座標にいくまでも移動しなければなりません。私達の生きるこの世界はこのような連続性で成り立っており、デジタルにおけるデザインではこの関連性を常に意識しておく必要があります。あなたならどのように100[px]から300[px]までボールを動かしますか? |
+ | |||
+ | ===== Easing ===== | ||
+ | 上記のような考えに対して代表的な取り組みがEasingと呼ばれるものです。始点と終点が決まっている場合、その過程をどのように移動するかを関数で表現します。このことをEasing関数等と読んだりもします。AfterEffect等の映像制作ソフトウェアやアニメーション作成ソフトウェアではそのような名称を聞いたことがある人もいるかも知れません。まずは以下のウェブサイトで様々なEasing関数を確認してみましょう。 | ||
+ | * https:// | ||
+ | |||
+ | |||
+ | ===== 物理演算 ===== | ||
+ | 私達は物理法則の中で普段生活しています.ボールを投げれば地面に落ちるし,壁に跳ね返りもします.動きの再構成の中でも特に物理演算の再構成は現実世界のシミュレーションのようで,ちょっとパラメータを変化させることで,地球上ではありえない動きをオブジェクトに与えることができます.例えば地球上では重力が働くことで,握ったボールを話せばそのまま下方向に自然落下していきます.このとき手を離れたボールは重力加速度によってどんどん速度が増し,最後は地面にぶつかります. | ||
+ | |||
+ | 加速度は一秒間で増加する速度のことです.つまり時間が経過するほどどんどん速度が早くなっていきます.例えば以下のようなコードを書いてみます. | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | < | ||
+ | var ball = { | ||
+ | x:0, | ||
+ | y:0, | ||
+ | v: { | ||
+ | x:0.0, | ||
+ | y:0.0 | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | function setup() { | ||
+ | createCanvas(400, | ||
+ | } | ||
+ | |||
+ | function draw() { | ||
+ | background(220); | ||
+ | |||
+ | ball.x += ball.v.x; | ||
+ | ball.y += ball.v.y; | ||
+ | circle(ball.x, | ||
+ | ball.v.y += 0.1; | ||
+ | } | ||
+ | |||
+ | function mousePressed() | ||
+ | { | ||
+ | ball.x = mouseX; | ||
+ | ball.y = mouseY; | ||
+ | ball.v.x = 0; | ||
+ | ball.v.y = 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | |||
+ | 非常にシンプルな計算ですが,物理法則に基づいた計算を行っています.下方向に重力が生じていると考え,フレーム更新毎に下方向の速度を0.1ずつ増やしているだけです.結果としてボールが自由に落下しているように感じられると思います. | ||
+ | |||
+ | では次は地面にぶつかったときに跳ね返らせてみます.高校物理習ったとおりですが,衝突したときにはその衝突した瞬間の速度と逆向きの速度になります.ただし同じ速度だと永遠とはね続けるので跳ね返り係数(k)を0.5などとして計算します. | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | |||
+ | < | ||
+ | var ball = { | ||
+ | x:0, | ||
+ | y:0, | ||
+ | v: { | ||
+ | x:0.0, | ||
+ | y:0.0 | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | function setup() { | ||
+ | createCanvas(400, | ||
+ | } | ||
+ | |||
+ | function draw() { | ||
+ | background(220); | ||
+ | |||
+ | ball.x += ball.v.x; | ||
+ | ball.y += ball.v.y; | ||
+ | if( ball.y > 400-5 ){ | ||
+ | ball.v.y = 0.5*(-ball.v.y); | ||
+ | ball.y = 400-5; | ||
+ | } | ||
+ | circle(ball.x, | ||
+ | ball.v.y += 0.1; | ||
+ | } | ||
+ | |||
+ | function mousePressed() | ||
+ | { | ||
+ | ball.x = mouseX; | ||
+ | ball.y = mouseY; | ||
+ | ball.v.x = 0; | ||
+ | ball.v.y = 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | |||
+ | 実行結果を確認するとたしかにボールが跳ねているように見えますね. 重力加速度を0.1としていますが,この値を変えることでボールの動きがどのように変化するかをよく観察してください.この0.1は重力加速度なので,この値が増えるということは,より重力が強い世界の場合となっています.なんだか重そうに見えませんか?落下の仕方,跳ね返り方で私達人間はその物体に対する印象が変わってしまいます. | ||
+ | |||
+ | <WRAP center round todo 60%> | ||
+ | ここまでのコードを参考にして,html上で重量加速度を自由に設定できるスライダーを用意し,重力加速度を操作することで画面内のボールを落としたり,浮かび上がらせたりする操作を実現してみましょう. | ||
+ | </ | ||
+ | |||
+ | さらに物理演算を利用したアプリケーションを作成したい場合はライブラリを利用すると良いでしょう.詳しくはThe Coding Trainで学習してみましょう. | ||
+ | * https:// | ||
+ | * テキストと組み合わせるとこんなこともできます:https:// | ||
+ | |||
+ | ===== 物理法則に従わなくたってよい ===== | ||
+ | 物理演算で遊ぶととっても楽しいのですが,私達がデザインしているのは現実世界だけでなく,デジタル世界だからこそ成立するものでも良いわけです.例えばアニメーションの世界では,キャラクターの動きを完全に物理法則に基づいて描いてしまうと,アニメーションの面白みがなくなり,単調なアニメーションになってしまいます.例えばキャラクターが高く飛び上がるときも,通常よりもしゃがみ込みから飛び上がる瞬間までの時間を長くとることで,アニメーションとしてのジャンプを強調(誇張)することができます. | ||
+ | |||
+ | 上記で記述した自由落下のアルゴリズムを少し変更し,落ち始めの瞬間は一瞬止まったようにみえるが,ちょっと時間が立つと「ストン!」と落ちるようなアニメーションに記述し直してみましょう.ニュートン力学ではなく,トムとジェリー力学ですね(https:// | ||
+ | |||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | ボールにかかる重力加速度を $a$,ボールの下方向の速度を$V$としたとき | ||
+ | |||
+ | \[ | ||
+ | a = \begin{cases} | ||
+ | 1.0 & V > 0.2 \\ | ||
+ | 0.01 & V \leqq 0.2 | ||
+ | \end{cases} | ||
+ | \] | ||
+ | |||
+ | として,プログラムを記述すると右図のようなアニメーションに変わります.ボールが下に落ちる速度がある一定の大きさになると,加速度が大きくなり,速度が落ちるとまた加速度が小さくなります.結果として,「落下する」というアニメーションを物理法則にとらわれずに記述することができました. | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP center round todo 60%> | ||
+ | 以上の内容を踏まえて, | ||
+ | * 軽いボール | ||
+ | * 重いボール | ||
+ | をそれぞれ物理法則を変えてもいいので,自分なりに表現してみましょう. | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== パーティクル ===== | ||
+ | この動きを一つだけで表現するのではなく、たくさんのオブジェクトに対して共通の動きを適応した代表的な手法にパーティクルがあります。これまでのサンプルを利用して、複数のボールを作成してみます。 | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | < | ||
+ | // ボールをたくさん作るので配列で宣言する | ||
+ | var balls = []; | ||
+ | |||
+ | function setup() { | ||
+ | createCanvas(400, | ||
+ | |||
+ | // ボールの個数を100個にして、ballsの配列に連想配列を初期化して追加する | ||
+ | for (let i = 0; i < 100; i++) { | ||
+ | balls.push({ | ||
+ | x: random(width), | ||
+ | y: random(height), | ||
+ | v: { | ||
+ | x: 0.0, | ||
+ | y: 0.0, | ||
+ | }, | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function draw() { | ||
+ | background(220); | ||
+ | |||
+ | // ボールの個数分の描画処理 | ||
+ | for (const ball of balls) { | ||
+ | let g = 1.0; | ||
+ | ball.x += ball.v.x; | ||
+ | ball.y += ball.v.y; | ||
+ | if (ball.y > 400 - 5) { | ||
+ | ball.v.y = 0.5 * -ball.v.y; | ||
+ | ball.y = 400 - 5; | ||
+ | } | ||
+ | ball.v.y += g; | ||
+ | |||
+ | noStroke(); | ||
+ | fill(0); | ||
+ | circle(ball.x, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{: | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | 同じプログラムでもそれがたくさんになると、印象が変わりますね。では次は重力の無い世界のプログラムに変更してみます。ただし重力がないとただそこにとどまるだけのボールになってしまうため、初速度を設定しておきます。初速度はそのボールに設定する最初の速度です。また飛んでいってそのまま見えなくなってしまうとつまらないので、一定期間飛び続けたら自動的に最初に戻るようにします。パーティクルではこのことをライフと読んでおり、パーティクル(粒子)が発生してからなくなるまでの寿命のことを意味します。 | ||
+ | |||
+ | <WRAP group> | ||
+ | <WRAP half column> | ||
+ | < | ||
+ | // ボールをたくさん作るので配列で宣言する | ||
+ | var balls = []; | ||
+ | |||
+ | // 初速度 | ||
+ | var v0 = 0.3; | ||
+ | |||
+ | function setup() { | ||
+ | createCanvas(400, | ||
+ | |||
+ | // ボールの個数を100個にして、ballsの配列に連想配列を初期化して追加する | ||
+ | for (let i = 0; i < 100; i++) { | ||
+ | balls[i] = { | ||
+ | x: width / 2, | ||
+ | y: height / 2, | ||
+ | v: { | ||
+ | x: random(-v0, v0), | ||
+ | y: random(-v0, v0), | ||
+ | }, | ||
+ | life: random(255), | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // 色が重なっているときの振る舞いを加算モードにする | ||
+ | // 詳細:https:// | ||
+ | blendMode(ADD); | ||
+ | } | ||
+ | |||
+ | function draw() { | ||
+ | // 画面の描画をすべてなくす | ||
+ | clear(); | ||
+ | |||
+ | // 背景色の描画 | ||
+ | background(0); | ||
+ | |||
+ | // ボールの個数分の描画処理 | ||
+ | for (const ball of balls) { | ||
+ | ball.x += ball.v.x; | ||
+ | ball.y += ball.v.y; | ||
+ | ball.life--; | ||
+ | |||
+ | // 寿命が付きたら最初に戻る | ||
+ | if (ball.life < 0) { | ||
+ | ball.x = width / 2; | ||
+ | ball.y = height / 2; | ||
+ | ball.v.x = random(-v0, v0); | ||
+ | ball.v.y = random(-v0, v0); | ||
+ | ball.life = random(255); | ||
+ | } | ||
+ | |||
+ | noStroke(); | ||
+ | fill(255, 150, 50, ball.life / 10); | ||
+ | circle(ball.x, | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <WRAP half column> | ||
+ | {{ : | ||
+ | </ | ||
+ | </ | ||
+ |