Python不能做游戏?游戏实战之-----《ink spill》(附游戏完整源码)
Posted 易果啥笔
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python不能做游戏?游戏实战之-----《ink spill》(附游戏完整源码)相关的知识,希望对你有一定的参考价值。
嗨喽~小伙伴们,大家早上好,中午好,晚上好呀,
通过前两章对Pygame的学习,我们了解了它的基本使用,附上链接:
2. Python不能做游戏?Pygame中的对象你了解吗?
现在,我们就开始真正动手写一个游戏。
这个游戏名字为:ink spill,中文名:墨水溢出。这是Python中一个非常典型的游戏,我们首先来看看游戏长什么样子以及应该怎么玩:
小伙伴们看完后,应该差不多明白这个游戏的玩法了。
现在,让我们站在“设计者”的角度来考虑,这个游戏应该怎么去制作。
一般说来,游戏制作要考虑三个方面的内容:
- 游戏道具 (图片,音效等)
- 逻辑控制 (游戏状态的逻辑控制)
- UI设计 (游戏界面的设计)
游戏道具 ,主要通过两种方式获得:一是加载图片,二是用代码直接绘制。
本游戏中,需要用到的图片有以下几个:
其余的,咱均用代码直接绘制。
分析一下游戏主界面:
它主要由以下几个部分组成:
- 界面正中央的大格子
- 界面左边的生命“计”
- 界面正下方的六个不同颜色的调色板
- 界面右下方的“重新游戏”按钮和“设置”按钮
在编写游戏之前,我们考虑一个问题:如何设计游戏的难度?
大伙首先想到的肯定是,控制小格子的数量,数量越多,难度越大:
除此之外,关于游戏难度,咱还可以创建一个 boxesToChange 变量,随机将某个小格子的周围格子变成与之同色,这样的格子数用boxesToChange来描述,这样,“简单难度”就将boxesToChange的值设大一点(使得同色的越多),“困难难度”就将boxesToChange的值设小一点(使得同色的越少)(或者直接设为0)。
前面几章说过,一个游戏会有各种各样的游戏状态,比如角色的血量,武器类型等。首先,让我们思考一下,在这个游戏中,需要哪些变量来存储哪些游戏状态(先自己想一想......)。
不知道小伙伴们能想到多少个游戏状态,答案在下面的代码中:
# 根据设置界面,有不同的整个格子尺寸、小格子数量和生命
# 小格子大小
SMALL_BOX_SIZE = 60 # 大小以像素为单位
MEDIUM_BOX_SIZE = 20
LARGE_BOX_SIZE = 11
# 整个格子的大小
SMALL_BOARD_SIZE = 6 # 大小以一个小格子为单位
MEDIUM_BOARD_SIZE = 17
LARGE_BOARD_SIZE = 30
# 最多几次操作(生命)
SMALL_MAX_LIFE = 10
MEDIUM_MAX_LIFE = 30
LARGE_MAX_LIFE = 64
FPS = 30
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
boxSize = MEDIUM_BOX_SIZE
PALETTE_GAP_SIZE = 10 # 调色板间隔大小
PALETTE_SIZE = 45
EASY = 0 # 难度:简单
MEDIUM = 1 # 难度:中等
HARD = 2 # 难度:困难
difficulty = MEDIUM # 游戏以难度“中等”模式开始
maxLife = MEDIUM_MAX_LIFE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE
# R G B
WHITE = (255, 255, 255)
DARKGRAY = (70, 70, 70)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
# 每个方案中的第一种颜色是背景色,接下来的六种是调色板颜色。
COLOR_SCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE),
((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45),
(241, 109, 149)),
((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0),
(197, 97, 211)),
((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)),
((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7),
(88, 155, 213)),
((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227),
(212, 86, 185)))
# 对颜色的处理
for i in range(len(COLOR_SCHEMES)):
assert len(COLOR_SCHEMES[i]) == 7, '颜色方案 %s 没有7种颜色!.' % (i)
# 背景色,调色板设置默认色
bgColor = COLOR_SCHEMES[0][0]
paletteColors = COLOR_SCHEMES[0][1:]
你会看到,记录游戏状态,用到了非常多的变量。依据变量的名称,咱可以很容易推出这个变量的作用是什么。实际上,之后的游戏逻辑控制,就是在更改这些变量的值。
接着,我们来考虑编写整个游戏框架。
接下来编写的代码会非常之多,请小伙伴们仔细体会每个函数的具体作用,这样才能更好的理解整个游戏。
框架编写如下:
def main():
# 需要用到函数之外的变量
global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE
# 初始化
pygame.init()
# 控制帧率
FPS_CLOCK = pygame.time.Clock()
# 创建窗口
DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
# 加载图片
LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
SPOT_IMAGE = pygame.image.load('inkspillspot.png')
SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')
# 设置窗口标题
pygame.display.set_caption('墨水溢出')
# 生成界面
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
# 记录上次点击界面下方调色板的颜色
lastPaletteClicked = None
while True: # 主游戏循环
paletteClicked = None
resetGame = False
# 画屏幕
DISPLAY_SURF.fill(bgColor)
# 加载图标和相关按钮
drawLogoAndButtons()
# 加载正中央的大格子并随机初始化每个小格子的颜色
drawBoard(mainBoard)
# 加载左侧的"生命计"
drawLifeMeter(life)
# 加载屏幕底部的六个调色板
drawPalettes()
# 判断玩家是否想要退出游戏
checkForQuit()
代码中有许多函数如
generateRandomBoard(),
我们还未实现,下面我们来实现这些函数:
一. generateRandomBoard() :
产生游戏主窗口-----中央的整个大格子:
def generateRandomBoard(width, height, difficulty=MEDIUM):
# 为整个大格子中的每个小格子创建具有随机颜色的数据结构。
board = []
for x in range(width):
column = []
for y in range(height):
column.append(random.randint(0, len(paletteColors) - 1))
board.append(column)
# 通过将一些小格子设置为与相邻格子相同的颜色,使同色整个大格子更容易。
# 确定要更改的小格子数。
if difficulty == EASY:
if boxSize == SMALL_BOX_SIZE:
boxesToChange = 100
else:
boxesToChange = 1500
elif difficulty == MEDIUM:
if boxSize == SMALL_BOX_SIZE:
boxesToChange = 5
else:
boxesToChange = 200
else:
boxesToChange = 0
# 挂邻居格子的颜色:
for i in range(boxesToChange):
# 随机选择要复制其颜色的框
x = random.randint(1, width - 2)
y = random.randint(1, height - 2)
# 随机选择要更改的邻居格子
direction = random.randint(0, 3)
if direction == 0: # 左,下
board[x - 1][y] = board[x][y]
board[x][y - 1] = board[x][y]
elif direction == 1: # 右,上
board[x + 1][y] = board[x][y]
board[x][y + 1] = board[x][y]
elif direction == 2: # 左,下
board[x][y - 1] = board[x][y]
board[x + 1][y] = board[x][y]
else: # 左,上
board[x][y + 1] = board[x][y]
board[x - 1][y] = board[x][y]
return board
二. drawLogoAndButtons() :
给界面加上部分按钮和图标:
def drawLogoAndButtons():
# 绘制墨水溢出徽标、设置和重置按钮。
# bait()函数用于将图片加载到DISPALY_SURF这个Surface对象上
DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0))
DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE,
(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height()))
DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height()))
代码中使用了大量坐标运算来控制图片的位置,请小伙伴们细细体会。
三. drawBoard(mainBoard) :
加载界面正中央的大格子并随机初始化每个小格子的颜色:
def drawBoard(board, transparency=255): # 透明度设置默认为255
# 彩色方块将绘制到临时曲面,然后绘制到DISPLAY_SURF曲面。这样我们就可以在DISPLAY_SURF的顶部绘制具有透明度的正方形了。
tempSurf = pygame.Surface(DISPLAY_SURF.get_size()) # 获取窗口尺寸
tempSurf = tempSurf.convert_alpha() # 支持透明
tempSurf.fill((0, 0, 0, 0)) # 先全部处理为黑色
for x in range(boardWidth):
for y in range(boardHeight):
# 获取每个小格子左上角的坐标
left, top = leftTopPixelCoordOfBox(x, y)
r, g, b = paletteColors[board[x][y]]
pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize))
left, top = leftTopPixelCoordOfBox(0, 0)
# 为防止边缘的小格子颜色与窗口背景色相同,在整个大格子外面包一层黑色细线,厚度为1px
pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1)
DISPLAY_SURF.blit(tempSurf, (0, 0))
代码中调用了leftTopPixelCoordOfBox(x,y),实现如下:
def leftTopPixelCoordOfBox(boxx, boxy):
# 返回某个小格子最左上方像素的x和y。
# 注意整个大格子位于窗口的正中央
x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2) # x_margin 为整个大格子最左边与窗口最左边的间距
y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2) # y_margin 为整个大格子最上边与窗口最上边的间距
return boxx * boxSize + x_margin, boxy * boxSize + y_margin
四. drawLifeMeter(life) :
绘制游戏界面左边的生命“计”并进行简单的生命“逻辑计算”:
def drawLifeMeter(currentLife):
# '生命计' 竖直放置,与上下边缘各相距20px
lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife)
# 绘制"生命计"的背景色,'生命计'的左上角位于坐标(20,20),宽20px,高20 + (maxLife * lifeBoxSize) px
pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize)))
for i in range(maxLife):
if currentLife >= (maxLife - i): # 画一个实心的红色方框
pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize))
# 加1px白色的边框
pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1)
在图形的位置定位上,我们用了大量的坐标运算,请小伙伴们细细体会。
五. drawPalettes() :
给界面添上正下方的六个调色板并进行简单的逻辑控制:
def drawPalettes():
# 在屏幕底部绘制六个调色板
numColors = len(paletteColors)
x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
for i in range(numColors):
left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
top = WINDOW_HEIGHT - PALETTE_SIZE - 10
pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE))
# 为了美观,在格子外2px再加2px厚度的对应颜色的区域
pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2)
六. checkForQuit():
玩家想要退出游戏时的处理:
def checkForQuit():
# 如果存在任何退出事件,则终止程序
for event in pygame.event.get(QUIT): # 获取所有退出事件
pygame.quit() # 如果存在任何退出事件,则终止
sys.exit()
for event in pygame.event.get(KEYUP): # 获取所有KEYUP(按键按下)事件
if event.key == K_ESCAPE:
pygame.quit() # 如果KEYUP(按键按下)事件是针对Esc键的,则终止
sys.exit()
pygame.event.post(event) # 将其他KEYUP(按键按下)事件对象放回原处
接着,我们来考虑游戏的主要逻辑控制。
由于我们并未创建按钮,所以当“鼠标点击”这个事件发生时,我们尝试着获取其点击的位置坐标(利用Event对象的 pos 属性:mousex, mousey = event.pos),将这个坐标与界面上某个图标的位置区域比较(使用pygame.Rest.collidepoint(mousex, mousey)函数),如果在这个区域内,就说明玩家想要点击该图标来实现对应的功能(如鼠标点击“reset”图标表示玩家想重新开始游戏),我们编写代码做出响应即可。基于这样的思想,咱可以将main()函数补充完整:
def main():
global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE
pygame.init()
FPS_CLOCK = pygame.time.Clock()
DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
# 加载图片
LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
SPOT_IMAGE = pygame.image.load('inkspillspot.png')
SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')
# 设置窗口标题
pygame.display.set_caption('墨水溢出')
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
while True: # 主游戏循环
paletteClicked = None
resetGame = False
# 画屏幕
DISPLAY_SURF.fill(bgColor)
# 加载图标和相关按钮
drawLogoAndButtons()
# 加载正中央的大格子并随机初始化每个小格子的颜色
drawBoard(mainBoard)
# 加载左侧的"生命计"
drawLifeMeter(life)
# 加载屏幕底部的六个调色板
drawPalettes()
# 判断玩家是否想要退出游戏
checkForQuit()
for event in pygame.event.get(): # 事件处理循环
if event.type == MOUSEBUTTONUP: # 如果事件是鼠标点击
mousex, mousey = event.pos # 获取鼠标点击的坐标
# 如果点击的是"SETTINGS"字样处
if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(),
SETTINGS_BUTTON_IMAGE.get_width(),
SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
# 就展示设置界面
resetGame = showSettingsScreen() # showSettingsScreen() 待实现
# 如果点击的是"RESET"字样处
elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(),
RESET_BUTTON_IMAGE.get_width(),
RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
# 就重新开始游戏
resetGame = True
else:
# 检查是否点击了调色板按钮,如果点击了,返回六个调色板中点击的那一个颜色的索引
paletteClicked = getColorOfPaletteAt(mousex, mousey) # getColorOfPaletteAt()待实现
if paletteClicked is not None and paletteClicked != lastPaletteClicked:
# 单击的调色板按钮与上次单击的调色板按钮不同,防止鼠标意外单击同一调色板两次
lastPaletteClicked = paletteClicked
# 对大格子填充颜色,floodAnimation()待实现
floodAnimation(mainBoard, paletteClicked)
# 点击一次,"生命"减少1
life -= 1
resetGame = False
if hasWon(mainBoard): # 如果赢了,hasWon()待实现
for i in range(4): # 成功的界面效果:闪烁边框4次
flashBorderAnimation(WHITE, mainBoard) # flashBorderAnimation()待实现
# "闪光"结束后,重新开始游戏
resetGame = True
# 暂停2s后再开始游戏
pygame.time.wait(2000)
elif life == 0:
# 生命降为零,玩家失败
drawLifeMeter(0)
# 更新界面
pygame.display.update()
# 等待0.4s
pygame.time.wait(400)
# 失败的结束效果:用黑色"闪光"4次
for i in range(4):
flashBorderAnimation(BLACK, mainBoard)。 # flashBorderAnimation()待实现
resetGame = True
# 暂停2s后开始重新游戏
pygame.time.wait(2000)
if resetGame:
# 重新开始游戏
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
上述主要逻辑控制代码中,还有部分函数未实现:
showSettingsScreen() :展示“设置”界面并提供相应逻辑控制:
def showSettingsScreen():
# 获取全局变量
global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor
# 此函数中的像素坐标是通过将inkspillsettings.png图像加载到图形编辑器中并从中读取像素坐标获得的
origDifficulty = difficulty
origBoxSize = boxSize
screenNeedsRedraw = True
while True:
if screenNeedsRedraw:
DISPLAY_SURF.fill(bgColor)
# 加载"设置"图片
DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0))
# 将"墨迹"图片标记放在选定的颜色左边
if difficulty == EASY:
DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4))
if difficulty == MEDIUM:
DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41))
if difficulty == HARD:
DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76))
# 将墨迹标记放置在选定尺寸旁边
if boxSize == SMALL_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150))
if boxSize == MEDIUM_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185))
if boxSize == LARGE_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220))
# 加载设置界面右边的颜色选择框
for i in range(len(COLOR_SCHEMES)):
drawColorSchemeBoxes(500, i * 60 + 30, i) # 待实现
# 更新界面
pygame.display.update()
screenNeedsRedraw = False # 默认情况下,不重新绘制屏幕
# 事件处理循环
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYUP:
if event.key == K_ESCAPE:
# 设置屏幕上的Esc键返回游戏
return not (origDifficulty == difficulty and origBoxSize == boxSize)
elif event.type == MOUSEBUTTONUP:
screenNeedsRedraw = True # 屏幕应该重新绘制
mousex, mousey = event.pos # 鼠标点击的坐标
# 检查难度按钮上是否有咔嗒声
if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
difficulty = EASY
elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
difficulty = MEDIUM
elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
difficulty = HARD
# 检查尺寸按钮上是否有点击
elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
# 小板尺寸设置:
boxSize = SMALL_BOX_SIZE
boardWidth = SMALL_BOARD_SIZE
boardHeight = SMALL_BOARD_SIZE
maxLife = SMALL_MAX_LIFE
elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey):
# 中板尺寸设置:
boxSize = MEDIUM_BOX_SIZE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE
maxLife = MEDIUM_MAX_LIFE
elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
# 大板尺寸设置:
boxSize = LARGE_BOX_SIZE
boardWidth = LARGE_BOARD_SIZE
boardHeight = LARGE_BOARD_SIZE
maxLife = LARGE_MAX_LIFE
elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
# 点击“Back To Game”按钮
return not (origDifficulty == difficulty and origBoxSize == boxSize)
for i in range(len(COLOR_SCHEMES)):
# 单击颜色方案按钮
if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex,
mousey):
bgColor = COLOR_SCHEMES[i][0]
paletteColors = COLOR_SCHEMES[i][1:]
上述代码中调用了 drawColorSchemeBoxes():实现如下:
def drawColorSchemeBoxes(x, y, schemeNum):
# 绘制“设置”屏幕上显示的颜色方案框
for boxy in range(2):
for boxx in range(3):
pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1],
(x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE))
if paletteColors == COLOR_SCHEMES[schemeNum][1:]:
# 将"墨迹"图片放置在所选配色方案旁边
DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y))
getColorOfPaletteAt(mousex, mousey) :获取被点击的调色板颜色的索引:
def getColorOfPaletteAt(x, y):
# 返回x和y参数覆盖的调色板颜色的索引
numColors = len(paletteColors)
xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
# 调色板底端距窗口底端10px
top = WINDOW_HEIGHT - PALETTE_SIZE - 10
for i in range(numColors):
# 六个调色板左上角的坐标:(left,top)
left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE)
# 找出鼠标单击区域是否在任何调色板内
if r.collidepoint(x, y):
return i # 如果在,则返回所点击的调色板的序号
# 如果x和y不在任何调色板上,则返回None
return None
hasWon(): 判断玩家是否胜利,实现如下:
def hasWon(board):
# 如果整个棋盘颜色相同,则表示玩家获胜
for x in range(boardWidth):
for y in range(boardHeight):
# 只要发现一个颜色与左上角的颜色不同,玩家还没有赢
if board[x][y] != board[0][0]:
return False
return True
floodAnimation(mainBoard, paletteClicked):用paletteClicked对应的颜色填充mainBoard(中央的大格子):
def floodAnimation(board, paletteClicked, animationSpeed=25):
origBoard = copy.deepcopy(board) # 深拷贝整个大格子
# 从左上角处的格子开始同色填充,floodFill()待实现
floodFill(board, board[0][0], paletteClicked, 0, 0)
for transparency in range(0, 255, animationSpeed):
# “新”大格子在大格子上慢慢变得不透明
drawBoard(origBoard)
# 更新透明度
drawBoard(board, transparency)
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
floodFill() :填充算法,实现如下:
def floodFill(board, oldColor, newColor, x, y):
# 洪水填充算法
if oldColor == newColor or board[x][y] != oldColor:
# 如果左上角的颜色和点击颜色没有相邻,直接返回
return
board[x][y] = newColor # 更改当前格子的颜色
# 对任何相邻格子进行递归调用:
if x > 0:
floodFill(board, oldColor, newColor, x - 1, y)
if x < boardWidth - 1:
floodFill(board, oldColor, newColor, x + 1, y)
if y > 0:
floodFill(board, oldColor, newColor, x, y - 1)
if y < boardHeight - 1:
floodFill(board, oldColor, newColor, x, y + 1)
flashBorderAnimation(Color, mainBoard):游戏胜利或失败的结束画面:
def flashBorderAnimation(color, board, animationSpeed=30):
# "闪光"效果结束后,需要回到原来(游戏刚结束)的界面,此处先保存原来的界面
origSurf = DISPLAY_SURF.copy()
flashSurf = pygame.Surface(DISPLAY_SURF.get_size())
flashSurf = flashSurf.convert_alpha()
# 实现"闪光"效果
for start, end, step in ((0, 256, 1), (255, 0, -1)):
# 外循环的第一次迭代将内循环的透明度设置为从0到255,第二次迭代将透明度设置为从255到0。这就是“闪光”。
for transparency in range(start, end, animationSpeed * step):
DISPLAY_SURF.blit(origSurf, (0, 0))
r, g, b = color
flashSurf.fill((r, g, b, transparency))
DISPLAY_SURF.blit(flashSurf, (0, 0))
drawBoard(board) # 在透明层的顶部绘制板
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
# 绘制原来(游戏刚结束)的界面
DISPLAY_SURF.blit(origSurf, (0, 0))
......我猜,小伙伴们可能已经懵了......但是做游戏,考虑的东西会非常之多,也许咱只有好好的理解每一个细节,才能真正做出好的游戏。
最后,附上游戏完整代码:
import random, sys, copy, pygame
from pygame.locals import * # 将所有的 Pygame 常量导入
# 根据设置界面,有不同的大格子尺寸、小格子数量和寿命
# 小格子大小
SMALL_BOX_SIZE = 60 # 大小以像素为单位
MEDIUM_BOX_SIZE = 20
LARGE_BOX_SIZE = 11
# 整个格子的大小
SMALL_BOARD_SIZE = 6 # 大小以一个小格子为单位
MEDIUM_BOARD_SIZE = 17
LARGE_BOARD_SIZE = 30
# 最多几次操作(生命)
SMALL_MAX_LIFE = 10
MEDIUM_MAX_LIFE = 30
LARGE_MAX_LIFE = 64
FPS = 30
WINDOW_WIDTH = 640
WINDOW_HEIGHT = 480
boxSize = MEDIUM_BOX_SIZE
PALETTE_GAP_SIZE = 10 # 调色板间隔大小
PALETTE_SIZE = 45
EASY = 0 # 难度:简单
MEDIUM = 1 # 难度:中等
HARD = 2 # 难度:困难
difficulty = MEDIUM # 游戏以难度“中等”模式开始
maxLife = MEDIUM_MAX_LIFE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE
# R G B
WHITE = (255, 255, 255)
DARKGRAY = (70, 70, 70)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
ORANGE = (255, 128, 0)
PURPLE = (255, 0, 255)
# 每个方案中的第一种颜色是背景色,接下来的六种是调色板颜色。
COLOR_SCHEMES = (((150, 200, 255), RED, GREEN, BLUE, YELLOW, ORANGE, PURPLE),
((0, 155, 104), (97, 215, 164), (228, 0, 69), (0, 125, 50), (204, 246, 0), (148, 0, 45),
(241, 109, 149)),
((195, 179, 0), (255, 239, 115), (255, 226, 0), (147, 3, 167), (24, 38, 176), (166, 147, 0),
(197, 97, 211)),
((85, 0, 0), (155, 39, 102), (0, 201, 13), (255, 118, 0), (206, 0, 113), (0, 130, 9), (255, 180, 115)),
((191, 159, 64), (183, 182, 208), (4, 31, 183), (167, 184, 45), (122, 128, 212), (37, 204, 7),
(88, 155, 213)),
((200, 33, 205), (116, 252, 185), (68, 56, 56), (52, 238, 83), (23, 149, 195), (222, 157, 227),
(212, 86, 185)))
# 对颜色的处理
for i in range(len(COLOR_SCHEMES)):
assert len(COLOR_SCHEMES[i]) == 7, '颜色方案 %s 没有7种颜色!.' % (i)
# 背景色,调色板设置默认色
bgColor = COLOR_SCHEMES[0][0]
paletteColors = COLOR_SCHEMES[0][1:]
def main():
global FPS_CLOCK, DISPLAY_SURF, LOGO_IMAGE, SPOT_IMAGE, SETTINGS_IMAGE, SETTINGS_BUTTON_IMAGE, RESET_BUTTON_IMAGE
pygame.init()
FPS_CLOCK = pygame.time.Clock()
DISPLAY_SURF = pygame.display.set_mode((WINDOW_WIDTH, WINDOW_HEIGHT))
# 加载图片
LOGO_IMAGE = pygame.image.load('inkspilllogo.png')
SPOT_IMAGE = pygame.image.load('inkspillspot.png')
SETTINGS_IMAGE = pygame.image.load('inkspillsettings.png')
SETTINGS_BUTTON_IMAGE = pygame.image.load('inkspillsettingsbutton.png')
RESET_BUTTON_IMAGE = pygame.image.load('inkspillresetbutton.png')
# 设置窗口标题
pygame.display.set_caption('墨水溢出')
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
while True: # 主游戏循环
paletteClicked = None
resetGame = False
# 画屏幕
DISPLAY_SURF.fill(bgColor)
# 加载图标和相关按钮
drawLogoAndButtons()
# 加载正中央的大格子并随机初始化每个小格子的颜色
drawBoard(mainBoard)
# 加载左侧的"生命计"
drawLifeMeter(life)
# 加载屏幕底部的六个调色板
drawPalettes()
# 判断玩家是否想要退出游戏
checkForQuit()
for event in pygame.event.get(): # 事件处理循环
if event.type == MOUSEBUTTONUP: # 如果事件是鼠标点击
mousex, mousey = event.pos # 获取鼠标点击的坐标
# 如果点击的是"SETTINGS"字样处
if pygame.Rect(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height(),
SETTINGS_BUTTON_IMAGE.get_width(),
SETTINGS_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
# 就展示设置界面
resetGame = showSettingsScreen()
# 如果点击的是"RESET"字样处
elif pygame.Rect(WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height(),
RESET_BUTTON_IMAGE.get_width(),
RESET_BUTTON_IMAGE.get_height()).collidepoint(mousex, mousey):
# 就重新开始游戏
resetGame = True
else:
# 检查是否点击了调色板按钮
paletteClicked = getColorOfPaletteAt(mousex, mousey)
if paletteClicked is not None and paletteClicked != lastPaletteClicked:
# 单击的调色板按钮与上次单击的调色板按钮不同,防止播放器意外单击同一调色板两次
lastPaletteClicked = paletteClicked
# 填充颜色
floodAnimation(mainBoard, paletteClicked)
# 点击一次,"生命"减少1
life -= 1
resetGame = False
if hasWon(mainBoard): # 如果赢了
for i in range(4): # 成功的界面效果:闪烁边框4次
flashBorderAnimation(WHITE, mainBoard)
# "闪光"结束后,重新开始游戏
resetGame = True
# 暂停2s后再开始游戏
pygame.time.wait(2000)
elif life == 0:
# 生命降为零,玩家失败
drawLifeMeter(0)
# 更新界面
pygame.display.update()
# 等待0.4s
pygame.time.wait(400)
# 失败的结束效果:用黑色"闪光"4次
for i in range(4):
flashBorderAnimation(BLACK, mainBoard)
resetGame = True
# 暂停2s后开始重新游戏
pygame.time.wait(2000)
if resetGame:
# 重新开始游戏
mainBoard = generateRandomBoard(boardWidth, boardHeight, difficulty)
life = maxLife
lastPaletteClicked = None
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
def checkForQuit():
# 如果存在任何退出事件,则终止程序
for event in pygame.event.get(QUIT): # 获取所有退出事件
pygame.quit() # 如果存在任何退出事件,则终止
sys.exit()
for event in pygame.event.get(KEYUP): # 获取所有KEYUP(按键按下)事件
if event.key == K_ESCAPE:
pygame.quit() # 如果KEYUP(按键按下)事件是针对Esc键的,则终止
sys.exit()
pygame.event.post(event) # 将其他KEYUP(按键按下)事件对象放回原处
def hasWon(board):
# 如果整个棋盘颜色相同,则表示玩家获胜
for x in range(boardWidth):
for y in range(boardHeight):
# 只要发现一个颜色与左上角的颜色不同,玩家还没有赢
if board[x][y] != board[0][0]:
return False
return True
def showSettingsScreen():
# 获取全局变量
global difficulty, boxSize, boardWidth, boardHeight, maxLife, paletteColors, bgColor
# 此函数中的像素坐标是通过将inkspillsettings.png图像加载到图形编辑器中并从中读取像素坐标获得的
origDifficulty = difficulty
origBoxSize = boxSize
screenNeedsRedraw = True
while True:
if screenNeedsRedraw:
DISPLAY_SURF.fill(bgColor)
# 加载"设置"图片
DISPLAY_SURF.blit(SETTINGS_IMAGE, (0, 0))
# 将"墨迹"图片标记放在选定的颜色左边
if difficulty == EASY:
DISPLAY_SURF.blit(SPOT_IMAGE, (30, 4))
if difficulty == MEDIUM:
DISPLAY_SURF.blit(SPOT_IMAGE, (8, 41))
if difficulty == HARD:
DISPLAY_SURF.blit(SPOT_IMAGE, (30, 76))
# 将墨迹标记放置在选定尺寸旁边
if boxSize == SMALL_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (22, 150))
if boxSize == MEDIUM_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (11, 185))
if boxSize == LARGE_BOX_SIZE:
DISPLAY_SURF.blit(SPOT_IMAGE, (24, 220))
# 加载设置界面右边的颜色选择框
for i in range(len(COLOR_SCHEMES)):
drawColorSchemeBoxes(500, i * 60 + 30, i)
# 更新界面
pygame.display.update()
screenNeedsRedraw = False # 默认情况下,不重新绘制屏幕
# 事件处理循环
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYUP:
if event.key == K_ESCAPE:
# 设置屏幕上的Esc键返回游戏
return not (origDifficulty == difficulty and origBoxSize == boxSize)
elif event.type == MOUSEBUTTONUP:
screenNeedsRedraw = True # 屏幕应该重新绘制
mousex, mousey = event.pos # 鼠标点击的坐标
# 检查难度按钮上是否有咔嗒声
if pygame.Rect(74, 16, 111, 30).collidepoint(mousex, mousey):
difficulty = EASY
elif pygame.Rect(53, 50, 104, 29).collidepoint(mousex, mousey):
difficulty = MEDIUM
elif pygame.Rect(72, 85, 65, 31).collidepoint(mousex, mousey):
difficulty = HARD
# 检查尺寸按钮上是否有点击
elif pygame.Rect(63, 156, 84, 31).collidepoint(mousex, mousey):
# 小板尺寸设置:
boxSize = SMALL_BOX_SIZE
boardWidth = SMALL_BOARD_SIZE
boardHeight = SMALL_BOARD_SIZE
maxLife = SMALL_MAX_LIFE
elif pygame.Rect(52, 192, 106, 32).collidepoint(mousex, mousey):
# 中板尺寸设置:
boxSize = MEDIUM_BOX_SIZE
boardWidth = MEDIUM_BOARD_SIZE
boardHeight = MEDIUM_BOARD_SIZE
maxLife = MEDIUM_MAX_LIFE
elif pygame.Rect(67, 228, 58, 37).collidepoint(mousex, mousey):
# 大板尺寸设置:
boxSize = LARGE_BOX_SIZE
boardWidth = LARGE_BOARD_SIZE
boardHeight = LARGE_BOARD_SIZE
maxLife = LARGE_MAX_LIFE
elif pygame.Rect(178, 418, 215, 34).collidepoint(mousex, mousey):
# 点击“Back To Game”按钮
return not (origDifficulty == difficulty and origBoxSize == boxSize)
for i in range(len(COLOR_SCHEMES)):
# 单击颜色方案按钮
if pygame.Rect(500, 30 + i * 60, MEDIUM_BOX_SIZE * 3, MEDIUM_BOX_SIZE * 2).collidepoint(mousex,
mousey):
bgColor = COLOR_SCHEMES[i][0]
paletteColors = COLOR_SCHEMES[i][1:]
def drawColorSchemeBoxes(x, y, schemeNum):
# 绘制“设置”屏幕上显示的颜色方案框
for boxy in range(2):
for boxx in range(3):
pygame.draw.rect(DISPLAY_SURF, COLOR_SCHEMES[schemeNum][3 * boxy + boxx + 1],
(x + MEDIUM_BOX_SIZE * boxx, y + MEDIUM_BOX_SIZE * boxy, MEDIUM_BOX_SIZE, MEDIUM_BOX_SIZE))
if paletteColors == COLOR_SCHEMES[schemeNum][1:]:
# 将"墨迹"图片放置在所选配色方案旁边
DISPLAY_SURF.blit(SPOT_IMAGE, (x - 50, y))
def flashBorderAnimation(color, board, animationSpeed=30):
# "闪光"效果结束后,需要回到原来(游戏刚结束)的界面,此处先保存原来的界面
origSurf = DISPLAY_SURF.copy()
flashSurf = pygame.Surface(DISPLAY_SURF.get_size())
flashSurf = flashSurf.convert_alpha()
# 实现"闪光"效果
for start, end, step in ((0, 256, 1), (255, 0, -1)):
# 外循环的第一次迭代将内循环的透明度设置为从0到255,第二次迭代将透明度设置为从255到0。这就是“闪光”。
for transparency in range(start, end, animationSpeed * step):
DISPLAY_SURF.blit(origSurf, (0, 0))
r, g, b = color
flashSurf.fill((r, g, b, transparency))
DISPLAY_SURF.blit(flashSurf, (0, 0))
drawBoard(board) # 在透明层的顶部绘制板
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
# 绘制原来(游戏刚结束)的界面
DISPLAY_SURF.blit(origSurf, (0, 0))
def floodAnimation(board, paletteClicked, animationSpeed=25):
origBoard = copy.deepcopy(board) # 深拷贝整个大格子
# 从左上角处的格子开始同色填充
floodFill(board, board[0][0], paletteClicked, 0, 0)
for transparency in range(0, 255, animationSpeed):
# “新”大格子在大格子上慢慢变得不透明
drawBoard(origBoard)
# 更新透明度
drawBoard(board, transparency)
# 更新界面
pygame.display.update()
# 控制帧率
FPS_CLOCK.tick(FPS)
def generateRandomBoard(width, height, difficulty=MEDIUM):
# 为整个大格子中的每个小格子创建具有随机颜色的数据结构。
board = []
for x in range(width):
column = []
for y in range(height):
column.append(random.randint(0, len(paletteColors) - 1))
board.append(column)
# 通过将一些小格子设置为与相邻格子相同的颜色,使解决整个大格子更容易。
# 确定要更改的小格子数。
if difficulty == EASY:
if boxSize == SMALL_BOX_SIZE:
boxesToChange = 100
else:
boxesToChange = 1500
elif difficulty == MEDIUM:
if boxSize == SMALL_BOX_SIZE:
boxesToChange = 5
else:
boxesToChange = 200
else:
boxesToChange = 0
# 挂邻居格子的颜色:
for i in range(boxesToChange):
# 随机选择要复制其颜色的框
x = random.randint(1, width - 2)
y = random.randint(1, height - 2)
# 随机选择要更改的邻居格子
direction = random.randint(0, 3)
if direction == 0: # 左,下
board[x - 1][y] = board[x][y]
board[x][y - 1] = board[x][y]
elif direction == 1: # 右,上
board[x + 1][y] = board[x][y]
board[x][y + 1] = board[x][y]
elif direction == 2: # 左,下
board[x][y - 1] = board[x][y]
board[x + 1][y] = board[x][y]
else: # 左,上
board[x][y + 1] = board[x][y]
board[x - 1][y] = board[x][y]
return board
def drawLogoAndButtons():
# 绘制墨水溢出徽标、设置和重置按钮。
DISPLAY_SURF.blit(LOGO_IMAGE, (WINDOW_WIDTH - LOGO_IMAGE.get_width(), 0))
DISPLAY_SURF.blit(SETTINGS_BUTTON_IMAGE,
(WINDOW_WIDTH - SETTINGS_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height()))
DISPLAY_SURF.blit(RESET_BUTTON_IMAGE, (WINDOW_WIDTH - RESET_BUTTON_IMAGE.get_width(),
WINDOW_HEIGHT - SETTINGS_BUTTON_IMAGE.get_height() - RESET_BUTTON_IMAGE.get_height()))
def drawBoard(board, transparency=255): # 透明度设置默认为255
# 彩色方块将绘制到临时曲面,然后绘制到DISPLAY_SURF曲面。这样我们就可以在DISPLAY_SURF的顶部绘制具有透明度的正方形了。
tempSurf = pygame.Surface(DISPLAY_SURF.get_size()) # 获取窗口尺寸
tempSurf = tempSurf.convert_alpha() # 支持透明
tempSurf.fill((0, 0, 0, 0)) # 先全部处理为黑色
for x in range(boardWidth):
for y in range(boardHeight):
left, top = leftTopPixelCoordOfBox(x, y)
r, g, b = paletteColors[board[x][y]]
pygame.draw.rect(tempSurf, (r, g, b, transparency), (left, top, boxSize, boxSize))
left, top = leftTopPixelCoordOfBox(0, 0)
# 为防止边缘的小格子颜色与窗口背景色相同,在整个大格子外面包一层黑色细线,厚度为1px
pygame.draw.rect(tempSurf, BLACK, (left - 1, top - 1, boxSize * boardWidth + 1, boxSize * boardHeight + 1), 1)
DISPLAY_SURF.blit(tempSurf, (0, 0))
def drawPalettes():
# 在屏幕底部绘制六个调色板
numColors = len(paletteColors)
x_margin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
for i in range(numColors):
left = x_margin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
top = WINDOW_HEIGHT - PALETTE_SIZE - 10
pygame.draw.rect(DISPLAY_SURF, paletteColors[i], (left, top, PALETTE_SIZE, PALETTE_SIZE))
# 为了美观,在格子外2px再加2px厚度的对应颜色的区域
pygame.draw.rect(DISPLAY_SURF, bgColor, (left + 2, top + 2, PALETTE_SIZE - 4, PALETTE_SIZE - 4), 2)
def drawLifeMeter(currentLife):
# '生命计' 竖直放置,与上下边缘各相距20px
lifeBoxSize = int((WINDOW_HEIGHT - 40) / maxLife)
# 绘制"生命计"的背景色,'生命计'的左上角位于坐标(20,20),宽20px,高20 + (maxLife * lifeBoxSize) px
pygame.draw.rect(DISPLAY_SURF, bgColor, (20, 20, 20, 20 + (maxLife * lifeBoxSize)))
for i in range(maxLife):
if currentLife >= (maxLife - i): # 画一个实心的红色方框
pygame.draw.rect(DISPLAY_SURF, RED, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize))
# 加1px白色的边框
pygame.draw.rect(DISPLAY_SURF, WHITE, (20, 20 + (i * lifeBoxSize), 20, lifeBoxSize), 1)
def getColorOfPaletteAt(x, y):
# 返回x和y参数覆盖的调色板颜色的索引
numColors = len(paletteColors)
xmargin = int((WINDOW_WIDTH - ((PALETTE_SIZE * numColors) + (PALETTE_GAP_SIZE * (numColors - 1)))) / 2)
# 调色板底端距窗口底端10px
top = WINDOW_HEIGHT - PALETTE_SIZE - 10
for i in range(numColors):
# 六个调色板左上角的坐标:(left,top)
left = xmargin + (i * PALETTE_SIZE) + (i * PALETTE_GAP_SIZE)
r = pygame.Rect(left, top, PALETTE_SIZE, PALETTE_SIZE)
# 找出鼠标单击区域是否在任何调色板内
if r.collidepoint(x, y):
return i # 如果在,则返回所点击的调色板的序号
# 如果x和y不在任何调色板上,则返回None
return None
def floodFill(board, oldColor, newColor, x, y):
# 洪水填充算法
if oldColor == newColor or board[x][y] != oldColor:
# 如果左上角的颜色和点击颜色没有相邻,直接返回
return
board[x][y] = newColor # 更改当前格子的颜色
# 对任何相邻格子进行递归调用:
if x > 0:
floodFill(board, oldColor, newColor, x - 1, y)
if x < boardWidth - 1:
floodFill(board, oldColor, newColor, x + 1, y)
if y > 0:
floodFill(board, oldColor, newColor, x, y - 1)
if y < boardHeight - 1:
floodFill(board, oldColor, newColor, x, y + 1)
def leftTopPixelCoordOfBox(boxx, boxy):
# 返回某个小格子最左上方像素的x和y。
# 注意整个大格子位于窗口的正中央
x_margin = int((WINDOW_WIDTH - (boardWidth * boxSize)) / 2) # x_margin 为整个大格子最左边与窗口最左边的间距
y_margin = int((WINDOW_HEIGHT - (boardHeight * boxSize)) / 2) # y_margin 为整个大格子最上边与窗口最上边的间距
return boxx * boxSize + x_margin, boxy * boxSize + y_margin
if __name__ == '__main__':
main()
喜欢的小伙伴们点个赞鼓励支持一下吧~
以上是关于Python不能做游戏?游戏实战之-----《ink spill》(附游戏完整源码)的主要内容,如果未能解决你的问题,请参考以下文章