乐趣区

关于游戏开发:pygame-游戏开发

简略的战旗游戏开发学习

在网上找寻教程之后搞出了这么个雏形

游戏介绍

游戏实现了战斗场景的回合制玩法:对战单方每个生物每一轮有一次口头机会,能够行走或攻打对方。每个生物属性有:行走范畴,速度,生命,挫伤,进攻,攻打 和 是否是近程兵种。当把对方生物都毁灭时,即胜利。

代码介绍

对于战旗类游戏的外围还是地图,尽管网上有六边形的地图教程然而没看太懂就做正方形的吧 首先定义函数,width 和 height 为地图的宽度和长度,bg_map 设置方格的背景色彩,entity_map 保留哪个方格上有生物。active_entity 示意以后要口头的生物。

class Map():
    def __init__(self, width, height, grid):
        self.width = width
        self.height = height
        self.bg_map = [[0 for x in range(self.width)] for y in range(self.height)]
        self.entity_map = [[None for x in range(self.width)] for y in range(self.height)]
        self.active_entity = None
        self.select = None
        self.setupMapImage(grid)
        self.setupMouseImage()

当我方生物抉择口头时,设置生物可行走范畴内的方格背景为 c.BG_RANGE。active_entity 的 inRange 函数判断一个方格是否在行走范畴内。

 def updateMap(self):
        for y in range(self.height):
            for x in range(self.width):
                self.bg_map[y][x] = c.BG_EMPTY
                if self.entity_map[y][x] is not None and self.entity_map[y][x].isDead():
                    self.entity_map[y][x] = None

        if self.active_entity is None or self.active_entity.state != c.IDLE:
            return
        map_x, map_y = self.active_entity.map_x, self.active_entity.map_y
        self.bg_map[map_y][map_x] = c.BG_ACTIVE
        
        for y in range(self.height):
            for x in range(self.width):
                if not self.isMovable(x,y) or not self.isValid(x,y):
                    continue
                if self.active_entity.inRange(self, x, y):
                    self.bg_map[y][x] = c.BG_RANGE
        mouse_x, mouse_y = pg.mouse.get_pos()
        self.checkMouseMove(mouse_x, mouse_y)

接下来欠缺下咱们的 map 代码

import pygame as pg
from .. import tool
from .. import constants as c

