[Lua / Python] Display a tile map generated by Tiled Map Editor


#1

Tile map viewer

The following code allows you to display a tile map created with Tiled software.
Tiled is a free tile map editor. You can download this software here https://www.mapeditor.org/


Manual

Report to Tiled documentation to create your tile map.
Once your map is done, export it in JSON format.

Loading the map

To load the map and use it under HARFANG, call this function:
TileMap: new (x, y, sx, sy, json_file_name, extra_path)

  • x, y : bottom-left corner map position
  • sx, sy : map size (in pixels)
  • json_file_name : path and file name of your map
  • extra_path : prefix added to the file path, if the resources are not in the default directory

Displaying the map

When the map is loaded, you can display it with TileMap:update()

Moving the map

To scroll the tiles, just move it with this functions:

  • TileMap:set_tilelayers_xpos(x) horizontal position in pixels
  • TileMap:set_tilelayers_ypos(y) vertical position in pixels

You can define a different position for each layer (e.g. to create a parallax scrolling…) . To do this, simply extend or redefine the set_tilelayers_pos() functions


Useful functions

TileMap:display_tilesets() : draw the tileset with id.


TileMap:draw_display_rect() : draw clipping area.

[Lua] TileMap module

Dependency: you can download json.lua module here: https://github.com/rxi/json.lua

-[[
 ===========================================================

              - HARFANG® 3D - www.harfang3d.com

                  Import/Display Tiled maps

 ===========================================================
]]

hg = require('harfang')
json = require('source/json')

--====================================================================
--              Image layer
--====================================================================

TM_ImageLayer=
{
    name="",
    id=0,
    offset=nil,
    opacity=1,
    visible=true,
    texture=nil,
    type="",
    position=nil
}

function TM_ImageLayer:new(json_parameters,extra_path)
    extra_path=extra_path or ""
    local o={}
    setmetatable(o,self)
    self.__index=self
    
    o.name = json_parameters["name"]
    o.type = json_parameters["type"]
    o.offset = hg.Vector2(json_parameters["offsetx"] or 0, json_parameters["offsety"] or 0)
    o.opacity = json_parameters["opacity"]
    o.id = json_parameters["id"]
    o.visible = json_parameters["visible"]
    o.texture = hg.GetPlus():LoadTexture(extra_path..json_parameters["image"])
    o.position = hg.Vector2(json_parameters["x"],json_parameters["y"])
    while not o.texture:IsReady() do
    end
    return o
end

--====================================================================
--              Tiles layer
--====================================================================

TM_TileLayer = 
{
    name="",
    id=0,
    size=nil,
    opacity=1,
    data=nil,
    type="",
    position=nil
}

function TM_TileLayer:new(json_parameters,extra_path)
    extra_path=extra_path or ""
    local o={}
    setmetatable(o,self)
    self.__index=self
    
    o.name = json_parameters["name"]
    o.size = hg.Vector2(json_parameters["width"], json_parameters["height"])
    o.opacity = json_parameters["opacity"]
    o.id = json_parameters["id"]
    o.visible = json_parameters["visible"]
    o.data = json_parameters["data"]
    o.type = json_parameters["type"]
    o.position = hg.Vector2(json_parameters["x"],json_parameters["y"])
    return o
end

--====================================================================
--              TileMap
--====================================================================

TileMap =
{
    infinite=false,
    size=nil,
    orientation="",
    renderorder="",
    tile_size=nil,
    tiles=nil,
    layers=nil,
    type="",
    position=nil,
    scale=1,
    display_rect=nil,
    color_temp=nil,
    plus=nil
}

function TileMap.load_json_tilemap(file_name)
    local json_script = hg.GetFilesystem():FileToString(file_name)
    json_script=string.gsub(json_script,"\\/","/") -- Tiled export bug fix
    if json_script ~= "" then
        local script_parameters = json.decode(json_script)
        return script_parameters
    end
    return nil
end

--[[
    ---------------------------------------------------------------------
    x,y : Display rectangle left-bottom corner
    sx,sy : Display rectangle size
    file_name : json scipt exported from Tiled
    extra_path : if project root is different of json assets path root
    ---------------------------------------------------------------------
]]

