Future Perfect

Future Perfect

Not enough ratings
Lua Gameplay Scripting
By 412.5ppm and 3 collaborators
How to create a first person multiplayer version of Minesweeper, and in doing so, learn about gameplay scripts in Future Perfect.

An accompanying YouTube playlist can be found here, and all assets created in the tutorial can be found here.[github.com]
   
Award
Favorite
Favorited
Unfavorite
Introduction

This Guide is focused on scripting in Future Perfect. It will step you through the creation of a simple game: Multiplayer first person Minesweeper. Through this tutorial, you will learn some core ideas about how the Future Perfect engine works, and how to set up and publish Future Perfect games on Steam Workshop.

The instructions in this Guide assume that you have a basic understanding of programming. Knowledge of Lua is not really necessary, as it is a very simple scripting language. Basically, if you don't know what a 'Bool' is, this Guide might not be for you. (But we encourage you to have a go anyway, you'll learn lots!)

Future Perfect is being actively developed, and is updated constantly. While new features are added, core concepts and functionality generally remain the same. Sometimes, a piece of this guide might be slightly out of date. However, we update it regularly.

Each section is accompanied by a video. These videos are not updated as frequently as the guide text. If you are following along with the video, and notice something is working differently in the game to what is shown on the video, check the guide text. It's likely the video needs to be updated. Also, the videos proceed much more slowly than you can likely read. So if you want to blast through the tutorial, you will probably benefit from sticking to the text.

Without further ado, let's get into some gameplay scripting!
Basic Concepts
Before we dive into scripting gameplay, let's look at the core ideas of Future Perfect's engine. The game world is a collection of entities. Entities are everything - the environment you're running around in, the avatar you control. Entities on their own can't do very much - they have a position and orientation, list of user defined tags and they can be optionally attached to another entity.

The real work is done by components that you add to an entity. Each component type exposes a piece of the engine's functionality - the model component is the primary way to get something drawn on screen, the collision component defines how the entity behaves in the physics simulation and the focus of this post is the script component that attaches a single Lua file to an entity. There are 16 different component types at the moment and they're all designed to be as simple as we could make them.

As it would be tedious to have to set up each entity from scratch by painstakingly adding and configuring all its components, you can store arbitrarily complex configurations of entities as blueprints. Note that blueprints are like cookie cutters - modifying a blueprint won't affect entities already created using it.

Blueprints are an asset - a file that lives in a package that is automatically processed by the game's asset pipeline. Worlds, Lua scripts, FBX models and WAV files are all examples of different types of assets. The game always watches for modifications to asset files and automatically builds and reloads them in the background.

Packages, in addition to being bags of assets, also maintain a list of dependencies on other packages (depending on a package makes all of its assets available) and can be easily published to Steam Workshop. You'll always be 'inside' of some package - either by explicitly opening one in the editor or in a temporary working package, so that whenever a new asset is created it has somewhere to go. Beware that the working package is deleted on exit!
Creating a Package

Alright, let's get started. I thought it would be fun to try to recreate the game of Minesweeper - a first person, multiplayer Minesweeper. With jump pads. Run Future Perfect and select 'Start Editor' from the main menu. First let's get out of this fleeting ‘working’ package that we start with - click on the 'Package Properties' button:



In the packages menu, fill in the 'Title' in the left pane. The folder name will be automatically filled in for you - just hit the green tick. See that 'Publish' button at the bottom? Yep, that's all it takes to get your package up on Steam Workshop (I know it's tempting, but please be patient!).

The middle pane shows the packages that our package will depend on - the game starts you with some nice defaults but for a Minesweeper game we'll definitely need a mine - search for the 'Mine' package in the right pane and drag it into 'Loaded Packages' in the middle.



The right pane shows all the packages you've subscribed to in Steam and you can use the search function to look for all packages available on Steam Workshop. We're finished here, so just hit the 'X' on the right.
Player Spawner

We're now editing a world called 'New' (see top left corner) - you can click on this tab to rename it. Select the default floor entity and use the sizer arrows along its edge to make it a bit bigger. If we'd start the game now, there would be no entity associated with the player so you would only control the default spectator camera. To fix that, place the 'Player Spawner' blueprint on the floor (mouse over the right edge of the screen to open the asset browser, type in 'player' to see only matching blueprints and drag&drop 'Player Spawner' into the editor).



