การเคลื่อนที่แบบโพรเจกไทล์และแรงโน้มถ่วงใน pygame ใช้งานได้เพียงบางครั้งเท่านั้น

เพื่อตอบคำถามอื่น ฉันเจาะลึกเกี่ยวกับ python + pygame การเคลื่อนที่แบบโปรเจกไทล์ โดยพื้นฐานแล้ว ฉันต้องการสร้างสไปรท์ จากนั้นเมื่อ "เปิดตัว" ด้วยความเร็วและมุมเริ่มต้นก็จะทำงานตามแรงโน้มถ่วงและฟิสิกส์ของนิวตัน

แอปสาธิตของฉันสร้างโพรเจกไทล์แบบสุ่มจำนวนหนึ่ง สำหรับขีปนาวุธบางลูกพวกมันจะบินขึ้นไปในเส้นทางพาราโบลาที่ถูกต้องก่อนที่จะลงจอด สมบูรณ์แบบ!

อย่างไรก็ตาม:

  • ดูเหมือนไม่มีขีปนาวุธไปทางซ้ายได้ (ในทิศทาง 270-360 องศา)
  • ขีปนาวุธบางลูกไม่เคยตกลงสู่พื้น

ฉัน สงสัย นี่เป็นเพราะฟังก์ชัน math.cos() และ math.sin() ซึ่งจะเปลี่ยนเครื่องหมายของผลลัพธ์ ขึ้นอยู่กับควอแดรนท์ ฉันคิดว่าฉันมีสมมติฐานที่ไม่ดีเช่นกันว่า 0 องศาคือ "12 นาฬิกา" และนี่คือ 90 องศาจริงๆ

ผลลัพธ์ที่ต้องการอย่างชัดเจนก็คือ อนุภาคสามารถเคลื่อนที่ไปทางซ้ายและขวา และไม่มีอนุภาคใดลอยขึ้นสู่วงโคจร

ball.png: ball.png

import pygame
import random
import math

# Window size
WINDOW_WIDTH  =1000
WINDOW_HEIGHT = 400
FPS           = 60

# background colours
INKY_GREY    = ( 128, 128, 128 )

# milliseconds since start
NOW_MS = 0

class ProjectileSprite( pygame.sprite.Sprite ):
    GRAVITY          = -9.8  

    def __init__( self, bitmap, velocity=0, angle=0 ):
        pygame.sprite.Sprite.__init__( self )
        self.image       = bitmap
        self.rect        = bitmap.get_rect()
        self.start_x     = WINDOW_WIDTH // 2
        self.start_y     = WINDOW_HEIGHT - self.rect.height
        self.rect.center = ( ( self.start_x, self.start_y ) )
        # Physics
        self.setInitialVelocityRadians( velocity, angle )

    def setInitialVelocityRadians( self, velocity, angle_rads ):
        global NOW_MS
        self.start_time = NOW_MS
        self.velocity   = velocity
        self.angle      = angle_rads 

    def update( self ):
        global NOW_MS
        if ( self.velocity > 0 ):
            time_change = ( NOW_MS - self.start_time ) / 150.0  # Should be 1000, but 100 looks better
            if ( time_change > 0 ):
                # re-calcualte the velocity
                velocity_x = self.velocity * math.cos( self.angle )
                velocity_y = self.velocity * math.sin( self.angle ) - ( self.GRAVITY * time_change )
                # re-calculate the displacement
                # x
                displacement_x  = velocity_x * time_change * math.cos( self.angle ) 
                # y
                half_gravity_time_squared = ( self.GRAVITY * ( time_change * time_change ) ) / 2.0
                displacement_y  = ( velocity_y * time_change * math.sin( self.angle ) ) - half_gravity_time_squared 

                # reposition sprite
                self.rect.center = ( ( self.start_x + int( displacement_x ), self.start_y - int( displacement_y ) ) )

                # Stop at the bottom of the window
                if ( self.rect.y >= WINDOW_HEIGHT - self.rect.height ):
                    self.rect.y = WINDOW_HEIGHT - self.rect.height
                    self.velocity = 0
                    #self.kill()



### MAIN
pygame.init()
SURFACE = pygame.HWSURFACE | pygame.DOUBLEBUF | pygame.RESIZABLE
WINDOW  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Projectile Motion Example")

