Python, Pygame, манипуляции с изображениями: растяните загруженный png, чтобы он стал текстурой для изометрической плитки.

Я 17-летний программист, пытаюсь запрограммировать изометрическую игру на питоне с помощью pygame. После завершения тайлового движка, работающего с не очень красивыми PNG-файлами, нарисованными с помощью GIMP, я подумал, можно ли будет визуализировать некоторые тайлы по текстуре. Я надеюсь, что предоставил все, что нужно, чтобы понять, в чем моя проблема, и, пожалуйста, извините мой не идеальный английский.

Просто то, что я хочу сделать, это создать изображение шириной 128 на 128 пикселей изометрической плитки, используя следующее изображение в качестве текстуры для всех трех сторон блока:

текстура

(Ссылки здесь, потому что мне еще не разрешено вставлять изображения, так как это мой первый пост)

Чтобы лучше объяснить, что я пытаюсь сделать, я нарисовал эту маленькую картинку: желаемый результат

Я уже порылся в интернете около 2 часов и так и не пришел к решению, кроме верхней части Плитки, вот что я уже получил в Коде:

Это модуль обработки изображений, метод transformToRightPart(), в котором мне нужна помощь:

import pygame

class Image(object):
    '''
    Use this Module to create Tiles by Texture to use them later in the Tileengine.
    It is important to run pygame.init() before creating objects of this class!
    Contains unfinished Elements!
    '''
    def __init__(self, path):
        self.loadFromPath(path)

    def getIMG(self):
        assert self.originalIMG is not None, "No picture to return"
        if not self.IMG == None:
            return self.IMG
        else:
            return self.originalIMG

    def loadFromPath(self, path):
        '''
        Don't do convert() or convert_alpha() here,
        as Objects of this class are created during the loading process,
        with no pygame.display() created.
        '''
        self.originalIMG = pygame.image.load(path)
        self.IMG = None

    def transformToTopPart(self):
        '''
        Transforms the loaded Image to the Top Part of an Isometric Tile, with the Dimensions 2:1,
        said in Pixels: 128 px Width by 64 px Height.
        '''
        self.IMG = pygame.transform.rotate(self.originalIMG, 45)
        self.IMG = pygame.transform.scale(self.IMG, (128, 64))

    def transformToRightPart(self):
        '''
        TODO!! Don't ask how (X.X)
        Transforms the loaded Image to the right Part of an Isometric Tile.
        '''
        assert False, "This method isn't finished, try something different ;)"

    def transformToLeftPart(self):
        '''
        Transforms the loaded Image to the left Part of an Isometric Tile.
        Due to the nice geometric fact, that the shape of the left part,
        is just the flipped right part shape and we don't lose quality by flipping,
        we do this little trick, to enshorten the code.
        '''
        self.originalIMG = pygame.transform.flip(self.originalIMG, True, False)
        self.transformToRightPart()
        self.IMG = pygame.transform.flip(self.IMG, True, False)
        self.originalIMG = pygame.transform.flip(self.originalIMG, True, False)

А это модуль, который создает окно с тайлом для рендеринга:

import pygame, sys

from ImageManipulation import Image
from pygame.locals import *

if __name__ == '__main__':
    pygame.init()
    FPS=20
    fpsClock = pygame.time.Clock()
    picture = Image("Stone_Floor_texture.png")
    picture.transformToTopPart()
    DISPLAY = pygame.display.set_mode((400,400),0,32)
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
        DISPLAY.blit(picture.getIMG(),(0,0))
        pygame.display.update()
        fpsClock.tick(FPS)

Вывод кода выглядит так:

мой вывод

Я пытаюсь добиться того, чтобы это выглядело примерно так:

желаемый результат


