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