Entities without a model component show up as dashed white square so our new spawner isn't much to look at. Click on the black bar with 'Player Spawner' label near top right to open up the entity editor panel:



The blue tab bar lists the entity's components - the spawner only has one script component on it. Click on it (the gear icon) to see all the fields this script defines. Each is bound to a variable of the Lua script and allows us to configure the script's function. The most important value for us is 'Player Blueprint' - the spawner script will use whatever blueprint you'll bind to this field as the player entity whenever a new player joins the game. Click on the empty field next to it to open up the asset browser, filtered to only show assets of the type matching the field (in this case blueprints). Drag the 'Avatar' blueprint from the asset browser and drop it on the empty field:



Press F5 or click on the play toolbar icon to spawn as 'Avatar' in our very sparse world. Now we're ready to pick up speed! Press F5 again to enter the inspect mode - which is just like the edit mode except that the game is running. Don't get too creative though as all changes are lost when the game is stopped. Press the stop toolbar icon to return to editor.
Building Blocks - Messaging Part 1/2

It's time to finally build something! Future Perfect makes it easy to import new assets but as an engineer I sadly lack the ability to art and will have to get creative with existing assets. Rummaging through the asset browser (the browser will soon show preview icons - until then, just let it surprise you!) the 'Stack' blueprint caught my eye:



The base of the stack looks like a reasonable tile and the blue glow could be used to flag tiles that we know to have mines. Digging a bit deeper I found the blueprints used by 'Stack': 'stack_01_base' and 'stack_01_c.'

The plan is to make a 'Tile' blueprint that will combine the two pieces above and allow the player to 'use' it (by looking at it and pressing 'E') to toggle visibility of the glowing part on and off. The interaction part is easy - the scripts on 'Avatar' (our player entity) already do all the hard work. If we tag our entity as 'usable', the player entity will send it 'Use' messages when appropriate.

Each script in Future Perfect lives in its own sandbox and is only allowed to directly influence the entity that its component belongs to. The only way to affect other entities is to send them messages - either by addressing a specific entity directly or by broadcasting to anyone who will listen. The 'Use' message handler on our tile entity wants to toggle the visibility of the model component on the flag entity but it can only do that by sending it a message.

Create a new script by clicking the green '+' icon at the top of the asset browser and selecting 'Script' from the drop down list. Type in ‘tile’ as the filename and hit ‘Create’.



The new script should now be visible in the asset browser (the filter has been set automatically to the name of the new asset).



It’s our first new asset! Double click on it to open it in an external text editor (the game will try to use whatever editor is associated with the .lua file extension on your system) and type in:

local flag = EntityId_Invalid --- EntityId local isFlagged = false function OnUse(message) if message.using then isFlagged = not isFlagged if flag ~= EntityId_Invalid then World_QueueMessage("Flag", flag, { value=isFlagged }) end end end
Gist[gist.github.com]

Message handlers are just regular Lua functions matching the pattern of 'On' followed by the message name. They take one parameter - a Lua table containing the payload of the message. We have decided not to try to impose standards for message names and the fields of the parameters table - we're hoping that consensus will emerge organically, driven by the most popular packages in the Workshop.
Building Blocks - Messaging Part 2/2

In the 'Use' handler, we're only interested in the 'using' field which set to true when the using interaction begins ('E' pressed) and false when it ends ('E' released). Our handler simply toggles a local boolean value and sends a new message to the flag entity. The second parameter to World_QueueMessage is the entity id of the receiver entity.

Notice how the 'flag' variable is marked up with a triple dash comment, the game uses this syntax to expose Lua variables to the editor - just like the player spawner's blueprint field we've seen earlier. In this case the field is tagged as 'EntityId' which lets the editor know that we're interested in a reference to another entity.

Create another script called 'flag' with a handler for the 'Flag' message:

local model = Self_GetComponent("Model") function OnFlag(message) Model_SetIsVisible(model, message.value) end
Gist[gist.github.com]

The script grabs a reference to the first model component on its entity when loaded and responds to the 'Flag' message by setting the model's visibility to the 'value' message parameter. As this script is going to run on our flag entity, it can directly access and manipulate that entity's components using the 'Self_<function name>' and '<component name>_<function name>' API functions. There are some queries that you can perform on other entities - those functions are prefixed with 'Entity_'. You can find the full list of the available functions in <Steam location>\Future Perfect\docs\api.html (the easiest way to find it is to right click on Future Perfect in Steam, select 'Properties', then the 'Local Files' tab and click on 'Browse Local Files...').

To put everything together, we'll create a new blueprint - click on '+' in the asset browser, select 'Blueprint' and name it ‘Tile’.




Double click on it to open a blueprint editor in a new tab opens. Next drag 'stack_01_base' and 'stack_01_c' blueprints from the asset browser into the blueprint editor. You'll have to dismiss the default filter by clicking on the orange 'blueprint' tag in the top right corner of the browser as the more fine grained blueprints are marked to be hidden by this filter, then type 'stack blueprint' in the search field to get to the blueprints we want (make sure you drag the blueprint asset - it's the one with hammer and wrench icon).

Select 'stack_01_base' and for convenience, enter 'Tile' in the name field of the entity editor panel and rename 'stack_01_c' to 'Flag'.



There are two ways to add a script component to an entity. Select the Tile entity again, then click the '+' at the right end of the blue component bar (if you don't see it, click on the black bar with entity's name to expand the entity editor panel) and pick 'Script'.



The new script component is added to the end of the component list but doesn't yet have a script asset associated with it. To give it a Lua script, click inside the empty script field to bring up the asset browser filtered to only show scripts. Find 'tile.lua' and drag it back into the empty script field. As soon as you'll do that, the 'Flag' field that we exposed earlier with the '---' markup appears in the entity panel! Now we can hook up the tile script to know which flag entity it should send the 'Flag' message when we use it: grab the target icon in 'Flag' field and drag it over the Flag entity. When you release it, the 'Flag' field should now contain the name of the Flag entity and you'll see a green arrow pointing from Tile to Flag:



Another way to add a script is to just drag it from the asset browser onto the title bar of the entity panel. Select the Flag entity, then bring up the asset browser by hovering over the right edge of the screen. Type in 'flag' into the search box and drag&drop the flag file on the bar with Flag's name (you'll see it change color to indicate it's a valid drop zone and highlight when you're right over it).



Now we just need to do a bit of housekeeping:

  • Select the Tile entity and in the 'Tags' field enter 'usable' and press Enter. This lets the player script know that this particular entity is interested in the 'Use' message.
  • We want the Flag entity to be attached to Tile. Select Flag and look for the 'Attached To' field. It has the same target widget we've used to connect our tile script to the Flag entity - just drag&drop it onto Tile (you should see the 'Nothing' value change to 'Tile').
  • Position both entities to be at the origin (0, 0, 0). You can use the manipulator or enter the position directly in the entity panel (notice how the Flag now moves with Tile).
  • Select the Flag entity and go to its collision component () - we don't want the flag to block movement so just hit the 'Delete' button to get rid of the component entirely.
  • Finally, go to Flag's model component () and uncheck the 'Visible' flag - we don't want the flag to be visible until it's toggled by the player.



Close the blueprint editor by clicking on the 'x' on its tab to return to the world editor. Let's give our new blueprint a spin - find it in the asset browser and drag it into the world. Position it close to the default floor entity so that we can get to it. You can clone a few more copies of it by holding 'Ctrl' while moving an existing one with the manipulator. Run the game (F5), get close enough to one of the tiles to see an orange highlight around it and press the use key ('E' by default). Assuming no you haven't missed any steps or hit one of the many bugs that we're busy weeding out - the flag should toggle on and off with each press of the use key!

Custom Avatar

The default Avatar blueprint is a great start but doesn’t quite fit our needs. We need him to be able to toggle flags at much greater distance than he’s currently able and we don’t really care about the score at the moment. The solution is to create a new blueprint - let’s call it ‘Sweeper’. Double click on Sweeper to open the blueprint editor, drag in ‘Avatar’ from the asset browser and move him to (0, 0, 0).



You can change his name to ‘Sweeper’ - entity names have no real impact on the game but they help to keep things organized in the editor and often aid debugging. Next we’ll go through his many script components.

  • Find the ‘Using Player’ script component - the UI is a little rough at the moment and you have to keep clicking on the gear icons until you see the one you want. Increase the ‘Max Use Distance’ to 5 (our distances are in meters).
  • Find the ‘Scoring Player’ script component and delete it. You can’t win minesweeper by scoring points!
  • Also delete the ‘Spawn Blueprint On Respawn’ script component.



