编写游戏无疑是理解面向对象最有效的方法,在学习python面向对象部分的过程中,我尝试写了如下一个简单的贪吃蛇小游戏,作为理解面向对象、事件触发机制、pygame模块的一个途径。OK,废话少说,上代码。
首先介绍页面布局,为了美观,我们把每块蛇的身体定义为14x14像素的矩形,实物为直径14像素的圆。为什么采用14像素这个值呢?因为在身体大小合适的15像素附近,不想用一个奇数导致确定圆心的过程中会有浮点数产生,增加额外的内存消耗。当然您也可以使用16像素balablabla。游戏标题为“Greedy Snake”,窗口大小采用560x560像素,560/14=40,故横向纵向分别可以产生40个贪吃蛇的身体长度。
定义Snake类,包含初始化蛇的位置和蛇移动的初始方向。用一个嵌套列表来存储蛇的身体的位置,初始蛇向右移动,使用复制身体位置的方式移动蛇。
class Snake(object):
"""This is the snake class, including the initializing, body, eat food, and direction to move."""
def __init__(self):
"""Using the random module to create a initial snake. The snake has three parts:
a head, a body, and a tail."""
self.pos_list = [[42, 42]]
self.direction = 'R'
def posList(self):
"Return all the points of the snake."
return self.pos_list
def changeDirection(self, direction):
"Change the direction of the snake."
self.direction = direction
def moveDirection(self):
"Change the points to keep the snake moving in the given direction."
body_length = len(self.pos_list) - 1
while body_length > 0:
self.pos_list[body_length] = deepcopy(self.pos_list[body_length - 1])
body_length -= 1
if self.direction == 'R':
self.pos_list[0][0] += 14
elif self.direction == 'L':
self.pos_list[0][0] -= 14
elif self.direction == 'U':
self.pos_list[0][1] -= 14
elif self.direction == 'D':
self.pos_list[0][1] += 14
def eatFood(self, foodPoint):
"The snake eats a point and add the point to its body."
self.pos_list.insert(0, foodPoint)
然后,是食物这个类。类中包含对于食物坐标的初始值,和更新食物坐标的方法。
class Food(object):
"""Initialize the random coordinate of the food and return it."""
def __init__(self):
x = randint(5, 35) * 14
y = randint(5, 35) * 14
self.food_position = [x, y]
def foodList(self):
"Return the coordinate of the food point."
return self.food_position
def updateFood(self):
"When the food is eaten, update the position of the food."
self.food_position[0] = randint(0, 39) * 14
self.food_position[1] = randint(0, 39) * 14
接下来,定义一个贪吃蛇的函数,在这个函数中编写贪吃蛇移动的逻辑,更新食物坐标,判断蛇是否吃到了食物、是否撞到了墙上。逻辑上,如前所述,窗口大小采用560x560,使用时钟控制每秒钟的画面不超过20帧从而控制游戏的画面感,屏幕底色为白色,食物和蛇均为红色,当贪吃蛇撞到墙或者撞到自身时,绘制一幅底色为褐色、字体为红色的“Game Over!”图案,并等待退出事件,关闭窗口。
def snakeMain():
"The logic to run the game."
pygame.init()
screen = pygame.display.set_mode((560, 560), 0, 32)
pygame.display.set_caption("Greedy Snake")
clock = pygame.time.Clock()
snake = Snake()
food = Food()
direction_flag = 'R'
while True:
screen.fill((255, 255, 255))
for event in pygame.event.get():
if event.type == QUIT:
exit()
if event.type == KEYDOWN:
if event.key == K_RIGHT:
direction_flag = 'R'
elif event.key == K_LEFT:
direction_flag = 'L'
elif event.key == K_UP:
direction_flag = 'U'
elif event.key == K_DOWN:
direction_flag = 'D'
# Draw the food and the snake
food_position = deepcopy(food.foodList())
food_position[0] += 7
food_position[1] += 7
pygame.draw.circle(screen, (255, 0, 0), food_position, 10)
snake_position = snake.posList()
for pos in snake_position:
temp_rect = pygame.Rect(pos[0], pos[1], 14, 14)
pygame.draw.rect(screen, (255, 0, 0), temp_rect)
pygame.display.update()
# 20 frame of pictures per second
# and move the snake
frame_rate = clock.tick(20)
snake.changeDirection(direction_flag)
snake.moveDirection()
#Judge whether the snake eats a food or not
food_position = deepcopy(food.foodList())
snake_head = deepcopy(snake.posList()[0])
if direction_flag == 'R':
snake_head[0] += 14
elif direction_flag == 'L':
snake_head[0] -= 14
elif direction_flag == 'U':
snake_head[1] -= 14
elif direction_flag == 'D':
snake_head[1] += 14
if snake_head == food_position:
snake.eatFood(food_position)
food.updateFood()
# Judge whether the snake gets a collision or not
gameover_flag = False
snake_position = snake.posList()
snake_head = snake_position[0]
if direction_flag == 'R':
if snake_head[0] > 546:
gameover_flag = True
elif direction_flag == 'L':
if snake_head[0] < 0:
gameover_flag = True
elif direction_flag == 'U':
if snake_head[1] < 0:
gameover_flag = True
elif direction_flag == 'D':
if snake_head[1] > 546:
gameover_flag = True
for pos in range(1, len(snake_position) - 1):
if snake_head == snake_position[pos]:
gameover_flag = True
if gameover_flag:
pygame.font.init()
screen.fill((100, 0, 0))
font = pygame.font.SysFont("arial", 32)
text = font.render("Game Over!", True, (255, 0, 0))
screen.blit(text, (260, 260))
pygame.display.update()
while True:
again_event = pygame.event.poll()
if again_event.type == QUIT:
exit()
最后,判断是否在当前命名空间中执行该主体函数,如果是,则执行贪吃蛇游戏。
if __name__ == '__main__':
snakeMain()
总体来说,这是一个非常简单的贪吃蛇版本,逻辑上还需要继续完善,bug还需要修复。在接下来的修改过程中,我会将它转变成1个像素1个像素之间的移动,而不再将窗口分割成多个矩形,这样可以增加蛇移动的流畅度。另一方面,方向移动方面,向前移动不会再接受向后移动的事件,向左或向右类似,很简单,通过if判断就可以实现。蛇体要采用三幅图像载入,让蛇头、蛇身、蛇尾更美观逼真。加入游戏声音之后,可玩性更强。在标题栏下方增加一个类似扫雷游戏的记分机制,使贪吃蛇能够记录该游戏的得分最高值。