pygame 中的弹丸运动和重力仅有时起作用 [英] Projectile motion and gravity in pygame only sometimes working

查看:66
本文介绍了pygame 中的弹丸运动和重力仅有时起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

为了回答另一个问题,我深入研究了 python + pygame

导入pygame随机导入导入数学# 窗口大小WINDOW_WIDTH =1000WINDOW_HEIGHT = 400每秒帧数 = 60# 背景颜色INKY_GREY = ( 128, 128, 128 )# 自开始以来的毫秒数现在_MS = 0类 ProjectileSprite( pygame.sprite.Sprite ):重力 = -9.8def __init__( self, bitmap, velocity=0, angle=0 ):pygame.sprite.Sprite.__init__(self)self.image = 位图self.rect = bitmap.get_rect()self.start_x = WINDOW_WIDTH//2self.start_y = WINDOW_HEIGHT - self.rect.heightself.rect.center = ( ( self.start_x, self.start_y ) )# 物理self.setInitialVelocityRadians( 速度,角度)def setInitialVelocityRadians(self,velocity,angle_rads):全球NOW_MSself.start_time = NOW_MSself.velocity = 速度self.angle = angle_rads定义更新(自我):全球NOW_MS如果( self.velocity > 0 ):time_change = ( NOW_MS - self.start_time )/150.0 # 应该是 1000,但 100 看起来更好如果( time_change > 0 ):# 重新计算速度速度_x = self.velocity * math.cos(self.angle)速度_y = self.velocity * math.sin( self.angle ) - ( self.GRAVITY * time_change )# 重新计算位移# X位移_x = 速度_x * 时间_变化 * math.cos(self.angle)#yhalf_gravity_time_squared = ( self.GRAVITY * ( time_change * time_change ) )/2.0位移_y = (velocity_y * time_change * math.sin(self.angle)) - half_gravity_time_squared# 重新定位精灵self.rect.center = ((self.start_x + int(displacement_x), self.start_y - int(displacement_y)))# 停在窗口底部如果( self.rect.y >= WINDOW_HEIGHT - self.rect.height ):self.rect.y = WINDOW_HEIGHT - self.rect.heightself.velocity = 0#self.kill()### 主要的pygame.init()表面 = pygame.HWSURFACE |pygame.DOUBLEBUF |pygame.RESIZABLEWINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE)pygame.display.set_caption("弹丸运动示例")# 加载资源图片sprite_image = pygame.image.load( "ball.png" )#.convert_alpha()# 制作一些精灵精灵 = pygame.sprite.Group()对于范围内的 i(20):速度 = random.randrange( 10, 50 )如果( random.randrange( -100, 101 ) > 0 ):角度 = math.radians(random.randrange(0, 45)) # 0-45 度别的:角度 = math.radians(random.randrange(315, 360)) # 减去 0-45 度new_sprite = ProjectileSprite( sprite_image, speed, angle )SPRITES.add( new_sprite )时钟 = pygame.time.Clock()完成 = 错误虽然没有完成:NOW_MS = pygame.time.get_ticks()# 处理用户输入对于 pygame.event.get() 中的事件:如果( event.type == pygame.QUIT ):完成 = 真elif ( event.type == pygame.KEYDOWN ):如果( event.unicode == '+' 或 event.scancode == pygame.K_PLUS ):# 按+"添加一个新的弹丸精灵速度 = random.randrange( 10,100 )角度 = math.radians(random.randrange(-45, 45))new_sprite = ProjectileSprite( sprite_image, speed, angle )SPRITES.add( new_sprite )# 处理连续按键键 = pygame.key.get_pressed()如果(键[pygame.K_ESCAPE]):# [Esc] 也退出完成 = 真# 重新绘制屏幕WINDOW.fill( INKY_GREY )SPRITES.update() # 重新定位精灵SPRITES.draw( WINDOW ) # 绘制精灵pygame.display.flip()# 更新窗口,但不超过60fpsclock.tick_busy_loop( FPS )pygame.quit()

此应用的公式直接取自维基百科文章.

解决方案

在 [-45, 45] 范围内设置一个随机角度.

NOW_MS = pygame.time.get_ticks()精灵 = pygame.sprite.Group()对于范围内的 i(20):速度 = random.randrange( 10, 50 )角度 = 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位移_x = self.velocity * math.sin(self.angle) * time_change位移_y = self.velocity * math.cos(self.angle) * time_change + half_gravity_time_squared

查看示例,其中我将建议应用于您的原始代码:

导入pygame随机导入导入数学# 窗口大小WINDOW_WIDTH =1000WINDOW_HEIGHT = 400每秒帧数 = 60# 背景颜色INKY_GREY = ( 128, 128, 128 )# 自开始以来的毫秒数现在_MS = 0类 ProjectileSprite( pygame.sprite.Sprite ):重力 = -9.8def __init__( self, bitmap, velocity=0, angle=0 ):pygame.sprite.Sprite.__init__(self)self.image = 位图self.rect = bitmap.get_rect()self.start_x = WINDOW_WIDTH//2self.start_y = WINDOW_HEIGHT - self.rect.heightself.rect.center = ( ( self.start_x, self.start_y ) )# 物理self.setInitialVelocityRadians( 速度,角度)def setInitialVelocityRadians(self,velocity,angle_rads):全球NOW_MSself.start_time = NOW_MSself.velocity = 速度self.angle = angle_rads定义更新(自我):全球NOW_MS如果( self.velocity > 0 ):time_change = ( NOW_MS - self.start_time )/150.0 # 应该是 1000,但 100 看起来更好如果( time_change > 0 ):# 重新计算速度half_gravity_time_squared = self.GRAVITY * time_change * time_change/2.0位移_x = self.velocity * math.sin(self.angle) * time_change位移_y = self.velocity * math.cos(self.angle) * time_change + half_gravity_time_squared# 重新定位精灵self.rect.center = ((self.start_x + int(displacement_x), self.start_y - int(displacement_y)))# 停在窗口底部如果( self.rect.y >= WINDOW_HEIGHT - self.rect.height ):self.rect.y = WINDOW_HEIGHT - self.rect.heightself.velocity = 0#self.kill()### 主要的pygame.init()SURFACE = pygame.HWSURFACE |pygame.DOUBLEBUF |pygame.RESIZABLEWINDOW = pygame.display.set_mode( ( WINDOW_WIDTH, WINDOW_HEIGHT ), SURFACE)pygame.display.set_caption("弹丸运动示例")# 加载资源图片sprite_image = pygame.image.load( "ball.png" )#.convert_alpha()# 制作一些精灵NOW_MS = pygame.time.get_ticks()精灵 = pygame.sprite.Group()对于范围内的 i(20):速度 = random.randrange( 10, 50 )角度 = math.radians(random.randrange(-45, 45))new_sprite = ProjectileSprite( sprite_image, speed, angle )SPRITES.add( new_sprite )时钟 = pygame.time.Clock()完成 = 错误虽然没有完成:NOW_MS = pygame.time.get_ticks()# 处理用户输入对于 pygame.event.get() 中的事件:如果( event.type == pygame.QUIT ):完成 = 真elif ( event.type == pygame.KEYDOWN ):如果( event.unicode == '+' 或 event.scancode == pygame.K_PLUS ):# 按+"添加一个新的弹丸精灵速度 = random.randrange( 10,100 )角度 = math.radians(random.randrange(-45, 45))new_sprite = ProjectileSprite( sprite_image, speed, angle )SPRITES.add( new_sprite )如果 event.key == pygame.K_n:对于 SPRITES 中的 s:s.start_time = NOW_MSs.velocity = random.randrange( 10, 50 )# 处理连续按键键 = pygame.key.get_pressed()如果(键[pygame.K_ESCAPE]):# [Esc] 也退出完成 = 真# 重新绘制屏幕WINDOW.fill( INKY_GREY )SPRITES.update() # 重新定位精灵SPRITES.draw( WINDOW ) # 绘制精灵pygame.display.flip()# 更新窗口,但不超过60fpsclock.tick_busy_loop( FPS )pygame.quit()

So to answer another question, I delved into python + pygame projectile motion. Basically I wanted to create a sprite, then when "launched" with an initial velocity and angle would behave as per gravity and Newtonian physics.

My demo app creates a bunch of random projectiles. For some of the projectiles they fly up, in the correct parabolic path, before landing; perfect!

However:

  • No projectiles seem to be able to go left (in the direction 270-360 degrees)
  • Some projectiles never land

I suspect this is because of the math.cos() and math.sin() functions which will change sign of the result, depending on the quadrant. I think I also have a bad assumption that 0 degrees is "12 o'clock", and that this is really 90 degrees.

Obviously the desired outcome is that particles can go both left and right, and that no particles fly off into orbit.

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()

Formulae for this app were taken directly from the wikipedia article.

解决方案

Set a random angle in range [-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 )

This angle defines the outgoing direction in relation to the upward direction of the window. Thus displacement_x depends on math.sin(self.angle) and displacement_y depends on math.cos(self.angle). Note half_gravity_time_squared has to be added, because self.GRAVITY is a negative value:

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

See the example, where I applied the suggestions to your original code:

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()

这篇关于pygame 中的弹丸运动和重力仅有时起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