function TileMap:new(x,y,sx,sy,json_file_name,extra_path)
    extra_path=extra_path or ""
    local o={}
    setmetatable(o,self)
    self.__index=self
    
    local json_parameters=TileMap.load_json_tilemap(extra_path..json_file_name)
    if json_parameters then

        o.infinite = json_parameters["infinite"]
        o.size = hg.Vector2(json_parameters["width"], json_parameters["height"])
        o.tile_size = hg.Vector2(json_parameters["tilewidth"], json_parameters["tileheight"])
        o.renderorder = json_parameters["renderorder"]
        o.orientation = json_parameters["orientation"]
        o.type = json_parameters["type"]
        o.position = hg.Vector2(0,0)
        o.scale=2
        o.display_rect=hg.Rect(x,y,x+sx,y+sy)
        o.color_temp=hg.Color(1,1,1,1)  --Used in layer display loop
        -- Load tilesets:
        o.tiles={}
        o.plus=hg.GetPlus()
        local tilesets = json_parameters["tilesets"]
        local dy=0
        for _,ts in ipairs(tilesets) do
            print(extra_path..ts["image"])
            local tex=o.plus:LoadTexture(extra_path..ts["image"])
            local ix,iy = ts["imagewidth"], ts["imageheight"]
            local sx,sy=ts["tilewidth"],ts["tileheight"]
            local nx=math.floor(ix/sx)
            local ny=math.floor(iy/sy)
            local y
            for y=0,ny-1 do
                for x=0,nx-1 do
                    local tl={size=hg.Vector2(sx,sy),uv_s = hg.Vector2((x+1)/nx,y/ny), uv_e = hg.Vector2(x/nx,(y+1)/ny),texture=tex
                    --Only used by TileMap:display_tilesets() (comment if tilesets displaying not used)
                    
                    ,qbx=x*sx,qby=dy,
                    qcx=(x+1)*sx,qcy=dy,
                    qdx=(x+1)*sx,qdy=dy+sy,
                    qax=x*sx,qay=dy+sy
                    
                    }
                    table.insert(o.tiles,tl)
                end
                dy=dy+sy
            end
        end

        -- Load tilelayers:
        o.layers={}
        local layers=json_parameters["layers"]
        for _,layer_dat in ipairs(layers) do
            if layer_dat["type"]=="imagelayer" then
                layer=TM_ImageLayer:new(layer_dat,extra_path)
            elseif layer_dat["type"]=="tilelayer" then
                layer=TM_TileLayer:new(layer_dat,extra_path)
            else
                layer=nil
            end
            if layer ~=nil then
                table.insert(o.layers,layer)
            end
        end

    else
        print("/!\\ Json file error:"..extra_path..json_file_name)
    end
    return o
end

--[[
    ---------------------------------------------------------------------
    Use this function to check if tilesets setup is correct
    ---------------------------------------------------------------------
]]

function TileMap:display_tilesets()
    local c=hg.Color.White
    local wsize=self.plus:GetRenderer():GetViewport():GetSize()
    local z=self.scale
    local n=1
    local tile
    for _,tile in ipairs(self.tiles) do
        self.plus:Quad2D(tile.qax*z,-tile.qay*z+wsize.y,tile.qbx*z,-tile.qby*z+wsize.y,tile.qcx*z,-tile.qcy*z+wsize.y,tile.qdx*z,-tile.qdy*z+wsize.y,c,c,c,c,tile.texture,
        tile.uv_s.x,tile.uv_s.y,tile.uv_e.x,tile.uv_e.y)
        self.plus:Text2D(tile.qax*z,-(tile.qay)*z+wsize.y,""..n,4*z,hg.Color.Yellow)
        n=n+1
    end
end

--[[
    -----------------------------------------------------------------------------
    Use this function to display entire tilemap without clipping optimization
    -----------------------------------------------------------------------------
]]

