简介
上一篇博文中,我们已经将棋子部署到地图上了,但是还远远不够,我们需要对棋子进行更多的操作。
玩过战棋游戏的小伙伴们应该都清楚,操作棋子有两种方式,一种是用鼠标控制,一种是用键盘控制,本次我们讲解如何用键盘来控制我们的棋子。
正文
通过键盘控制棋子移动,在屏幕上我们是通过光标看到键盘操作的效果的。因此我们需要制作一个 光标类。
这个光标类应该具备以下几个功能:
- 光标移动
- 光标选中棋子
- 光标取消选中
- 光标控制棋子移动
首先我们创建一个光标类,光标本质上也是一种棋子,但是和棋子不一样的是它具有的功能和棋子完全不同。因此我们单独将光标类拎出来。
class Cursor:
def __init__(self,raw,col,map_obj):
self.map_obj = map_obj
self.cursor_raw = raw
self.cursor_col = col
self.cursor = [
pygame.image.load('../images/未选中光标.png'),
pygame.image.load('../images/选中光标.png'),
]
self.status = 0
self.current_obj = None
在光标类的初始化函数中,我们传入了地图对象,这是为了可以通过地图对象获取到光标选中棋子,同时也可以约束光标移动范围。
另外,光标有 两种状态,一种为选中状态,一种选中状态,通过status控制
现在我们在屏幕中导入光标试试
def main():
pygame.init()
clock = pygame.time.Clock() # 设置时钟
clock.tick(10) # 每秒执行60次
m = Map()
m.load_map(3,5,Dogface())
m.load_map(4,7,Store())
b = Block()
screen = pygame.display.set_mode((m.width,m.height)) # 显示窗口
color = (255,255,0)
screen.fill(color)
c = Cursor(0,0,m)
while True:
# 轮询事件
for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果检测到事件是关闭窗口
sys.exit()
else:
m.create(screen,b)
screen.blit(c.cursor[c.status],(c.cursor_col*m.block,c.cursor_raw*m.block))
pygame.display.update()
pygame.quit()
现在我们看到的效果应该是这样的,在左上角有一个小小的光标:
一、光标移动
我们将光标设置到屏幕后,就可以开始实现第一个功能,光标移动了。
这个功能还是比较容易实现的,就是通过键盘上下左右四个按钮来改变光标的方位。
首先我们在 光标类中添加上下左右的移动判断
class Cursor:
def __init__(self,raw,col,map_obj):
self.map_obj = map_obj
self.cursor_raw = raw
self.cursor_col = col
self.cursor = [
pygame.image.load('../images/未选中光标.png'),
pygame.image.load('../images/选中光标.png'),
]
self.status = 0
self.current_obj = None
def move_up(self):
if self.cursor_raw > 0:
self.cursor_raw -= 1
def move_down(self):
if self.cursor_raw < self.map_obj.real_height:
self.cursor_raw += 1
def move_left(self):
if self.cursor_col > 0:
self.cursor_col -= 1
def move_right(self):
if self.cursor_col < self.map_obj.real_width:
self.cursor_col += 1
然后我们需要监听键盘的按键事件。
pygame提供了event帮我们监听键盘的按键类型,我们只需要在主函数中添加判断即可
def main():
pygame.init()
clock = pygame.time.Clock() # 设置时钟
clock.tick(10) # 每秒执行60次
m = Map()
m.load_map(3,5,Dogface())
m.load_map(4,7,Store())
b = Block()
screen = pygame.display.set_mode((m.width,m.height)) # 显示窗口
color = (255,255,0)
screen.fill(color)
c = Cursor(0,0,m)
while True:
m.create(screen,b)
screen.blit(c.cursor[c.status],(c.cursor_col*m.block,c.cursor_raw*m.block))
pygame.display.update()
# 轮询事件
for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果检测到事件是关闭窗口
sys.exit()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_UP):
c.move_up()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN):
c.move_down()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT):
c.move_left()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT):
c.move_right()
else:
pass
pygame.quit()
现在我们的光标就可以自由的移动啦。(网上随便找了一个动图制作软件,请忽略水印,如果有更好的gif制作软件,请强推我,万分感谢!)
顺便提一下,从这个时候,我们将地图渲染移出了事件监听循环 ,目的是为了解决以后敌方棋子无法自动行动的bug。但是这又不可避免的增大了系统的消耗。
二、光标选中棋子
光标可以移动后,我我们就要选择我们的棋子。这个功能主要实现两个子功能:
- 状态改变:从未选中->选中
- 棋子显示可移动范围
我们先来实现第一个子功能,状态改变。
还记得前面初始化光标类的时候提到的status吗?我们就是通过status来控制光标状态的,0是未选中状态,1是选中状态。
所以我们只需要设置当键盘按在a键的时候,光标的状态发生改变
def main():
pygame.init()
clock = pygame.time.Clock() # 设置时钟
clock.tick(10) # 每秒执行60次
m = Map()
m.load_map(3,5,Dogface())
m.load_map(4,7,Store())
b = Block()
screen = pygame.display.set_mode((m.width,m.height)) # 显示窗口
color = (255,255,0)
screen.fill(color)
c = Cursor(0,0,m)
while True:
m.create(screen,b)
screen.blit(c.cursor[c.status],(c.cursorX*m.block,c.cursorY*m.block))
pygame.display.update()
# 轮询事件
for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果检测到事件是关闭窗口
sys.exit()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_UP):
c.move_up()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN):
c.move_down()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT):
c.move_left()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT):
c.move_right()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_a):
c.status = 1
else:
pass
pygame.quit()
现在我们可以看到,按下键盘a键,光标变红了,但是我们的要求是选中棋子才会变红,所以我们要给选中的目标加个判断。
添加判断分三个步骤:
- 获取光标当前坐标
- 获取当前坐标对应的格子类别
- 比较获取格子类别
这时候,我们在光标类中导入的map_ob就起到作用了。
首先我们在 光标类添加获取当前坐标对应对象方法
@property
def get_cursor_index_obj(self):
return self.map_obj.empty_map[self.cursor_raw][self.cursor_col]
接着在 光标类添加 catch方法比对获取到的对象是不是我们的 棋子类:
def catch(self):
if isinstance(self.get_cursor_index_obj,Dogface):
self.status = 1
最后只需要在主函数的时间监听中调用catch方法即可
def main():
pygame.init()
clock = pygame.time.Clock() # 设置时钟
clock.tick(10) # 每秒执行60次
m = Map()
m.load_map(3,5,Dogface())
m.load_map(4,7,Store())
b = Block()
screen = pygame.display.set_mode((m.width,m.height)) # 显示窗口
color = (255,255,0)
screen.fill(color)
c = Cursor(0,0,m)
while True:
m.create(screen,b)
screen.blit(c.cursor[c.status],(c.cursorX*m.block,c.cursorY*m.block))
pygame.display.update()
# 轮询事件
for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果检测到事件是关闭窗口
sys.exit()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_UP):
c.move_up()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN):
c.move_down()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT):
c.move_left()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT):
c.move_right()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_a):
c.catch()
else:
pass
pygame.quit()
只时候只有棋子类才能被我们光标选中了。 我们光标选中的第一个子功能到这就结束了,接下来我们讲解光标选中的第二个子功能——显示棋子移动范围。
首先我们需要新建一个格子类,叫做可移动格子类
class Removable(Block):
def set_block(self):
self.block = pygame.image.load('../images/blue.png')
一个很朴素的想法就是,一个棋子可以移动的范围就是他上下左右四个方向所能到的位置
于是我们新建一个算法类,在算法类中实现棋子可移动范围算法
removable = Removable()
def movable(m,raw,col,step=5):
for i in range(col-step,col+step+1):
if i >= 0 and i = 0 else 0
right = raw + (step-abs(i-col)) if raw + abs(i-col)
在这个算法中我们首先要把棋子所在的行,列,步数传过来。然后从上到下显示可以移动的范围。
最上面一行肯定只有 一个格子可以到达,第二行从左到右有 三个格子,第三行从左到右有 五个格子,依次类推,一直到棋子所在行数从左到右的格子达到最大值—— 2*step,再往下就逐渐减小。直到最下面一行只有 一个格子可以到达。
然后我们在光标的 catch类中调用这个方法,但是发现好像没有办法获取到棋子的当前坐标,所以我们要先在 格子类中添加获取当前坐标方法,并且添加设置当前坐标方法
class Block:
def __init__(self):
self.block= None
self.cur_raw = 0
self.cur_col = 0
self.set_block()
def set_block(self):
pass
def get_cur_index(self):
return self.cur_raw, self.cur_col
def set_cur_index(self,raw,col):
self.cur_raw = raw
self.cur_col = col
然后修改 地图类的加载棋子方法
def load_map(self,status):
raw, col = status.get_cur_index()
self.empty_map[raw][col] = status
顺便修改 主函数的加载棋子方式:
def main():
pygame.init()
clock = pygame.time.Clock() # 设置时钟
clock.tick(10) # 每秒执行60次
m = Map()
d = Dogface()
d.set_cur_index(3,5)
m.load_map(d)
store = Store()
store.set_cur_index(4,7)
m.load_map(store)
b = Block()
screen = pygame.display.set_mode((m.width,m.height)) # 显示窗口
color = (255,255,0)
screen.fill(color)
c = Cursor(0,0,m)
while True:
m.create(screen,b)
screen.blit(c.cursor[c.status],(c.cursorX*m.block,c.cursorY*m.block))
pygame.display.update()
# 轮询事件
for event in pygame.event.get():
if event.type == pygame.QUIT: # 如果检测到事件是关闭窗口
sys.exit()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_UP):
c.move_up()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_DOWN):
c.move_down()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_LEFT):
c.move_left()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_RIGHT):
c.move_right()
elif (event.type == pygame.KEYDOWN and event.key == pygame.K_a):
c.catch()
else:
pass
pygame.quit()
最后在 光标类的catch方法中调用显示可移动范围函数
def catch(self):
if isinstance(self.get_cursor_index_obj,Dogface):
self.status = 1
self.curent_obj = self.get_cursor_index_obj
raw, col = self.get_cursor_index_obj.get_cur_index()
movable(self.map_obj,raw,col)
现在我们来看看效果,目前设置棋子
看起来很完美了,但是这个算法还是有bug的,例如像这样:
乍一看好像没啥问题,但是仔细数数,最右边的格子能走到吗?很明显是走不到的,原因在于目前这个算法没有判断到遇到障碍的情况。
那么怎么解决这个问题呢?一番查找后,终于在某站找到了相关视频(别去找度娘了,度娘搜出来全都是网课教学)。
解决这个问题用到的算法是—— 洪水算法,个人理解是基于图算法的广度优先实现的。由于洪水算法这个在度娘可以搜到,所以我这就不班门弄斧了。当然如果感兴趣的小伙伴想要了解的话,也可以私聊或者评论我,我可以看情况另开一篇博文讲讲这个算法的实现原理。
现在,我就直接贴出已经实现好的算法:
洪水算法
def flood(m,cur_obj):
next_d = deque([cur_obj.get_cur_index()])
over = deque([])
step = cur_obj.step+1
while step:
cur_d = deepcopy(next_d)
next_d = deque([])
while cur_d:
index = cur_d.popleft()
raw = index[0]
col = index[1]
if raw-1>=0 and (raw-1,col) not in over and m.empty_map[raw-1][col] == 0:
next_d.append((raw-1,col))
if raw+1=0 and (raw,col-1) not in over and m.empty_map[raw][col-1] == 0:
next_d.append((raw,col-1))
if col+1
改一下catch方法
def catch(self):
if isinstance(self.get_cursor_index_obj,Dogface):
self.status = 1
self.curent_obj = self.get_cursor_index_obj
flood(self.map_obj,self.get_cursor_index_obj)
注意,我这次是直接传了cur_obj过去,小伙伴们要看仔细哟。
现在我们来看看效果
现在看起来效果就好多了,遇到障碍会绕路减少可移动范围。
至于红色的部分就是棋子的可攻击范围,也就是flood方法中的attack。由于这个攻击范围还没有具体实现,所以暂时不在这篇文章的讲解范围内。
总结
又是么有评论,关注,收藏的一周,不过看到蹭蹭蹭上涨的阅读量还是很满足的,说明大家对战棋开发的求知欲还是很旺盛的。
但是,这篇文章还是阉割了。本来想把光标类的四个功能都写完了,但是发现要写的东西太多了。所以只能先分成上下两篇(其实就是想偷懒)。希望大家可以小小的原谅我。当然如果大家能给我点反馈的话,说不定今后的更新量会上升呢。
最后还是老样子,我们下周见咯。
Original: https://blog.csdn.net/Sean_TS_Wang/article/details/121655833
Author: Sean_TS_Wang
Title: pygame战棋游戏制作之战棋光标设置上(三)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/780847/
转载文章受原作者版权保护。转载请注明原作者出处!