1

マップエディタを作成しようとしています。マップは、各六角形がマップのタイルである六角形のグリッドになる予定です。タイルは、その領域 (海、牧草地、砂漠、山など) をグラフィカルに表現したものになります。マップは任意のサイズを想定しています。今のところ、ここで要件を凍結しましょう:)

PyQt4 を使用したい (設計要件として使用)。Qt/PyQt を使い始めたばかりなので、広大さの問題に直面しています。この Qt は大きすぎて、すべてを把握することはできません。そして、ここで私はあなたの親切で最も歓迎される経験を求めています.

少しグーグルで調べた後、QGraphicalView/Scene アプローチを使用することにしました。実際、QGraphicalView から継承する独自の hexgrid クラスを作成し、QGraphicalPolygonItem から継承する RegularPolygon クラスを作成することを考えていました。

今、彼らは疑問と問題を抱えています。

私の主な疑問は、「私のアプローチは正しいものですか?」ということです。投稿の冒頭で説明した必要性について考えてみてください: 各六角形が特定のタイプ (海、砂漠、牧草地、山など) のタイルになる六角形マップ。エディタが動くとパフォーマンスが気になる(スクロールが気持ちいい?とか)。

これまでのところ、問題は精度に関するものです。すべての六角形を作成して描画することで、六角形グリッドを描画しています(これは私には悪いように聞こえます...パフォーマンスについて考えています)。いくつかの式を使用して各六角形の頂点を計算し、そこから多角形を作成しました。2 つの連続する六角形の側面が同じ位置で正確に一致することを期待していますが、六角形の側面が同じ位置で完全に一致する (良い) 場合もあれば、一致しない場合もあるため、丸めは私の欲求に少し合っているようです。 1ピクセルの違い(悪い)と思われるもの。これにより、グリッドの視覚的な印象が悪くなります。たぶん、私は自分自身をよく説明していません...コードを渡して、自分で実行したほうがよいでしょう

要約すると:

  1. 私のアプローチが将来のパフォーマンスの問題を引き起こすと思いますか?
  2. 六角形が辺を共有するように正確に配置されていないのはなぜですか? この問題を回避するには?

コード:

#!/usr/bin/python
"""
Editor of the map.
"""

__meta__ =  \
{
    (0,0,1): (
              [ "Creation" ],
              [ ("Victor Garcia","vichor@xxxxxxx.xxx") ]
             )
} 

import sys, math
from PyQt4 import QtCore, QtGui

# ==============================================================================
class HexGrid(QtGui.QGraphicsView):
    """
    Graphics view for an hex grid.
    """

    # --------------------------------------------------------------------------
    def __init__(self, rect=None, parent=None):
        """
        Initializes an hex grid. This object will be a GraphicsView and it will
        also handle its corresponding GraphicsScene.
            rect -- rectangle for the graphics scene.
            parent -- parent widget
        """
        super(HexGrid,self).__init__(parent)

        self.scene = QtGui.QGraphicsScene(self)
        if rect != None: 
            if isinstance(rect, QtCore.QRectF): self.scene.setSceneRect(rect)
            else: raise StandardError ('Parameter rect should be QtCore.QRectF')
        self.setScene(self.scene)

# ==============================================================================
class QRegularPolygon(QtGui.QGraphicsPolygonItem):
    """
    Regular polygon of N sides
    """

    def __init__(self, sides, radius, center, angle = None, parent=None):
        """
        Initializes an hexagon of the given radius.
            sides -- sides of the regular polygon
            radius -- radius of the external circle
            center -- QPointF containing the center
            angle -- offset angle in radians for the vertices
        """
        super(QRegularPolygon,self).__init__(parent)

        if sides < 3: 
            raise StandardError ('A regular polygon at least has 3 sides.')
        self._sides = sides
        self._radius = radius
        if angle != None: self._angle = angle
        else: self._angle = 0.0
        self._center = center

        points = list()
        for s in range(self._sides):
            angle = self._angle + (2*math.pi * s/self._sides)
            x = center.x() + (radius * math.cos(angle))
            y = center.y() + (radius * math.sin(angle))
            points.append(QtCore.QPointF(x,y))

        self.setPolygon( QtGui.QPolygonF(points) )


