pygame战棋游戏制作之战棋光标设置上(三)

简介

上一篇博文中,我们已经将棋子部署到地图上了,但是还远远不够,我们需要对棋子进行更多的操作。

玩过战棋游戏的小伙伴们应该都清楚,操作棋子有两种方式,一种是用鼠标控制,一种是用键盘控制,本次我们讲解如何用键盘来控制我们的棋子。

正文

通过键盘控制棋子移动,在屏幕上我们是通过光标看到键盘操作的效果的。因此我们需要制作一个 光标类

这个光标类应该具备以下几个功能:

  1. 光标移动
  2. 光标选中棋子
  3. 光标取消选中
  4. 光标控制棋子移动

首先我们创建一个光标类,光标本质上也是一种棋子,但是和棋子不一样的是它具有的功能和棋子完全不同。因此我们单独将光标类拎出来。

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()

现在我们看到的效果应该是这样的,在左上角有一个小小的光标:

pygame战棋游戏制作之战棋光标设置上(三)

一、光标移动

我们将光标设置到屏幕后,就可以开始实现第一个功能,光标移动了。

这个功能还是比较容易实现的,就是通过键盘上下左右四个按钮来改变光标的方位。

首先我们在 光标类中添加上下左右的移动判断

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制作软件,请强推我,万分感谢!)

pygame战棋游戏制作之战棋光标设置上(三)

顺便提一下,从这个时候,我们将地图渲染移出了事件监听循环 ,目的是为了解决以后敌方棋子无法自动行动的bug。但是这又不可避免的增大了系统的消耗。

二、光标选中棋子

光标可以移动后,我我们就要选择我们的棋子。这个功能主要实现两个子功能:

  1. 状态改变:从未选中->选中
  2. 棋子显示可移动范围

我们先来实现第一个子功能,状态改变。

还记得前面初始化光标类的时候提到的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键,光标变红了,但是我们的要求是选中棋子才会变红,所以我们要给选中的目标加个判断。

添加判断分三个步骤:

  1. 获取光标当前坐标
  2. 获取当前坐标对应的格子类别
  3. 比较获取格子类别

这时候,我们在光标类中导入的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)

现在我们来看看效果,目前设置棋子

pygame战棋游戏制作之战棋光标设置上(三)

看起来很完美了,但是这个算法还是有bug的,例如像这样:

pygame战棋游戏制作之战棋光标设置上(三)

乍一看好像没啥问题,但是仔细数数,最右边的格子能走到吗?很明显是走不到的,原因在于目前这个算法没有判断到遇到障碍的情况。

那么怎么解决这个问题呢?一番查找后,终于在某站找到了相关视频(别去找度娘了,度娘搜出来全都是网课教学)。

解决这个问题用到的算法是—— 洪水算法,个人理解是基于图算法的广度优先实现的。由于洪水算法这个在度娘可以搜到,所以我这就不班门弄斧了。当然如果感兴趣的小伙伴想要了解的话,也可以私聊或者评论我,我可以看情况另开一篇博文讲讲这个算法的实现原理。

现在,我就直接贴出已经实现好的算法:

洪水算法
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过去,小伙伴们要看仔细哟。

现在我们来看看效果

pygame战棋游戏制作之战棋光标设置上(三)

现在看起来效果就好多了,遇到障碍会绕路减少可移动范围。

至于红色的部分就是棋子的可攻击范围,也就是flood方法中的attack。由于这个攻击范围还没有具体实现,所以暂时不在这篇文章的讲解范围内。

总结

又是么有评论,关注,收藏的一周,不过看到蹭蹭蹭上涨的阅读量还是很满足的,说明大家对战棋开发的求知欲还是很旺盛的。

但是,这篇文章还是阉割了。本来想把光标类的四个功能都写完了,但是发现要写的东西太多了。所以只能先分成上下两篇(其实就是想偷懒)。希望大家可以小小的原谅我。当然如果大家能给我点反馈的话,说不定今后的更新量会上升呢。

最后还是老样子,我们下周见咯。

Original: https://blog.csdn.net/Sean_TS_Wang/article/details/121655833
Author: Sean_TS_Wang
Title: pygame战棋游戏制作之战棋光标设置上(三)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/780847/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球