Close the blueprint editor and select the Player Spawner back in the world editor - it may be a bit tricky because it’s hard to see, just look for a bit of that white square poking through the floor. Earlier we set it up to use the Avatar blueprint but now we want to use our customized version. Select the script component and change ‘Player Blueprint’ to Sweeper.

Setting Boundaries

It’s pretty easy to fall from our little platform right now and with nothing to catch you, it’s pretty much game over. Adding jump pads later isn’t going to make things safer so we might as well address this now. Drag in ‘Game Bounds’ blueprint from the asset browser and select its trigger component:



Set the ‘Extents’ to (200, 100, 200) - that should be a big enough volume for our game. Now if you ever leave the bounds of Game Bounds, you will automatically respawn back at the start.
Stepping on Mines

We’ve got flagging of tiles covered but the really fun part of minesweeper is revealing a tile to see if it contains a mine. In a first person minesweeper, I think the best way to reveal a tile is to step on it. If the player gets it wrong, the punishment will be swift.

Reopen our Tile blueprint by double clicking on it in the asset browser and select the Tile entity. Add a ‘Trigger’ component to it and set its Filter field to ‘Player’.



The trigger component will send a ‘Trigger’ message to our entity whenever a player enters or leaves the configured bounds. Close the blueprint editor and reopen tile.lua to add a handler for this message:

function OnTrigger(message) print(message.enter, EntityId_ToString(message.otherId)) end
Gist[gist.github.com]

When trying to figure out how things work - print is your best friend. We’re planning a visual debugging environment for the finished game but until then, printing is the only way to really see what’s going on under the hood. Fortunately script hotloading makes this a pretty painless process.

So let’s see if our trigger works - delete any tiles you have in the world and create few new ones from the Tile blueprint (remember that blueprints are like cookie cutters so the old tiles have no idea we’ve just added a trigger). Run the game and walk over one of the tiles.

Bring up the console with ‘~’ key and you should see something like:

true EntityId(index: 22 use: 0) false EntityId(index: 22 use: 0)

You can see that ‘message.enter’ is true when the player enters the trigger and false when he exits. The details of the entity id are for another post but the important thing to know is that the index and use values together uniquely identify an entity.



If you print more text than fits in the console, you can see it all in the log file located in ‘%appdata%\Future Perfect\log.txt’. Now return to your text editor and make the handler a bit more useful:

local isMine = false local nearbyMineCount = 0 local isRevealed = false function OnTrigger(message) if message.enter and not isFlagged and not isRevealed then isRevealed = true if isMine then -- TODO: boom! print("Yay, a mine!") else -- TODO: display nearbyMineCount print("There are", nearbyMineCount, "mines nearby!") end end end
Gist[gist.github.com]

We’ve added few variables to keep track of whether the tile has a mine and how many of its neighbouring tiles have mines. These are set to dummy values for now - we’ll initialize them properly later.

If the Trigger message is notifying us of a player entering, the tile has not been flagged (so that the player doesn’t accidentally trigger a mine he correctly identified) and the tile hasn’t been previously revealed - we reveal it! Run the game and make sure that the script prints ‘There are 0 mines nearby!’ when you step on a tile.

First let’s implement the case when the tile does contain a mine:

local mineBlueprint --- Blueprint local isMine = true function OnTrigger(message) if message.enter and not isFlagged and not isRevealed then isRevealed = true if isMine then local coords = Self_GetCoords() World_SpawnBlueprint(mineBlueprint, coords.origin + Vec3(0, 1, 0), coords:GetAngles()) World_EndGame() else -- TODO: display nearbyMineCount print("There are", nearbyMineCount, "mines nearby!") end end end
Gist[gist.github.com]

I’ve changed the value of ‘isMine’ to true so that we can easily test the new code. Modify OnTrigger to spawn a mine blueprint 1 meter above the tile and end the game when a mine is revealed. To allow us to specify which blueprint to use for mines, I’ve added the ‘mineBlueprint’ variable and marked it up as blueprint so that the editor knows what to do with it. The ‘Coords’ type represents a position and orientation - for details have a look at ‘<Steam location>\Future Perfect\system\core\scripts\coords.lua’.

We need to update the Tile blueprint again - open it in the blueprint editor, select the Tile entity, then the script component. It now has a ‘Mine Blueprint’ field and it’s hooked up to our ‘mineBlueprint’ variable in ‘tile.lua’. Drag the Mine blueprint into it and close the blueprint editor. Recreate the test tiles, run the game and try stepping on some freshly mined tiles!

Counting Mines

Onto the next puzzle - how do we handle the second part of the reveal code? We need to display the value of ‘nearbyMineCount’ on top of the tile. Fortunately there’s a package called ‘Scoreboard’ that contains some useful assets - open up the package properties menu, find the Scoreboard package and drag it into ‘Loaded Packages’.



Close the package menu and drag the ‘Number’ blueprint from the asset browser. That looks pretty good (provided that you’re looking at it from the right direction). Select its model component and you'll see that it has a bunch of skins defined - each causing the model to display a different digit:



There's also a handy ‘digit.lua’ script that responds to a ‘SetDigit’ message by changing the skin to the requested number.

Let’s return to our ‘tile.lua’ and finish our reveal code:

local number = EntityId_Invalid --- EntityId local isMine = false local nearbyMineCount = math.floor(math.random() * 8) + 1 function OnTrigger(message) if message.enter and not isFlagged and not isRevealed then isRevealed = true if isMine then local coords = Self_GetCoords() World_SpawnBlueprint(mineBlueprint, coords.origin + Vec3(0, 1, 0), coords:GetAngles()) World_EndGame() else World_QueueMessage("SetDigit", number, { digit=nearbyMineCount }) end end end
Gist[gist.github.com]

We have added a new entity reference to a number entity - we’ll hook that up in a minute in the Tile blueprint. When the tile is revealed, we send the number entity a ‘SetDigit’ message. For testing purposes, I switched ‘isMine’ back to false and ‘nearbyMineCount’ is randomized so that we can see the different digits.

Next open the Tile blueprint in the blueprint editor and drag the ‘Number’ blueprint into it. Attach the new Scoreboard Number entity to our Tile entity. Add a new script component and set it to use the ‘digit.lua’ script. Now that the number entity is set up, select the Tile entity, switch to its script component and set the new ‘Number’ field to Scoreboard Number (drag&drop the target onto the number).



Position to number to float just above the surface of the tile. To disable grid snapping, hold down Alt while using the manipulator.



We don’t want the digit to show up until the tile is revealed so select the number entity, switch to its model component and set the ‘Skin’ to ‘off’. Close the blueprint editor, once again - recreate the test tile entities and hit F5:

Generating Minefields

The Tile blueprint is almost done so now we can generate a grid of tiles. Create new script called 'minefield’:



local tileBlueprint --- Blueprint local width --- number local height --- number function OnCreate() for i = 0, width - 1 do for j = 0, height - 1 do local location = Vec3(5*i, 0, 5*j) local tile = World_SpawnBlueprint(tileBlueprint, location, Angles(), Self_GetId()) end end end
Gist[gist.github.com]

The script exposes three fields to editor - blueprint for the tile we’ll use and width and height as of the grid as numbers. The engine calls OnCreate and OnDestroy functions when a script component is created and destroyed - but only when the game is running. While in editor, only automator scripts get to run.

Our initial implementation of OnCreate simply spawns a width*height grid of tileBlueprints. The last parameter to ‘World_SpawnBlueprint’ is an optional entity reference that the newly created tiles will be attached to. We pass in the entity id of the script’s own entity (‘Self_GetId’) so that all tiles will be children of the minefield entity.

Go back to the world editor and for the last time delete any test tiles. Drag in 'Empty' blueprint from the asset browser, position it near the floor and add the ‘minefield.lua’ script to it. Hook up 'Tile Blueprint' to our Tile blueprint and set Width/Height to a reasonable minefield size - say 16x16:



Run the game and walk over some of the tiles to make sure they all reveal a different random number (but only when not flagged):