function TileMap:display_map()
    local c=self.color_temp
    local s=self.scale
    local layer
    for _,layer in ipairs(self.layers) do
        c.a=layer.opacity
        if layer.type=="imagelayer" then
            local w,h=layer.texture:GetWidth(),layer.texture:GetHeight()
            local x=layer.position.x+layer.offset.x
            local y=layer.position.y+layer.offset.y
            self.plus:Quad2D((x+w)*s,y*s,(x+w)*s,(y+h)*s,x*s,(y+h)*s,x*s,y*s,c,c,c,c,layer.texture,1,0,0,1)
        elseif layer.type=="tilelayer" then
            x=layer.position.x
            y=layer.position.y
            local sx=self.tile_size.x
            local sy=self.tile_size.y
            local ti=1
            for y0=layer.size.y-1,0,-1 do
                for x0=0,layer.size.x-1 do
                    local tii=layer.data[ti]
                    if tii>0 then
                        local tile=self.tiles[tii]
                        local tx=tile.size.x
                        local ty=tile.size.y
                        self.plus:Quad2D((x0*sx+tx+x)*s,(y0*sy+y)*s,(x0*sx+tx+x)*s,(y0*sy+ty+y)*s,(x0*sx+x)*s,(y0*sy+ty+y)*s,(sx*x0+x)*s,(sy*y0+y)*s,c,c,c,c,tile.texture,
                        tile.uv_s.x,tile.uv_s.y,tile.uv_e.x,tile.uv_e.y)
                    end
                    ti=ti+1
                end
            end
        end
    end
end


--[[
    -----------------------------------------------------------------------------
    Use this function to display clipping rectangle
    -----------------------------------------------------------------------------
]]

function TileMap:draw_display_rect()
    local c=hg.Color.White
    self.plus:Line2D(self.display_rect.sx,self.display_rect.sy,self.display_rect.ex,self.display_rect.sy,c,c)
    self.plus:Line2D(self.display_rect.ex,self.display_rect.sy,self.display_rect.ex,self.display_rect.ey,c,c)
    self.plus:Line2D(self.display_rect.ex,self.display_rect.ey,self.display_rect.sx,self.display_rect.ey,c,c)
    self.plus:Line2D(self.display_rect.sx,self.display_rect.ey,self.display_rect.sx,self.display_rect.sy,c,c)
end


--[[
    -----------------------------------------------------------------------------
    Internal functions
    -----------------------------------------------------------------------------
]]

function TileMap:display_tilelayer(layer)
    local c=self.color_temp
    c.a=layer.opacity
    local wsize=self.plus:GetRenderer():GetViewport():GetSize()
    local xis=-math.floor(layer.position.x/self.tile_size.x)
    local yis=-math.floor(layer.position.y/self.tile_size.x)

    local drwi=math.ceil(self.display_rect:GetWidth()/self.scale/self.tile_size.x)
    local drhi=math.ceil(self.display_rect:GetHeight()/self.scale/self.tile_size.y+1)
    local xie=xis+drwi-1
    local yie=yis+drhi-1
    local s=self.scale
    local scrsx=s*(layer.position.x+xis*self.tile_size.x)+self.display_rect:GetX()-self.tile_size.x*s
    local scrsy=wsize.y-(s*(layer.position.y+yis*self.tile_size.y)+wsize.y-self.display_rect.ey-self.tile_size.x*s)
    local sy=scrsy-self.tile_size.y*s
    
    xis=xis-1
    yis=yis-1

    for y=yis,yie-1 do
        local sx=scrsx
        local ym=y%layer.size.y
        for x=xis,xie do
            local ti=layer.data[x%layer.size.x +ym*layer.size.x+1]
            if ti>0 then
                local tile=self.tiles[ti]
                local tx=tile.size.x*s
                local ty=tile.size.y*s
                self.plus:Quad2D(sx+tx,sy, sx+tx,sy+ty, sx,sy+ty, sx,sy ,c,c,c,c,tile.texture,tile.uv_s.x,tile.uv_s.y,tile.uv_e.x,tile.uv_e.y)
            end
            sx=sx+self.tile_size.x*s
        end
        sy=sy-self.tile_size.y*s
    end

end