# Load resource image(s)
sprite_image = pygame.image.load( "ball.png" )#.convert_alpha()

# Make some sprites 
SPRITES = pygame.sprite.Group()   
for i in range( 20 ):
    speed = random.randrange( 10, 50 )
    if ( random.randrange( -100, 101 ) > 0 ):
        angle = math.radians( random.randrange( 0, 45 ) )  # 0-45 degrees
    else:
        angle = math.radians( random.randrange( 315, 360 ) )  # minus 0-45 degrees
    new_sprite = ProjectileSprite( sprite_image, speed, angle )
    SPRITES.add( new_sprite )


clock = pygame.time.Clock()
done  = False
while not done:
    NOW_MS = pygame.time.get_ticks()

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.KEYDOWN ):
            if ( event.unicode == '+' or event.scancode == pygame.K_PLUS ):
                # Pressing '+' adds a new projectile sprite
                speed = random.randrange( 10,100 )
                angle = math.radians( random.randrange( -45, 45 ) )
                new_sprite = ProjectileSprite( sprite_image, speed, angle )
                SPRITES.add( new_sprite )

    # Handle continuous-keypresses
    keys = pygame.key.get_pressed()
    if ( keys[pygame.K_ESCAPE] ):
        # [Esc] exits too
        done = True

    # Repaint the screen
    WINDOW.fill( INKY_GREY )
    SPRITES.update()          # re-position the sprites
    SPRITES.draw( WINDOW )    # draw the sprites

    pygame.display.flip()
    # Update the window, but not more than 60fps
    clock.tick_busy_loop( FPS )

pygame.quit()

สูตรสำหรับแอพนี้นำมาจากบทความวิกิพีเดียโดยตรง

สาธิตภาพเคลื่อนไหว .gif


person Kingsley    schedule 24.04.2019    source แหล่งที่มา
comment
ใช้ print() เพื่อแสดงค่าในตัวแปรในช่วงเวลาต่างๆ และเพื่อแสดงข้อมูลว่าส่วนใดของโค้ดถูกดำเนินการ สามารถช่วยค้นหาปัญหาได้ หรือเรียนรู้วิธีการใช้ดีบักเกอร์   -  person furas    schedule 24.04.2019
comment
คุณสามารถวิ่งด้วยลูกบอลเพียงลูกเดียวโดยมี FPS ที่น้อยกว่ามากและไม่ต้องทำความสะอาดหน้าจอเพื่อดูวิถีและค่าในตัวแปรในเวลาเดียวกัน   -  person furas    schedule 24.04.2019
comment
ถ้าฉันบวกมุม 270 มันจะดูดีขึ้นแต่ยังคงเคลื่อนที่ไปในทิศทางที่ถูกต้องเท่านั้น angle = math.radians( random.randrange(-45, 45)+270 ) ถ้าฉันใช้ลบใน velocity_x = -self.velocity * math.cos( self.angle ) แล้วลูกบอลทั้งหมดจะเคลื่อนที่ไปในทิศทางซ้ายเท่านั้น คุณสามารถใช้ความเร็วบวกและลบแบบสุ่มได้   -  person furas    schedule 24.04.2019


คำตอบ (1)


ตั้งค่ามุมแบบสุ่มในช่วง [-45, 45]

NOW_MS = pygame.time.get_ticks()
SPRITES = pygame.sprite.Group()   
for i in range( 20 ):
   speed = random.randrange( 10, 50 )
   angle = math.radians( random.randrange( -45, 45 ) )
   new_sprite = ProjectileSprite( sprite_image, speed, angle )
   SPRITES.add( new_sprite )

มุมนี้กำหนดทิศทางขาออกโดยสัมพันธ์กับทิศทางขึ้นของหน้าต่าง ดังนั้น displacement_x ขึ้นอยู่กับ math.sin(self.angle) และ displacement_y ขึ้นอยู่กับ math.cos(self.angle) ต้องเพิ่มหมายเหตุ half_gravity_time_squared เนื่องจาก self.GRAVITY เป็นค่าลบ:

