Catch up - F# UI and new mechanics
September 18, 2020 - Friday afternoon
I spent some time focused just on game design - I wrote and rewrote outlines, made and played with paper models, wrote stories of imagined player gameplay sessions and tried to spec everything out fully. I'm now working my way through the implementation - some of the mechanics I ended up cutting out, some aren't in but will be eventually, and some I added during implementation. But - there is some gameplay in the game now, it finally doesn't feel like just a simple simulation.
Close to the end of the design phase I realized I wanted to add a lot of mechanics, which would mean a lot more UI as well. I want to keep the interface to a minimum and I'll still try to manage that - by showing things contextually based on zoom and where the camera is rather than presenting all the UI available all the time. To make this mountain of UI more approachable to develop, I rewrote what UI I had in the game in F# and built all the new stuff on top of that. Now it's a super smooth experience to pump out components. I upgraded Unity to 2020.1 as well - UI Toolkit has a much improved experience and only a couple things broke!
F# UI
I considered using Elmish and writing a custom renderer for Unity, but it looked like it might be complicated and I couldn't find docs. I was partway through trying to reverse engineer the React renderer before deciding to just take a stab at writing a minimal MVU architecture implementation from scratch. I also prefer to not add external dependencies if possible and I like trying to do stuff I maybe shouldn't.
But it worked! And here's what it looks like in action. This is a basic component that just shows a resident's first and last name.
(I opted for the completely unnecessary semicolons as a convention to keep it looking Elmy)let residentScreen _update resident =
div []
[ text [] resident.name.first
; text [] resident.name.last
]
Let's add a reusable container element styled by a USS class.
let panel =
div [ Class "panel__container" ]
let residentScreen _update resident =
panel
[ text [] resident.name.first
; text [] resident.name.last
]
I added animation, which just uses Unity's animation curve:
let panel =
div [ Class "panel__container"
; Animate
{ style = Right
duration = 0.2
start = 100.0
finish = 0.0 }
; Animate
{ style = Opacity
duration = 0.2
start = 0.0
finish = 1.0 } ]
I can also mix world-space and screen-space elements in the same element tree:
let residentScreen _update resident =
panel
[ text [] resident.name.first
; text [] resident.name.last
; spotlight [] resident.key
]
And if we want to make the spotlight toggleable:
let residentScreen update resident showSpotlight =
panel
[ text [] resident.name.first
; text [] resident.name.last
; button []
(if showSpotlight then "Hide" else "Spot"
, fun _ ->
update (ResidentScreen (resident.key, not showSpotlight) ) ) )
; if showSpotlight then
spotlight [] resident.key
]
There's a problem with the toggle button - if we select a second resident, it's setting the selected resident back to the previous one. We'll need to add a key to clarify when we want the element tree to update the button, since we can't key by the callback.
let residentScreen update resident showSpotlight =
panel
[ text [] resident.name.first
; text [] resident.name.last
; stringKey resident.key
(button []
(if showSpotlight then "Hide" else "Spot"
, fun _ ->
update (ResidentScreen (resident.key, not showSpotlight) ) ) )
; if showSpotlight then
spotlight [] resident.key
]
Since I can put basically anything in there, here's an image backed by a render texture. It gets layout from UI Tk while rendering a different camera.
; residentCamera [ Class "res_cam"; Class "center" ] resident.key
And a basic model might look like:
type ResidentScreen =
| Basic
| Metrics
| Activity
type MainScreen =
| JustTheWorld
| ResidentScreen of Key.Resident * ResidentScreen
let residentScreen update (resident, activity) =
function
| Basic ->
panel
[ text [] resident.name.first
; text [] resident.name.last
; stringKey resident.key
(button []
("Metrics"
, fun _ ->
update (ResidentScreen (resident.key, Metrics) ) ) )
; stringKey resident.key
(button []
("Activity"
, fun _ ->
update (ResidentScreen (resident.key, Activity) ) ) )
]
| Metrics ->
panel
[ metric "Health" (percent resident.metrics.health)
; metric "Wealth" resident.wealth
; metric "Energy" (percent resident.metrics.energy)
; metric "Nutrition" (percent resident.metrics.energy)
; metric "Satisfaction" (percent resident.satisfaction)
]
| Activity ->
panel
[ multiline [] activity ]
Since UIDocuments can render to a render texture which can then be used with a Unity material, you could totally render UI Tk layout-ed elements onto world objects for a diagetic panel with this method as well. Maybe I'll give that a shot in a later post.
The implementation method I use is basically just swapping between fresh and stale caches, keyed by a recursive positional index type that looks like:
type Pos =
| PosS of Pos * string
| PosI of Pos * int
| Root
If there's interest I could write a follow up post that goes a little more in-depth on this stuff. I'm just showing off how nice writing UI is here.
New Mechanics
With this in hand I was able to crank through mechanics with basic UI faster. Here's a rundown of the new mechanics in the game:
Feature areas are underwater features like Hydrothermal Vents or Submerged Cities which first need to be Scanned by the Boat to be revealed. Once revealed, they can be developed for either resources, tourism or science.
A developed area can be assigned a workboat - a smaller drone vessel that can be equipped with specific equipment which allows it to work a respective developed area. Current equipment are a Passenger Cabin for tourism, an Extractor for resources and an Analyzer for science. The main player boat can have a task queue so the player can queue up some actions (like scanning or building) and then move their attention elsewhere.
I've added some new building types:
- Restaurant
- Factory
- Workshop
- Grocer
- Retailer
- Workboat Station
- Boat Station
Buildings also have a wealth and filament cost (they're 3D printed structures). There are two filament types, Conductive Filament and Structural Filament. These can be produced from materials at the Boat Station by workers.
Deconstructing buildings is possible as a Boat task. Buildings will also wear down slowly, going through states of Inefficient, Closed or Shuttered - each having a worse impact on its function. The player can maintain buildings directly, this is the only 'direct player interaction' mechanic. Buildings can be leveled up, and cost both the village and the building's own wealth.
Recipes decide what processor type buildings (most of the new ones) actually produce. They describe input resources, an output and how much work is required. The player can select which recipe a building works on, while residents are necessary to carry out the work. Some have minimum building level requirements to be used.
I redid some of the resident behavior, and there are three main work roles: manager, buyer, worker. Managers produce purchase orders for buyers to execute based on the recipe the player selects. Buyers keep the inputs coming, spending the building's wealth to buy resources, and workers contribute to the work done in a building. Residents also have an updated recreation phase - they'll choose between going to the park (no cost), restaurant (gain satisfaction + nutrition) or on tour if an appropriate workboat/feature area is available (high satisfaction gain). If certain metrics get too bad, Residents will start Evacuating - they'll leave the village forever (ever.. ever..)
Platforms have varieties now - standard, agricultural, industrial, and residential. The special platforms provide bonuses to buildings of certain types that are built onto them. They'll be awarded in limited quantity (once I've added Contracts / the quest system).
The village itself can be improved via advancements, this is the research tree mechanic. An advancement can be focused; after which work done, wealth generated and actual research (say from a developed area) contribute points to complete it. Policies are advancements which can be turned on and off and will usually cost some wealth to keep on. An example is the "Tiny house" advancement, which increases the housing capacity for all houses.
There's also an equipment inventory, through which equipment can be.. equipped onto workboats. The tax rate is also adjustable - higher tax means a faster satisfaction drain.
More resources have been added and there are a couple product verticals. The chains are:
Dishes
Raw Food (Farm, Workboat Station + Resource) -> Food (Grocer) -> Dish (Restaurant)
Packaged Goods
Raw Material (Workboat Station + Resource) -> Material (Workshop) -> Good (Factory) -> PackagedGood (Retailer)
I've also finally added some sound effects to the game, many of which are just cuts of me banging on things.
Progress
It feels like slow going sometimes, but after seeing the features added I feel that a good amount has been done. I generally will try to spend some time upfront to decrease maintenance burden or make it easier to develop content at the cost of more time spent on the infra stuff. I'm still learning how difficult design is, I'm not where I want to be with the game yet but it's certainly improved. So - I'm willing to bump my report card numbers.
Measure | Last | Now | Why |
---|---|---|---|
Art | 2 | 2 | Unchanged - new placeholders |
UX | 2 | 3 | A lot more interaction opportunities |
Gameplay | 1 | 2 | Much of the first set of features are implemented |
Simulation | 3 | 3 | Mostly unchanged |
Marketability | 1 | 2 | I can actually talk about gameplay! |
Next up
Most of the mechanics are entirely unbalanced. I'll need to make sure the scale makes sense and progression actually feels like progress. The core gameplay loop still needs plenty of work, and I also need to implement a few more features in the first set:
- Village levels and reputation
- Quest system, aka
- Contracts
- Requests
There's not a lot of content for each of the content types yet, so I'll need to fill in more there to start fleshing out the game. For the next set of features, I'm thinking of adding these multiplayer items:
- A global view
- Resource regions
- Cities (clan mechanic)
- Village movement
As well as:
- Specializations
- Next tier of buildings (and project type buildings)
- Resident socialization
I'm struggling to design emergent gameplay mechanics though I already have the dynamic system. I think it's because the residents largely ignore each other, so the socialization system will help when it's implemented. I also need to add some performance regression tests, and automated cost estimation. There's likely going to be a 'time dilation' in effect for villages when they're simulated on the server to keep costs low. The compute cost mostly scales with resident count currently, which works out well if there are sudden spikes of new players. Generally the longer a player sticks around the more they'll cost in compute, but also I'd expect they'd be more valuable.
I've given more thought to monetization, and I really like the battle-pass model for fremium games. I'll likely try something in that vein. I'm not against rewarded ads, but I'd like to stay away from time-gates. My thinking right now is more of a pay-to-participate approach and to avoid pay-to-win. I also plan on having plenty of aesthetic items to accessorize the residents with, as well as special buildings, platforms and boats.
Okay - this post has gone on too long. Since it looks like I'm making a habit of taking long breaks, I figure a big post when I'm here isn't the worst. Til next time!
If you've read this whole thing (woah! thanks!) maaaybe there's a chance you'd like to check out the game? I'm in need of a few testers - this would be a very early test of the game, with the goal of making sure it at least runs okay on some devices other than my own. If you've got an Android and are willing to help me out, please send me an email at the link below 🙏.