-1

I'm a 17 year old programmer, trying to program an isometric game in python, with pygame. After finishing a tile engine, working with not good looking, gimp-drawn PNG's, I wondered, if it would be possible to render some Tiles by texture. I hope I provided all what's needed to understand, what's my issue and please excuse my not perfect English.

Simply what I want to do, is to generate a 128 by 128 Pixel width Image of an Isometric Tile, using the following picture as texture for all three sides of the Block:

texture

(Links here because I'm not yet allowed to put pictures in, due to it's my first post)

To explain better, what I'm trying to do, I have drawn this little picture: desired output

I have already searched the Internet for about 2 hours and didn't come to a solution, except for the top part of the Tile, here is what I already got in Code:

This is the Image Manipulation Module, the transformToRightPart() is the method where I need help:

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)

And this is the Module, which creates a window with the tile to render:

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)

The output of the code looks like this:

my output

What I'm trying to achieve is, that it looks, something like this:

desired output

Spektre
  • 49,595
  • 11
  • 110
  • 380
Blarify
  • 29
  • 1
  • 11
  • see [2D Diamond (isometric) map editor - Textures extended infinitely?](https://stackoverflow.com/a/36454198/2521214) for some ideas. Yes it can be done with seamless textures. Either use 3D rendering or 2D by copying pixels into isometric planes view. In the latter you need to define 3 regions of your sprite and map the texture pixels to it. I do not use python so I cant tell what is wrong in your code but using rotations looks like overkill you just swap basis vectors ... – Spektre Jun 19 '17 at 06:44
  • Great thanks Spektre, for changing the links in my question to pictures and thanks for the article you mentioned, doesn't help anything with my proplem, but ist quite interesting though! :D - Actually I'm bound to python, so unfortunatly I can't use a 3D rendering engine. – Blarify Jun 19 '17 at 16:07
  • do you have direct pixel access? otherwise you would need to construct your sprite with rotations translations and cropping/masking for which you need to know the coordinate systems ... – Spektre Jun 19 '17 at 17:00
  • (0,0) is the upper left corner, while the first value say's how many pixels it goes to the right and the secound how many pixels down. The approuch I'm trying now, is to scale the texture sprite to 64x64, create 64 1px*64px Subsurfaces of the sprite, each one one Pixel wide column and then move every secound column, all the column's one pixel upwards. As always, after I fixed one error, the next one comes and I don't get it to run. - You could also have direct pixel access, I found an article, but it's to complicated for me to understand: http://www.petercollingridge.co.uk/book/export/html/550 – Blarify Jun 19 '17 at 18:01
  • added answer with direct pixel approach I had in mind ... funny took me longer to write the answer than to code this. – Spektre Jun 19 '17 at 18:36
  • Big thanks for this - Now all I need to do, is to learn C Plus Plus and then do the equivalent of your code in python ^^ - I'm going to print your code out right now and take it with me tomorrow to school and try to understand what you did. - I tried to give you an upvote, there was a message saying, it would be recorded, but not be released until I get 15 reputation O.o – Blarify Jun 19 '17 at 20:44
  • the important stuff is starting by `[render sprite]` what you need to know to understand the code is that: `ptxr[y][x]` is pixel at `x,y` in texture and `pspr[y][x]` is pixel at `x,y` in sprite ... I loop through all the pixels in texture compute corresponding coordinate in sprite and copy the pixel that is all ....you could also add some lighting to enhance the 3D look – Spektre Jun 19 '17 at 21:00
  • 1
    Spektre, really big thanks for all the effort you put in helping me, I got my solution to work now, but after I'm a bit deeper in programming, I will rework the code, taking you're code example into account, as I'm now not experienced to understand anything of what's going on there. :D – Blarify Jun 20 '17 at 17:15

2 Answers2

1

Big thanks to Spektre for all the effort he made, trying to help me, but all in all, after two days of over-thinking the problem and bug-fixing, I came up with a solution myself. It might not be as fast or efficient as targeting the pixels directly in an array, like Spektre did in his c++ example, but it is a way, you're only dependencies are pygame, and it is easy to understand.

What did I do? - I wrote two functions, the first getting a surface containing only a single column of another surface, with an index, referring to the x position of the column. And the second, calculating a coefficient, how far down each row should get moved, if the last row should get shifted down a certain amount of pixels and then returning a surface with the shifted picture.

Here is the magic Code:

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

After all, big respect to Spektres coding skills, even though I'm to dumb to understand anything from the c plus plus example, as I'm a total beginner.

Blarify
  • 29
  • 1
  • 11
0

Well I did this by simply copy the texture pixels into sprite using plane projections (basis vectors of each side) + some rescaling as the texture does not correspond with your sprite resolution. I did it in C++ so here my commented code (you can extract the equations from it):

// [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;

The texture must be square otherwise the right side rendering will have access violation troubles not to mention visible seams ...

Here output sprite example of this code:

rendered sprite

Now how it works:

overview

ignore the VCL init/load/exit stuff handling images as the important stuff is just the rendering.

Each part consist of setting start point (red square) and convert texture x,y coordinates into offset from that start point in plane projection basis vectors (the black arrows).

And the offset is also multiplied by the resolution ratio between texture and sprite to handle their different sizes.

Look here to understand the direct pixel access I used:

PS

You can add lighting to enhance 3D look ... This is how it looks when top side is 100% left side is 75% and right side is 50% of intensity:

lighting

simulating light coming from above left side

Spektre
  • 49,595
  • 11
  • 110
  • 380