person Blarify    schedule 18.06.2017    source источник
comment
см. редактор карт 2D Diamond (изометрический) - бесконечное расширение текстур? для некоторых идей. Да, это можно сделать с бесшовными текстурами. Либо используйте 3D-рендеринг, либо 2D, копируя пиксели в вид изометрических плоскостей. В последнем вам нужно определить 3 области вашего спрайта и сопоставить с ними пиксели текстуры. Я не использую python, поэтому не могу сказать, что не так в вашем коде, но использование поворотов выглядит излишним, вы просто меняете базовые векторы...   -  person Spektre    schedule 19.06.2017
comment
Большое спасибо Spektre за замену ссылок в моем вопросе на картинки и спасибо за статью, которую вы упомянули, ничего не помогает с моей проблемой, но довольно интересно! :D - На самом деле я привязан к Python, поэтому, к сожалению, я не могу использовать движок 3D-рендеринга.   -  person Blarify    schedule 19.06.2017
comment
у вас есть прямой доступ к пикселям? в противном случае вам нужно будет построить свой спрайт с переводами вращения и обрезкой/маскировкой, для которых вам нужно знать системы координат...   -  person Spektre    schedule 19.06.2017
comment
(0,0) — это верхний левый угол, а первое значение указывает, на сколько пикселей оно уходит вправо, а второе — на сколько пикселей вниз. Подход, который я пробую сейчас, состоит в том, чтобы масштабировать спрайт текстуры до 64x64, создать 64 1px * 64px Subsurface спрайта, каждый столбец шириной в один пиксель, а затем переместить каждый второй столбец, все столбцы на один пиксель вверх. Как всегда, после того, как я исправил одну ошибку, появляется следующая, и я не могу ее запустить. - У вас также может быть прямой доступ к пикселям, я нашел статью, но мне сложно понять: petercollingridge.co.uk/book/export/html/550   -  person Blarify    schedule 19.06.2017
comment
добавлен ответ с прямым пиксельным подходом, который я имел в виду ... забавно, что мне потребовалось больше времени, чтобы написать ответ, чем закодировать это.   -  person Spektre    schedule 19.06.2017
comment
Большое спасибо за это - теперь все, что мне нужно сделать, это изучить C Plus Plus, а затем сделать эквивалент вашего кода на python ^^ - я собираюсь распечатать ваш код прямо сейчас и взять его с собой завтра в школу и попытайтесь понять, что вы сделали. - Я пытался проголосовать за вас, было сообщение, что оно будет записано, но не будет выпущено, пока я не наберу 15 репутации O.o   -  person Blarify    schedule 19.06.2017
comment
важные вещи начинаются с [render sprite] что вам нужно знать, чтобы понять код, это то, что: ptxr[y][x] это пиксель в x,y в текстуре и pspr[y][x] это пиксель в x,y в спрайте ... я перебираю все пиксели в текстуре вычисляя соответствующие координаты в спрайте и скопируйте пиксель, это все .... вы также можете добавить немного освещения, чтобы улучшить 3D-вид   -  person Spektre    schedule 20.06.2017
comment
Spektre, действительно большое спасибо за все усилия, которые вы приложили, чтобы помочь мне, теперь у меня есть свое решение, но после того, как я немного углублюсь в программирование, я переработаю код, принимая во внимание ваш пример кода, как У меня сейчас нет опыта, чтобы понять что-либо из того, что там происходит. :D   -  person Blarify    schedule 20.06.2017


Ответы (2)


Большое спасибо Spektre за все усилия, которые он приложил, пытаясь помочь мне, но в целом, после двух дней обдумывания проблемы и исправления ошибок, я сам придумал решение. Это может быть не так быстро или эффективно, как нацеливание на пиксели непосредственно в массиве, как это сделал Spektre в своем примере на C++, но это способ, вы только зависите от pygame, и его легко понять.

Что я сделал? - Я написал две функции, первая из которых получает поверхность, содержащую только один столбец другой поверхности, с индексом, относящимся к позиции x столбца. И второе, вычисление коэффициента, насколько вниз должна быть сдвинута каждая строка, если последняя строка должна быть сдвинута вниз на определенное количество пикселей, а затем возвращение поверхности со сдвинутой картинкой.

Вот волшебный код:

import pygame

from pygame.locals import *
from pygame import Surface

def getColumn(surface, index):
    assert index <= surface.get_width(), "index can't be bigger, than surface width"
    height = surface.get_height()
    subsurf = Surface((1,height)) # Create Surface 1 px by picture-height high, to store the output in
    subsurf.blit(surface.subsurface(pygame.Rect( (index,0),(1,height) )),(0,0)) # Blit a one pixel width subsurface with x Position at index of the image to subsurf
    return subsurf

def shiftRightDown(surface, pixels):
    size = surface.get_size()
    newSize = (size[0], size[1]+pixels)
    coeff = pixels / size[0]
    returnSurface = Surface(newSize)
    for i in range(size[1]): # here happens the magic
        returnSurface.blit(getColumn(surface, i), (i,0+int(i*coeff)))
    return returnSurface

В конце концов, большое уважение к навыкам кодирования Spektres, хотя я слишком глуп, чтобы понять что-либо из примера c plus plus, поскольку я новичок.

person Blarify    schedule 20.06.2017