So how do we place the mines? Let’s start with a Lua representation of an empty minefield, then add mines one by one and update nearby mine counts on neighboring tiles for each mine we add. I chose to store the grid as a flat table of nearby mine counts with -1 used to mark tiles with mines. To map from (x, y) location of a tile to an index into the 1 dimensional table we use ‘x + y*width’.



Open the minefield script again and add a ‘CreateMinefield’:

local mineCount --- number local function CreateMinefield() -- Store the minefield as flat array of numbers indicating the number of nearby mines. -- We'll use -1 if the tile has a mine on it. minefield = {} -- First initialize the field to all zeros - we start with no mines for x = 0, width - 1 do for y = 0, height - 1 do minefield[x + y*width] = 0 end end -- We won't allow more than half of all tiles to be mines mineCount = math.min(mineCount, width*height / 2) -- Now place 'mineCount' mines in the field for i = 1, mineCount do PlaceMine(minefield) end return minefield end
Gist[gist.github.com]

Add a simple utility function to increase the nearby mine counts at tile (x, y) that we’ll need for ‘PlaceMine’:

local function IncrementTile(minefield, x, y) -- Check that x,y within bounds of the minefield if x >= 0 and x < width and y >= 0 and y < height then current = minefield[x + y*width] -- Check that not a mine if current ~= -1 then minefield[x + y*width] = current + 1 end end end
Gist[gist.github.com]

And finally implement ‘PlaceMine’ (make sure that IncrementTile goes first, then PlaceMine, CreateMinefield and OnCreate):

local function PlaceMine(minefield) -- Keep trying random tile locations until we find one that doesn't have a mine on it local x, y while true do x = math.floor(math.random() * width) y = math.floor(math.random() * height) if minefield[x + y*width] ~= -1 then -- No mine here yet! break end end -- Place the mine minefield[x + y*width] = -1 -- Increment nearby mine counts IncrementTile(minefield, x - 1, y - 1) IncrementTile(minefield, x, y - 1) IncrementTile(minefield, x + 1, y - 1) IncrementTile(minefield, x - 1, y) IncrementTile(minefield, x + 1, y) IncrementTile(minefield, x - 1, y + 1) IncrementTile(minefield, x, y + 1) IncrementTile(minefield, x + 1, y + 1) end
Gist[gist.github.com]

PlaceMine keeps generating a random location (x, y) until one is found that doesn't contain a mine (i.e. doesn't equal to -1). We then mark the tile as mine and use IncrementTile on all the neighboring tiles.

Update the OnCreate function to use GenerateMinefield and send a ‘SetupTile’ message to each tile that it creates, telling it whether it has a mine and how many mines are nearby using the generated minefield table:

function OnCreate() local minefield = CreateMinefield() for i = 0, width - 1 do for j = 0, height - 1 do local location = Vec3(5*i, 0, 5*j) local tile = World_SpawnBlueprint(tileBlueprint, location, Angles(), Self_GetId()) local nearbyMineCount = minefield
local isMine = nearbyMineCount == -1
local tileInfo = { isMine=isMine, nearbyMineCount=nearbyMineCount })
World_QueueMessage("SetupTile", tile, tileInfo)
end
end
end[/code]
Gist[gist.github.com]

We need to add a handler to ‘tile.lua’ for the ‘SetupTile’ message which just pulls the values out of the message parameter table and stores it in the variables we’ve already declared (I also changed ‘nearbyMineCount’ to be initialized to 8 instead of a random number to make it easier to see if there are any problems with the message handler):

local nearbyMineCount = 8 function OnSetupTile(message) isMine = message.isMine nearbyMineCount = message.nearbyMineCount end
Gist[gist.github.com]

Return to the world editor and select the minefield entity script component - we need to set the new ‘Mine Count’ field to for example 40. Run the game and test that everything works. We now have a pretty much fully functional version of minesweeper!

Message Cascades

When a zero tile is revealed, it’s just busy work to reveal all its neighboring tiles as we know for sure there are no mines there. We can easily automate this - open ‘tile.lua’ and let’s do a bit of surgery:

local function Reveal() isRevealed = true if isMine then local coords = Self_GetCoords() World_SpawnBlueprint(mineBlueprint, coords.origin + Vec3(0, 1, 0), coords:GetAngles()) World_EndGame() else World_QueueMessage("SetDigit", number, { digit=nearbyMineCount }) -- If we reveal a tile with no mines, it automatically reveals all nearby tiles if nearbyMineCount == 0 then local nearby = World_TestSphereOverlap(Self_GetCoords().origin, 6) for i = 1, #nearby do if nearby[] ~= Self_GetId() then World_QueueMessage("Reveal", nearby[]) end end end end end function OnReveal() if not isRevealed then Reveal() end end function OnTrigger(message) if message.enter and not isFlagged and not isRevealed then Reveal() end end
Gist[gist.github.com]

I pulled out the meat of the OnTrigger handler to a local Reveal function. If ‘nearbyCount’ is 0, we do a collision query with World_TestSphereOverlap to get all entities within 6 meter radius around our tile's position - this is definitely going to give us all of the neighboring tiles. We iterate over the entities found and send them a 'Reveal' message (we don't really have any parameters to pass so we don’t).

The OnReveal handler simply calls our local Reveal function (making sure to check the isRevealed flag to prevent an infinite message loop). Run the game and try to find a tile with zero neighboring mines:

Jump Pads Make Everything Better

It can be tricky to get a decent overview of the minefield from ground level - so as a finishing touch let’s modify OnCreate in ‘minefield.lua’ to spawn a jump pad between every second row and column of the grid:

local jumpPadBlueprint --- Blueprint function OnCreate() local minefield = CreateMinefield() for i = 0, width - 1 do for j = 0, height - 1 do local location = Vec3(5*i, 0, 5*j) local tile = World_SpawnBlueprint(tileBlueprint, location, Angles(), Self_GetId()) local nearbyMineCount = minefield
local isMine = nearbyMineCount == -1
local tileInfo = { isMine=isMine, nearbyMineCount=nearbyMineCount })
World_QueueMessage("SetupTile", tile, tileInfo)

if i % 2 == 0 and j % 2 == 0 then
World_SpawnBlueprint(jumpPadBlueprint,
location + Vec3(2.5, 0, 2.5), Angles(), Self_GetId())
end
end
end
end[/code]
Gist[gist.github.com]

Select the minefield entity’s script component and set ‘Jump Pad Blueprint’ to ‘Jump Pad’:

Conclusion


Now it’s your turn to have some fun with this - experiment, break things, make your ideas come to life or just play some Minesweeper. And then tell us what you like and what sucks. We’re only getting started and your feedback is hugely valuable to us at this point!

Discuss on the...

Unknown Worlds forums[forums.unknownworlds.com]
Steam Future Perfect forums

You can find all assets created in this tutorial on GitHub[github.com]
10 Comments
somerandombaddeveloper May 14, 2021 @ 11:01am 
@wanted514 it is a good learning opportunity
b0nehead Feb 14, 2021 @ 3:10pm 
There are other game engines where you could make an actual game and possibly some money for your time..what's the point of investing time into this?
热心网友 Oct 3, 2017 @ 3:43am 
可以666
AuRoN240195 Dec 19, 2014 @ 7:26pm 
A Multi-player editor would be great, and would potentially make the arduous task of making a huge game significantly easier. :o
Freaky Dec 4, 2014 @ 6:57am 
Is a multiplayer editor planned?
Arkandos Dec 2, 2014 @ 1:16pm 
Seems like a good way to solve the problem.
-D-  [author] Nov 29, 2014 @ 12:38pm 
Our blueprints are cloned instead of instanced for simplicity and robustness. Keeping track of dependencies on blueprints is a significant cognitiive load - especially when you start overriding some properties on the instanced entities and conflicting changes happen upstream in a blueprint (or perhaps another blueprint that your blueprint depends on). Add multiplayer editing to the mix and it would be pretty chaotic :).

For blueprints that change often (e.g. Tile in this guide) you'd use a kind of a spawner but it's not quite ready and I didn't want to complicate things any more - hence the unnecessary deleting & recreating. We're also planning editor functionality to mass update entities from their original blueprints but it will be a user initiated action rather than automatic.
Arkandos Nov 29, 2014 @ 5:01am 
What is the reasoning behind not updating entities when their blueprint changes?
This means you have to delete and replace all entities every time you make a change? Sounds very annoying.
GISP Nov 28, 2014 @ 11:32pm 
Nice guide!
A1steaksa Nov 28, 2014 @ 8:21pm 
Oh, you beautiful human beings.