|
|
| (5 intermediate revisions by one other user not shown) |
| Line 1: |
Line 1: |
| {{FIXME}} remove off-site links for images once uploading works again http://springrts.com/mantis/view.php?id=4612
| | ==Interessted in creating a game on the spring engine?== |
|
| |
|
| | Honestly, here isn't really the place to ask. The amount of available developers here is 0. We are all working on our own projects. |
|
| |
|
| This text talks about how to put features onto the map, using Lua.
| | Sucks, but we are a small community. I would suggest posting on moddb. There are usually people there looking for work. |
| | |
| The text can be read in two ways:
| |
| *As a Lua tutorial that expands on [[Lua:Tutorial_GettingStarted]] and {{FIXME}} that other page in sandbox
| |
| *For mappers as way to put features onto their map.
| |
| **In this case you can skip some of the "finer details" and just copypaste what seems interessting.
| |
| | |
| | |
| To follow the examples you can either...
| |
| * ...use your WIP map in .sdd format
| |
| * ...or unpack an existing map
| |
| * ...or use the test-mutator example from previous tutorial.
| |
| | |
| This text will be based around the map '''"DesertTriad"'''
| |
| | |
| =Creating a feature: Spring.CreateFeature=
| |
| | |
| Again we use our test-hill in the bottom right corner, of which we already know the coordinates:
| |
| | |
| [[File:DesertTriadHillCoordinates.jpg]]
| |
| | |
| '''We want to create a feature on this hill.'''
| |
| | |
| Create a file: <code>LuaRules\gadgets\MyLuaFeatureTesting.lua</code>
| |
| | |
| At [[Lua_SyncedCtrl#Feature_Handling]] we find the function <code>Spring.CreateFeature</code>
| |
| | |
| We need the parameters: ''featurename'' and ''x,y,z'' coordinates. The other parameters do not matter for now.
| |
| | |
| The map "DesertTriad" already includes some rock features that we could use. If using the test-mutator then we can also use whatever features the game includes, usually that is mostly wrecks of destroyed units.
| |
| | |
| Another group of features that we can use are the ones hardcoded in the engine: Geovents and trees.
| |
| | |
| We use a feature named "treetype0", it is a tree that comes with Spring engine.
| |
| | |
| Putting feature name and coordinates as parameters:
| |
| | |
| <code>Spring.CreateFeature ("treetype0", 3830, 390, 3600)</code>
| |
| | |
| ==GameStart() or Initialize() or GamePreload() ?==
| |
| Where should we put the CreateFeature code?
| |
| | |
| There are three callin functions that appear suitable:
| |
| | |
| '''GameStart() Initialize() GamePreload() - can callin can I take?'''
| |
| | |
| * <code>GameStart()</code> is called as soon as the game begins.
| |
| **However, it is not yet called when the players are ingame but have not readied up yet.
| |
| ** => If this callin was used then features would not appear until the game is started. Thus players could not see while choosing their start positions etc.
| |
| | |
| * <code>GamePreload()</code> is called as soon as soon the "game is ingame."
| |
| **Features spawned in this function will be visible before readying up.
| |
| **However, it is only called once: Right after the game has finished loading.
| |
| ***That means if we want to test our Lua by typing ''/luarules reload'' then it will not trigger again.
| |
| | |
| * <code>Initialize()</code> is called when the gadget is initialized.
| |
| **Features spawned in this function will be visible before readying up.
| |
| **When typing ''/luarules reload'' the gadgets get initialized again and the function gets called again.
| |
| ***This makes it suitable for testing.
| |
| | |
| => Both <code>Initialize()</code> and <code>GamePreload()</code> can be used to spawn features without "delay."
| |
| | |
| => Maybe <code>GamePreload()</code> is a bit "more technical correct" but <code>Initialize()</code> is more suited for testing, so we use that.
| |
| | |
| | |
| =complete gadget that plants a tree=
| |
| Thus the gadget now looks like this:
| |
| | |
| <code><pre>
| |
| function gadget:GetInfo()
| |
| return {
| |
| name = "lua map stuff",
| |
| desc = "create some features",
| |
| author = "youe name",
| |
| date = "the day a lua was made",
| |
| license = "GNU GPL, v2",
| |
| layer = 0,
| |
| enabled = true,
| |
| }
| |
| end
| |
| | |
| if not gadgetHandler:IsSyncedCode() then
| |
| | |
| function gadget:Initialize()
| |
| Spring.CreateFeature ("treetype0", 3830, 390, 3600)
| |
| end
| |
| | |
| else --unsynced part below
| |
| | |
| end
| |
| </pre></code>
| |
| | |
| The result:
| |
| | |
| [[File:OneTreeOnHill.jpg]]
| |
| | |
| [File:http://abload.de/img/onetreeonhillpcs0m.jpg]
| |
| | |
| {{Tip|'''Such simple code is really all it takes to put features on a map.'''
| |
| | |
| If one wanted one could now replaces the featurename and coordinates and use it to populate a map. In fact some maps do just that.}}
| |
| | |
| {{Tip|If you need to remove the tree for testing purposes, then reclaim it with the Commander or a constructor. (We will learn a better way soon)}}
| |
| | |
| | |
| ==If a tree falls from the sky... does it make a sound?==
| |
| One interessting thing is that Spring physics do not get activated until the game actually starts.
| |
| | |
| Why is that relevant for spawning features?
| |
| | |
| If we use a too high y-coordinate (vertical axis / height) then the tree will hover in the air until the game starts.
| |
| | |
| [[File:FeaturesFallAtGameStart.gif]]
| |
|
| |
| [File:http://abload.de/img/featuresfallatgamestaaesnm.gif]
| |
| | |
| And then it will suddendly fall down! (Look at above gif and watch the small countdown)
| |
| | |
| Similiar if we use too low y-coordinate then the tree will be created underground and only "pop" above ground once the game starts!
| |
| | |
| {{Tip|It is important to spawn features (and units) at the correct y-coordinate.}}
| |
| | |
| ===GetGroundHeight===
| |
| Instead of reading the ground height from the tooltip we can also use the function <code>Spring.GetGroundHeight</code>:
| |
| | |
| <code><pre>
| |
| function gadget:Initialize()
| |
| x = 3830
| |
| z = 3600
| |
| y = Spring.GetGroundHeight (x,z)
| |
| Spring.CreateFeature ("treetype0", x,y,z)
| |
| end
| |
| </pre></code>
| |
| | |
| | |
| =Functional forest=
| |
| We can put our tree-spawner code into a function and call it from <code>Initialize()</code> multiple times with different coordinates:
| |
| <code><pre>
| |
| function PlantTree (x,z)
| |
| y = Spring.GetGroundHeight (x,z)
| |
| Spring.CreateFeature ("treetype0", x,y,z)
| |
| end
| |
| | |
| function gadget:Initialize()
| |
| PlantTree (3830,3600)
| |
| PlantTree (3850,3650)
| |
| PlantTree (3800,3650)
| |
| PlantTree (3810,320)
| |
| end
| |
| </pre></code>
| |
| | |
| Result:
| |
| | |
| [[File:TreesSpawnedOnHill.jpg]]
| |
| | |
| [File:http://abload.de/img/treesspawnedonhill74shj.jpg]
| |
| | |
| {{Tip|For practice try to make a function that spawns geothermal spots! The featurename is "geovent".}}
| |
| | |
| =Deleting all features from map=
| |
| Sometimes one might want to test the spawning of features again & again, on a clean plate.
| |
| The function below deletes all features on the map:
| |
| <code><pre>
| |
| function DestroyAllFeatures ()
| |
| local features = Spring.GetAllFeatures ()
| |
| for i in pairs(features) do
| |
| Spring.DestroyFeature (features[i])
| |
| end
| |
| end</pre></code>
| |
| | |
| It can be called in <code>Initialize()</code> ''before'' we spawn our features, to ensure we always test on an empty map.
| |
| | |
| When packing the map for release, one might want to comment it out again.
| |
| | |
| =A forest is many, many, many trees=
| |
| A handful of trees is often not enough for a map. Let's turn the whole bottom left corner of the map into a forest.
| |
| | |
| But planting a whole forest by typing coordinates of many trees seems very cumbersome?
| |
| | |
| What about randomizing the placement of the tree?
| |
| | |
| We look up the coordinates of that area: x-coordinate we want 3500 to 4000, for z-coordinate we want 3000 to 4000.
| |
| | |
| ...and put it into a random number generator function.
| |
| | |
| <code><pre>
| |
| x = math.random (3500,4000)
| |
| z = math.random (3000,4000)
| |
| PlantTree (x,z)</pre></code>
| |
| | |
| We want to plant many trees. Instead of copy-pasting those lines 100 times, we use a loop:
| |
| <code><pre>
| |
| function gadget:Initialize()
| |
| DestroyAllFeatures ()
| |
| for i=1,100 do
| |
| x = math.random (3500,4000)
| |
| z = math.random (3000,4000)
| |
| PlantTree (x,z)
| |
| end
| |
| end</pre></code>
| |
| | |
| The result:
| |
| | |
| [[File:TreesSpawnedRandomly.jpg]]
| |
| | |
| [File:http://abload.de/img/treesspawnedrandomlyf3snt.jpg]
| |
| | |
| {{Tip|Forests are perfect for such random placement because the excact position of an indiviual tree does not matter much.}}
| |
| | |
| ==Spawning features in patterns==
| |
| With a little creativity one can quickly fill the map in interessting ways.
| |
| | |
| For example try to create a function that spawns small clusters of trees.
| |
| | |
| Or imagine a function that spawns a big mineral crystal, surrounded by smaller rocks.
| |
| | |
| Or imagine an ore-mining facility with pieces of ore spread around it.
| |
| | |
| etc
| |
| | |
| Below is some examples that are hopefully understandable without too lenghty explainations.
| |
| | |
| ==Lua rocks==
| |
| <code><pre>
| |
| function Rock (x,z)
| |
| local featureName = "pdrock" .. math.random (1, 4)
| |
| Spring.CreateFeature (featureName, x,Spring.GetGroundHeight (x,z),z, math.random (0,32000))
| |
| end
| |
| | |
| function rockCircle (x,z, radius, numberOfRocks)
| |
| for d = 0,360, 360/numberOfRocks do
| |
| local x = x + (math.sin (math.rad(d)) * radius)
| |
| local z = z + (math.cos (math.rad(d)) * radius)
| |
| Rock (x, z)
| |
| end
| |
| end
| |
| </pre></code>
| |
| Example usage:
| |
| <code><pre>rockCircle (2000,2000, 240, 25)</pre></code>
| |
| | |
| Result:
| |
| | |
| [[File:CircleOfRocks.jpg]]
| |
| [File:http://abload.de/img/circle_of_rockso5stt.jpg]
| |
| | |
| *Note 1: <code>local featureName = "pdrock" .. math.random (1, 4)</code>
| |
| The rock features included in map "DesertTriad" are named "pdrock1" to "pdrock4". So we take "pdrock" and append a random number in range of 1 to 4 to that base name.
| |
| | |
| *Note 2: The heading-parameter is used here too, randomized. The heading (rotation) of features is probally not measured 0 to 32000, but for this case that does not matter. If the number is too high it will simply "wrap around."
| |
| | |
| | |
| ==In my maps I have seen rings of smoke through the trees...==
| |
| ''...and the voices of those who stand looking.''
| |
| | |
| <code><pre>
| |
| function TreeSpiral (x,z)
| |
| for d = 0,720, 20 do
| |
| local x = x + (math.sin (math.rad(d)) * d/2)
| |
| local z = z + (math.cos (math.rad(d)) * d/2)
| |
| PlantTree (x,z)
| |
| end
| |
| y = Spring.GetGroundHeight (x,z)
| |
| Spring.CreateFeature ("geovent", x,y,z)
| |
| end
| |
| </pre></code>
| |
| | |
| Example usage:
| |
| <code><pre>TreeSpiral (2000,2000)</pre></code>
| |
| | |
| Result:
| |
| | |
| A geothermal vent in the middle of a spiral of trees.
| |
| | |
| [[File:TreeSpiralWithGeovent.jpg]]
| |
| | |
| [File:http://abload.de/img/treespiralwithgeoventu1swc.jpg]
| |
| | |
| =Typing so many coordinates sucks!=
| |
| We saw how some typing could be saved by using loops or random placement.
| |
| But surely there must be another way?
| |
| | |
| We could try to save the coordinates of all features of the map into a file.
| |
| | |
| If we wanted to do that, we would preferably save the coordinates in a way that can also be read back by another script.
| |
| For now that seems too difficult so we think of another way.
| |
| | |
| * think * think * think *
| |
| | |
| ==Coordinates Helper widget==
| |
| Spring allows players to mark tactical locations on the map by holding ~ or ^ and clicking the mousewheel.
| |
| | |
| That might also be useful for marking locations of features, if we can just save those coordinates?
| |
| | |
| Let's see if we can create a small '''helper widget''' for that purpose.
| |
| | |
| Create a file <code>LuaUI\widgets\MapMarkerCoordinatesLogger.lua</code>
| |
| | |
| | |
| ==MapDrawCmd: 1,2,3 testing!==
| |
| For start let's just see if we can print the coordinates of a map marker to the console.
| |
| | |
| We discover the callin: <code>MapDrawCmd(playerID, cmdType, px, py, pz, label)</code>
| |
| | |
| <code><pre>
| |
| function widget:GetInfo()
| |
| return {
| |
| name = "MapMarkerCoordinatesLogger",
| |
| desc = "prints map marker coordinates",
| |
| author = "this lame tutorial or something",
| |
| date = "9 Nov 2010",
| |
| license = "GNU GPL, v2",
| |
| layer = -5,
| |
| enabled = true,
| |
| }
| |
| end
| |
| | |
| function widget:MapDrawCmd(playerID, cmdType, px, py, pz, label)
| |
| Spring.Echo (px, py,pz)
| |
| end</pre></code>
| |
| | |
| Result:
| |
| | |
| [[File:MapMarkerCoordinatesToChat.jpg]]
| |
| | |
| [File:http://abload.de/img/mapmarkercoordinatestnwsng.jpg]
| |
| | |
| We see that the coordinates of the markers get printed to chat. (and thus to infolog.txt, too)
| |
| | |
| Not bad, but could be less bad.
| |
| | |
| ==Turning test into something useful==
| |
| | |
| One obvious issue is that it does not just log placing markers but also outputs numbers when drawing lines or deleting.
| |
| | |
| Let's upgrade the widget so that:
| |
| #It only logs placing markers but not erasing or lines.
| |
| #instead of coordinates it outputs a whole function call like <code>PlantTree (3830,3600)</code>
| |
| #writes to a file instead of chat
| |
| #The functionname that gets written should be changeable from within game
| |
| #It has a cooler name
| |
| | |
| {{Tip|'''Before reading onwards: Try figure out a solution by yourself!'''
| |
| | |
| 1) Sadly the wiki does not tell us how we know the difference between marker/lines/erasing. We must figure it out!
| |
| | |
| <code>MapDrawCmd(playerID, cmdType, px, py, pz, label)</code>
| |
| | |
| The paremter ''cmdType'' looks interessting. Let's just print that one too:
| |
| <code><pre>Spring.Echo (px, py,pz, cmdType)</pre></code>
| |
| | |
| We see that cmdType has the value "point" when placing markers and other values when doing other things.
| |
| | |
| So if cmdType is of wrong value we just return and do nothing:
| |
| <code><pre>
| |
| function widget:MapDrawCmd(playerID, cmdType, px, py, pz, label)
| |
| if cmdType ~= "point" then return end
| |
| ...
| |
| </pre></code>
| |
| | |
| | |
| 2) Creating the complete function call like "PlantTree (3830,3600)" is just a matter of appending some strings:
| |
| <code><pre>
| |
| local functionName = "PlantTree"
| |
| local s = functionName .. " (" .. px ..", " .. pz .. ")"
| |
| Spring.Echo (s)
| |
| </pre></code>
| |
| | |
| Note that we only save the X and Z coordinates because the Y-coordinate we can later get anyway with Spring.GetGroundHeight
| |
| | |
| 3) About writing to file we can find many tutorials on web if we google something like "Lua write to file" or similiar.
| |
| | |
| The result:
| |
| <code><pre>
| |
| local file = io.open("myCoordinatesFile.txt", "a")
| |
| file:write(s)
| |
| file:flush()
| |
| file:close()
| |
| </pre></code>
| |
| | |
| 4) Markers can have a label text attached to them. Let's use that to allow user to change the functionname.
| |
| <code><pre>if label ~= "" then functionName = label end</pre></code>
| |
| | |
| 5) Since the widget writes points into files for features placement, we name it: '''Pifffp'''
| |
| | |
| | |
| | |
| ==Complete Pifffp widget==
| |
| Putting everything together we get:
| |
| | |
| <code><pre>
| |
| function widget:GetInfo()
| |
| return {
| |
| name = "Pifffp aka MapMarkerCoordinatesLogger",
| |
| desc = "prints Lua functions for placing featues",
| |
| author = "this lame tutorial or something",
| |
| date = "today",
| |
| license = "GNU GPL, v2",
| |
| layer = -5,
| |
| enabled = true,
| |
| }
| |
| end
| |
| | |
| local functionName = "PlantTree"
| |
| function widget:MapDrawCmd(playerID, cmdType, px, py, pz, label)
| |
| if cmdType ~= "point" then return end
| |
| if label ~= "" then functionName = label end
| |
| --Spring.Echo (px, py,pz, cmdType, "-"..label.."-")
| |
|
| |
| local s = functionName .. " (" .. px ..", " .. pz .. ")"
| |
| Spring.Echo (s)
| |
|
| |
| local file = io.open("myCoordinatesFile.txt", "a")
| |
| file:write(s .. "\n")
| |
| file:flush()
| |
| file:close()
| |
| end
| |
| </pre></code>
| |
| | |
| | |
| ==Using the Pifffp tool as a user/mapper==
| |
| Activate the widget in F11 list.
| |
| | |
| Now start placing map markers. Every time you place a marker the coordinates get logged into '''myCoordinatesFile.txt''', already formated as parameters to a function call.
| |
| | |
| (The file should usually be written next to spring.exe)
| |
| | |
| To change the name of the function place a marker with a label text. All folling markers will then use that text, until a new label text is used.
| |
| | |
| Example usage:
| |
| | |
| [[File:Pifffp_Markers.jpg]]
| |
| | |
| [File:http://abload.de/img/pifffp_markers7lsq1.jpg]
| |
| | |
| Example output:
| |
| | |
| <code><pre>
| |
| PlantTree (1977, 3569)
| |
| PlantTree (1933, 3372)
| |
| PlantTree (1901, 3489)
| |
| PlantTree (1850, 3612)
| |
| PlantTree (2008, 3643)
| |
| PlantTree (2101, 3497)
| |
| PlantTree (2112, 3637)
| |
| PlantTree (2228, 3605)
| |
| PlantTree (2226, 3464)
| |
| PlantTree (2043, 3406)
| |
| PlantTree (2163, 3357)
| |
| PlantTree (2293, 3278)
| |
| PlantTree (2125, 3179)
| |
| PlantTree (1986, 3193)
| |
| Rock (1526, 3099)
| |
| Rock (1576, 3024)
| |
| Rock (1500, 2926)
| |
| Rock (1373, 3035)
| |
| Rock (1438, 3212)
| |
| Rock (1556, 3264)
| |
| Rock (1699, 3122)
| |
| Rock (1670, 2919)
| |
| Rock (1398, 2918)
| |
| Geovent (1818, 3298)
| |
| rockCircle (2308, 2788)
| |
| </pre></code>
| |
| | |
| We recognize these functions from the beginning of the tutorial.
| |
| | |
| Hopefully still have them ready in our <code>LuaRules\gadgets\MyLuaFeatureTesting.lua</code> file so that we only need to paste the Pifffp output into <code>Initialize</code>
| |
| | |
| (I you are making a real map you might want to use a better filename like "spawnMyFeatures.lua")
| |
| | |
| Note that for the function rockCircle we still need to fill in the parameters radius and amount of rocks like so:
| |
| <code><pre>
| |
| rockCircle (2308, 2788, 200, 25)</pre></code>
| |
| | |
| Example result:
| |
| | |
| [[File:Pifffp_ResultFeatures.jpg]]
| |
| | |
| [File:http://abload.de/img/pifffp_resultfeaturesiisdi.jpg]
| |
| | |
| Now we have created ourselves a quick way to put features onto map with Lua.
| |