half_gravity_time_squared = self.GRAVITY * time_change * time_change / 2.0
displacement_x = self.velocity * math.sin(self.angle) * time_change 
displacement_y = self.velocity * math.cos(self.angle) * time_change + half_gravity_time_squared

ดูตัวอย่างที่ฉันใช้คำแนะนำกับโค้ดต้นฉบับของคุณ:

import pygame
import random
import math

# Window size
WINDOW_WIDTH  =1000
WINDOW_HEIGHT = 400
FPS           = 60

# background colours
INKY_GREY    = ( 128, 128, 128 )

# milliseconds since start
NOW_MS = 0

class ProjectileSprite( pygame.sprite.Sprite ):
    GRAVITY          = -9.8  

    def __init__( self, bitmap, velocity=0, angle=0 ):
        pygame.sprite.Sprite.__init__( self )
        self.image       = bitmap
        self.rect        = bitmap.get_rect()
        self.start_x     = WINDOW_WIDTH // 2
        self.start_y     = WINDOW_HEIGHT - self.rect.height
        self.rect.center = ( ( self.start_x, self.start_y ) )
        # Physics
        self.setInitialVelocityRadians( velocity, angle )

    def setInitialVelocityRadians( self, velocity, angle_rads ):
        global NOW_MS
        self.start_time = NOW_MS
        self.velocity   = velocity
        self.angle      = angle_rads 

    def update( self ):
        global NOW_MS
        if ( self.velocity > 0 ):
            time_change = ( NOW_MS - self.start_time ) / 150.0  # Should be 1000, but 100 looks better
            if ( time_change > 0 ):

                # re-calcualte the velocity
                half_gravity_time_squared = self.GRAVITY * time_change * time_change / 2.0
                displacement_x = self.velocity * math.sin(self.angle) * time_change 
                displacement_y = self.velocity * math.cos(self.angle) * time_change + half_gravity_time_squared

                # reposition sprite
                self.rect.center = ( ( self.start_x + int( displacement_x ), self.start_y - int( displacement_y ) ) )

                # Stop at the bottom of the window
                if ( self.rect.y >= WINDOW_HEIGHT - self.rect.height ):
                    self.rect.y = WINDOW_HEIGHT - self.rect.height
                    self.velocity = 0
                    #self.kill()

### MAIN
pygame.init()
SURFACE = pygame.HWSURFACE | pygame.DOUBLEBUF | pygame.RESIZABLE
WINDOW  = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE )
pygame.display.set_caption("Projectile Motion Example")

# Load resource image(s)
sprite_image = pygame.image.load( "ball.png" )#.convert_alpha()

# Make some sprites 
NOW_MS = pygame.time.get_ticks()
SPRITES = pygame.sprite.Group()   
for i in range( 20 ):
    speed = random.randrange( 10, 50 )
    angle = math.radians( random.randrange( -45, 45 ) )
    new_sprite = ProjectileSprite( sprite_image, speed, angle )
    SPRITES.add( new_sprite )


clock = pygame.time.Clock()
done  = False
while not done:
    NOW_MS = pygame.time.get_ticks()

    # Handle user-input
    for event in pygame.event.get():
        if ( event.type == pygame.QUIT ):
            done = True
        elif ( event.type == pygame.KEYDOWN ):
            if ( event.unicode == '+' or event.scancode == pygame.K_PLUS ):
                # Pressing '+' adds a new projectile sprite
                speed = random.randrange( 10,100 )
                angle = math.radians( random.randrange( -45, 45 ) )
                new_sprite = ProjectileSprite( sprite_image, speed, angle )
                SPRITES.add( new_sprite )
            if event.key == pygame.K_n:
                for s in SPRITES:
                    s.start_time = NOW_MS
                    s.velocity = random.randrange( 10, 50 )

    # Handle continuous-keypresses
    keys = pygame.key.get_pressed()
    if ( keys[pygame.K_ESCAPE] ):
        # [Esc] exits too
        done = True

    # Repaint the screen
    WINDOW.fill( INKY_GREY )
    SPRITES.update()          # re-position the sprites
    SPRITES.draw( WINDOW )    # draw the sprites

    pygame.display.flip()
    # Update the window, but not more than 60fps
    clock.tick_busy_loop( FPS )

pygame.quit()
person Rabbid76    schedule 24.04.2019