在Python Pygame游戏中实现引力模拟与碰撞检测
本文介绍如何在Python Pygame游戏中模拟真实的引力效果。通过编写gravity函数控制玩家垂直运动,实现角色下落逻辑,并结合碰撞检测防止角色穿模。文章详细讲解了如何设置地面边界、处理落地反弹以及优化游戏循环中的物理更新,为后续添加跳跃和平台交互奠定基础。
Pygame游戏引力模拟概述
学习如何利用Python的Pygame模块开发电脑游戏,并掌握基础引力效果的实现方法。
现实世界由运动和物理规律主导,充满了动态变化。物理学描述了物质在空间中的运动方式。由于电脑游戏世界缺乏真实的物质实体,因此不存在天然的物理法则,程序员必须通过代码手动模拟这些物理行为。
在大多数电脑游戏中,物理模拟主要聚焦于两个核心方面:引力作用和碰撞检测。
当你在游戏中添加敌人时,通常会实现碰撞检测逻辑。本文将在此基础上进一步引入引力机制,因为引力的实现依赖于碰撞检测。思考一下引力为何需要碰撞检测?无需过度担心,随着示例代码的开发,这一逻辑将变得显而易见。
在现实世界中,引力是物体间相互吸引的倾向,质量越大的物体产生的引力越强。但在电脑游戏物理模拟中,无需创建具有巨大质量的物体来精确计算引力;只需在游戏世界中编程设定一个物体向假设的最大质量对象(如地面)下落的趋势即可。
实现引力函数与主循环调用
添加引力函数逻辑
回顾玩家对象,其已具备控制运动的属性。利用这些属性,我们可以编写代码将玩家精灵向下拉向屏幕底部。
在Pygame坐标系中,Y轴数值越大,位置越接近屏幕底部。
虽然现实世界中引力作用于所有物体,但在平台游戏中,引力通常是选择性的。如果将引力应用于整个游戏世界,所有平台都会坠落到地面。因此,我们仅对玩家和敌人精灵应用引力,而保持平台静止。
首先,在你的 Player 类中定义一个 gravity 函数:
def gravity(self):
self.movey += 3.2 # 玩家掉落的多快
这是一个基础函数。无论玩家意图如何运动,该函数首先强制设置玩家的垂直运动状态。即,代码确保玩家始终处于下落状态,这本质上就是引力的表现。
为了让引力函数生效,必须在游戏的主循环中调用它。这样,每次处理游戏循环时,Python都会将下落运动应用到玩家身上。
在代码的主循环中,添加以下第一行调用:
player.gravity() # 检查引力
player.update()
启动游戏观察效果。注意下落速度可能较快:玩家会从屏幕顶部迅速下落,并立即掉出游戏屏幕范围。
你的引力模拟功能正常,但可能过于剧烈。
作为测试,尝试调整玩家下落的速度参数。
添加地面碰撞与落地处理
为引力添加地面限制
当前游戏无法检测角色掉落出世界边界的问题。在某些游戏中,如果玩家掉出世界,精灵会被删除并在新位置重生;在另一些游戏中,玩家会失去分数或生命。无论你想实现何种机制,都必须能够侦测玩家何时消失在屏幕外。
在Python中,可以使用 if 语句来检查特定条件。
你需要判断玩家是否正在下落,以及下落的程度。如果玩家落到屏幕底部,则执行相应操作。为简化处理,将玩家精灵的位置设置为距离底部边缘上方 20 像素处。
使你的 gravity 函数逻辑如下所示:
def gravity(self):
self.movey += 3.2 # 玩家掉落的多快
if self.rect.y > worldy and self.movey >= 0:
self.movey = 0
self.rect.y = worldy-ty
然后启动游戏。精灵依然会下落,但会在屏幕底部停止。不过,你可能无法在地面层之上看到精灵。一个简单的解决方法是,在精灵碰撞游戏世界底部后,通过在新 Y 位置加上另一个 -ty,使精灵弹跳得更高:
def gravity(self):
self.movey += 3.2 # 玩家掉落的多快
if self.rect.y > worldy and self.movey >= 0:
self.movey = 0
self.rect.y = worldy-ty-ty
现在,你的玩家在屏幕底部弹跳,且恰好位于地面精灵上方。
后续扩展与完整代码
玩家真正需要的是反抗引力的能力。引力的难点在于,除非有物体提供反作用力来推开引力,否则无法反抗它。因此在接下来的文章中,你将添加地面和平台碰撞检测以及跳跃功能。在此期间,尝试将引力逻辑应用到敌人精灵上。
以下是完整的代码示例:
#!/usr/bin/env python3
# draw a world
# add a player and player control
# add player movement
# add enemy and basic collision
# add platform
# add gravity
# GNU All-Permissive License
# Copying and distribution of this file, with or without modification,
# are permitted in any medium without royalty provided the copyright
# notice and this notice are preserved. This file is offered as-is,
# without any warranty.
import pygame
import sys
import os
'''
Objects
'''
class Platform(pygame.sprite.Sprite):
# x location, y location, img width, img height, img file
def __init__(self,xloc,yloc,imgw,imgh,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img)).convert()
self.image.convert_alpha()
self.rect = self.image.get_rect()
self.rect.y = yloc
self.rect.x = xloc
class Player(pygame.sprite.Sprite):
'''
Spawn a player
'''
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.score = 1
self.images = []
for i in range(1,9):
img = pygame.image.load(os.path.join('images','hero' + str(i) + '.png')).convert()
img.convert_alpha()
img.set_colorkey(ALPHA)
self.images.append(img)
self.image = self.images[0]
self.rect = self.image.get_rect()
def gravity(self):
self.movey += 3.2 # how fast player falls
if self.rect.y > worldy and self.movey >= 0:
self.movey = 0
self.rect.y = worldy-ty-ty
def control(self,x,y):
'''
control player movement
'''
self.movex += x
self.movey += y
def update(self):
'''
Update sprite position
'''
self.rect.x = self.rect.x + self.movex
self.rect.y = self.rect.y + self.movey
# moving left
if self.movex < 0:
self.frame += 1
if self.frame > ani*3:
self.frame = 0
self.image = self.images[self.frame//ani]
# moving right
if self.movex > 0:
self.frame += 1
if self.frame > ani*3:
self.frame = 0
self.image = self.images[(self.frame//ani)+4]
# collisions
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
for enemy in enemy_hit_list:
self.health -= 1
print(self.health)
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
for g in ground_hit_list:
self.health -= 1
print(self.health)
class Enemy(pygame.sprite.Sprite):
'''
Spawn an enemy
'''
def __init__(self,x,y,img):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(os.path.join('images',img))
#self.image.convert_alpha()
#self.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.counter = 0
def move(self):
'''
enemy movement
'''
distance = 80
speed = 8
if self.counter >= 0 and self.counter <= distance:
self.rect.x += speed
elif self.counter >= distance and self.counter <= distance*2:
self.rect.x -= speed
else:
self.counter = 0
self.counter += 1
class Level():
def bad(lvl,eloc):
if lvl == 1:
enemy = Enemy(eloc[0],eloc[1],'yeti.png') # spawn enemy
enemy_list = pygame.sprite.Group() # create enemy group
enemy_list.add(enemy) # add enemy to group
if lvl == 2:
print("Level " + str(lvl) )
return enemy_list
def loot(lvl,lloc):
print(lvl)
def ground(lvl,gloc,tx,ty):
ground_list = pygame.sprite.Group()
i=0
if lvl == 1:
while i < len(gloc):
ground = Platform(gloc[i],worldy-ty,tx,ty,'ground.png')
ground_list.add(ground)
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return ground_list
def platform(lvl,tx,ty):
plat_list = pygame.sprite.Group()
ploc = []
i=0
if lvl == 1:
ploc.append((0,worldy-ty-128,3))
ploc.append((300,worldy-ty-256,3))
ploc.append((500,worldy-ty-128,4))
while i < len(ploc):
j=0
while j <= ploc[i][2]:
plat = Platform((ploc[i][0]+(j*tx)),ploc[i][1],tx,ty,'ground.png')
plat_list.add(plat)
j=j+1
print('run' + str(i) + str(ploc[i]))
i=i+1
if lvl == 2:
print("Level " + str(lvl) )
return plat_list
'''
Setup
'''
worldx = 960
worldy = 720
fps = 40 # frame rate
ani = 4 # animation cycles
clock = pygame.time.Clock()
pygame.init()
main = True
BLUE = (25,25,200)
BLACK = (23,23,23 )
WHITE = (254,254,254)
ALPHA = (0,255,0)
world = pygame.display.set_mode([worldx,worldy])
backdrop = pygame.image.load(os.path.join('images','stage.png')).convert()
backdropbox = world.get_rect()
player = Player() # spawn player
player.rect.x = 0
player.rect.y = 0
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10 # how fast to move
eloc = []
eloc = [200,20]
gloc = []
#gloc = [0,630,64,630,128,630,192,630,256,630,320,630,384,630]
tx = 64 #tile size
ty = 64 #tile size
i=0
while i <= (worldx/tx)+tx:
gloc.append(i*tx)
i=i+1
enemy_list = Level.bad( 1, eloc )
ground_list = Level.ground( 1,gloc,tx,ty )
plat_list = Level.platform( 1,tx,ty )
'''
Main loop
'''
while main == True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit()
main = False
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_LEFT or event.key == ord('a'):
print("LEFT")
player.control(-steps,0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
print("RIGHT")
player.control(steps,0)
if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps,0)
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps,0)
if event.key == pygame.K_UP or event.key == ord('w'):
print('jump')
if event.key == ord('q'):
pygame.quit()
sys.exit()
main = False
world.blit(backdrop, backdropbox)
player.gravity() # check gravity
player.update()
player_list.draw(world)
enemy_list.draw(world)
ground_list.draw(world)
plat_list.draw(world)
for e in enemy_list:
e.move()
pygame.display.flip()
clock.tick(fps)
总结
到此这篇关于在你的 Python 游戏中模拟引力的文章就介绍到这了,更多相关在你的 Python 游戏中模拟引力内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