class Map():
    def __init__(self, width, height, grid):
        self.width = width
        self.height = height
        self.bg_map = [[0 for x in range(self.width)] for y in range(self.height)]
        self.entity_map = [[None for x in range(self.width)] for y in range(self.height)]
        self.active_entity = None
        self.select = None
        self.setupMapImage(grid)
        self.setupMouseImage()

    def setupMapImage(self, grid):
        self.grid_map = [[0 for x in range(self.width)] for y in range(self.height)]
        if grid is not None:
            for data in grid:
                x, y, type = data['x'], data['y'], data['type']
                self.grid_map[y][x] = type
        
        self.map_image = pg.Surface((self.width * c.REC_SIZE, self.height * c.REC_SIZE)).convert()
        self.rect = self.map_image.get_rect()
        self.rect.x = 0
        self.rect.y = 0
        for y in range(self.height):
            for x in range(self.width):
                type = self.grid_map[y][x]
                if type != c.MAP_EMPTY:
                    if c.MAP_HEXAGON:
                        base_x, base_y = tool.getHexMapPos(x, y)
                        self.map_image.blit(tool.GRID[type], (base_x, base_y))
                    else:
                        self.map_image.blit(tool.GRID[type], (x * c.REC_SIZE, y * c.REC_SIZE))
        self.map_image.set_colorkey(c.BLACK)

    def setupMouseImage(self):
        self.mouse_frames = []
        frame_rect = (0, 0, 25, 27)
        self.mouse_image = tool.get_image(tool.GFX[c.MOUSE], *frame_rect, c.BLACK, 1)
        self.mouse_rect = self.mouse_image.get_rect()
        pg.mouse.set_visible(False)

    def isValid(self, map_x, map_y):
        if c.MAP_HEXAGON:
            if map_y % 2 == 0:
                max_x = self.width
            else:
                max_x = self.width - 1
        else:
            max_x = self.width
        if (map_x < 0 or map_x >= max_x or
            map_y < 0 or map_y >= self.height):
            return False
        return True
    
    def isMovable(self, map_x, map_y):
        return (self.entity_map[map_y][map_x] == None and 
                self.grid_map[map_y][map_x] != c.MAP_STONE)

    def getMapIndex(self, x, y):
        if c.MAP_HEXAGON:
            return tool.getHexMapIndex(x, y)
        else:
            return (x//c.REC_SIZE, y//c.REC_SIZE)

    def getDistance(self, x1, y1, map_x2, map_y2):
        if c.MAP_HEXAGON:
            x2, y2 = tool.getHexMapPos(map_x2, map_y2)
            x2 += c.HEX_X_SIZE // 2
            y2 += c.HEX_Y_SIZE // 2
            distance = (abs(x1 - x2) + abs(y1 - y2))
        else:
            map_x1, map_y1 = self.getMapIndex(x1, y1)
            x2 = map_x2 * c.REC_SIZE + c.REC_SIZE//2
            y2 = map_y2 * c.REC_SIZE + c.REC_SIZE//2
            distance = (abs(x1 - x2) + abs(y1 - y2))
            if map_x1 != map_x2 and map_y1 != map_y2:
               distance -= c.REC_SIZE//2
        return distance

    def checkMouseClick(self, x, y):
        if self.active_entity is None:
            return False
        
        map_x, map_y = self.getMapIndex(x, y)
        if not self.isValid(map_x, map_y):
            return False

        entity = self.entity_map[map_y][map_x]
        if ((entity is None or entity == self.active_entity) and 
            self.active_entity.inRange(self, map_x, map_y)):
            self.active_entity.setDestination(map_x, map_y)
            return True
        elif entity is not None:
            if self.active_entity.isRemote():
                self.active_entity.setTarget(entity)
                return True
            elif self.select is not None:
                self.active_entity.setDestination(self.select[0], self.select[1], entity)
                return True
        return False

    def checkMouseMove(self, x, y):
        if self.active_entity is None:
            return False

        map_x, map_y = self.getMapIndex(x, y)
        if not self.isValid(map_x, map_y):
            return False
        
        self.select = None
        entity = self.entity_map[map_y][map_x]
        if ((self.isMovable(map_x, map_y) or entity == self.active_entity) and 
            self.active_entity.inRange(self, map_x, map_y)):
                self.bg_map[map_y][map_x] = c.BG_SELECT
        elif entity is not None:
            if entity.group_id != self.active_entity.group_id:
                if self.active_entity.isRemote():
                    self.bg_map[map_y][map_x] = c.BG_ATTACK
                else:
                    dir_list = tool.getAttackPositions(map_x, map_y)
                    res_list = []
                    for offset_x, offset_y in dir_list:
                        if self.isValid(map_x + offset_x, map_y + offset_y):
                            type = self.bg_map[map_y + offset_y][map_x + offset_x]
                            if type == c.BG_RANGE or type == c.BG_ACTIVE:
                                res_list.append((map_x + offset_x, map_y + offset_y))
                    if len(res_list) > 0:
                        min_dis = c.MAP_WIDTH
                        for tmp_x, tmp_y in res_list:
                            distance = self.getDistance(x, y, tmp_x, tmp_y)
                            if distance < min_dis:
                                min_dis = distance
                                res = (tmp_x, tmp_y)
                        self.bg_map[res[1]][res[0]] = c.BG_SELECT
                        self.bg_map[map_y][map_x] = c.BG_ATTACK
                        self.select = res

    def setEntity(self, map_x, map_y, value):
        self.entity_map[map_y][map_x] = value

    def drawMouseShow(self, surface):
        x, y = pg.mouse.get_pos()
        map_x, map_y = self.getMapIndex(x, y)
        if self.isValid(map_x, map_y):
            self.mouse_rect.x = x
            self.mouse_rect.y = y
            surface.blit(self.mouse_image, self.mouse_rect)

    def updateMap(self):
        for y in range(self.height):
            for x in range(self.width):
                self.bg_map[y][x] = c.BG_EMPTY
                if self.entity_map[y][x] is not None and self.entity_map[y][x].isDead():
                    self.entity_map[y][x] = None

        if self.active_entity is None or self.active_entity.state != c.IDLE:
            return
        map_x, map_y = self.active_entity.map_x, self.active_entity.map_y
        self.bg_map[map_y][map_x] = c.BG_ACTIVE
        
        for y in range(self.height):
            for x in range(self.width):
                if not self.isMovable(x,y) or not self.isValid(x,y):
                    continue
                if self.active_entity.inRange(self, x, y):
                    self.bg_map[y][x] = c.BG_RANGE
        mouse_x, mouse_y = pg.mouse.get_pos()
        self.checkMouseMove(mouse_x, mouse_y)

    def drawBackground(self, surface):
        if c.MAP_HEXAGON:
            return self.drawBackgroundHex(surface)

        pg.draw.rect(surface, c.LIGHTYELLOW, pg.Rect(0, 0, c.MAP_WIDTH, c.MAP_HEIGHT))
        
        for y in range(self.height):
            for x in range(self.width):
                if self.bg_map[y][x] == c.BG_EMPTY:
                    color = c.LIGHTYELLOW
                elif self.bg_map[y][x] == c.BG_ACTIVE:
                    color = c.SKY_BLUE
                elif self.bg_map[y][x] == c.BG_RANGE:
                    color = c.NAVYBLUE
                elif self.bg_map[y][x] == c.BG_SELECT:
                    color = c.GREEN
                elif self.bg_map[y][x] == c.BG_ATTACK:
                    color = c.GOLD
                pg.draw.rect(surface, color, (x * c.REC_SIZE, y * c.REC_SIZE, 
                        c.REC_SIZE, c.REC_SIZE))
        
        surface.blit(self.map_image, self.rect)

        for y in range(self.height):
            # draw a horizontal line
            start_pos = (0, 0 + c.REC_SIZE * y)
            end_pos = (c.MAP_WIDTH, c.REC_SIZE * y)
            pg.draw.line(surface, c.BLACK, start_pos, end_pos, 1)

        for x in range(self.width):
            # draw a horizontal line
            start_pos = (c.REC_SIZE * x, 0) 
            end_pos = (c.REC_SIZE * x, c.MAP_HEIGHT)
            pg.draw.line(surface, c.BLACK, start_pos, end_pos, 1)

    def calHeuristicDistance(self, x1, y1, x2, y2):
        if c.MAP_HEXAGON:
            dis_y = abs(y1 - y2)
            dis_x = abs(x1 - x2)
            half_y = dis_y // 2
            if dis_y >= dis_x:
                dis_x = 0
            else:
                dis_x -= half_y
            return (dis_y + dis_x)
        else:
            return abs(x1 - x2) + abs(y1 - y2)

    def drawBackgroundHex(self, surface):
        Y_LEN = c.HEX_Y_SIZE // 2
        X_LEN = c.HEX_X_SIZE // 2

        pg.draw.rect(surface, c.LIGHTYELLOW, pg.Rect(0, 0, c.MAP_WIDTH, c.MAP_HEIGHT))

        for y in range(self.height):
            for x in range(self.width):
                if self.bg_map[y][x] == c.BG_EMPTY:
                    color = c.LIGHTYELLOW
                elif self.bg_map[y][x] == c.BG_ACTIVE:
                    color = c.SKY_BLUE
                elif self.bg_map[y][x] == c.BG_RANGE:
                    color = c.NAVYBLUE
                elif self.bg_map[y][x] == c.BG_SELECT:
                    color = c.GREEN
                elif self.bg_map[y][x] == c.BG_ATTACK:
                    color = c.GOLD

                base_x, base_y = tool.getHexMapPos(x, y)
                points = [(base_x, base_y + Y_LEN//2 + Y_LEN), (base_x, base_y + Y_LEN//2),
                          (base_x + X_LEN, base_y), (base_x + X_LEN * 2, base_y + Y_LEN//2),
                          (base_x + X_LEN * 2, base_y + Y_LEN//2 + Y_LEN), (base_x + X_LEN, base_y + Y_LEN*2)]
                pg.draw.polygon(surface, color, points)

        surface.blit(self.map_image, self.rect)

        for y in range(self.height):
            for x in range(self.width):
                if y % 2 == 1 and x == self.width - 1:
                    continue
                base_x, base_y = tool.getHexMapPos(x, y)
                points = [(base_x, base_y + Y_LEN//2 + Y_LEN), (base_x, base_y + Y_LEN//2),
                          (base_x + X_LEN, base_y), (base_x + X_LEN * 2, base_y + Y_LEN//2),
                          (base_x + X_LEN * 2, base_y + Y_LEN//2 + Y_LEN), (base_x + X_LEN, base_y + Y_LEN*2)]
                pg.draw.lines(surface, c.BLACK, True, points)

接下来咱们要搞定的就是人物类,咱们的函数要实现:1. 生物属于敌我哪一方 2. 初始时生物在地图上的地位 3. 保留生物的所有属性 4. 生物的行走 5. 生物的三种状态那么看代码

import pygame as pg
from .. import tool
from .. import constants as c
from .. import AStarSearch
from . import map

class FireBall():
    def __init__(self, x, y, enemy, hurt):
        # first 3 Frames are flying, last 4 frams are exploding
        frame_rect = (0,0,14,14)
        self.image = tool.get_image(tool.GFX[c.FIREBALL], *frame_rect, c.BLACK, c.SIZE_MULTIPLIER)
        self.rect = self.image.get_rect()
        self.rect.centerx = x
        self.rect.centery = y
        self.enemy = enemy
        self.hurt = hurt
        self.done = False
        self.calVelocity()
    
    def calVelocity(self):
        #print('calVelocity: x:', self.enemy.rect.centerx, self.rect.centerx, 'y:', self.enemy.rect.centery,self.rect.centery)
        dis_x = self.enemy.rect.centerx - self.rect.centerx
        dis_y = self.enemy.rect.centery - self.rect.centery
        distance = (dis_x ** 2 + dis_y ** 2) ** 0.5
        self.x_vel = (dis_x * 10)/distance
        self.y_vel = (dis_y * 10)/distance
        
    def update(self):
        self.rect.x += self.x_vel
        self.rect.y += self.y_vel
        if abs(self.rect.x - self.enemy.rect.x) + abs(self.rect.y - self.enemy.rect.y) < 25:
            self.enemy.setHurt(self.hurt)
            self.done = True
    
    def draw(self, surface):
        surface.blit(self.image, self.rect)

class EntityAttr():
    def __init__(self, data):
        self.max_health = data[c.ATTR_HEALTH]
        self.range = data[c.ATTR_RANGE]
        self.damage = data[c.ATTR_DAMAGE]
        self.attack = data[c.ATTR_ATTACK]
        self.defense = data[c.ATTR_DEFENSE]
        self.speed = data[c.ATTR_SPEED]
        if data[c.ATTR_REMOTE] == 0:
            self.remote = False
        else:
            self.remote = True
    
    def getHurt(self, enemy_attr):
        offset = 0
        if self.attack > enemy_attr.defense:
            offset = (self.attack - enemy_attr.defense) * 0.05
        elif self.attack < enemy_attr.defense:
            offset = (self.attack - enemy_attr.defense) * 0.025
        hurt = int(self.damage * (1 + offset))
        return hurt

class Entity():
    def __init__(self, group, sheet, map_x, map_y, data):
        self.group = group
        self.group_id = group.group_id
        self.map_x = map_x
        self.map_y = map_y
        self.frames = []
        self.frame_index = 0
        self.loadFrames(sheet)
        self.image = self.frames[self.frame_index]
        self.rect = self.image.get_rect()
        self.rect.x, self.rect.y = self.getRectPos(map_x, map_y)
        
        self.attr = EntityAttr(data)
        self.health = self.attr.max_health
        self.weapon = None
        self.enemy = None
        self.state = c.IDLE
        self.animate_timer = 0.0
        self.current_time = 0.0
        self.move_speed = c.MOVE_SPEED
    
    def getRectPos(self, map_x, map_y):
        if c.MAP_HEXAGON:
            base_x, base_y = tool.getHexMapPos(map_x, map_y)
            return (base_x + 4, base_y + 6)
        else:
            return(map_x * c.REC_SIZE + 5, map_y * c.REC_SIZE + 8)

    def getRecIndex(self, x, y):
        if c.MAP_HEXAGON:
            x += c.HEX_X_SIZE // 2 - 4
            y += c.HEX_Y_SIZE // 2 - 6
            map_x, map_y = tool.getHexMapIndex(x, y)
        else:
            map_x, map_y = (x//c.REC_SIZE, y//c.REC_SIZE)
        return (map_x, map_y)

    def loadFrames(self, sheet):
        frame_rect_list = [(64, 0, 32, 32), (96, 0, 32, 32)]
        for frame_rect in frame_rect_list:
            self.frames.append(tool.get_image(sheet, *frame_rect, 
                            c.BLACK, c.SIZE_MULTIPLIER))
        
    def setDestination(self, map_x, map_y, enemy=None):
        self.dest_x, self.dest_y = self.getRectPos(map_x, map_y)
        self.next_x, self.next_y = self.rect.x, self.rect.y
        self.enemy = enemy
        self.state = c.WALK
    
    def setTarget(self, enemy):
        self.enemy = enemy
        self.state = c.ATTACK

    def getHealthRatio(self):
        if self.health > 0:
            return self.health / self.attr.max_health
        else:
            return 0
    
    def isDead(self):
        return self.health <= 0
    
    def isRemote(self):
        return self.attr.remote

    def inRange(self, map, map_x, map_y):
        location = AStarSearch.AStarSearch(map, (self.map_x, self.map_y), (map_x, map_y))
        if location is not None:
            _, _, distance = AStarSearch.getFirstStepAndDistance(location)
            if distance <= self.attr.range:
                return True
        return False
    
    def putHurt(self, enemy):
        hurt = self.attr.getHurt(enemy.attr)
        enemy.setHurt(hurt)

    def setHurt(self, damage):
        self.health -= damage
        if self.isDead():
            self.group.removeEntity(self)
    
    def shoot(self, enemy):
        hurt = self.attr.getHurt(enemy.attr)
        self.weapon = FireBall(*self.rect.center, self.enemy, hurt)
     
    def walkToDestination(self, map):
        if self.rect.x == self.next_x and self.rect.y == self.next_y:
            source = self.getRecIndex(self.rect.x, self.rect.y)
            dest = self.getRecIndex(self.dest_x, self.dest_y)
            location = AStarSearch.AStarSearch(map, source, dest)
            if location is not None:
                map_x, map_y, _ = AStarSearch.getFirstStepAndDistance(location)
                self.next_x, self.next_y = self.getRectPos(map_x, map_y)
            else:
                self.state = c.IDLE

        if c.MAP_HEXAGON and self.rect.x != self.next_x and self.rect.y != self.next_y:
            self.rect.x += self.move_speed if self.rect.x < self.next_x else -self.move_speed
            self.rect.y += self.move_speed if self.rect.y < self.next_y else -self.move_speed
        elif self.rect.x != self.next_x:
            self.rect.x += self.move_speed if self.rect.x < self.next_x else -self.move_speed
        elif self.rect.y != self.next_y:
            self.rect.y += self.move_speed if self.rect.y < self.next_y else -self.move_speed

    def update(self, game_info, map):
        self.current_time = game_info[c.CURRENT_TIME]
        if self.state == c.WALK:
            if (self.current_time - self.animate_timer) > 250:
                if self.frame_index == 0:
                    self.frame_index = 1
                else:
                    self.frame_index = 0
                self.animate_timer = self.current_time

            if self.rect.x != self.dest_x or self.rect.y != self.dest_y:
                self.walkToDestination(map)
            else:
                map.setEntity(self.map_x, self.map_y, None)
                self.map_x, self.map_y = self.getRecIndex(self.dest_x, self.dest_y)
                map.setEntity(self.map_x, self.map_y, self)
                if self.enemy is None:
                    self.state = c.IDLE
                else:
                    self.state = c.ATTACK
        elif self.state == c.ATTACK:
            if self.attr.remote:
                if self.weapon is None:
                    self.shoot(self.enemy)
                else:
                    self.weapon.update()
                    if self.weapon.done:
                        self.weapon = None
                        self.enemy = None
                        self.state = c.IDLE
            else:
                self.putHurt(self.enemy)
                self.enemy = None
                self.state = c.IDLE
        
        if self.state == c.IDLE:
            self.frame_index = 0

    def draw(self, surface):
        self.image = self.frames[self.frame_index]
        surface.blit(self.image, self.rect)
        width = self.rect.width * self.getHealthRatio()
        height = 5
        pg.draw.rect(surface, c.RED, pg.Rect(self.rect.left, self.rect.top - height - 1, width, height))
        
        if self.weapon is not None:
            self.weapon.draw(surface)

class EntityGroup():
    def __init__(self, group_id):
        self.group = []
        self.group_id =  group_id
        self.entity_index = 0

    def createEntity(self, entity_list, map):
        for data in entity_list:
            entity_name, map_x, map_y = data['name'], data['x'], data['y']
            if map_x < 0:
                map_x = c.GRID_X_LEN + map_x
            if map_y < 0:
                map_y = c.GRID_Y_LEN + map_y
            
            entity = Entity(self, tool.GFX[entity_name], map_x, map_y, tool.ATTR[entity_name])
            self.group.append(entity)
            map.setEntity(map_x, map_y, entity)
        
        #self.group = sorted(self.group, key=lambda x:x.attr.speed, reverse=True)
    
    def removeEntity(self, entity):
        for i in range(len(self.group)):
            if self.group[i] == entity:
                if (self.entity_index > i or
                    (self.entity_index >= len(self.group) - 1)):
                    self.entity_index -= 1
        self.group.remove(entity)
    
    def isEmpty(self):
        if len(self.group) == 0:
            return True
        return False

    def nextTurn(self):
        self.entity_index = 0

    def getActiveEntity(self):
        if self.entity_index >= len(self.group):
            entity = None
        else:
            entity = self.group[self.entity_index]
        return entity

    def consumeEntity(self):
        self.entity_index += 1

    def update(self, game_info, map):
        for entity in self.group:
            entity.update(game_info, map)

    def draw(self, surface):
        for entity in self.group:
            entity.draw(surface)

实现这两个类后咱们的游戏(手游)基本上就实现了

游戏运行截图

退出移动版