# ==============================================================================
def main():
    """
    That's it: the  main function
    """
    app = QtGui.QApplication(sys.argv)

    grid = HexGrid(QtCore.QRectF(0.0, 0.0, 500.0, 500.0))

    radius = 50
    sides = 6

    apothem = radius * math.cos(math.pi/sides)
    side = 2 * apothem * math.tan(math.pi/sides)

    xinit = 50
    yinit = 50
    angle = math.pi/2
    polygons = list()

    for x in range(xinit,xinit+20):
        timesx = x - xinit
        xcenter = x + (2*apothem)*timesx
        for y in range(yinit, yinit+20):
            timesy = y - yinit
            ycenter = y + ((2*radius)+side)*timesy

            center1 = QtCore.QPointF(xcenter,ycenter)
            center2 = QtCore.QPointF(xcenter+apothem,ycenter+radius+(side/2))

            h1 = QRegularPolygon(sides, radius, center1, angle)
            h2 = QRegularPolygon(sides, radius, center2, angle)

            # adding polygons to a list to avoid losing them when outside the
            # scope (loop?). Anyway, just in case
            polygons.append(h1)
            polygons.append(h2)

            grid.scene.addItem(h1)
            grid.scene.addItem(h2)

    grid.show()
    app.exec_()

# ==============================================================================
if __name__ == '__main__':
    main()

最後になりましたが、長い投稿で申し訳ありません:)

ありがとうビクター

4

4 に答える 4

4

個人的には、各六角形のタイルを個別の SVG 画像として定義し、QImage および QSvgRenderer クラスを使用して、ズーム レベルが変化するたびに QPixmaps (アルファ チャネル付き) にレンダリングします。各タイルを表示するための QGraphicsItem サブクラスを作成します。

コツは、(直立した) 六角形の幅が 2 の倍数、高さが 4 の倍数、幅/高さが約 sqrt(3/4) になるようにズーム レベルを選択することです。六角形はどちらの方向にもわずかに押しつぶされていますが、直径が少なくとも 8 ピクセルのすべての六角形では、その効果は知覚できません。

六角形の幅が 、2*w高さが である場合4*h、(直立した) 六角形をデカルト座標にマッピングする方法は次のとおりです。

長方形グリッドの六角形

六角形の各辺が である場合ah=a/2そしてw=a*sqrt(3)/2、したがってw/h=sqrt(3).

最適な表示品質を得るには、整数wとを選択hして、それらの比率が約 になるようにしますsqrt(3) ≃ 1.732。これは、六角形がわずかにつぶれることを意味しますが、問題ありません。それは知覚できません。

座標は常に整数であるため、事前にレンダリングされた六角形タイルにアルファ チャネルがあり、アルファ トランジションをスムーズにするための境界線がある限り、安全に (ディスプレイ アーティファクトなしで) 使用できます。各長方形のタイルは、2*w+2*b幅が4*h+2*bピクセル、高さがbピクセルです。

オーバーラップするすべてのタイルでピクセルが部分的にしか不透明ではない、認識可能な継ぎ目 (背景色のにじみ) を避けるために、追加の境界線が必要です。境界線を使用すると、タイルを隣接するタイルにうまくブレンドできます。これは、SVG タイルに小さな境界領域を含めると、SVG レンダラーが自動的に行うことです。

xが右および下に成長する典型的な画面座標を使用する場合y、六角形の座標X,Y0,0自明です。

y = 3*h*Y
if Y is even, then:
    x = 2*w*X
else:
    x = 2*w*X + w

明らかに、六角形の奇数行は六角形の半分の右側に配置されます。

QGraphicsItem をサブクラス化し、バウンディング ポリゴンを使用する (マウスおよびインタラクション テスト用) ということは、マウスがどの六角形のタイルの上に乗っているかを知りたい場合に、Qt がすべての重い作業を行うことを意味します。

ただし、画面座標から六角形への逆マッピングは自分で行うことができます。