function TileMap:display_imagelayer(layer)
    local s=self.scale
    local c=self.color_temp
    c.a=layer.opacity
    local wsize=self.plus:GetRenderer():GetViewport():GetSize()
    local w,h=layer.texture:GetWidth()*s,layer.texture:GetHeight()*s
    local x=(layer.position.x+layer.offset.x)*s+self.display_rect:GetX()
    local y=wsize.y-((layer.position.y+layer.offset.y)*s+wsize.y-self.display_rect.ey)
    self.plus:Quad2D(x+w,y-h,x+w,y,x,y,x,y-h,c,c,c,c,layer.texture,1,0,0,1)
end

function TileMap:set_tilelayers_xpos(x)
    local i,layer
    for i,layer in ipairs(self.layers) do
        if layer.type=="tilelayer" then layer.position.x=x end
    end
end

function TileMap:set_tilelayers_ypos(y)
    local i,layer
    for i,layer in ipairs(self.layers) do
        if layer.type=="tilelayer" then layer.position.y=y end
    end
end

--[[
    -----------------------------------------------------------------------------
    Display tilemap with clipping optimization.
    -----------------------------------------------------------------------------
]]

function TileMap:update()
    local layer
    for _,layer in ipairs(self.layers) do
        if layer.type=="tilelayer" then
            self:display_tilelayer(layer)
        elseif layer.type=="imagelayer" then
            self:display_imagelayer(layer)
        end
    end
end

[Python] TileMap module

"""
 ===========================================================

              - HARFANG® 3D - www.harfang3d.com

                  Import/Display Tiled maps

 ===========================================================
"""

import harfang as hg
from math import *
import json

##====================================================================
##              Image layer
##====================================================================

class TM_ImageLayer:

    def __init__(self,json_parameters,extra_path):
        self.name = json_parameters["name"]
        self.type = json_parameters["type"]
        self.offset = hg.Vector2(0,0)
        if "offsetx" in json_parameters:
            self.offset.x=json_parameters["offsetx"]
        if "offsety" in json_parameters:
            self.offset.y=json_parameters["offsety"]
        self.opacity = json_parameters["opacity"]
        self.id = json_parameters["id"]
        self.visible = json_parameters["visible"]
        self.texture = hg.GetPlus().LoadTexture(extra_path+json_parameters["image"])
        self.position = hg.Vector2(json_parameters["x"], json_parameters["y"])
        while not self.texture.IsReady():
            pass



#====================================================================
#              Tiles layer
#====================================================================

class TM_TileLayer:
    def __init__(self,json_parameters,extra_path):
        self.name = json_parameters["name"]
        self.size = hg.Vector2(json_parameters["width"], json_parameters["height"])
        self.opacity = json_parameters["opacity"]
        self.id = json_parameters["id"]
        self.visible = json_parameters["visible"]
        self.data = json_parameters["data"]
        self.type = json_parameters["type"]
        self.position = hg.Vector2(json_parameters["x"], json_parameters["y"])


#====================================================================
#              TileMap
#====================================================================


"""
    ---------------------------------------------------------------------
    x,y : Display rectangle left-bottom corner
    sx,sy : Display rectangle size
    file_name : json scipt exported from Tiled
    extra_path : if project root is different of json assets path root
    ---------------------------------------------------------------------
"""

