1

我在 python 中开发了一个 RogueLike,我尝试用 OOP 和我的一点知识来为学生构建一个 python 课程。

mapRogue = ['~~~~~~~~~~',
            '~~~~.....Y',
            'YYYYY+YYYY',
            'YYYY....YY']

我想将此字符串映射转换为 2D 列表,其中包含定义我在 RogueLike 中的图块性质的对象。为此,我决定在读取此变量时使用字典来映射字符键和类以进行实例化mapRogue

我找到了一个使用继承的解决方案,但是恕我直言,这段代码并没有我想要的那么优雅,如果我以后想添加其他类型的平铺行为,可能不是很灵活。

使用继承的 DOOR 类

class Tile(object):
    #a tile of the map and its properties
    def __init__(self, name, char, position, blocked, color=(255, 255, 255), bgcolor=(0, 0, 0)):
        self.name = name
        self.blocked = blocked
        self.char = char
        self.color = color
        self.bgcolor = bgcolor
        self.position = position

class Door(Tile):
    def __init__(self,  name, char, position, blocked, bgcolor, key,color=(255, 255, 255), open=False ):
        Tile.__init__( self,name, char, position, blocked, color, bgcolor)
        self.state = open
        self.key = key

    def opening(self, key):
        if self.key == key:
            self.state = True

tilesObject = {".": {"name": 'floor', "obj": Tile, "bgcolor": (233, 207, 177), "block": False},
               "Y": {"name": 'forest', "obj": Tile, "bgcolor": (25, 150, 64), "block": True},
               "~": {"name": 'water', "obj": Tile, "bgcolor": (10, 21, 35), "block": False},
               "+": {"name": 'doors', "obj": Door, "bgcolor": (10, 10, 25), "block": False}}
import types
def load(mymap):
    tileMap = []
    x, y = (0,0)
    for line in mymap:
        tileLine = []
        for value in line:
            try:
                tile = tilesObject[value]
            except KeyError:
                return "Error on key"
            if tile["obj"].__name__ ==  "Door":
                obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"], key="42isTheKey", open=False)
            else:
                obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"])

            x += 1
            tileLine.append(obj)
        x = 0
        y += 1
        tileMap.append(tileLine)
    return tileMap


for line in load(mapRogue):
    for obj in line:
        print obj , "\n"

使用组合的 DOOR 类

我怀疑还有其他使用组合和/或策略模式的答案,所以我尝试用 Door 行为装饰 Tile 对象,但我被这个字典阻止了......

实际上我尝试了多种解决方案但没有成功,你有没有一个建议可以帮助我使用优雅的 oop 和 python 解决这个概念问题?

class Tile(object):
    #a tile of the map and its properties
    def __init__(self, name, char, position, blocked, color=(255, 255, 255), bgcolor=(0, 0, 0), door=None):
        self.name = name
        self.blocked = blocked
        self.char = char
        self.color = color
        self.bgcolor = bgcolor
        self.door = door
        self.position = position

# Door decorate the Tile object using composition
class Door(object):
    def __init__(self, key, open=False):
        self.state = open
        self.key = key

    def opening(self, key):
        if self.key == key:
            self.state = True

tilesObject = {".": {"name": 'floor', "obj": Tile, "bgcolor": (233, 207, 177), "block": False},
               "Y": {"name": 'forest', "obj": Tile, "bgcolor": (25, 150, 64), "block": True},
               "~": {"name": 'water', "obj": Tile, "bgcolor": (10, 21, 35), "block": False},
               "+": {"name": 'doors', "obj": Door, "bgcolor": (10, 10, 25), "block": False}}

def load(mymap):
    tileMap = []
    x, y = (0,0)
    for line in mymap:
        tileLine = []
        for value in line:
            try:
                tile = tilesObject[value]
            except KeyError:
                return "Error on key"

            # Here i need to detect when obj is Door 
                    # because i need to define a special Tile 
                    # decorated by Door behavior, 
                    # so it seems this is not a good solution :/

            x += 1
            tileLine.append(obj)
        x = 0
        y += 1
        tileMap.append(tileLine)
    return tileMap

一些信息的更新:

感谢@User和@Hyperborreus的回答,你是对的,我在这里简化了我的例子,在我的代码中,我有两层:

  • Tile不动的,
  • 以及GameObjects可以移动、攻击、防御和许多其他功能,composition就像本教程中使用的一样

使用pygame,我使用函数显示我的所有Tiles对象。draw_tile()

所以在这一点上,我需要一个DoorTile类之间的链接,以便稍后为玩家正确计算 fov,因为Door我的角色有行为并限制了我的角色的视野(属性被阻止或 fovState)。在那之后,我gameObject在这些已经绘制的Tile表面上绘制了所有的 。Door 是仅特定于 Tile 的计算的一部分,以及 roguelike 中的其他东西,所以这解释了为什么我定义了Door我希望的 like。

所以可能你对游戏定义字典的提议是正确的,我需要改变我实例化对象的方式,门/瓷砖的 oop 定义是一样的,但是当我阅读包含项目、门和的初始字符串映射时静态对象,我将gameObject实例化和Tile实例化分开..

字典在字符串列表中定义的 rogueLike 映射上实例化元素的想法基于此处建立的想法:https ://bitbucket.org/BigYellowCactus/dropout/

也许这段代码的创建者@dominic-kexel 也可以帮助我们解决这一点?

4

2 回答 2

1

恕我直言,您应该区分“图块”(底层底图)和“对象”(玩家可以与之交互的东西,例如打开的门、攻击的龙或杀死的坑)。

如果您想将此与 3D 视频游戏进行比较,“图块”将是您无法与之交互的环境,“对象”将是可点击的东西。

