在你的 Python 遊戲中添加投擲機制
這是仍在進行中的關於使用 Pygame 模塊在 Python 3 中創建電腦遊戲的第十二部分。先前的文章是:
- 通過構建一個簡單的擲骰子遊戲去學習怎麼用 Python 編程
- 使用 Python 和 Pygame 模塊構建一個遊戲框架
- 如何在你的 Python 遊戲中添加一個玩家
- 用 Pygame 使你的遊戲角色移動起來
- 如何向你的 Python 遊戲中添加一個敵人
- 在 Pygame 遊戲中放置平台
- 在你的 Python 遊戲中模擬引力
- 為你的 Python 平台類遊戲添加跳躍功能
- 使你的 Python 遊戲玩家能夠向前和向後跑
- 在你的 Python 平台類遊戲中放一些獎勵
- 添加計分到你的 Python 遊戲
我的上一篇文章本來是這一系列文章的最後一篇,它鼓勵你為這個遊戲編寫自己的附加程序。你們很多人都這麼做了!我收到了一些電子郵件,要求幫助我還沒有涵蓋的常用機制:戰鬥。畢竟,跳起來躲避壞人是一回事,但是有時候讓他們走開是一件非常令人滿意的事。在電腦遊戲中向你的敵人投擲一些物品是很常見的,不管是一個火球、一支箭、一道閃電,還是其它適合遊戲的東西。
與迄今為止你在這個系列中為你的平台遊戲編程的任何東西不同,可投擲物品有一個生存時間。在你投擲一個物品後,它會如期在移動一段距離後消失。如果它是一支箭或其它類似的東西,它可能會在通過屏幕的邊緣時而消失。如果它是一個火球或一道閃電,它可能會在一段時間後熄滅。
這意味著每次生成一個可投擲的物品時,也必須生成一個獨特的衡量其生存時間的標準。為了介紹這個概念,這篇文章演示如何一次只投擲一個物品。(換句話說,每次僅存在一個投擲物品)。 一方面,這是一個遊戲的限制條件,但另一方面,它卻是遊戲本身的運行機制。你的玩家不能每次同時投擲 50 個火球,因為每次僅允許一個投擲物品,所以當你的玩家釋放一個火球來嘗試擊中一名敵人就成為了一項挑戰。而在幕後,這也使你的代碼保持簡單。
如果你想啟用每次投擲多個項目,在完成這篇教程後,通過學習這篇教程所獲取的知識來挑戰你自己。
創建 Throwable 類
如果你跟隨學習這系列的其它文章,那麼你應該熟悉在屏幕上生成一個新的對象基礎的 __init__
函數。這和你用來生成你的 玩家 和 敵人 的函數是一樣的。這裡是生成一個 throwable
對象的 __init__
函數來:
class Throwable(pygame.sprite.Sprite):
"""
生成一個 throwable 對象
"""
def __init__(self, x, y, img, throw):
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.firing = throw
同你的 Player
類或 Enemy
類的 __init__
函數相比,這個函數的主要區別是,它有一個 self.firing
變數。這個變數保持跟蹤一個投擲的物品是否在當前屏幕上活動,因此當一個 throwable
對象創建時,將變數設置為 1
的合乎情理的。
判斷存活時間
接下來,就像使用 Player
和 Enemy
一樣,你需要一個 update
函數,以便投擲的物品在瞄準敵人拋向空中時,它會自己移動。
測定一個投擲的物品存活時間的最簡單方法是偵測它何時離開屏幕。你需要監視的屏幕邊緣取決於你投擲的物品的物理特性。
- 如果你的玩家正在投擲的物品是沿著水平軸快速移動的,像一隻弩箭或箭或一股非常快的魔法力量,而你想監視你遊戲屏幕的水平軸極限。這可以通過
worldx
定義。 - 如果你的玩家正在投擲的物品是沿著垂直方向或同時沿著水平方向和垂直方向移動的,那麼你必須監視你遊戲屏幕的垂直軸極限。這可以通過
worldy
定義。
這個示例假設你投擲的物品向前移動一點並最終落到地面上。不過,投擲的物品不會從地面上反彈起來,而是繼續掉落出屏幕。你可以嘗試不同的設置來看看什麼最適合你的遊戲:
def update(self,worldy):
'''
投擲物理學
'''
if self.rect.y < worldy: #垂直軸
self.rect.x += 15 #它向前移動的速度有多快
self.rect.y += 5 #它掉落的速度有多快
else:
self.kill() #移除投擲對象
self.firing = 0 #解除火力發射
為使你的投擲物品移動地更快,增加 self.rect
的動量值。
如果投擲物品不在屏幕上,那麼該物品將被銷毀,以及釋放其所佔用的寄存器。另外,self.firing
將被設置回 0
以允許你的玩家來進行另一次射擊。
設置你的投擲對象
就像使用你的玩家和敵人一樣,你必須在你的設置部分中創建一個精靈組來保持投擲對象。
此外,你必須創建一個非活動的投擲對象來供開始的遊戲使用。如果在遊戲開始時卻沒有一個投擲對象,那麼玩家在第一次嘗試投擲一柄武器時,投擲將失敗。
這個示例假設你的玩家使用一個火球作為開始的武器,因此,每一個投擲實例都是由 fire
變數指派的。在後面的關卡中,當玩家獲取新的技能時,你可以使用相同的 Throwable
類來引入一個新的變數以使用一張不同的圖像。
在這代碼塊中,前兩行已經在你的代碼中,因此不要重新鍵入它們:
player_list = pygame.sprite.Group() #上下文
player_list.add(player) #上下文
fire = Throwable(player.rect.x,player.rect.y,'fire.png',0)
firepower = pygame.sprite.Group()
注意,每一個投擲對象的起始位置都是和玩家所在的位置相同。這使得它看起來像是投擲對象來自玩家。在第一個火球生成時,使用 0
來顯示 self.firing
是可用的。
在主循環中獲取投擲行為
沒有在主循環中出現的代碼不會在遊戲中使用,因此你需要在你的主循環中添加一些東西,以便能在你的遊戲世界中獲取投擲對象。
首先,添加玩家控制。當前,你沒有火力觸發器。在鍵盤上的按鍵是有兩種狀態的:釋放的按鍵,按下的按鍵。為了移動,你要使用這兩種狀態:按下按鍵來啟動玩家移動,釋放按鍵來停止玩家移動。開火僅需要一個信號。你使用哪個按鍵事件(按鍵按下或按鍵釋放)來觸發你的投擲對象取決於你的品味。
在這個代碼語句塊中,前兩行是用於上下文的:
if event.key == pygame.K_UP or event.key == ord('w'):
player.jump(platform_list)
if event.key == pygame.K_SPACE:
if not fire.firing:
fire = Throwable(player.rect.x,player.rect.y,'fire.png',1)
firepower.add(fire)
與你在設置部分創建的火球不同,你使用一個 1
來設置 self.firing
為不可用。
最後,你必須更新和繪製你的投擲物品。這個順序很重要,因此把這段代碼放置到你現有的 enemy.move
和 player_list.draw
的代碼行之間:
enemy.move() # 上下文
if fire.firing:
fire.update(worldy)
firepower.draw(world)
player_list.draw(screen) # 上下文
enemy_list.draw(screen) # 上下文
注意,這些更新僅在 self.firing
變數被設置為 1 時執行。如果它被設置為 0 ,那麼 fire.firing
就不為 true
,接下來就跳過更新。如果你嘗試做上述這些更新,不管怎樣,你的遊戲都會崩潰,因為在遊戲中將不會更新或繪製一個 fire
對象。
啟動你的遊戲,嘗試挑戰你的武器。
檢測碰撞
如果你玩使用了新投擲技巧的遊戲,你可能會注意到,你可以投擲對象,但是它卻不會對你的敵人有任何影響。
原因是你的敵人沒有被查到碰撞事故。一名敵人可能會被你的投擲物品所擊中,但是敵人卻從來不知道被擊中了。
你已經在你的 Player
類中完成了碰撞檢測,這非常類似。在你的 Enemy
類中,添加一個新的 update
函數:
def update(self,firepower, enemy_list):
"""
檢測火力碰撞
"""
fire_hit_list = pygame.sprite.spritecollide(self,firepower,False)
for fire in fire_hit_list:
enemy_list.remove(self)
代碼很簡單。每個敵人對象都檢查並看看它自己是否被 firepower
精靈組的成員所擊中。如果它被擊中,那麼敵人就會從敵人組中移除和消失。
為集成這些功能到你的遊戲之中,在主循環中調用位於新觸發語句塊中的函數:
if fire.firing: # 上下文
fire.update(worldy) # 上下文
firepower.draw(screen) # 上下文
enemy_list.update(firepower,enemy_list) # 更新敵人
你現在可以嘗試一下你的遊戲了,大多數的事情都如預期般的那樣工作。不過,這裡仍然有一個問題,那就是投擲的方向。
更改投擲機制的方向
當前,你英雄的火球只會向右移動。這是因為 Throwable
類的 update
函數將像素添加到火球的位置,在 Pygame 中,在 X 軸上一個較大的數字意味著向屏幕的右側移動。當你的英雄轉向另一個方向時,你可能希望它投擲的火球也拋向左側。
到目前為止,你已經知道如何實現這一點,至少在技術上是這樣的。然而,最簡單的解決方案卻是使用一個變數,在一定程度上對你來說可能是一種新的方法。一般來說,你可以「設置一個標記」(有時也被稱為「翻轉一個位」)來標明你的英雄所面向的方向。在你做完後,你就可以檢查這個變數來得知火球是向左移動還是向右移動。
首先,在你的 Player
類中創建一個新的變數來代表你的遊戲所面向的方向。因為我的遊戲天然地面向右側,由此我把面向右側作為默認值:
self.score = 0
self.facing_right = True # 添加這行
self.is_jumping = True
當這個變數是 True
時,你的英雄精靈是面向右側的。當玩家每次更改英雄的方向時,變數也必須重新設置,因此,在你的主循環中相關的 keyup
事件中這樣做:
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps, 0)
player.facing_right = False # 添加這行
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0)
player.facing_right = True # 添加這行
最後,更改你的 Throwable
類的 update
函數,以檢測英雄是否面向右側,並恰當地添加或減去來自火球位置的像素:
if self.rect.y < worldy:
if player.facing_right:
self.rect.x += 15
else:
self.rect.x -= 15
self.rect.y += 5
再次嘗試你的遊戲,清除掉你遊戲世界中的一些壞人。
![Python 平台類使用投擲能力](/data/attachment/album/202011/30/124608xvor8t40mmmpsmoh.jpg "Python platformer with throwing capability")
作為一項額外的挑戰,當徹底打敗敵人時,嘗試增加你玩家的得分。
完整的代碼
#!/usr/bin/env python3
# 作者: Seth Kenlon
# GPLv3
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <[http://www.gnu.org/licenses/>][17].
import pygame
import pygame.freetype
import sys
import os
'''
變數
'''
worldx = 960
worldy = 720
fps = 40
ani = 4
world = pygame.display.set_mode([worldx, worldy])
forwardx = 600
backwardx = 120
BLUE = (80, 80, 155)
BLACK = (23, 23, 23)
WHITE = (254, 254, 254)
ALPHA = (0, 255, 0)
tx = 64
ty = 64
font_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "fonts", "amazdoom.ttf")
font_size = tx
pygame.freetype.init()
myfont = pygame.freetype.Font(font_path, font_size)
'''
對象
'''
def stats(score, health):
myfont.render_to(world, (4, 4), "Score:"+str(score), BLUE, None, size=64)
myfont.render_to(world, (4, 72), "Health:"+str(health), BLUE, None, size=64)
class Throwable(pygame.sprite.Sprite):
"""
生成一個投擲的對象
"""
def __init__(self, x, y, img, throw):
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.firing = throw
def update(self, worldy):
'''
投擲物理學
'''
if self.rect.y < worldy:
if player.facing_right:
self.rect.x += 15
else:
self.rect.x -= 15
self.rect.y += 5
else:
self.kill()
self.firing = 0
# x 位置, y 位置, img 寬度, img 高度, img 文件
class Platform(pygame.sprite.Sprite):
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.image.set_colorkey(ALPHA)
self.rect = self.image.get_rect()
self.rect.y = yloc
self.rect.x = xloc
class Player(pygame.sprite.Sprite):
"""
生成一名玩家
"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.movex = 0
self.movey = 0
self.frame = 0
self.health = 10
self.damage = 0
self.score = 0
self.facing_right = True
self.is_jumping = True
self.is_falling = True
self.images = []
for i in range(1, 5):
img = pygame.image.load(os.path.join('images', 'walk' + 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):
if self.is_jumping:
self.movey += 3.2
def control(self, x, y):
"""
控制玩家移動
"""
self.movex += x
def jump(self):
if self.is_jumping is False:
self.is_falling = False
self.is_jumping = True
def update(self):
"""
更新精靈位置
"""
# 向左移動
if self.movex < 0:
self.is_jumping = True
self.frame += 1
if self.frame > 3 * ani:
self.frame = 0
self.image = pygame.transform.flip(self.images[self.frame // ani], True, False)
# 向右移動
if self.movex > 0:
self.is_jumping = True
self.frame += 1
if self.frame > 3 * ani:
self.frame = 0
self.image = self.images[self.frame // ani]
# 碰撞
enemy_hit_list = pygame.sprite.spritecollide(self, enemy_list, False)
if self.damage == 0:
for enemy in enemy_hit_list:
if not self.rect.contains(enemy):
self.damage = self.rect.colliderect(enemy)
if self.damage == 1:
idx = self.rect.collidelist(enemy_hit_list)
if idx == -1:
self.damage = 0 # 設置傷害回 0
self.health -= 1 # 減去 1 單位健康度
ground_hit_list = pygame.sprite.spritecollide(self, ground_list, False)
for g in ground_hit_list:
self.movey = 0
self.rect.bottom = g.rect.top
self.is_jumping = False # 停止跳躍
# 掉落世界
if self.rect.y > worldy:
self.health -=1
print(self.health)
self.rect.x = tx
self.rect.y = ty
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
for p in plat_hit_list:
self.is_jumping = False # 停止跳躍
self.movey = 0
if self.rect.bottom <= p.rect.bottom:
self.rect.bottom = p.rect.top
else:
self.movey += 3.2
if self.is_jumping and self.is_falling is False:
self.is_falling = True
self.movey -= 33 # 跳躍多高
loot_hit_list = pygame.sprite.spritecollide(self, loot_list, False)
for loot in loot_hit_list:
loot_list.remove(loot)
self.score += 1
print(self.score)
plat_hit_list = pygame.sprite.spritecollide(self, plat_list, False)
self.rect.x += self.movex
self.rect.y += self.movey
class Enemy(pygame.sprite.Sprite):
"""
生成一名敵人
"""
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):
"""
敵人移動
"""
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
def update(self, firepower, enemy_list):
"""
檢測火力碰撞
"""
fire_hit_list = pygame.sprite.spritecollide(self, firepower, False)
for fire in fire_hit_list:
enemy_list.remove(self)
class Level:
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, 'tile-ground.png')
ground_list.add(ground)
i = i + 1
if lvl == 2:
print("Level " + str(lvl))
return ground_list
def bad(lvl, eloc):
if lvl == 1:
enemy = Enemy(eloc[0], eloc[1], 'enemy.png')
enemy_list = pygame.sprite.Group()
enemy_list.add(enemy)
if lvl == 2:
print("Level " + str(lvl))
return enemy_list
# x 位置, y 位置, img 寬度, img 高度, img 文件
def platform(lvl, tx, ty):
plat_list = pygame.sprite.Group()
ploc = []
i = 0
if lvl == 1:
ploc.append((200, worldy - ty - 128, 3))
ploc.append((300, worldy - ty - 256, 3))
ploc.append((550, 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, 'tile.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
def loot(lvl):
if lvl == 1:
loot_list = pygame.sprite.Group()
loot = Platform(tx*5, ty*5, tx, ty, 'loot_1.png')
loot_list.add(loot)
if lvl == 2:
print(lvl)
return loot_list
'''
Setup 部分
'''
backdrop = pygame.image.load(os.path.join('images', 'stage.png'))
clock = pygame.time.Clock()
pygame.init()
backdropbox = world.get_rect()
main = True
player = Player() # 生成玩家
player.rect.x = 0 # 轉到 x
player.rect.y = 30 # 轉到 y
player_list = pygame.sprite.Group()
player_list.add(player)
steps = 10
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 0)
firepower = pygame.sprite.Group()
eloc = []
eloc = [300, worldy-ty-80]
enemy_list = Level.bad(1, eloc)
gloc = []
i = 0
while i <= (worldx / tx) + tx:
gloc.append(i * tx)
i = i + 1
ground_list = Level.ground(1, gloc, tx, ty)
plat_list = Level.platform(1, tx, ty)
enemy_list = Level.bad( 1, eloc )
loot_list = Level.loot(1)
'''
主循環
'''
while main:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
try:
sys.exit()
finally:
main = False
if event.type == pygame.KEYDOWN:
if event.key == ord('q'):
pygame.quit()
try:
sys.exit()
finally:
main = False
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'):
player.jump()
if event.type == pygame.KEYUP:
if event.key == pygame.K_LEFT or event.key == ord('a'):
player.control(steps, 0)
player.facing_right = False
if event.key == pygame.K_RIGHT or event.key == ord('d'):
player.control(-steps, 0)
player.facing_right = True
if event.key == pygame.K_SPACE:
if not fire.firing:
fire = Throwable(player.rect.x, player.rect.y, 'fire.png', 1)
firepower.add(fire)
# 向向滾動世界
if player.rect.x >= forwardx:
scroll = player.rect.x - forwardx
player.rect.x = forwardx
for p in plat_list:
p.rect.x -= scroll
for e in enemy_list:
e.rect.x -= scroll
for l in loot_list:
l.rect.x -= scroll
# 向後滾動世界
if player.rect.x <= backwardx:
scroll = backwardx - player.rect.x
player.rect.x = backwardx
for p in plat_list:
p.rect.x += scroll
for e in enemy_list:
e.rect.x += scroll
for l in loot_list:
l.rect.x += scroll
world.blit(backdrop, backdropbox)
player.update()
player.gravity()
player_list.draw(world)
if fire.firing:
fire.update(worldy)
firepower.draw(world)
enemy_list.draw(world)
enemy_list.update(firepower, enemy_list)
loot_list.draw(world)
ground_list.draw(world)
plat_list.draw(world)
for e in enemy_list:
e.move()
stats(player.score, player.health)
pygame.display.flip()
clock.tick(fps)
via: https://opensource.com/article/20/9/add-throwing-python-game
作者:Seth Kenlon 選題:lujun9972 譯者:robsean 校對:wxy
本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive