Pygame 级别/菜单状态
Posted
技术标签:
【中文标题】Pygame 级别/菜单状态【英文标题】:Pygame level/menu states 【发布时间】:2013-01-19 23:42:22 【问题描述】:使用下面的代码,实现游戏状态以控制关卡的最简单和最简单的方法是什么?如果我想从标题屏幕开始然后加载一个级别,并在完成后进入下一个级别?如果有人可以解释处理此问题的最简单方法,那就太好了!
import pygame
from pygame import *
WIN_WIDTH = 1120 - 320
WIN_HEIGHT = 960 - 320
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 0
FLAGS = 0
CAMERA_SLACK = 30
def main():
global level
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
timer = pygame.time.Clock()
level = 0
bg = Surface((32,32))
bg.convert()
bg.fill(Color("#0094FF"))
up = left = right = False
entities = pygame.sprite.Group()
player = Player(32, 32)
enemy = Enemy(32,32)
platforms = []
x = 0
y = 0
if level == 0:
level = [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" E ",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPP PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPP P",
" PPPP P",
" PPPP PPPPPPP",
" PPPPPPPPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
"PPPPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPPP PPPP PPPPPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",]
#background = pygame.image.load("Untitled.png")
total_level_width = len(level[0]) * 32
total_level_height = len(level) * 32
# build the level
for row in level:
for col in row:
if col == "P":
p = Platform(x, y)
platforms.append(p)
entities.add(p)
if col == "E":
e = ExitBlock(x, y)
platforms.append(e)
entities.add(e)
x += 32
y += 32
x = 0
camera = Camera(complex_camera, total_level_width, total_level_height)
entities.add(player)
entities.add(enemy)
while 1:
timer.tick(60)
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYUP and e.key == K_UP:
up = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
# draw background
for y in range(20):
for x in range(25):
screen.blit(bg, (x * 32, y * 32))
# draw background
#screen.blit(background, camera.apply((0,0)))
#draw entities
for e in entities:
screen.blit(e.image, camera.apply(e))
# update player, update camera, and refresh
player.update(up, left, right, platforms)
enemy.update(platforms)
camera.update(player)
pygame.display.flip()
class Camera(object):
def __init__(self, camera_func, width, height):
self.camera_func = camera_func
self.state = Rect(0, 0, width, height)
def apply(self, target):
try:
return target.rect.move(self.state.topleft)
except AttributeError:
return map(sum, zip(target, self.state.topleft))
def update(self, target):
self.state = self.camera_func(self.state, target.rect)
def complex_camera(camera, target_rect):
l, t, _, _ = target_rect
_, _, w, h = camera
l, t, _, _ = -l + HALF_WIDTH, -t +HALF_HEIGHT, w, h
l = min(0, l) # stop scrolling left
l = max(-(camera.width - WIN_WIDTH), l) # stop scrolling right
t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling bottom
return Rect(l, t, w, h)
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Player(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.xvel = 0
self.yvel = 0
self.onGround = False
self.image = Surface((32,32))
self.image.fill(Color("#0000FF"))
self.image.convert()
self.rect = Rect(200, 1200, 32, 32)
def update(self, up, left, right, platforms):
if self.rect.top > 1440 or self.rect.top < 0:
main()
if self.rect.left > 1408 or self.rect.right < 0:
main()
if up:
if self.onGround:
self.yvel = 0
self.yvel -= 10 # only jump if on the ground
if left:
self.xvel = -10
if right:
self.xvel = 10
if not self.onGround:
self.yvel += 0.3 # only accelerate with gravity if in the air
if self.yvel > 80: self.yvel = 80 # max falling speed
if not(left or right):
self.xvel = 0
self.rect.left += self.xvel # increment in x direction
self.collide(self.xvel, 0, platforms) # do x-axis collisions
self.rect.top += self.yvel # increment in y direction
self.onGround = False; # assuming we're in the air
self.collide(0, self.yvel, platforms) # do y-axis collisions
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, ExitBlock):
pygame.event.post(pygame.event.Event(QUIT))
if xvel > 0: self.rect.right = p.rect.left
if xvel < 0: self.rect.left = p.rect.right
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
if yvel < 0:
self.rect.top = p.rect.bottom
class Enemy(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.yVel = 0
self.xVel = 0
self.image = Surface((32,32))
self.image.fill(Color("#00FF00"))
self.image.convert()
self.rect = Rect(300, 1200, 32, 32)
self.onGround = False
self.right_dis = False
def update(self, platforms):
if not self.onGround:
self.yVel += 0.3
if self.rect.left == 96:
self.right_dis = False
if self.rect.right == 480:
self.right_dis = True
if not self.right_dis:
self.xVel = 2
if self.right_dis:
self.xVel = -2
self.rect.left += self.xVel # increment in x direction
self.collide(self.xVel, 0, platforms) # do x-axis collisions
self.rect.top += self.yVel # increment in y direction
self.onGround = False; # assuming we're in the air
self.collide(0, self.yVel, platforms) # do y-axis collisions
def collide(self, xVel, yVel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if xVel > 0: self.rect.right = p.rect.left
if xVel < 0: self.rect.left = p.rect.right
if yVel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
if yVel < 0:
self.rect.top = p.rect.bottom
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
#self.image = Surface([32, 32], pygame.SRCALPHA, 32) #makes blocks invisible for much better artwork
self.image = Surface((32,32)) #makes blocks visible for building levels
self.image.convert()
self.rect = Rect(x, y, 32, 32)
def update(self):
pass
class ExitBlock(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image = pygame.image.load("end.png")
if __name__ == "__main__":
main()
【问题讨论】:
一旦你有一个加载关卡的功能,在标题屏幕上点击一个按钮将执行load_map("level1")
。 level1 将进行级别胜利检查,因此在完成该地图后加载下一张地图。 load_map("level2")
等等。
【参考方案1】:
我还制作了一个游戏,其中有一个游戏菜单、一个关卡菜单、一个加载部分和一个游戏部分。 我这样做的方式是在主游戏循环中, 我查看了一堆 elif 语句来确定游戏处于什么“模式”,并执行适当的操作。 它似乎工作得很好,我建议你也试试。
我知道我的代码真的很长,但是如果你转到上面写着#game loop
的地方(使用 ctrl+f 找到它),你可以看到 elifs 来确定模式。希望这会有所帮助。
#basic stuff
import pygame, sys, random
pygame.init()
window=pygame.display.set_mode((1500, 800), pygame.FULLSCREEN)
winrect=window.get_rect()
#colors
GREEN=(19, 225, 30)
BLUE=(41, 2, 245)
YELLOW=(251, 240, 32)
WHITE=(255, 255, 255)
BLACK=(0, 0, 0)
RED=(255, 0, 0)
#text
bigfont=pygame.font.SysFont('calibri', 75)
font=pygame.font.SysFont('calibri', 40)
texts=
so=bigfont.render('Ball Bounce', True, BLUE)
rect=so.get_rect()
rect.top=winrect.top+100
rect.centerx=winrect.centerx
texts['title']=[so, rect]
so=font.render('Start', True, BLUE)
rect=so.get_rect()
so1=pygame.Surface((400, 50))
so2=pygame.Surface((400, 50))
rect1=so1.get_rect()
so1.fill(YELLOW)
so2.fill(RED)
pygame.draw.rect(so1, BLACK, rect1, 5)
pygame.draw.rect(so2, BLACK, rect1, 5)
rect.center=rect1.center
so1.blit(so, rect)
so2.blit(so, rect)
rect1.centerx=winrect.centerx
rect1.top=texts['title'][1].top+300
texts['start']=[so1, rect1, so2]
so=bigfont.render('Levels', True, BLUE)
rect=so.get_rect()
rect.centerx=winrect.centerx
rect.top=winrect.top+100
texts['levels']=[so, rect]
#levels [locked, unlocked, completed/mouseover, rect, state(locked, unlocked, completed)]
levels=[]
lock=pygame.image.load('images/lock.png').convert()
lock=pygame.transform.scale(lock, (100, 100))
lock.set_colorkey(lock.get_at((1, 1)))
for i in range(1, 21):
so=pygame.Surface((100, 100))
so.fill(YELLOW)
rect=so.get_rect()
pygame.draw.rect(so, BLACK, rect, 5)
so1=pygame.Surface((100, 100))
so1.fill(RED)
pygame.draw.rect(so1, BLACK, rect, 5)
text=font.render(str(i), True, BLUE)
textrect=text.get_rect()
textrect.center=rect.center
so.blit(text, textrect)
so1.blit(text, textrect)
locked=pygame.Surface((100, 100))
locked.blit(so, rect)
locked.blit(lock, lock.get_rect())
if i<=5:
rect.top=texts['levels'][1].bottom+25
elif i<=10:
rect.top=levels[0][3].bottom+50
elif i<=15:
rect.top=levels[7][3].bottom+50
else:
rect.top=levels[12][3].bottom+50
if i==1 or i==6 or i==11 or i==16:
rect.right=winrect.centerx-200
elif i==2 or i==7 or i==12 or i==17:
rect.right=winrect.centerx-75
elif i==3 or i==8 or i==13 or i==18:
rect.centerx=winrect.centerx
elif i==4 or i==9 or i==14 or i==19:
rect.left=winrect.centerx+75
else:
rect.left=winrect.centerx+200
if i==1:
levels.append([locked, so, so1, rect, 1])
else:
levels.append([locked, so, so1, rect, 1])
#Wall class (0=horizontal, 1=vertical)
class cwall(pygame.Rect):
'orientation (hor, vert), location, holesize, winrect'
def __init__(self, orientation, location, holesize, winrect):
self.orientation=orientation
if orientation==0:
self.height=5
self.width=winrect.width
self.centery=location
if orientation==1:
self.width=5
self.height=winrect.height
self.centerx=location
self.holesize=holesize
self.bbottomright=round(pygame.mouse.get_pos()[self.orientation]+self.holesize/2)
self.ttopleft=round(pygame.mouse.get_pos()[self.orientation]-self.holesize/2)
def update(self):
self.bbottomright=round(pygame.mouse.get_pos()[self.orientation]+self.holesize/2)
self.ttopleft=round(pygame.mouse.get_pos()[self.orientation]-self.holesize/2)
if self.bbottomright<self.holesize:
self.bbottomright=self.holesize
if self.ttopleft>self.right-self.holesize and self.orientation==0:
self.ttopleft=self.right-self.holesize
if self.ttopleft>self.bottom-self.holesize and self.orientation==1:
self.ttopleft=self.bottom-self.holesize
#Ball Class
class cball(pygame.Rect):
'diameter, speed, color, winrect'
def __init__(self, diameter, speed, color, winrect):
self.width=diameter
self.height=diameter
self.speed=speed
self.color=color
self.direction=random.randint(1, 4)
self.center=(random.randint(round(diameter/2), round(winrect.right-diameter/2)), random.randint(round(diameter/2), round(winrect.bottom-diameter/2)))
def update(self, winrect, walls):
if self.direction/2==round(self.direction/2):
self.right+=self.speed
else:
self.right-=self.speed
if self.direction<=2:
self.top+=self.speed
else:
self.top-=self.speed
for wall in walls:
if wall.collidepoint(self.center):
if wall.orientation==0 and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright):
if self.direction==1:
self.direction=3
self.bottom=wall.top
elif self.direction==2:
self.direction=4
self.bottom=wall.top
elif self.direction==3:
self.direction=1
self.top=wall.bottom
else:
self.direction=2
self.top=wall.bottom
elif wall.orientation==1 and (self.centery<wall.ttopleft or self.centery>wall.bbottomright):
if self.direction==1:
self.direction=2
self.left=wall.right
elif self.direction==2:
self.direction=1
self.right=wall.left
elif self.direction==3:
self.direction=4
self.left=wall.right
else:
self.direction=3
self.right=wall.left
elif wall.orientation==0:
if self.bottom>wall.top and self.centery<wall.top and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright):
if self.direction==1:
self.direction=3
self.bottom=wall.top
elif self.direction==2:
self.direction=4
self.bottom=wall.top
elif self.top<wall.bottom and self.centery>wall.bottom and (self.centerx<wall.ttopleft or self.centerx>wall.bbottomright):
if self.direction==3:
self.direction=1
self.top=wall.bottom
if self.direction==4:
self.direction=2
self.top=wall.bottom
else:
if self.left<wall.right and self.centerx>wall.right and (self.centery<wall.ttopleft or self.centery>wall.bbottomright):
if self.direction==1:
self.direction=2
self.left=wall.right
elif self.direction==3:
self.direction=4
self.left=wall.right
elif self.right>wall.left and self.centerx<wall.left and (self.centery<wall.ttopleft or self.centery>wall.bbottomright):
if self.direction==2:
self.direction=1
self.right=wall.left
if self.direction==4:
self.direction=3
self.right=wall.left
if self.top<0:
if self.direction==3:
self.direction=1
self.top=0
elif self.direction==4:
self.direction=2
self.topn=0
if self.bottom>winrect.bottom:
if self.direction==1:
self.direction=3
self.bottom=winrect.bottom
elif self.direction==2:
self.direction=4
self.bottom=winrect.bottom
if self.left<0:
if self.direction==1:
self.direction=2
self.left=0
elif self.direction==3:
self.direction=4
self.left=0
if self.right>winrect.right:
if self.direction==2:
self.direction=1
self.right=winrect.right
if self.direction==4:
self.direction=3
self.right=winrect.right
for box in boxes:
if box[0].collidepoint(self.center) and self.color==box[1]:
return True
return False
#Game loop setup
mode='title'
#Game loop
while True:
if mode=='title':
#event loop
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
sys.exit()
if event.type==pygame.MOUSEBUTTONDOWN:
if texts['start'][1].collidepoint(event.pos):
mode='levels'
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_ESCAPE:
pygame.quit()
sys.exit()
#screen update
window.fill(GREEN)
mouse=pygame.mouse.get_pos()
if texts['start'][1].collidepoint(mouse):
window.blit(texts['start'][2], texts['start'][1])
else:
window.blit(texts['start'][0], texts['start'][1])
window.blit(texts['title'][0], texts['title'][1])
pygame.display.update()
elif mode=='levels':
#event loop
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
sys.exit()
if event.type==pygame.MOUSEBUTTONDOWN:
for level in levels:
if level[3].collidepoint(event.pos) and level[4]!=0:
mode='loading'
loadinglevel=levels.index(level)+1
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_ESCAPE:
pygame.quit()
sys.exit()
#screen update
window.fill(GREEN)
for level in levels:
if level[3].collidepoint(pygame.mouse.get_pos()) and level[4]==1:
window.blit(level[2], level[3])
else:
window.blit(level[level[4]], level[3])
window.blit(texts['levels'][0], texts['levels'][1])
pygame.display.update()
elif mode=='loading':
if loadinglevel==1:
walls=[cwall(1, winrect.width/2, 100, winrect)]
balls=[]
for i in range(2):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(2):
balls.append(cball(20, 3, YELLOW, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), winrect.height), GREEN), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), YELLOW))
elif loadinglevel==2:
walls=[cwall(1, winrect.width/2, 100, winrect)]
balls=[]
for i in range(4):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(4):
balls.append(cball(20, 3, YELLOW, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), winrect.height), GREEN), (pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), YELLOW))
elif loadinglevel==3:
walls=[cwall(1, winrect.width/3, 100, winrect), cwall(1, 2*winrect.width/3, 100, winrect)]
balls=[]
for i in range(2):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(2):
balls.append(cball(20, 3, YELLOW, winrect))
for i in range(2):
balls.append(cball(20, 3, BLUE, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/3), winrect.height), GREEN), (pygame.Rect(round(winrect.width/3), 0, round(winrect.width/3), winrect.height), YELLOW),
(pygame.Rect(round(2*winrect.width/3), 0, round(winrect.width/3), winrect.height), BLUE))
elif loadinglevel==4:
walls=[cwall(1, winrect.width/3, 100, winrect), cwall(1, 2*winrect.width/3, 100, winrect)]
balls=[]
for i in range(4):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(4):
balls.append(cball(20, 3, YELLOW, winrect))
for i in range(4):
balls.append(cball(20, 3, BLUE, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/3), winrect.height), GREEN), (pygame.Rect(round(winrect.width/3), 0, round(winrect.width/3), winrect.height), YELLOW),
(pygame.Rect(round(2*winrect.width/3), 0, round(winrect.width/3), winrect.height), BLUE))
elif loadinglevel==7:
walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)]
balls=[]
for i in range(2):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(2):
balls.append(cball(20, 3, YELLOW, winrect))
for i in range(2):
balls.append(cball(20, 3, BLUE, winrect))
for i in range(2):
balls.append(cball(20, 3, RED, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), GREEN),
(pygame.Rect(0, round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), RED),
(pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), round(winrect.height/2)), YELLOW),
(pygame.Rect(round(winrect.width/2), round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), BLUE))
elif loadinglevel==8:
walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)]
balls=[]
for i in range(4):
balls.append(cball(20, 3, GREEN, winrect))
for i in range(4):
balls.append(cball(20, 3, YELLOW, winrect))
for i in range(4):
balls.append(cball(20, 3, BLUE, winrect))
for i in range(4):
balls.append(cball(20, 3, RED, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), GREEN),
(pygame.Rect(0, round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), RED),
(pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), round(winrect.height/2)), YELLOW),
(pygame.Rect(round(winrect.width/2), round(winrect.height/2), round(winrect.width/2), round(winrect.height/2)), BLUE))
elif loadinglevel==5:
walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)]
balls=[]
for i in range(10):
balls.append(cball(20, 3, RED, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), RED),
(pygame.Rect(0, round(winrect.height/2), winrect.width, round(winrect.height/2)), WHITE),
(pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), WHITE))
elif loadinglevel==6:
walls=[cwall(1, winrect.width/2, 100, winrect), cwall(0, winrect.height/2, 100, winrect)]
balls=[]
for i in range(20):
balls.append(cball(20, 3, RED, winrect))
boxes=((pygame.Rect(0, 0, round(winrect.width/2), round(winrect.height/2)), RED),
(pygame.Rect(0, round(winrect.height/2), winrect.width, round(winrect.height/2)), WHITE),
(pygame.Rect(round(winrect.width/2), 0, round(winrect.width/2), winrect.height), WHITE))
mode='playing'
elif mode=='playing':
while True:
#event loop
for event in pygame.event.get():
if event.type==pygame.QUIT:
pygame.quit()
sys.exit()
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_ESCAPE:
pygame.quit()
sys.exit()
#updates
updates=[]
for wall in walls:
wall.update()
for ball in balls:
updates.append(ball.update(winrect, walls))
#Seeing if won
won=True
for update in updates:
if not update:
won=False
break
if won:
if levels[loadinglevel][4]==0:
levels[loadinglevel][4]=1
levels[loadinglevel-1][4]=2
mode='levels'
break
#blitting
window.fill(WHITE)
for box in boxes:
pygame.draw.rect(window, box[1], box[0])
for wall in walls:
if wall.orientation==0:
pygame.draw.rect(window, BLACK, (wall.left, wall.top, wall.ttopleft, wall.height))
pygame.draw.rect(window, BLACK, (wall.bbottomright, wall.top, wall.right-wall.bbottomright, wall.height))
else:
pygame.draw.rect(window, BLACK, (wall.left, wall.top, wall.width, wall.ttopleft))
pygame.draw.rect(window, BLACK, (wall.left, wall.bbottomright, wall.width, wall.bottom-wall.holesize))
for ball in balls:
pygame.draw.circle(window, ball.color, ball.center, round(ball.width/2))
pygame.draw.circle(window, BLACK, ball.center, round(ball.width/2), 2)
pygame.display.update()
pygame.time.Clock().tick(100)
【讨论】:
我建议将您的关卡加载放在单独的函数中,以提高可读性 @BartlomiejLewandowski 这可能会有所帮助,但我编写代码时并不知道它会回答这个问题,而且我现在不想更改它。 这只是对未来的一些建议:)【参考方案2】:首先,让我们摆脱这些丑陋的 if 块:
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
if e.type == KEYDOWN and e.key == K_UP:
up = True
if e.type == KEYDOWN and e.key == K_LEFT:
left = True
if e.type == KEYDOWN and e.key == K_RIGHT:
right = True
if e.type == KEYUP and e.key == K_UP:
up = False
if e.type == KEYUP and e.key == K_LEFT:
left = False
if e.type == KEYUP and e.key == K_RIGHT:
right = False
我们可以将它们改写为:
for e in pygame.event.get():
if e.type == QUIT: raise SystemExit, "QUIT"
if e.type == KEYDOWN and e.key == K_ESCAPE:
raise SystemExit, "ESCAPE"
pressed = pygame.key.get_pressed()
up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)]
这稍后会派上用场。
回到主题:我们想要的是一堆不同的场景。每个场景都必须负责自己的屏幕渲染和事件处理。
让我们尝试将现有代码提取到一个游戏场景中,以便以后可以添加其他场景。我们首先创建一个空的Scene
类,它将作为我们场景的基类:
class Scene(object):
def __init__(self):
pass
def render(self, screen):
raise NotImplementedError
def update(self):
raise NotImplementedError
def handle_events(self, events):
raise NotImplementedError
我们的计划是覆盖每个子类中的每个方法,因此我们在基类中提出NotImplementedError
s,以便我们很容易发现是否忘记这样做(我们也可以使用 ABC,但让我们保持简单) .
现在让我们将所有与运行游戏状态相关的内容(基本上就是所有内容)放入一个新的GameScene
类中。
class GameScene(Scene):
def __init__(self):
super(GameScene, self).__init__()
level = 0
self.bg = Surface((32,32))
self.bg.convert()
self.bg.fill(Color("#0094FF"))
up = left = right = False
self.entities = pygame.sprite.Group()
self.player = Player(32, 32)
self.enemy = Enemy(32,32)
self.platforms = []
x = 0
y = 0
if level == 0:
level = [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" E ",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPP PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPP P",
" PPPP P",
" PPPP PPPPPPP",
" PPPPPPPPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
"PPPPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPPP PPPP PPPPPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",]
#background = pygame.image.load("Untitled.png")
total_level_width = len(level[0]) * 32
total_level_height = len(level) * 32
# build the level
for row in level:
for col in row:
if col == "P":
p = Platform(x, y)
self.platforms.append(p)
self.entities.add(p)
if col == "E":
e = ExitBlock(x, y)
self.platforms.append(e)
self.entities.add(e)
x += 32
y += 32
x = 0
self.camera = Camera(complex_camera, total_level_width, total_level_height)
self.entities.add(self.player)
self.entities.add(self.enemy)
def render(self, screen):
for y in range(20):
for x in range(25):
screen.blit(self.bg, (x * 32, y * 32))
for e in self.entities:
screen.blit(e.image, self.camera.apply(e))
def update(self):
pressed = pygame.key.get_pressed()
up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)]
self.player.update(up, left, right, self.platforms)
self.enemy.update(self.platforms)
self.camera.update(self.player)
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN and e.key == K_ESCAPE:
pass #somehow go back to menu
还不完美,但是一个好的开始。与实际游戏玩法相关的所有内容都被提取到自己的类中。有些变量必须是实例变量,因此必须通过self
访问它们。
现在我们需要修改main
-函数来实际使用这个类:
def main():
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
timer = pygame.time.Clock()
running = True
scene = GameScene()
while running:
timer.tick(60)
if pygame.event.get(QUIT):
running = False
return
scene.handle_events(pygame.event.get())
scene.update()
scene.render(screen)
pygame.display.flip()
请注意,我更改了两件小事:我使用pygame.event.get(QUIT)
仅首先获得可能的QUIT
-事件,因为这是我们在主循环中唯一感兴趣的事件。所有其他事件都直接传递到当前场景:scene.handle_events(pygame.event.get())
。
此时,我们可以考虑将一些类提取到他们自己的文件中,但让我们继续吧。
让我们创建一个标题菜单:
class TitleScene(object):
def __init__(self):
super(TitleScene, self).__init__()
self.font = pygame.font.SysFont('Arial', 56)
self.sfont = pygame.font.SysFont('Arial', 32)
def render(self, screen):
# beware: ugly!
screen.fill((0, 200, 0))
text1 = self.font.render('Crazy Game', True, (255, 255, 255))
text2 = self.sfont.render('> press space to start <', True, (255, 255, 255))
screen.blit(text1, (200, 50))
screen.blit(text2, (200, 350))
def update(self):
pass
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN and e.key == K_SPACE:
self.manager.go_to(GameScene(0))
这只是显示一个绿色背景和一些文字。如果玩家按下SPACE,我们想要开始第一关。注意这一行:
self.manager.go_to(GameScene(0))
这里我将参数0
传递给GameScene
类,所以让我们对其进行修改,使其接受这个参数:
class GameScene(Scene):
def __init__(self, level):
...
level = 0
行可以删除,正如您已经猜到的那样。
那么self.manager
是什么?它只是一个为我们管理场景的小助手类。
class SceneMananger(object):
def __init__(self):
self.go_to(TitleScene())
def go_to(self, scene):
self.scene = scene
self.scene.manager = self
它从标题场景开始,并将每个场景的manager
字段设置为自身,以允许更改当前场景。如何实现这样的场景管理器有很多可能性,这是最简单的方法。一个缺点是每个场景都必须知道后面是哪个场景,但这不应该让我们现在感到困扰。
让我们使用我们的新SceneMananger
:
def main():
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
timer = pygame.time.Clock()
running = True
manager = SceneMananger()
while running:
timer.tick(60)
if pygame.event.get(QUIT):
running = False
return
manager.scene.handle_events(pygame.event.get())
manager.scene.update()
manager.scene.render(screen)
pygame.display.flip()
直截了当。让我们快速添加第二个关卡和一个赢/输屏幕,我们就完成了。
class CustomScene(object):
def __init__(self, text):
self.text = text
super(CustomScene, self).__init__()
self.font = pygame.font.SysFont('Arial', 56)
def render(self, screen):
# ugly!
screen.fill((0, 200, 0))
text1 = self.font.render(self.text, True, (255, 255, 255))
screen.blit(text1, (200, 50))
def update(self):
pass
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN:
self.manager.go_to(TitleScene())
下面是完整的代码。注意Player
类的变化:它不再调用main
函数,而是调用场景中的方法来指示玩家到达出口或死亡。
另外,我改变了玩家和敌人的位置。现在,您指定实体将在关卡中出现的位置。例如。 Player(5, 40)
将在关卡的第 5 列第 40 行创建玩家。作为奖励,敌人可以进行正确的碰撞检测。
我将关卡的描述提取到名为levels
的字典中,因此可以根据需要轻松更改和添加关卡(稍后,您可能希望每个关卡一个文件,所以这是一个好的开始)。它可以扩展以保持玩家的起始位置,但您也可以创建一个特殊的图块,例如*
用于起始位置,E
用于关卡描述中的敌人。
import pygame
from pygame import *
WIN_WIDTH = 1120 - 320
WIN_HEIGHT = 960 - 320
HALF_WIDTH = int(WIN_WIDTH / 2)
HALF_HEIGHT = int(WIN_HEIGHT / 2)
DISPLAY = (WIN_WIDTH, WIN_HEIGHT)
DEPTH = 0
FLAGS = 0
CAMERA_SLACK = 30
levels = 0: 'level': [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" E ",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPP PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPP P",
" PPPP P",
" PPPP PPPPPPP",
" PPPPPPPPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
"PPPPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPPP PPPP PPPPPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",],
'enemies': [(9, 38)],
1: 'level': [
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" E ",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPPP PPPPPPPPPPPPPPPP",
" PPPPPPPPPPPPPPPP",
" PPPP P",
" PPPP P",
" PPPP PPPPPPP",
" PPPPPPPPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
" PPPP PPPPPPP",
"PPPPP PPPP PPPPPPP",
"PPP PPPPPPPPPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPP PPPPPPP",
"PPP PPPPPPPP PPPP PPPPPPP",
"PPP PPPP",
"PPP PPPP",
"PPP PPPPP PPPP",
"PPP P PPPPPPPPPPPPPPPPPP",
"PPP P PPPPPPPPPPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",
"PPPPPPPPPPPPPPP PPPPPPPPPPPPPPPPPP",],
'enemies': [(9, 38), (18, 38), (15, 15)]
class Scene(object):
def __init__(self):
pass
def render(self, screen):
raise NotImplementedError
def update(self):
raise NotImplementedError
def handle_events(self, events):
raise NotImplementedError
class GameScene(Scene):
def __init__(self, levelno):
super(GameScene, self).__init__()
self.bg = Surface((32,32))
self.bg.convert()
self.bg.fill(Color("#0094FF"))
up = left = right = False
self.entities = pygame.sprite.Group()
self.player = Player(5, 40)
self.player.scene = self
self.platforms = []
self.levelno = levelno
levelinfo = levels[levelno]
self.enemies = [Enemy(*pos) for pos in levelinfo['enemies']]
level = levelinfo['level']
total_level_width = len(level[0]) * 32
total_level_height = len(level) * 32
# build the level
x = 0
y = 0
for row in level:
for col in row:
if col == "P":
p = Platform(x, y)
self.platforms.append(p)
self.entities.add(p)
if col == "E":
e = ExitBlock(x, y)
self.platforms.append(e)
self.entities.add(e)
x += 32
y += 32
x = 0
self.camera = Camera(complex_camera, total_level_width, total_level_height)
self.entities.add(self.player)
for e in self.enemies:
self.entities.add(e)
def render(self, screen):
for y in range(20):
for x in range(25):
screen.blit(self.bg, (x * 32, y * 32))
for e in self.entities:
screen.blit(e.image, self.camera.apply(e))
def update(self):
pressed = pygame.key.get_pressed()
up, left, right = [pressed[key] for key in (K_UP, K_LEFT, K_RIGHT)]
self.player.update(up, left, right, self.platforms)
for e in self.enemies:
e.update(self.platforms)
self.camera.update(self.player)
def exit(self):
if self.levelno+1 in levels:
self.manager.go_to(GameScene(self.levelno+1))
else:
self.manager.go_to(CustomScene("You win!"))
def die(self):
self.manager.go_to(CustomScene("You lose!"))
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN and e.key == K_ESCAPE:
self.manager.go_to(TitleScene())
class CustomScene(object):
def __init__(self, text):
self.text = text
super(CustomScene, self).__init__()
self.font = pygame.font.SysFont('Arial', 56)
def render(self, screen):
# ugly!
screen.fill((0, 200, 0))
text1 = self.font.render(self.text, True, (255, 255, 255))
screen.blit(text1, (200, 50))
def update(self):
pass
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN:
self.manager.go_to(TitleScene())
class TitleScene(object):
def __init__(self):
super(TitleScene, self).__init__()
self.font = pygame.font.SysFont('Arial', 56)
self.sfont = pygame.font.SysFont('Arial', 32)
def render(self, screen):
# ugly!
screen.fill((0, 200, 0))
text1 = self.font.render('Crazy Game', True, (255, 255, 255))
text2 = self.sfont.render('> press space to start <', True, (255, 255, 255))
screen.blit(text1, (200, 50))
screen.blit(text2, (200, 350))
def update(self):
pass
def handle_events(self, events):
for e in events:
if e.type == KEYDOWN and e.key == K_SPACE:
self.manager.go_to(GameScene(0))
class SceneMananger(object):
def __init__(self):
self.go_to(TitleScene())
def go_to(self, scene):
self.scene = scene
self.scene.manager = self
def main():
pygame.init()
screen = pygame.display.set_mode(DISPLAY, FLAGS, DEPTH)
pygame.display.set_caption("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
timer = pygame.time.Clock()
running = True
manager = SceneMananger()
while running:
timer.tick(60)
if pygame.event.get(QUIT):
running = False
return
manager.scene.handle_events(pygame.event.get())
manager.scene.update()
manager.scene.render(screen)
pygame.display.flip()
class Camera(object):
def __init__(self, camera_func, width, height):
self.camera_func = camera_func
self.state = Rect(0, 0, width, height)
def apply(self, target):
try:
return target.rect.move(self.state.topleft)
except AttributeError:
return map(sum, zip(target, self.state.topleft))
def update(self, target):
self.state = self.camera_func(self.state, target.rect)
def complex_camera(camera, target_rect):
l, t, _, _ = target_rect
_, _, w, h = camera
l, t, _, _ = -l + HALF_WIDTH, -t +HALF_HEIGHT, w, h
l = min(0, l) # stop scrolling left
l = max(-(camera.width - WIN_WIDTH), l) # stop scrolling right
t = max(-(camera.height-WIN_HEIGHT), t) # stop scrolling bottom
return Rect(l, t, w, h)
class Entity(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
class Player(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.xvel = 0
self.yvel = 0
self.onGround = False
self.image = Surface((32,32))
self.image.fill(Color("#0000FF"))
self.image.convert()
self.rect = Rect(x*32, y*32, 32, 32)
def update(self, up, left, right, platforms):
if self.rect.top > 1440 or self.rect.top < 0:
self.scene.die()
if self.rect.left > 1408 or self.rect.right < 0:
self.scene.die()
if up:
if self.onGround:
self.yvel = 0
self.yvel -= 10 # only jump if on the ground
if left:
self.xvel = -10
if right:
self.xvel = 10
if not self.onGround:
self.yvel += 0.3 # only accelerate with gravity if in the air
if self.yvel > 80: self.yvel = 80 # max falling speed
if not(left or right):
self.xvel = 0
self.rect.left += self.xvel # increment in x direction
if self.collide(self.xvel, 0, platforms): # do x-axis collisions
self.rect.top += self.yvel # increment in y direction
self.onGround = False; # assuming we're in the air
self.collide(0, self.yvel, platforms) # do y-axis collisions
def collide(self, xvel, yvel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if isinstance(p, ExitBlock):
self.scene.exit()
return False
if xvel > 0: self.rect.right = p.rect.left
if xvel < 0: self.rect.left = p.rect.right
if yvel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
if yvel < 0:
self.rect.top = p.rect.bottom
return True
class Enemy(Entity):
def __init__(self, x, y):
Entity.__init__(self)
self.yVel = 0
self.xVel = 2 # start moving immediately
self.image = Surface((32,32))
self.image.fill(Color("#00FF00"))
self.image.convert()
self.rect = Rect(x*32, y*32, 32, 32)
self.onGround = False
def update(self, platforms):
if not self.onGround:
self.yVel += 0.3
# no need for right_dis to be a member of the class,
# since we know we are moving right if self.xVel > 0
right_dis = self.xVel > 0
# create a point at our left (or right) feet
# to check if we reached the end of the platform
m = (1, 1) if right_dis else (-1, 1)
p = self.rect.bottomright if right_dis else self.rect.bottomleft
fp = map(sum, zip(m, p))
# if there's no platform in front of us, change the direction
collide = any(p for p in platforms if p.rect.collidepoint(fp))
if not collide:
self.xVel *= -1
self.rect.left += self.xVel # increment in x direction
self.collide(self.xVel, 0, platforms) # do x-axis collisions
self.rect.top += self.yVel # increment in y direction
self.onGround = False; # assuming we're in the air
self.collide(0, self.yVel, platforms) # do y-axis collisions
def collide(self, xVel, yVel, platforms):
for p in platforms:
if pygame.sprite.collide_rect(self, p):
if xVel > 0:
self.rect.right = p.rect.left
self.xVel *= -1 # hit wall, so change direction
if xVel < 0:
self.rect.left = p.rect.right
self.xVel *= -1 # hit wall, so change direction
if yVel > 0:
self.rect.bottom = p.rect.top
self.onGround = True
if yVel < 0:
self.rect.top = p.rect.bottom
class Platform(Entity):
def __init__(self, x, y):
Entity.__init__(self)
#self.image = Surface([32, 32], pygame.SRCALPHA, 32) #makes blocks invisible for much better artwork
self.image = Surface((32,32)) #makes blocks visible for building levels
self.image.convert()
self.rect = Rect(x, y, 32, 32)
def update(self):
pass
class ExitBlock(Platform):
def __init__(self, x, y):
Platform.__init__(self, x, y)
self.image = Surface((32,32)) #makes blocks visible for building levels
self.image.convert()
self.rect = Rect(x, y, 32, 32)
if __name__ == "__main__":
main()
【讨论】:
这非常有用,感谢您如此深入地解释事情!这真的非常有帮助,教会了我很多。我有一个关于您用于与墙壁碰撞的碰撞检测的问题,我可以通过检查与实体 [] 的碰撞来轻松检查与玩家的碰撞,如果碰撞是玩家,那么做某事,我正确吗?跨度> @user1758231 一个简单的方法是获取玩家的rect
并使用Rect.collidelist
检查它是否与敌人的矩形碰撞,例如if self.player.rect.collidelist(e.rect for e in self.enemies) ...
以上是关于Pygame 级别/菜单状态的主要内容,如果未能解决你的问题,请参考以下文章