单纯的瓦片可以是一个单一类的实例,并保存所有瓦片相关和共有的信息,例如渲染提示(哪个字符使用哪种颜色)或移动方面(可以通过、移动速度等)。

然后将对象放置在瓷砖的顶部。

想象一下,你有一张有很多地板和墙壁的地图,在两个位置你有两扇门。所有“地砖”的行为都相同(您可以在地板上行走,无论使用哪种地砖),但您的头会靠在墙上(无论墙在哪里)。但门不同:一扇门需要“绿钥匙”,另一扇门需要“异见精灵的绣花钥匙”。

这种差异是您的if问题出现的地方。门需要额外的信息。为了定义地图,您需要所有瓦片(每个类中相同)和放置在某些瓦片上的另一个对象列表(每个对象不同)。

Doors、Wells、Dragons、Switches 等可以从一个公共基类继承,该基类实现标准动作,如“检查”、“交互”、“攻击”、“大喊”,也许还有特殊动作的特殊接口。

所以一个完整的游戏定义可能是这样的:

game = {'baseMap': '#here comes your 2D array of tiles',
'objects': [ {'class': Door, 'position': (x, y), 'other Door arguments': ...}, 
{'class': Door, 'position': (x2, y2), 'other Door arguments': ...},
{'class': Dragon, 'position': (x3, y3), 'dragon arguments': ...}, ] }

然后为了实例化实际对象(OO 意义上的对象,而不是游戏意义上的对象),遍历这个定义,并使用字典项作为关键字参数(双星号)调用每个对象的 c'tor。这只是众多方法中的一种。

对于渲染,如果磁贴为空,则显示磁贴表示,如果磁贴上有对象,则显示对象表示。


这就是我对双星号的意思:

class Door:
    def __init__ (self, position, colour, creaking = True):
        print (position, colour, creaking)

objDefs = [...,
          {'class': Door, 'kwargs': {'position': (2, 3), 'colour': 'black'} },
          ...]

#Here you actually iterate over objDefs
objDef = objDefs [1]
obj = objDef ['class'] (**objDef ['kwargs'] )

大编辑:

这是一个关于如何使用瓦片和对象实现地图渲染的想法。(只是我的两分钱):

#! /usr/bin/python3.2

colours = {'white': 7, 'green': 2, 'blue': 4, 'black': 0, 'yellow': 3}

class Tile:
    data = {'.': ('Floor', 'white', True),
        'Y': ('Forest', 'green', False),
        '~': ('Water', 'blue', False) }

    def __init__ (self, code, position):
        self.code = code
        self.position = position
        self.name, self.colour, self.passable = Tile.data [code]

    def __str__ (self):
        return '\x1b[{}m{}'.format (30 + colours [self.colour], self.code)

class GameObject:
    #here got he general interfaces common to all game objects
    def __str__ (self):
        return '\x1b[{}m{}'.format (30 + colours [self.colour], self.code)

class Door (GameObject):
    def __init__ (self, code, position, colour, key):
        self.code = code
        self.position = position
        self.colour = colour
        self.key = key

    def close (self): pass
        #door specific interface

class Dragon (GameObject):
    def __init__ (self, code, position, colour, stats):
        self.code = code
        self.position = position
        self.colour = colour
        self.stats = stats

    def bugger (self): pass
        #dragon specific interface

class Map:
    def __init__ (self, codeMap, objects):
        self.tiles = [ [Tile (c, (x, y) ) for x, c in enumerate (line) ] for y, line in enumerate (codeMap) ]
        self.objects = {obj ['args'] ['position']: obj ['cls'] (**obj ['args'] ) for obj in objects}

    def __str__ (self):
        return '\n'.join (
            ''.join (str (self.objects [ (x, y) ] if (x, y) in self.objects else tile)
                for x, tile in enumerate (line) )
            for y, line in enumerate (self.tiles)
            ) + '\n\x1b[0m'

mapRouge = ['~~~~~~~~~~',
            '~~~~.....Y',
            'YYYYY.YYYY',
            'YYYY....YY']

objects = [ {'cls': Door,
        'args': {'code': '.', 'position': (5, 2), 'colour': 'black',
        'key': 'Ancient Key of Constipation'} },
    {'cls': Dragon,
        'args': {'code': '@',  'position': (7, 3), 'colour': 'yellow',
        'stats': {'ATK': 20, 'DEF': 20} } } ]

theMap = Map (mapRouge, objects)
print (theMap)

这是结果:

在此处输入图像描述

于 2013-10-08T20:32:25.270 回答
0

这是您问题的简单解决方案:

kw = tile.copy()
cls = kw.pop('obj')
obj = cls(**kw)

        if tile["obj"].__name__ ==  "Door":
            obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"], key="42isTheKey", open=False)
        else:
            obj = tile["obj"](name=tile["name"], position=(x, y), char=value, blocked=tile["block"],bgcolor=tile["bgcolor"])

这是因为我重视你的所作所为:

我同意 Hyperboreus 的观点,即您在位置 = 瓷砖和放在瓷砖顶部的东西之间有所不同。

在另一款游戏中真正奏效的是瓷砖是相互连接的:

class Tile:
    def __init__(self):
        self.left_tile = None
        self.right_tile = None
        ...
        self.content = [FreeSpaceToWalk()]

    def can_I_walk_there(self, person):
        for content in self.content:
            if not content.can_I_walk_there(person): return False
        return True

这样,您可以通过按位置连接不相邻的图块来制作门户。

以下是一些内容:

class Door:
    password = '42'
    def can_I_walk_there(self, person):
        return person.what_is_the_password_for(self) == self.password

class FreeSpaceToWalk:
    def can_I_walk_there(self, person):
        return True

class Wall:
    def can_I_walk_there(self, person):
        return False
于 2013-10-08T21:16:41.350 回答