class TileMap:

    @staticmethod
    def load_json_tilemap(file_name):
        json_script = hg.GetFilesystem().FileToString(file_name)
        json_script = json_script.replace("\/", "/")
        if json_script != "":
            script_parameters = json.loads(json_script)
            return script_parameters
        return None

    def __init__(self,x,y,sx,sy,json_file_name,extra_path):

        json_parameters=self.load_json_tilemap(extra_path+json_file_name)
        if json_parameters is not None:

            self.infinite = json_parameters["infinite"]
            self.size = hg.Vector2(json_parameters["width"], json_parameters["height"])
            self.tile_size = hg.Vector2(json_parameters["tilewidth"], json_parameters["tileheight"])
            self.renderorder = json_parameters["renderorder"]
            self.orientation = json_parameters["orientation"]
            self.type = json_parameters["type"]
            self.position = hg.Vector2(0,0)
            self.scale=2
            self.display_rect=hg.Rect(x,y,x+sx,y+sy)
            self.color_temp=hg.Color(1,1,1,1)  #Used in layer display loop
            # Load tilesets:
            self.tiles=[]
            self.plus=hg.GetPlus()
            tilesets = json_parameters["tilesets"]
            dy=0
            for ts in tilesets:
                print(extra_path+ts["image"])
                tex=self.plus.LoadTexture(extra_path+ts["image"])
                ix,iy = ts["imagewidth"], ts["imageheight"]
                sx,sy=ts["tilewidth"],ts["tileheight"]
                nx=floor(ix/sx)
                ny=floor(iy/sy)
                for y in range(0,ny):
                    for x in range(0,nx):
                        tl={"size":hg.Vector2(sx,sy),"uv_s":hg.Vector2((x+1)/nx,y/ny), "uv_e":hg.Vector2(x/nx,(y+1)/ny),"texture":tex
                        #Only used by TileMap:display_tilesets() (comment if tilesets displaying not used)
                        ,"qbx":x*sx,"qby":dy,
                        "qcx":(x+1)*sx,"qcy":dy,
                        "qdx":(x+1)*sx,"qdy":dy+sy,
                        "qax":x*sx,"qay":dy+sy
                        }
                        self.tiles.append(tl)
                    dy+=sy

            #-- Load tilelayers:
            self.layers=[]
            layers=json_parameters["layers"]
            for layer_dat in layers:
                if layer_dat["type"]=="imagelayer":
                    layer=TM_ImageLayer(layer_dat,extra_path)
                elif layer_dat["type"]=="tilelayer":
                    layer=TM_TileLayer(layer_dat,extra_path)
                else:
                    layer=None

                if layer is not None:
                    self.layers.append(layer)

        else:
            print("/!\ Json file error:"+extra_path+json_file_name)

    """
    ---------------------------------------------------------------------
    Use this function to check if tilesets setup is correct
    ---------------------------------------------------------------------
    """

    def display_tilesets(self):
        c=hg.Color.White
        wsize=self.plus.GetRenderer().GetViewport().GetSize()
        z=self.scale
        n=1
        for tile in self.tiles:
            self.plus.Quad2D(tile["qax"]*z,-tile["qay"]*z+wsize.y,tile["qbx"]*z,-tile["qby"]*z+wsize.y,tile["qcx"]*z,-tile["qcy"]*z+wsize.y,tile["qdx"]*z,-tile["qdy"]*z+wsize.y,c,c,c,c,tile["texture"],
            tile["uv_s"].x,tile["uv_s"].y,tile["uv_e"].x,tile["uv_e"].y)
            self.plus.Text2D(tile["qax"]*z,-(tile["qay"])*z+wsize.y,""+str(n),4*z,hg.Color.Yellow)
            n+=1

    """
    -----------------------------------------------------------------------------
    Use this function to display entire tilemap without clipping optimization
    -----------------------------------------------------------------------------
    """

    def display_map(self):
        c=self.color_temp
        s=self.scale
        for layer in self.layers:
            c.a=layer.opacity
            if layer.type=="imagelayer":
                w,h=layer.texture.GetWidth(),layer.texture.GetHeight()
                x=layer.position.x+layer.offset.x
                y=layer.position.y+layer.offset.y
                self.plus.Quad2D((x+w)*s,y*s,(x+w)*s,(y+h)*s,x*s,(y+h)*s,x*s,y*s,c,c,c,c,layer.texture,1,0,0,1)
            elif layer.type=="tilelayer":
                x=layer.position.x
                y=layer.position.y
                sx=self.tile_size.x
                sy=self.tile_size.y
                ti=1
                for y0 in range(layer.size.y-1,-1,-1):
                    for x0 in range(0,layer.size.x) :
                        tii=layer.data[ti]
                        if tii>0 :
                            tile=self.tiles[tii]
                            tx=tile.size.x
                            ty=tile.size.y
                            self.plus.Quad2D((x0*sx+tx+x)*s,(y0*sy+y)*s,(x0*sx+tx+x)*s,(y0*sy+ty+y)*s,(x0*sx+x)*s,(y0*sy+ty+y)*s,(sx*x0+x)*s,(sy*y0+y)*s,c,c,c,c,tile.texture,
                            tile.uv_s.x,tile.uv_s.y,tile.uv_e.x,tile.uv_e.y)
                        ti=ti+1


    """
        -----------------------------------------------------------------------------
        Use this function to display clipping rectangle
        -----------------------------------------------------------------------------
    """

    def draw_display_rect(self):
        c=hg.Color.White
        self.plus.Line2D(self.display_rect.sx,self.display_rect.sy,self.display_rect.ex,self.display_rect.sy,c,c)
        self.plus.Line2D(self.display_rect.ex,self.display_rect.sy,self.display_rect.ex,self.display_rect.ey,c,c)
        self.plus.Line2D(self.display_rect.ex,self.display_rect.ey,self.display_rect.sx,self.display_rect.ey,c,c)
        self.plus.Line2D(self.display_rect.sx,self.display_rect.ey,self.display_rect.sx,self.display_rect.sy,c,c)


    """
        -----------------------------------------------------------------------------
        Internal functions
        -----------------------------------------------------------------------------
    """

    def display_tilelayer(self,layer):
        c=self.color_temp
        c.a=layer.opacity
        wsize=self.plus.GetRenderer().GetViewport().GetSize()
        xis=-floor(layer.position.x/self.tile_size.x)
        yis=-floor(layer.position.y/self.tile_size.x)

        drwi=ceil(self.display_rect.GetWidth()/self.scale/self.tile_size.x)
        drhi=ceil(self.display_rect.GetHeight()/self.scale/self.tile_size.y+1)
        xie=xis+drwi-1
        yie=yis+drhi-1
        s=self.scale
        scrsx=s*(layer.position.x+xis*self.tile_size.x)+self.display_rect.GetX()-self.tile_size.x*s
        scrsy=wsize.y-(s*(layer.position.y+yis*self.tile_size.y)+wsize.y-self.display_rect.ey-self.tile_size.x*s)
        sy=scrsy-self.tile_size.y*s

        xis=xis-1
        yis=yis-1

        for y in range(yis,yie):
            sx=scrsx
            ym=y%layer.size.y
            for x in range(xis,xie):
                ti=layer.data[int(x%layer.size.x +ym*layer.size.x)]
                if ti>0:
                    tile=self.tiles[ti-1]
                    tx=tile["size"].x*s
                    ty=tile["size"].y*s
                    self.plus.Quad2D(sx+tx,sy, sx+tx,sy+ty, sx,sy+ty, sx,sy ,c,c,c,c,tile["texture"],tile["uv_s"].x,tile["uv_s"].y,tile["uv_e"].x,tile["uv_e"].y)
                sx=sx+self.tile_size.x*s
            sy=sy-self.tile_size.y*s

    def display_imagelayer(self,layer):
        s=self.scale
        c=self.color_temp
        c.a=layer.opacity
        wsize=self.plus.GetRenderer().GetViewport().GetSize()
        w,h=layer.texture.GetWidth()*s,layer.texture.GetHeight()*s
        x=(layer.position.x+layer.offset.x)*s+self.display_rect.GetX()
        y=wsize.y-((layer.position.y+layer.offset.y)*s+wsize.y-self.display_rect.ey)
        self.plus.Quad2D(x+w,y-h,x+w,y,x,y,x,y-h,c,c,c,c,layer.texture,1,0,0,1)

    def set_tilelayers_xpos(self,x):
        for i,layer in enumerate(self.layers):
            if layer.type=="tilelayer":
                layer.position.x=x

    def set_tilelayers_ypos(self,y):
        for i,layer in enumerate(self.layers):
            if layer.type=="tilelayer":
                layer.position.y=y

    """
        -----------------------------------------------------------------------------
        Display tilemap with clipping optimization.
        -----------------------------------------------------------------------------
    """

    def update(self):
        for layer in self.layers:
            if layer.type=="tilelayer":
                self.display_tilelayer(layer)
            elif layer.type=="imagelayer":
                self.display_imagelayer(layer)

