Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kivy support #59

Open
evbo opened this issue Jul 20, 2015 · 19 comments
Open

Kivy support #59

evbo opened this issue Jul 20, 2015 · 19 comments

Comments

@evbo
Copy link

evbo commented Jul 20, 2015

When displaying tiles in OpenGL formatted coordinate systems (y=0 occurs at bottom of page instead of top of page), there is a simple transformation needed to ensure data appearing at the top of the page has coordinates starting from max(y) rather than 0. Would it make sense to incorporate an image loader that handles this simple, yet common transformation?

See below where I had to subclass TiledMap and override the reload_images method just to set the image_loader to one that sets the y ordinate based on the current tileset's tileheight and height. Is there a cleaner, more future-version-compatible-robust way for loading images with correct coordinates for OpenGL displays?

class OpenGLTiledMap(TiledMap):


    def reload_images(self):

        def GL_image_loader(filename, flags, **kwargs):
            """ convert the y coordinate to opengl format where
                the bottom-most coordinate in the image is 0
            """
            ts = self.fname2ts[filename]
            def load((x,y,w,h)=None, flags=None):
                GL_y = ts.height - ts.tileheight - y
                return filename, (x,GL_y,w,h), flags

            return load

        self.fname2ts = dict((ts.source, ts) for ts in self.tilesets)
        self.image_loader = GL_image_loader
        super(OpenGLTiledMap, self).reload_images()

Also, this issue comes up again after you've loaded the images from the tileset and now need to play them as widgets in your OpenGL display window. Again, the conversion is needing to know tileset specific dimensions. Is there a convenient way to look this up?

For instance, I do something kinda ugly like this:

layer = self.map.get_layer_by_name('ground')
max_y = len(layer.data)
for (x, y, ts_props) in layer.tiles():
    # correct the y value for openGL displays
    y = max_y - y
@bitcraft
Copy link
Owner

Thanks for your interest with pytmx. As far as inverting the y-coordinate, there is a way to do that already (has partial support). Pass 'invert_y=True' to whatever loader or to the TiledMap constructor. That value will be saved in the TiledMap instance. I think the proper solution would be to modify TiledTileLayer.parse_xml to be inverted_axis aware. That would be somewhere around here: https://github.com/bitcraft/PyTMX/blob/master/pytmx/pytmx.py#L916
It would remove the need for a special loader and all data access would be flipped. Of course, then the data in memory will be flipped as well. Thoughts?

    tmx_data = pytmx.TiledMap(..., invert_y=True)

As far as image loading goes, there is another approach to image loading that you may be interested in. You can define a function to load images, without resorting to subclassing...pass an argument to the TiledMap constructor. Please see the link below for more information:
https://github.com/bitcraft/pytmx#custom-image-loading

What graphics library are you using now? ...just curious.

@evbo
Copy link
Author

evbo commented Jul 21, 2015

thanks, the built-in invert_y argument looks promising and it appears to only need a simple fix:

line 316 - 317 of pytmx.py:

if self.invert_y:
    o.y -= tileset.tileheight

should be:

if self.invert_y:
    o.y = tileset.height - tileset.tileheight - o.y

However, this code never actually runs for me because self.objects is empty since my Tiled tmx file does not contain objectgroups.

In otherwords, for me self.objects is empty since none of my layers are the expected instance on line 629:

if isinstance(layer, TiledObjectGroup)) 

Also, I am already using the image_loader approach you recommend. See my original comment, with the only difference being that I subclass in order to pass the current tileset. It is necessary to know the current tileset in order to get height and tileheight information. I need to subclass so that reload_images passes the current tileset to the image_loader function.

@bitcraft
Copy link
Owner

I guess I'm confused here. Does your graphics lib require a flipped tileset image? Could you tell me what graphics lib you are using? That would help me out a lot here. Any code that you've already created would be nice, too. I'd like to see what you are doing, and how I can make pytmx better with your contributions. Thanks.

@evbo
Copy link
Author

evbo commented Jul 21, 2015

Sorry, forgot to mention I am using the Python framework Kivy. It displays content assuming y=0 is at the bottom of the page (hence why I must change content at the top of the page to have y>0)

@bitcraft
Copy link
Owner

Awesome! I'm a big fan of kivy. I've gotten a few requests for ivy support now, I suppose I should do something about it. It would be a great help if you could send your changes in a PR (or diff/paste), so I have somewhere to start.

@evbo
Copy link
Author

evbo commented Jul 21, 2015

Sorry, I'm not too familiar with git hub but here is a working example with Kivy and Tiled tmx:

from kivy.uix.widget import Widget

from kivy.properties import StringProperty

