Day 22 - PongGame 問題詢問

大家好,由於 Day 22 的 PongGame 我想嘗試跟老師不同的做法,但遇到個問題卡住了,想上來請教大家。

老師在課程中提供的解法是球一開始以 x + 10 跟 y + 10 的方式前進,碰到牆壁反彈的時候方向 * -1,我想做的方式是給一個初始角度(不限制 45度),再計算反彈角度後往那個方向移動。

目前遇到的問題如下,看起來球是有順利往另外一個方向反彈,但可能因為與 y.cor() 距離條件設定的關係,球最後在高度 277.13 跟 294.45 之間不斷反彈跳不出來。

想請教球與牆壁碰撞的判斷式該怎麼改比較好?
謝謝

while game_is_on:
    ball.move(ball.initial_heading, 10)
    screen.update()
    time.sleep(0.05)

    while ball.ycor() > 290 or ball.ycor() < -290:
        prev_heading = ball.initial_heading
        bounce_heading = 360 - prev_heading
        ball.bounce(bounce_heading)

球的位置 log:
(265.00,285.79)
(270.00,294.45)
(280.00,277.13)
(285.00,285.79)
(290.00,294.45)
(300.00,277.13)
(305.00,285.79)
(310.00,294.45)
(320.00,277.13)
(325.00,285.79)
(330.00,294.45)
(340.00,277.13)
(345.00,285.79)
(350.00,294.45)
(360.00,277.13)

課程使用的 Turtle Class 文件:turtle — Turtle graphics — Python 3.9.2 documentation

程式碼截圖如下:

1 Like

看起來是個大工程,當你解決牆的問題後,可能還會有球拍的問題。

假設開始球向上走,與水平X軸夾角45度,那麼行走距離是

move : x : y = 1.414 : 1 : 1

水平X軸夾角越小 Y 就越小,有可能就會在設定的區間彈不停。

但我測不出會在上下牆彈不停,只是有時會在球拍跟牆之間彈跳。

在老師用球拍中間測擊中的距離時,我就覺得會出問題,

也就是球拍若長一點(我技術差,一開始就設大球拍),

球拍兩端就會出錯,很明顯有擊中,卻落過去了。

我個人覺得老師上課只是教大家學 python 入門的用法,

並不是求最佳解,所以她的程式有些是有問題的,但不妨礙教學。

我也對本題作了一些改動,也想過用各種角度彈跳,但實在太花時間,

有礙學習進度,所以放棄了。

還是等全部上完課程,而我依然有那個熱度,再回頭改吧!

1 Like

同意,我也會繼續上後面的課程,老師教的解法先能夠理解、應用為主,優化留著之後有餘力再進行。

謝謝你的分享 :+1:

2 Likes

(3/5 更新:感謝 @rabrus 說明。本 video和老師的解法類似,老師的比較詳細,所以大家可以不用看)

之後還感興趣的話,可以參考這個教學(使用 turtle)。不過我還沒看,想等上到這堂課時,自己想過、寫過,再來參考。

YouTube 說明中,還附 source code 連結,佛心講師 。

2 Likes

我有點忍不住寂寞,所以先看了。
結果他的作法跟老師的完全一樣,只是老師用 Class,他改 function 而已。
也是用球拍中心計算擊中條件,球拍大一點也是出問題。
感覺老師的還比較詳細。

1 Like

這個有解釋 水平 跟 垂直 的碰撞
希望有幫助理解

1 Like

while loop每次iterate,角度都被重設了

main.py Ln27
ball.initial_heading

Ln30 可以把 ball.heading() 也 print 看看

1 Like

print 了角度後發現盲點,剛剛已經試成功了,方式如下:

main.py

from turtle import Screen
from paddle import Paddle
from ball import Ball
from scoreboard import Scoreboard
import time

screen = Screen()
screen.bgcolor("black")
screen.setup(width=800, height=600)
screen.title("Pong")
screen.tracer(0)

r_paddle = Paddle((350, 0))
l_paddle = Paddle((-350, 0))

ball = Ball()
scoreboard = Scoreboard()

screen.listen()
screen.onkey(r_paddle.up, "Up")
screen.onkey(r_paddle.down, "Down")
screen.onkey(l_paddle.up, "w")
screen.onkey(l_paddle.down, "s")


game_is_on = True
heading = ball.initial_heading

while game_is_on:
    ball.move(heading, 10)
    print(heading)
    screen.update()
    time.sleep(0.05)
    # Detect collision with wall
    if ball.ycor() > 290 or ball.ycor() < -290:
        heading = 360 - heading
        ball.move(heading, 10)
        print(heading)
    #Detect collision with paddle
    if ball.distance(r_paddle) < 50 and ball.xcor() > 320 or ball.distance(l_paddle) < 50 and ball.xcor() < -320:
        heading = 540 - heading
        ball.move(heading, 10)
    #Detect R paddle misses
    if ball.xcor() > 380:
        heading = heading + 180
        ball.reset_position(heading)
        scoreboard.l_point()
        print(heading)
    #Detect L paddle misses:
    if ball.xcor() < -380:
        heading = heading + 180
        ball.reset_position(heading)
        scoreboard.r_point()
        print(heading)

ball.py

from turtle import Turtle

INITIALHEADING = 50

class Ball(Turtle):
    def __init__(self):
        super().__init__()
        self.penup()
        self.shape("circle")
        self.color("white")
        self.speed("fastest")
        self.initial_heading = INITIALHEADING

    def move(self, heading, movement):
        self.setheading(heading)
        self.forward(movement)

    def reset_position(self, heading):
        self.goto(0, 0)
        self.move(heading, 10)


會出現問題的原因主要應該是之前把初始 heading 放在 while loop 裡面,導致改為反彈的角度後馬上又變回初始角度。

謝謝大家的幫忙! :pray:

2 Likes

題外話

這個遊戲,我看了老師跟樓主的實現方式
發現都是以 ball 為核心,一直觀察它

ball 會發生的事情有:

  1. 移動
  2. 跟牆壁發生碰撞
  3. 跟球拍發生碰撞
  4. 重設位置

如果我在球場上,加一些陷阱,例如坑洞、改變方向等等,
我預想到 main.py 的 while-loop,當中的 條件判斷 會愈來愈多,難以判斷。

請問 將觀察的視點 改為:
「球場上發生的事情」,以及「影響的對象」,

這個思考方向正確嗎?

我的理解是:

  1. 判斷這些 event 分別是由哪個物件或條件觸發。
  2. 思考有共通點的 event 可以怎樣寫最有效率且具備面對未來需求變動的彈性。

基於上面兩點,只是剛好 PongGame 的 event 觸發都跟 ball 有關,所以才會看起來像是只以 ball 為主體去撰寫各種條件判斷。

假設玩家可以按某個鍵發動一次讓對手 paddle 移動速度變慢的陷阱,這個 event 跟 ball 無關,這邊就得另外寫陷阱的物件以及觸發條件,也會更複雜。

其實跟你提的「球場上發生的事情」,以及「影響的對象」應該一樣,只是剛好觀察的結論是以 ball 為主要 event 觸發者去架構程式邏輯。要換成別的寫法應該也可以,不過沒特別這樣嘗試就是了@@