[Lua] Example of implementation

--[[

 ===========================================================

              - HARFANG® 3D - www.harfang3d.com

                    - Tiled importer -


 ===========================================================
]]

hg = require('harfang')
json = require('source/json')
tilemap_loader = require('source/tile_map')

hg.LoadPlugins()

local resolution = hg.Vector2(1600,900)
local antialiasing=8
local screenMode=hg.Windowed
local plus = hg.GetPlus()
hg.LoadPlugins()
local fd_root=hg.StdFileDriver("./")
hg.MountFileDriver(hg.StdFileDriver('source/assets/'),'assets/')

plus:CreateWorkers()

plus:RenderInit(resolution.x, resolution.y,"", antialiasing, screenMode)
plus:SetBlend2D(hg.BlendAlpha)
plus:SetCulling2D(hg.CullNever)    -- !!! To display inverted sprites

local renderer=plus:GetRendererAsync()

-- -----------------------------------------------
--                  Load Tiled data
-- -----------------------------------------------

local tilemap=TileMap:new(100,100,600,350,"castle_dungeon.json","assets/CastleDungeonPack/")

-- -----------------------------------------------
--                   Main loop
-- -----------------------------------------------

local xpos=0
local ypos=0

while not plus:KeyDown(hg.KeyEscape) and not plus:IsAppEnded() do
    local delta_t = plus:UpdateClock()
    local dts = hg.time_to_sec_f(delta_t)

    if plus:MouseButtonDown(hg.Button0) then
        local mdx, mdy = plus:GetMouseDt()
        xpos=xpos+mdx/tilemap.scale
        tilemap:set_tilelayers_xpos(xpos)
        ypos=ypos-mdy/tilemap.scale
        tilemap:set_tilelayers_ypos(ypos)
    end

    -- End rendering:
    plus:Clear(hg.Color.Green*0.2)
    plus:WaitForRender()    --Must call this function in mutltithread mode 
    
    --tilemap:display_tilesets()
    tilemap:update()
    tilemap:draw_display_rect()
    plus:WaitForRender()    --Must call this function in mutltithread mode 

    plus:Flip()
    plus:EndFrame()