まず、座標ペアがどの長方形のグリッド セル (上の画像の緑色のグリッド ライン) にあるかを計算します。

u = int(x / w)
v = int(y / h)

すべての座標が非負であると仮定しましょう。それ以外の場合は、「で割ったときの非負の余り」%と読まなければなりません。(つまり、すべての に対して、負であっても;はここでは常に正の整数です。)0 <= a % b < baab

原点が上の画像のようになっている場合、六角形の奇数行ごとに 1 グリッド セル右にシフトすることを除いて、3 行ごとに 2 行は自明です。

if v % 3 >= 1:
    if v % 6 >= 4:
        X = int((u - 1) / 2)
        Y = int(v / 3)
    else:
        X = int(u / 2)
        Y = int(v / 3)

3 行ごとに、斜めの境界を持つ長方形のグリッド セルが含まれていますが、心配する必要はありません\

    (x % w) * h   >=   (y % h) * w

右上の三角形の部分にいるかどうかを確認します。境界が/wrt の場合。上の画像、確認する必要があるのは

    (x % w) * h + (y % h) * w   >=   (w * h - (w + h) / 2)

右下の三角形の部分にいるかどうかを確認します。

長方形グリッド セルの 4 列と 6 行の各セクションには、上記のテスト句のいずれかを使用して、処理する必要がある 8 つのケースがあります。(私は怠け者なので、ここで if 句を正確に処理することはできません。前述のように、Qt に任せたいと思います。) この四角形の領域は、六角形のマップ全体に対して正確に繰り返されます。したがって、完全な座標変換には最大 9 つの if 句が必要になる場合があり (書き方によって異なります)、書くのが少し面倒です。

たとえば、カーソルが置かれている六角形に対するマウス カーソルの位置を決定したい場合は、まず上記を使用してマウスが置かれている六角形を決定し、次にマウス座標からその六角形の座標を差し引いて、その六角形に対する座標を取得します。現在の六角形。

于 2013-09-21T20:13:34.587 に答える
1

この main() 関数を試してください。あなたが使用した外接円(半径)の代わりに、内接円の半径(ri)を使用しました。今は少し良くなったように見えますが、まだ完全ではありません。六角形の上と下で斜辺の描き方が違うと思います。

def main():
    """
    That's it: the  main function
    """
    app = QtGui.QApplication(sys.argv)

    grid = HexGrid(QtCore.QRectF(0.0, 0.0, 500.0, 500.0))

    radius = 50 # circumscribed circle radius
    ri = int(radius / 2 * math.sqrt(3)) # inscribed circle radius
    sides = 6

    apothem = int(ri * math.cos(math.pi/sides))
    side = int(2 * apothem * math.tan(math.pi/sides))

    xinit = 50
    yinit = 50
    angle = math.pi/2
    polygons = list()

    for x in range(xinit,xinit+20):
        timesx = x - xinit
        xcenter = x + (2*apothem-1)*timesx
        for y in range(yinit, yinit+20):
            timesy = y - yinit
            ycenter = y + ((2*ri)+side)*timesy

            center1 = QtCore.QPointF(xcenter,ycenter)
            center2 = QtCore.QPointF(xcenter+apothem,ycenter+ri+(side/2))

            h1 = QRegularPolygon(sides, ri, center1, angle)
            h2 = QRegularPolygon(sides, ri, center2, angle)

            # adding polygons to a list to avoid losing them when outside the
            # scope (loop?). Anyway, just in case
            polygons.append(h1)
            polygons.append(h2)

            grid.scene.addItem(h1)
            grid.scene.addItem(h2)

    grid.show()
    app.exec_()
于 2013-09-18T12:04:08.873 に答える
0

ここにポリゴンが本当に必要ですか?後で、ゲームはラスター イメージを使用することになると思います。そのため、ポリゴンは表示目的にすぎません。多角形のすべての角を表す点群を取り、その下に線を引くことができます。これにより、丸め/浮動小数点演算などの問題を回避できます。

于 2013-09-19T20:26:53.000 に答える