Что ж, я сделал это, просто скопировав пиксели текстуры в спрайт, используя плоские проекции (базовые векторы каждой стороны) + некоторый масштаб, поскольку текстура не соответствует разрешению вашего спрайта. Я сделал это на C++, так что вот мой закомментированный код (вы можете извлечь из него уравнения):

// [constants]
const int sxs=128;              // target sprite resolution [pixels]
const int sys=128;
const int height=32;            // height/thickness of your tile [pixels]
const DWORD cback=0x00FFFFFF;   // background color (or invisible for the sprite)

// [variables]
DWORD **ptxr,**pspr;            // direct pixel access pointers (any 32bit variable type)
Graphics::TBitmap *txr,*spr;    // VCL bitmaps
int txs,tys,x,y,x0,y0,xx,yy,th;

// [init]
// create VCL bitmaps (can ignore this)
txr=new Graphics::TBitmap; // input texture
spr=new Graphics::TBitmap; // output sprite
// load texture
txr->LoadFromFile("texture.bmp");
txs=txr->Width;
tys=txr->Height;

// prepare sprite resolution
spr->SetSize(sxs,sys);
// allow direct pixel access
txr->HandleType=bmDIB; txr->PixelFormat=pf32bit; ptxr=new DWORD*[tys]; for (y=0;y<tys;y++) ptxr[y]=(DWORD*)txr->ScanLine[y];
spr->HandleType=bmDIB; spr->PixelFormat=pf32bit; pspr=new DWORD*[sys]; for (y=0;y<sys;y++) pspr[y]=(DWORD*)spr->ScanLine[y];

// [render sprite]
th=height*(txs-1)/(sxs-1);  // height of tile in texture [pixels]
// clear
for (y=0;y<sys;y++)
 for (x=0;x<sxs;x++)
  pspr[y][x]=cback;
// top side
x0=0; y0=(sys*3/4)-height;
for (y=0;y<tys;y++)
 for (x=0;x<txs;x++)
    {
    // isometric projection of top side
    xx=x0+(x+y)*(sxs-1)/((txs-1)*2);
    yy=y0+(x-y)*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[y][x];
    }
// left side
x0=0; y0=(sys*3/4)-height;
for (y=0;(y<tys)&&(y<th);y++)
 for (x=0;x<txs;x++)
    {
    // isometric projection of top side
    xx=x0+(x      )*(sxs-1)/((txs-1)*2);
    yy=y0+(x+(4*y))*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[y][x];
    }
// right side
x0=sxs/2; y0=sys-height-1;
for (y=0;(y<txs)&&(y<th);y++) // x,y are swapped to avoid connection seems
 for (x=0;x<tys;x++)
    {
    // isometric projection of top side
    xx=x0+(+x      )*(sxs-1)/((txs-1)*2);
    yy=y0+(-x+(4*y))*(sxs-1)/((txs-1)*4);
    // copy pixel from texture to sorite
    if ((xx>=0)&&(xx<sxs)&&(yy>=0)&&(yy<sys))
     pspr[yy][xx]=ptxr[x][y];
    }

// here do your stuff with your sprite spr I render source and resulting images into bitmap to show on screen
// you can ignoe this
bmp->SetSize(txs+5+sxs,max(tys,sys));
bmp->Canvas->Brush->Color=clBtnFace;
bmp->Canvas->FillRect(TRect(0,0,bmp->Width,bmp->Height));
bmp->Canvas->Draw(0,0,txr);
bmp->Canvas->Draw(txs+5,0,spr);

// [exit]
// release memory
delete[] ptxr;
delete[] pspr;
if (txr) delete txr; txr=NULL;
if (spr) delete spr; spr=NULL;

Текстура должна быть квадратной, иначе при рендеринге правой стороны будут проблемы с нарушением прав доступа, не говоря уже о видимых швах...

Вот пример вывода спрайта этого кода:

обработанный спрайт

Теперь, как это работает:

обзор

игнорируйте элементы VCL init/load/exit, обрабатывающие изображения, так как важным является только рендеринг.

Каждая часть состоит из установки начальной точки (красный квадрат) и преобразования координат текстуры x,y в смещение от этой начальной точки в базисных векторах плоской проекции (черные стрелки).

И смещение также умножается на отношение разрешения между текстурой и спрайтом, чтобы обрабатывать их разные размеры.

Посмотрите здесь, чтобы понять, какой прямой доступ к пикселям я использовал:

ПС

Вы можете добавить освещение, чтобы улучшить 3D-вид... Вот как это выглядит, когда верхняя сторона составляет 100%, левая сторона - 75%, а правая сторона - 50% интенсивности:

освещение

имитация света сверху слева

person Spektre    schedule 19.06.2017