こんにちは!
前回こんな記事を書きました↓
本記事は『Pythonで物理シミュレーションをしよう!』シリーズの”Day5”です!
Contents
前回までに学んだこと
”Day4”の記事では「トランポリンで弾む猫」の物理シミュレーションを実践しました。
本記事では「バンジージャンプする猫」の物理シミュレーションを実践します。
…え?スパイダーマン!?
街中を蜘蛛の糸で飛び回るスパイダーマンと伸縮するロープで”びよんびよん”する猫が”くるる”ちゃんの中ではリンクしたようです!笑
ヒーロー大好きな3歳児の”くるる”ちゃんが今日も可愛い(*・ω・)ノ♪
【Day5】Pyxelで物理シミュレーション
正解!
”Day4”記事では猫がトランポリンに当たったときに、ばねの特性(復元力)で弾んでいました。
今回のバンジーは最初から猫がロープ(ばね)に繋がれている状態の物理シミュレーションです。
大正解!”ばねの特性”は物理の基礎ですが、基礎をしっかり修得していれば、あらゆる対象の物理シミュレーションが自作のソースコードで実現できますよ!
応用技術に注目しがちですが、基礎をしっかり修得し、応用も創造できる『骨太な技術者』になりたいですね(*・ω・)ノ♪
基礎が大事!
バンジーの物理シミュレーション
”くるる”ちゃんが黒板に何かを書き始めました!
- トランポリ:”復元力”で猫が弾む
- バンジー:”復元力”でロープが伸縮
- どちらも”復元力”という力による現象
マインドマップ・テキスト・グラフ・イラストなど、脳内にあるイメージを何らかの形で可視化すると、頭の中が整理できますよ
【復習】トランポリンと復元力
”Day4”の記事で勉強したトランポリンは以下のイメージだったよね♪
猫には常に”重力”が加わっているけど、ばねに当たったときには復元力も加わる!
これを数式に落とし込んでみるる(*・ω・)ノ♪
【重力の運動方程式】
f1 = m・g (質量:m[kg], 重力加速度g[m/s^2])
【復元力の運動方程式】
f2 = k・x (ばね定数:k[N/m], 変位x[m])
【トランポリンに当たったときの運動方程式】
F = f1 – f2
F = m・g – k・x
※y軸の向きに注意
最後はソースコードに落とし込む!
1 2 3 4 5 6 7 8 9 10 |
# f = m * g (自由落下) f = self.Cats[i].weight * G # トランポリンに衝突 if ((self.ground.pos1.y <= self.Cats[i].pos.y + CAT_H) and(self.ground.pos1.x <= self.Cats[i].pos.x + CAT_W) and (self.Cats[i].pos.x <= self.ground.pos2.x)): x = self.Cats[i].pos.y + CAT_H - self.ground.pos1.y # f = m * g - k * x (重力 - 復元力) f += -K * x |
InputするだけでなくOutputすることが知識や技術を習得するための近道だと感じています。人に説明するも良し!本サイトのソースコードを改良するも良しです!楽しく”Input ⇒ Output”を実践して下さいね
バンジーロープと復元力
本記事の前半で”はやぶさ先生”が以下のヒントをくれました
このヒントに基づいて、絵を描いてみます。
”Day4”の記事で勉強したトランポリンと違って、猫には常に重力と復元力が加わるので、条件分岐なしの以下の数式で表現できます。
【重力の運動方程式】
f1 = m・g (質量:m[kg], 重力加速度g[m/s^2])
【復元力の運動方程式】
f2 = k・x (ばね定数:k[N/m], 変位x[m])
【ばね変位】
x = Yc – Y (猫のy座標 – 天井のy座標)
【バンジーの運動方程式】
F = f1 – f2
F = m・g – k・x
※y軸の向きに注意
最後はソースコードに落とし込む!
1 2 3 4 |
# ばね変位:x(猫のy座標 - 天井のy座標) x = self.Cats[i].pos.y - self.ceiling.pos2.y # f = m * g - k * x(重力 - 復元力) f = self.Cats[i].weight * G - K * x |
くるるちゃんと同じように自力で数式を算出できた人は、大変よくできました!まだ、難しいと感じる人は、自分のペースで良いので丁寧に理解していきましょう(*・ω・)ノ♪
くるるの失敗!?
さきほどまで、自信満々だった”くるる”ちゃんが(´・ω・`)な顔をしています。
うん。。
以下の絵は伸びた”ばね”が縮むときに発生する復元力が上方向(y軸と反対の向き)にのみ働くイメージで書いたの。
でも、このシミュレーション結果を見てみると…
“ばね”が天井よりも上に伸びてから縮むときは、下方向(y軸と同じ向き)の力が働くと思うの。
つまり、猫が天井より上か下かで条件分岐が必要だと思うの!
今回はパラメータ定義を以下のように設定しています。
【各パラメータ】
- 猫の質量:m = 1 [kg]
- 重力加速度:g = 9.8 [m/s^2]
- ばね定数:k = 5 [N/m]
- 天井の位置(y座標):Y = 30[pixel]
※ 1m = 1pixel
猫が天井よりも下にいるときと上にいるときの”力の向き”を考えてみて下さい。
猫が天井よりも下にいるとき
天井の位置:Y = 30[pixel]で固定値だから、猫の位置:Ycが天井よりも下にいる状態は以下の通りです。
【猫が天井よりも下にいるときの条件式】
Y < Yc
30 < Yc
もし猫の位置:Yc = 40なら、ばね変位は以下の式で算出できます。
【猫が天井よりも下にいるときの”ばね変位”】
x = Yc – Y
x = 40 – 30
x = 10
先ほど求めたバンジーの運動方程式に代入してみます。
【バンジーの運動方程式】
F = m・g – k・x
F = 1*9.8 – 5・10
F = 9.8 – 50
F = -40.2
F < 0
Fがマイナスになったので、くるるの予想通り上方向(y軸と反対の向き)の力が発生して、猫が上方向に運動するね♪
猫が天井よりも上にいるとき
同じように考えてみます。
【猫が天井よりも上にいるときの条件式】
Y > Yc
30 > Yc
もし猫の位置:Yc =20なら、ばね変位は以下の式で算出できます。
【猫が天井よりも下にいるときの”ばね変位”】
x = Yc – Y
x = 20 – 30
x = -10
先ほど求めたバンジーの運動方程式に代入してみます。
【バンジーの運動方程式】
F = m・g – k・x
F = 1*9.8 – 5・(-10)
F = 9.8 + 50
F = 59.8
F > 0
Fがプラスになってる!!
具体的な数値を代入することで、下方向(y軸と同じの向き)の力が発生して、猫が下方向に運動する状態を確認できた!
そして、猫の位置が天井より上でも下でも同じ運動方程式が使えることも分かった!猫の位置によって数式を変える必要はないみたい!
算出した運動方程式に対し、具体的な数値を代入することで、不自然な解になっていないか?
を確認できますよ(*・ω・)ノ♪
どういたしまして(*’ω’*)
間違えてもOK!失敗しても大丈夫!!重要なのは、その後の行動です。勇気を出して自分の失敗を告白してくれた”くるる”ちゃんに拍手♪おかげで丁寧な解説ができました
最終的に完成したソースコード -バンジージャンプ猫-
以上より、最終的に完成した「バンジージャンプ猫」のソースコードが以下です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
import pyxel # ====== Image parameters ====== WINDOW_H = 120 WINDOW_W = 160 CAT_H = 16 CAT_W = 16 # ===== Physical parameters ===== FPS = 30 # フレームレート [f/s] DT = 1 / FPS # ステップ時間 [s] G = 9.8 # 重力加速度 [kg.m/s^2] K = 5 # ばね定数 [n/m] class Vec2: def __init__(self, x, y): self.x = x self.y = y class cat: def __init__(self, img_id): self.pos = Vec2(0, 0) self.vec = 0 self.vel = 0 self.weight = 1 self.time = 0 self.img_cat = img_id def update(self, x, y, dx): self.pos.x = x self.pos.y = y self.vec = dx class Ceiling: def __init__(self): self.pos1 = Vec2(0, 30) self.pos2 = Vec2(WINDOW_W, 35) self.color = 12 # blue class Spring: def __init__(self): self.pos1 = Vec2(0, 0) self.pos2 = Vec2(0, 0) self.color = 12 # blue def update(self, x1, y1, x2, y2, color): self.pos1.x = x1 self.pos1.y = y1 self.pos2.x = x2 self.pos2.y = y2 self.color = color class App: def __init__(self): self.IMG_ID0_X = 60 self.IMG_ID0_Y = 65 self.IMG_ID0 = 0 self.IMG_ID1 = 1 # self.IMG_ID2 = 2 pyxel.init(WINDOW_W, WINDOW_H, fps = FPS, caption="Hello Pyxel") pyxel.image(self.IMG_ID0).load(0, 0, "assets/pyxel_logo_38x16.png") pyxel.image(self.IMG_ID1).load(0, 0, "assets/cat_16x16.png") pyxel.mouse(True) # make instance self.Cats = [] self.Springs = [] self.ceiling = Ceiling() pyxel.run(self.update, self.draw) def update(self): if pyxel.btnp(pyxel.KEY_Q): pyxel.quit() # ====== ctrl cat ====== if pyxel.btnp(pyxel.MOUSE_LEFT_BUTTON): new_cat = cat(self.IMG_ID1) new_cat.update(pyxel.mouse_x, pyxel.mouse_y, new_cat.vec) self.Cats.append(new_cat) new_spring = Spring() self.Springs.append(new_spring) cat_count = len(self.Cats) for i in range(cat_count): if self.Cats[i].pos.y < WINDOW_H: # ばね変位:x(猫のy座標 - 天井のy座標) x = self.Cats[i].pos.y - self.ceiling.pos2.y # f = m * g - k * x(重力 - 復元力) f = self.Cats[i].weight * G - K * x # a = f / m (ニュートンの運動方程式) alpha = f / self.Cats[i].weight # v += a * dt (積分:加速度 ⇒ 速度) self.Cats[i].vel += alpha * DT # y += v * dt (積分:速度 ⇒ 位置) self.Cats[i].pos.y += self.Cats[i].vel * DT # 経過時間 self.Cats[i].time += DT # debug print("Cat No.", i) print("v = ", self.Cats[i].vel) print("y = ", self.Cats[i].pos.y) print("f = ", f) # Cat update self.Cats[i].update(self.Cats[i].pos.x, self.Cats[i].pos.y, self.Cats[i].vec) # Spring update self.Springs[i].update(self.Cats[i].pos.x + CAT_W / 2, self.ceiling.pos2.y, self.Cats[i].pos.x + CAT_W / 2, self.Cats[i].pos.y, pyxel.frame_count % 16) else: del self.Cats[i] break def draw(self): pyxel.cls(0) pyxel.text(55, 40, "Are you Kururu?", pyxel.frame_count % 16) pyxel.blt(self.IMG_ID0_X, self.IMG_ID0_Y, self.IMG_ID0, 0, 0, 38, 16) # ======= draw cat ======== for cats in self.Cats: pyxel.blt(cats.pos.x, cats.pos.y, cats.img_cat, 0, 0, CAT_W, CAT_H, 5) # ======= draw springs ======== for springs in self.Springs: pyxel.line(springs.pos1.x, springs.pos1.y, springs.pos2.x, springs.pos2.y, springs.color) # ======= draw ceiling ======== pyxel.rect(self.ceiling.pos1.x, self.ceiling.pos1.y, self.ceiling.pos2.x, self.ceiling.pos2.y, self.ceiling.color) App() |
実は本記事”Day5”のソースコードは、”Day4”記事で紹介したソースコードとほとんど同じです。
どちらも”ばね特性”(復元力)を題材にしているので、当然かもしれませんが…それでも学びがあったのではないでしょうか?
くるるちゃんは、また1つ賢くなりました♪
おわりに
『Pythonで物理シミュレーションをしよう!Day5 -復元力2-』について説明しました。
”Day4”と同じ”ばねの復元力”を題材にしたチュートリアルでしたが、簡単に違いをまとめると以下の通りです。
【Day4 -トランポリンの物理シミュレーション-】
- 猫とばねが非接触の状態がある
- 復元力が上方向にしか発生しない
【Day5 -バンジーの物理シミュレーション-】
- 猫とばねが常に接触状態
- 復元力が上下方向に発生する
pyxelを使って、楽しく「物理」や「プログラミング」の勉強ができると良いなーとか考えながら、本記事を書きあげました。
本記事をきっかけに「物理」や「プログラミング」に興味をもってくれる人が増えたら最高に嬉しいです!
息ピッタリでした!笑
本シリーズ最終回??
次回予告
『Pythonで物理シミュレーションしよう!』シリーズ記事ですが、本記事が最終回の予定でした。
ただ、”くるる”ちゃんが最終回とは別の理由で泣き出すから…
”くるる”ちゃんのピュア想いが私の心に突き刺さった…!
”くるる”ちゃん!顔を上げて!猫が静止するよ!
嬉しさのあまり踊りだす、”くるる”ちゃんがすごく可愛い♪
というわけで、次回が本シリーズの最終回になります。
”Day4”では、思わせぶりな記事を書いてしまい申し訳ありませんでした。本シリーズは良くも悪くも無計画で作成しているため、今回のようなハプニングが発生しました!笑
”くるる”ちゃんと同じ思いの人がいたら嬉しいです。
本記事楽しかったかな?次回もお楽しみに~