tkinterにおける跳ね返り処理
解決済
回答 1
投稿
- 評価
- クリップ 0
- VIEW 384
前提・実現したいこと
Pythonを使ってBOIDモデルを実装しています.
発生している問題・エラーメッセージ
Python標準のtkinterを使っているのですが,birdが複数いる時の跳ね返りの処理が上手く動作せず,右下の角に収束してしまいます.
該当のソースコード
import math
import random
from statistics import mean
from tkinter import *
class Coordinate():
def __init__(self):
self.x = 0
self.y = 0
def reset(self):
self.x = 0
self.y = 0
class Bird():
"""
Bird Class
"""
def __init__(self, x, y, vx, vy, identifier, field_info, others, view):
"""Bird Class Constructor
BOID用のエージェントです
:param x: x coordinate
:param y: y coordinate
:param vx: x方向の移動量
:param vy: y方向の移動量
:param id: 識別用id
:param field_info: (field_width, field_height, fish_radius, fish_speed)
:param others: other fish
:param view: 視野の広さ(半径)
"""
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.identifier = identifier
self.others = others
self.view = view
self.field_info = field_info
self.inverse_x = self.field_info[0] - self.x
self.inverse_y = self.field_info[1] - self.y
self.r1 = 2.0 # cohesion coefficient
self.r2 = 0.5 # separation coefficient
self.r3 = 1.0 # alignment coefficient
self.neighbors = [] # 近くにいるエージェント
self.v1 = Coordinate()
self.v2 = Coordinate()
self.v3 = Coordinate()
def set_others(self, others):
self.others = others
self.find_neighbors()
# 結合
def cohesion(self):
center_pull_coeff = 10
if len(self.neighbors) == 0:
return
self.v1.x = mean([agent.x for agent in self.neighbors if not agent.identifier == self.identifier])
self.v1.y = mean([agent.y for agent in self.neighbors if not agent.identifier == self.identifier])
self.v1.x = (self.v1.x - self.x) / center_pull_coeff
self.v1.y = (self.v1.y - self.y) / center_pull_coeff
# 離脱
def separation(self):
personal_space = 50
for agent in self.neighbors:
if self.identifier == agent.identifier:
continue
dist = self.calc_distance(agent, self)
if dist <= personal_space:
self.v2.x -= (agent.x - self.x) / dist
self.v2.y -= (agent.y - self.y) / dist
def _separation(self):
if len(self.neighbors) == 0:
return
self.v2.x = mean([agent.x for agent in self.neighbors if not agent.identifier == self.identifier]) - self.x
self.v2.y = mean([agent.y for agent in self.neighbors if not agent.identifier == self.identifier]) - self.y
# 距離の計算
def calc_distance(self, a, b):
return min(math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2),
math.sqrt((a.inverse_x - b.x) ** 2 + (a.inverse_y - b.y) ** 2))
# 整列
def alignment(self):
if len(self.neighbors) == 0:
return
self.v3.x = mean([agent.x for agent in self.neighbors if not self.identifier == agent.identifier])
self.v3.y = mean([agent.y for agent in self.neighbors if not self.identifier == agent.identifier])
self.v3.x = (self.v3.x - self.vx) / 2
self.v3.y = (self.v3.y - self.vy) / 2
def _collision_detection(self):
if self.x - self.field_info[2] <= 0:
self.vx *= -1
self.x = self.field_info[2]
if self.x + self.field_info[2] >= self.field_info[0]:
self.vx *= -1
self.x = self.field_info[0] - self.field_info[2]
if self.y - self.field_info[2] <= 0:
self.vy *= -1
self.y = self.field_info[2]
if self.y + self.field_info[2] >= self.field_info[1]:
self.vy *= -1
self.y = self.field_info[1] - self.field_info[2]
def step(self):
self.cohesion()
self.separation()
self.alignment()
def find_neighbors(self):
self.neighbors = [neighbor for neighbor in self.others
if self.calc_distance(self, neighbor) <= self.view and
not self.identifier == neighbor.identifier]
def update(self):
self.vx += self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
self.vy += self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
distance = math.sqrt(self.vx ** 2 + self.vy ** 2)
if distance > self.field_info[3]:
self.vx = (self.vx / distance) * self.field_info[3]
self.vy = (self.vy / distance) * self.field_info[3]
self.x += int(self.vx)
self.y += int(self.vy)
self._collision_detection()
print(self.vx, self.vy)
self.inverse_x = self.field_info[0] - self.x
self.inverse_y = self.field_info[1] - self.y
def clear_movement(self):
self.v1.reset()
self.v2.reset()
self.v3.reset()
def draw(self):
self.clear_movement()
self.step()
self.update()
return self.x, self.y, self.vx, self.vy
root = Tk()
WIDTH = 1000
HEIGHT = 1000
fish_radian = 10
bird_num = 3
SPEED = 5
VIEW = 50
c = Canvas(root, width=WIDTH, height=HEIGHT)
c.pack()
fish_list = [Bird(random.randint(0, WIDTH), random.randint(0, HEIGHT), random.randint(-SPEED, SPEED),
random.randint(-SPEED, SPEED), i, (WIDTH, HEIGHT, fish_radian, SPEED), [], VIEW) for i in range(bird_num)]
[fish.set_others(fish_list) for fish in fish_list]
def animate():
c.delete("all")
for fish in fish_list:
fish.draw()
c.create_oval(fish.x - fish_radian, fish.y - fish_radian, fish.x+fish_radian, fish.y+fish_radian)
c.create_line(fish.x, fish.y, fish.x + fish.vx * 3, fish.y + fish.vy * 3)
[fish.set_others(fish_list) for fish in fish_list]
root.after(20, animate)
animate()
root.mainloop()
試したこと
条件式を色々変えて試しました.
ほぼ全ての条件で同様にウィンドウの右下に収束してしまいました.
補足情報(FW/ツールのバージョンなど)
MacOS Catalina
Python 3.6.8
-
気になる質問をクリップする
クリップした質問は、後からいつでもマイページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
クリップを取り消します
-
良い質問の評価を上げる
以下のような質問は評価を上げましょう
- 質問内容が明確
- 自分も答えを知りたい
- 質問者以外のユーザにも役立つ
評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。
質問の評価を上げたことを取り消します
-
評価を下げられる数の上限に達しました
評価を下げることができません
- 1日5回まで評価を下げられます
- 1日に1ユーザに対して2回まで評価を下げられます
質問の評価を下げる
teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。
- プログラミングに関係のない質問
- やってほしいことだけを記載した丸投げの質問
- 問題・課題が含まれていない質問
- 意図的に内容が抹消された質問
- 過去に投稿した質問と同じ内容の質問
- 広告と受け取られるような投稿
評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。
質問の評価を下げたことを取り消します
この機能は開放されていません
評価を下げる条件を満たしてません
質問の評価を下げる機能の利用条件
この機能を利用するためには、以下の事項を行う必要があります。
- 質問回答など一定の行動
-
メールアドレスの認証
メールアドレスの認証
-
質問評価に関するヘルプページの閲覧
質問評価に関するヘルプページの閲覧
checkベストアンサー
+1
self.vx += self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
self.vy += self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
self.vx, self.vy がどんどん変化して、どんどん早くなっていくと思うのですが、意図した動作ですか?
ソースの理解を兼ねてリファクタリングしてみました。
問題そうな部分も修正してみました。
import random
from statistics import mean
from tkinter import *
class Field:
WIDTH = 300
HEIGHT = 200
class Coordinate():
def __init__(self):
self.reset()
def reset(self):
self.x = 0
self.y = 0
class Bird():
NUM = 3
RADIAN = 10
SPEED = 5
VIEW = 50
@staticmethod
def setup():
Bird.birds = [Bird() for _ in range(Bird.NUM)]
def __init__(self):
self.x = random.randint(0, Field.WIDTH)
self.y = random.randint(0, Field.HEIGHT)
self.vx = random.randint(-self.SPEED, self.SPEED)
self.vy = random.randint(-self.SPEED, self.SPEED)
self.view = self.VIEW
self.inverse_x = Field.WIDTH - self.x
self.inverse_y = Field.HEIGHT - self.y
self.r1 = 2.0 # cohesion coefficient
self.r2 = 0.5 # separation coefficient
self.r3 = 1.0 # alignment coefficient
self.v1 = Coordinate()
self.v2 = Coordinate()
self.v3 = Coordinate()
def neighbors(self):
return [agent for agent in self.birds if agent != self]
# 結合
def cohesion(self):
neighbors = self.neighbors()
if not neighbors:
return
center_pull_coeff = 10
self.v1.x = mean([agent.x for agent in neighbors])
self.v1.y = mean([agent.y for agent in neighbors])
self.v1.x = (self.v1.x - self.x) / center_pull_coeff
self.v1.y = (self.v1.y - self.y) / center_pull_coeff
# 離脱
def separation(self):
personal_space = 50
for agent in self.neighbors():
dist = self.calc_distance(agent)
if dist and dist <= personal_space:
self.v2.x -= (agent.x - self.x) / dist
self.v2.y -= (agent.y - self.y) / dist
def _separation(self):
neighbors = self.neighbors()
if not neighbors:
return
self.v2.x = mean([agent.x for agent in neighbords]) - self.x
self.v2.y = mean([agent.y for agent in neighbords]) - self.y
# 距離の計算
def calc_distance(self, target):
return ((target.x - self.x) ** 2 + (target.y - self.y) ** 2) ** 0.5
# 整列
def alignment(self):
neighbors = self.neighbors()
if not neighbors:
return
self.v3.x = mean([agent.x for agent in neighbors])
self.v3.y = mean([agent.y for agent in neighbors])
self.v3.x = (self.v3.x - self.vx) / 2
self.v3.y = (self.v3.y - self.vy) / 2
def _collision_detection(self):
if self.x - self.RADIAN < 0:
self.vx *= -1
self.x = self.RADIAN
if self.x + self.RADIAN > Field.WIDTH:
self.vx *= -1
self.x = Field.WIDTH - self.RADIAN
if self.y - self.RADIAN < 0:
self.vy *= -1
self.y = self.RADIAN
if self.y + self.RADIAN > Field.HEIGHT:
self.vy *= -1
self.y = Field.HEIGHT - self.RADIAN
def step(self):
self.cohesion()
self.separation()
self.alignment()
def update(self):
dx = self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
dy = self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
self.vx += dx if self.vx >= 0 else -dx
self.vy += dy if self.vy >= 0 else -dy
distance = (self.vx ** 2 + self.vy ** 2) ** 0.5
if distance > self.SPEED:
self.vx = (self.vx / distance) * self.SPEED
self.vy = (self.vy / distance) * self.SPEED
self.x += int(self.vx)
self.y += int(self.vy)
self._collision_detection()
print(self.vx, self.vy)
self.inverse_x = Field.WIDTH - self.x
self.inverse_y = Field.HEIGHT - self.y
def clear_movement(self):
self.v1.reset()
self.v2.reset()
self.v3.reset()
def draw(self, drawer):
self.clear_movement()
self.step()
self.update()
drawer.create_oval(self.x - self.RADIAN, self.y - self.RADIAN, self.x+self.RADIAN, self.y+self.RADIAN)
drawer.create_line(self.x, self.y, self.x + self.vx * 3, self.y + self.vy * 3)
def main():
Bird.setup()
root = Tk()
canvas = Canvas(root, width=Field.WIDTH, height=Field.HEIGHT)
canvas.pack()
def animate():
canvas.delete("all")
for bird in Bird.birds:
bird.draw(canvas)
root.after(20, animate)
animate()
root.mainloop()
if __name__ == '__main__':
main()
投稿
-
回答の評価を上げる
以下のような回答は評価を上げましょう
- 正しい回答
- わかりやすい回答
- ためになる回答
評価が高い回答ほどページの上位に表示されます。
-
回答の評価を下げる
下記のような回答は推奨されていません。
- 間違っている回答
- 質問の回答になっていない投稿
- スパムや攻撃的な表現を用いた投稿
評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。
15分調べてもわからないことは、teratailで質問しよう!
- ただいまの回答率 88.36%
- 質問をまとめることで、思考を整理して素早く解決
- テンプレート機能で、簡単に質問をまとめられる
2019/12/01 21:47
https://qiita.com/takashi/items/9d684a6e3742a15e8726
こちらの記事を参考にして書いたコードなのですが,self.vx, self.vyに関しては,その下にあります条件式で,一定の値を超えた場合は調整するようにしております.
2019/12/01 22:18
dx = self.r1 * self.v1.x + self.r2 * self.v2.x + self.r3 * self.v3.x
dy = self.r1 * self.v1.y + self.r2 * self.v2.y + self.r3 * self.v3.y
self.vx += dx if self.vx >= 0 else -dx
self.vy += dy if self.vy >= 0 else -dy
でいかがでしょう?
回答欄のソースコードも修正してみました。
2019/12/01 22:37
ありがとうございます!
まだ,BOIDとしての動きには問題があるので修正しようと思います.ありがとうございます.