python - pygame中的弹丸运动和重力仅有时有效
问题描述
所以为了回答另一个问题,我深入研究了 python + pygame projectile motion。基本上我想创建一个精灵,然后当以初始速度和角度“发射”时,其行为将按照重力和牛顿物理学。
我的演示应用程序创建了一堆随机射弹。对于一些射弹,它们在着陆前以正确的抛物线路径向上飞行;完美的!
然而:
- 似乎没有弹丸能够向左移动(方向 270-360 度)
- 一些弹丸永远不会降落
我怀疑这是因为math.cos()
andmath.sin()
函数会根据象限改变结果的符号。我想我也有一个错误的假设,即 0 度是“12 点”,而这实际上是 90 度。
显然,理想的结果是粒子可以左右移动,并且没有粒子飞入轨道。
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()
此应用程序的公式直接取自维基百科文章。
解决方案
在 [-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()
推荐阅读
- vba - 访问 - VBA 无效使用 null
- ios - 无法取消选择 collectionView 中的项目
- gforth - 如何在 Forth 中构建素数生成器?
- symfony - 如何手动将错误添加到 Symfony 中的表单组件?
- elasticsearch - 更改弹性搜索中的评分算法
- glsl - 如何在我的着色器中修复无法识别的 OpenGL 版本?
- android - 在关闭的应用程序中收到通知时转到特定活动?
- angular - Angular 6 使用哪个 Http 模块
- css - 与 Radium 和 Material-UI 一起使用时,React 悬停样式不起作用
- json - 如何从scala中的foreach读取地图值