end
plus:RenderUninit()

[Python] Example of implementation

"""
 ===========================================================

              - HARFANG® 3D - www.harfang3d.com

                    - Tiled importer -


 ===========================================================
"""

import harfang as hg
import json
from tile_map import *

hg.LoadPlugins()

resolution = hg.Vector2(1600,900)
antialiasing=8
screenMode=hg.Windowed
plus = hg.GetPlus()
hg.LoadPlugins()
fd_root=hg.StdFileDriver("./")
hg.MountFileDriver(hg.StdFileDriver('../assets/'),'assets/')

plus.CreateWorkers()

plus.RenderInit(int(resolution.x), int(resolution.y), antialiasing, screenMode)
plus.SetBlend2D(hg.BlendAlpha)
plus.SetCulling2D(hg.CullNever)    #-- !!! To display inverted sprites

renderer=plus.GetRendererAsync()

# -----------------------------------------------
#                  Load Tiled data
# -----------------------------------------------

tilemap=TileMap(100,100,1200,600,"castle_dungeon.json","assets/CastleDungeonPack/")

#-- -----------------------------------------------
#--                   Main loop
#-- -----------------------------------------------

xpos=0
ypos=0

while not plus.KeyDown(hg.KeyEscape) and not plus.IsAppEnded():
    delta_t = plus.UpdateClock()
    dts = hg.time_to_sec_f(delta_t)

    if plus.MouseButtonDown(hg.Button0):
        mdx, mdy = plus.GetMouseDt()
        xpos=xpos+mdx/tilemap.scale
        tilemap.set_tilelayers_xpos(xpos)
        ypos=ypos-mdy/tilemap.scale
        tilemap.set_tilelayers_ypos(ypos)

    #-- End rendering:
    plus.Clear(hg.Color.Green*0.2)
    plus.WaitForRender()    #--Must call this function in mutltithread mode
    
    #tilemap.display_tilesets()
    tilemap.update()
    tilemap.draw_display_rect()
    plus.WaitForRender()    #--Must call this function in mutltithread mode

    plus.Flip()
    plus.EndFrame()

plus.RenderUninit()