from kivy.core.image import Image as CoreImage
from kivy.uix.image import Image

from pytmx import TiledMap


class TileGrid(Widget):
    """Creates a Kivy grid and puts the tiles in a KivyTiledMap in it."""
    map_file = StringProperty('path\to\tmx.tmx')

    def __init__(self, **kwargs):

        self.map = OpenGLTiledMap(self.map_file)
        # self.map = TiledMap(self.map_file, invert_y=True)

        super(TileGrid, self).__init__(
            rows=self.map.height, cols=self.map.width,
            row_force_default=True,
            row_default_height=self.map.tileheight,
            col_force_default=True,
            col_default_width=self.map.tilewidth,
            **kwargs
        )

        layer = self.map.get_layer_by_name('ground')
        max_y = len(layer.data)
        for (x, y, ts_props) in layer.tiles():

            tileset_fname = ts_props[0]
            (tx, ty, tw, th) = ts_props[1]

            texture = CoreImage(tileset_fname).texture.get_region(
                tx, ty, tw, th)


            y = max_y - y


            pos = (x*tw, y*th)
            image = Image(  texture=texture, 
                            texture_size=(tw, th),
                            pos=pos)


            self.add_widget(image)


class OpenGLTiledMap(TiledMap):


    def reload_images(self):

        def GL_image_loader(filename, flags, **kwargs):
            """ convert the y coordinate to opengl format where
                the bottom-most coordinate in the image is 0
            """
            ts = self.fname2ts[filename]
            def load((x,y,w,h)=None, flags=None):
                GL_y = ts.height - ts.tileheight - y
                return filename, (x,GL_y,w,h), flags

            return load

        self.fname2ts = dict((ts.source, ts) for ts in self.tilesets)
        self.image_loader = GL_image_loader
        super(OpenGLTiledMap, self).reload_images()



from kivy.app import App

from kivy.base import EventLoop
EventLoop.ensure_window()

class MyApp(App):
    def build(self):
        layout = TileGrid()
        return layout

MyApp().run()

@bitcraft
Copy link
Owner

No worries! Thanks so much for sharing that. I will take a look at it later today.

@labuzm
Copy link
Contributor

labuzm commented Jan 3, 2016

Any news on this?

@labuzm
Copy link
Contributor

labuzm commented Jan 3, 2016

How about simply passing tileset object into image_loader? (see #72). This approach still requires manually converting y axis coordinate inside the loader, but it provides all necessary data required to do the math.
You can use it this way: https://gist.github.com/labuzm/6ee08adfa64e6dc6e4e0 This also wouldn't break anything.

@bitcraft
Copy link
Owner

Iabuzm, I've been busy with other projects at the moment, but I will look into your proposal. Thanks for your suggestions, I will check back when I get time.

@bitcraft bitcraft changed the title convenient Image Loader for OpenGL formatted display coordinates Kivy support Nov 16, 2020
@ghost
Copy link

ghost commented Mar 2, 2021

helo i am on a project and i need to use pytmx i need pytmx support for kivy today itself plz

@bitcraft
Copy link
Owner

bitcraft commented Mar 3, 2021

Hi thanks for your interest. I haven’t worked with kivy compatibility lately. Have you tried the code that is in this thread?

@ghost
Copy link

ghost commented Mar 3, 2021

yes but i am getting some errors in this part of the code

def load((x,y,w,h)=None, flags=None):

@ghost
Copy link

ghost commented Mar 3, 2021

can you give me a clean code from that

@ghost
Copy link

ghost commented Mar 3, 2021

when i am using opengltilemap class its working fine

2

but when i am using your code
self.map = TiledMap(self.map_file, invert_y=True)
i am getting the image
Capture

its displaying another image in my directorythat i used in that tmx file

@ghost
Copy link

ghost commented Mar 3, 2021

i am not giving up on pytmx because i love pytmx with pygame

@ghost
Copy link

ghost commented Mar 3, 2021

and the first image is correct i made i test tmx
it worked with the opengltiled class in the thread

but not working with your way of coding i love that neet

bythaby this is the only image i am using
tiles

@ghost
Copy link

ghost commented Mar 3, 2021

and i am a student at 13 years sorry to ask this much questions wasting your time i would like to know how to scale our scrolling tmx file accourding to the window

@bitcraft
Copy link
Owner

bitcraft commented Mar 3, 2021

looks pretty good so far. one thing i notice is that the images don't seem to align with the tiles. that looks like the margin or spacing of the tileset isn't set or being used properly. if you can share the map and code you are using, i could help you get this fixed. as for scrolling, you would have to rely on Kivy to do that...its been a long time since i've used it so i don't have a good idea for that right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants