1-- Urho2D tile map example.
2-- This sample demonstrates:
3--     - Creating a 2D scene with tile map
4--     - Displaying the scene using the Renderer subsystem
5--     - Handling keyboard to move and zoom 2D camera
6--     - Interacting with the tile map
7
8require "LuaScripts/Utilities/Sample"
9
10function Start()
11    -- Execute the common startup for samples
12    SampleStart()
13
14    -- Enable OS cursor
15    input.mouseVisible = true
16
17    -- Create the scene content
18    CreateScene()
19
20    -- Create the UI content
21    CreateInstructions()
22
23    -- Setup the viewport for displaying the scene
24    SetupViewport()
25
26    -- Set the mouse mode to use in the sample
27    SampleInitMouseMode(MM_RELATIVE)
28
29    -- Hook up to the frame update events
30    SubscribeToEvents()
31end
32
33function CreateScene()
34    scene_ = Scene()
35
36    -- Create the Octree component to the scene. This is required before adding any drawable components, or else nothing will
37    -- show up. The default octree volume will be from (-1000, -1000, -1000) to (1000, 1000, 1000) in world coordinates it
38    -- is also legal to place objects outside the volume but their visibility can then not be checked in a hierarchically
39    -- optimizing manner
40    scene_:CreateComponent("Octree")
41
42    -- Create a scene node for the camera, which we will move around
43    -- The camera will use default settings (1000 far clip distance, 45 degrees FOV, set aspect ratio automatically)
44    cameraNode = scene_:CreateChild("Camera")
45    -- Set an initial position for the camera scene node above the plane
46    cameraNode.position = Vector3(0.0, 0.0, -10.0)
47    local camera = cameraNode:CreateComponent("Camera")
48    camera.orthographic = true
49    camera.orthoSize = graphics.height * PIXEL_SIZE
50    camera.zoom = 1.0 * Min(graphics.width / 1280, graphics.height / 800) -- Set zoom according to user's resolution to ensure full visibility (initial zoom (1.0) is set for full visibility at 1280x800 resolution)
51
52    -- Get tmx file
53    local tmxFile = cache:GetResource("TmxFile2D", "Urho2D/isometric_grass_and_water.tmx")
54    if tmxFile == nil then
55        return
56    end
57
58    local tileMapNode = scene_:CreateChild("TileMap")
59    tileMapNode.position = Vector3(0.0, 0.0, -1.0)
60
61    local tileMap = tileMapNode:CreateComponent("TileMap2D")
62    tileMap.tmxFile = tmxFile
63
64    -- Set camera's position
65    local info = tileMap.info
66    local x = info.mapWidth * 0.5
67    local y = info.mapHeight * 0.5
68    cameraNode.position = Vector3(x, y, -10.0)
69end
70
71function CreateInstructions()
72    -- Construct new Text object, set string to display and font to use
73    local instructionText = ui.root:CreateChild("Text")
74    instructionText:SetText("Use WASD keys and mouse to move, Use PageUp PageDown to zoom.\n LMB to remove a tile, RMB to swap grass and water.")
75    instructionText:SetFont(cache:GetResource("Font", "Fonts/Anonymous Pro.ttf"), 15)
76
77    -- Position the text relative to the screen center
78    instructionText.horizontalAlignment = HA_CENTER
79    instructionText.verticalAlignment = VA_CENTER
80    instructionText:SetPosition(0, ui.root.height / 4)
81end
82
83function SetupViewport()
84    -- Set up a viewport to the Renderer subsystem so that the 3D scene can be seen. We need to define the scene and the camera
85    -- at minimum. Additionally we could configure the viewport screen size and the rendering path (eg. forward / deferred) to
86    -- use, but now we just use full screen and default render path configured in the engine command line options
87    local viewport = Viewport:new(scene_, cameraNode:GetComponent("Camera"))
88    renderer:SetViewport(0, viewport)
89end
90
91function MoveCamera(timeStep)
92    -- Do not move if the UI has a focused element (the console)
93    if ui.focusElement ~= nil then
94        return
95    end
96
97    -- Movement speed as world units per second
98    local MOVE_SPEED = 4.0
99
100    -- Read WASD keys and move the camera scene node to the corresponding direction if they are pressed
101    if input:GetKeyDown(KEY_W) then
102        cameraNode:Translate(Vector3(0.0, 1.0, 0.0) * MOVE_SPEED * timeStep)
103    end
104    if input:GetKeyDown(KEY_S) then
105        cameraNode:Translate(Vector3(0.0, -1.0, 0.0) * MOVE_SPEED * timeStep)
106    end
107    if input:GetKeyDown(KEY_A) then
108        cameraNode:Translate(Vector3(-1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
109    end
110    if input:GetKeyDown(KEY_D) then
111        cameraNode:Translate(Vector3(1.0, 0.0, 0.0) * MOVE_SPEED * timeStep)
112    end
113
114    if input:GetKeyDown(KEY_PAGEUP) then
115        local camera = cameraNode:GetComponent("Camera")
116        camera.zoom = camera.zoom * 1.01
117    end
118
119    if input:GetKeyDown(KEY_PAGEDOWN) then
120        local camera = cameraNode:GetComponent("Camera")
121        camera.zoom = camera.zoom * 0.99
122    end
123end
124
125function SubscribeToEvents()
126    -- Subscribe HandleUpdate() function for processing update events
127    SubscribeToEvent("Update", "HandleUpdate")
128
129    -- Listen to mouse clicks
130    SubscribeToEvent("MouseButtonDown", "HandleMouseButtonDown")
131
132    -- Unsubscribe the SceneUpdate event from base class to prevent camera pitch and yaw in 2D sample
133    UnsubscribeFromEvent("SceneUpdate")
134end
135
136function HandleUpdate(eventType, eventData)
137    -- Take the frame time step, which is stored as a float
138    local timeStep = eventData["TimeStep"]:GetFloat()
139
140    -- Move the camera, scale movement with time step
141    MoveCamera(timeStep)
142end
143
144function HandleMouseButtonDown(eventType, eventData)
145    local tileMapNode = scene_:GetChild("TileMap", true)
146    local map = tileMapNode:GetComponent("TileMap2D")
147    local layer = map:GetLayer(0)
148
149    success, x, y = map:PositionToTileIndex(GetMousePositionXY())
150    if success then
151        -- Get tile's sprite. Note that layer.GetTile(x, y).sprite is read-only, so we get the sprite through tile's node
152        local n = layer:GetTileNode(x, y)
153        if n == nil then
154            return
155        end
156        local sprite = n:GetComponent("StaticSprite2D")
157
158        if input:GetMouseButtonDown(MOUSEB_RIGHT) then
159            -- Swap grass and water
160            if layer:GetTile(x, y).gid < 9 then -- First 8 sprites in the "isometric_grass_and_water.png" tileset are mostly grass and from 9 to 24 they are mostly water
161                sprite.sprite = layer:GetTile(0, 0).sprite -- Replace grass by water sprite used in top tile
162            else sprite.sprite = layer:GetTile(24, 24).sprite end -- Replace water by grass sprite used in bottom tile
163        else sprite.sprite = nil end -- 'Remove' sprite
164    end
165end
166
167function GetMousePositionXY()
168    local camera = cameraNode:GetComponent("Camera")
169    local screenPoint = Vector3(input.mousePosition.x / graphics.width, input.mousePosition.y / graphics.height, 10)
170    local worldPoint = camera:ScreenToWorldPoint(screenPoint)
171    return Vector2(worldPoint.x, worldPoint.y)
172end
173
174-- Create XML patch instructions for screen joystick layout specific to this sample app
175function GetScreenJoystickPatchString()
176    return
177        "<patch>" ..
178        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/attribute[@name='Is Visible']\" />" ..
179        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom In</replace>" ..
180        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button0']]\">" ..
181        "        <element type=\"Text\">" ..
182        "            <attribute name=\"Name\" value=\"KeyBinding\" />" ..
183        "            <attribute name=\"Text\" value=\"PAGEUP\" />" ..
184        "        </element>" ..
185        "    </add>" ..
186        "    <remove sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/attribute[@name='Is Visible']\" />" ..
187        "    <replace sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]/element[./attribute[@name='Name' and @value='Label']]/attribute[@name='Text']/@value\">Zoom Out</replace>" ..
188        "    <add sel=\"/element/element[./attribute[@name='Name' and @value='Button1']]\">" ..
189        "        <element type=\"Text\">" ..
190        "            <attribute name=\"Name\" value=\"KeyBinding\" />" ..
191        "            <attribute name=\"Text\" value=\"PAGEDOWN\" />" ..
192        "        </element>" ..
193        "    </add>" ..
194        "</patch>"
195end
196