简略的战旗游戏开发学习
在网上找寻教程之后搞出了这么个雏形
游戏介绍
游戏实现了战斗场景的回合制玩法:对战单方每个生物每一轮有一次口头机会,能够行走或攻打对方。每个生物属性有:行走范畴,速度,生命,挫伤,进攻,攻打 和 是否是近程兵种。当把对方生物都毁灭时,即胜利。
代码介绍
对于战旗类游戏的外围还是地图,尽管网上有六边形的地图教程然而没看太懂就做正方形的吧 首先定义函数,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)
实现这两个类后咱们的游戏(手游)基本上就实现了
游戏运行截图