Player Housing Tutorial
This is an advanced-level tutorial on how to design and implement player housing.
Before working through this tutorial, you should be familiar with:
- Data storage options
- Detailing the variety of ways in which data can be stored/persisted in HeroEngine. Particular attention should be paid to Arbitrary Root Nodes
- Detailing HeroEngine's replication feature functions for the communication of server data to client(s). It is useful to have studied the Replication Tutorial.
- System areas
- Area servers that act as a "services" for game systems
In this tutorial you will:
- be exposed to some of the design issues in player housing
- learn the techniques for implementing storage of player housing data
- learn the techniques for implementing instanced player housing
- learn the techniques for implementing player generated content (i.e. customizable housing decorations)
Player housing is one of those check box "me too" types of features that users have come to expect in games, and which designers consequently feel compelled to include. However, it is worth pointing out that even AAA games do not always include player housing (e.g. World of Warcraft) and you do not have to have it to be successful. Getting the feature of player housing "right" requires a significant development investment and is extremely complicated -- we recommend careful consideration of the costs/benefit to your design before implementation.
The design for player housing typically includes:
- The ability of a user to "travel" to their house, either by way of clicking a button or by walking to a particular street in a neighborhood.
- The ability to modify selected portions of their house
- Decorating it with alternate furnishings or possibly adding expansions to the floorplan.
- It may or may not be possible for other players to enter a user's house (typically by invitation), and this may be possible even when the owner is not online.
Beyond these features, houses often support other game-specific capabilities such as allowing the user to "craft", communicate with people located elsewhere in the game world, administer the mechanics of the house, and so on.
What problems does this tutorial address?
- Introduces the complexities of player housing
- Utilizing Arbitrary Root Nodes to store data
- Efficient usage of instanced areas for player housing
- Modification of areas without performing actual persisted edits
What problems does this tutorial not address?
- This tutorial is not a step by step "How to implement player housing" type of tutorial, rather it is intended to orient the user to the complexities and provide information to make good design choices.
- Player housing involves significant architectural design
Why have player housing?
Player housing can be an opportunity to increase user retention/fun by allowing for the ownership and modification of a portion of the game world to suit a user's individual vision. Housing can also provide an additional element in the layers of "community" that keep users coming back to your game.
Many games have implemented bad versions of player housing, each making a variety of mistakes that ultimately often boil down to not understanding the purpose for player housing in their game. The designers should determine what the purposes of player housing is in your game.
Some questions for designers:
- Why would a user go to their house?
- Are houses meant to be private sanctuaries or gathering places (or something in between)?
- Do houses in your game serve to increase the feeling of player individuality by allowing them to customize their space? If so, how much freedom should the player have to modify the house?
- What benefit(s)/detriments are there for the owner of a house? What consequence does choosing not to have a house have? Is there a game play reason that requires players to have a house?
What level of integration will player housing have within your game areas?
Allowing users to choose a location in the game world as the site for their house seems like an appealing design, but there are tradeoffs and variations. Houses that are totally separate from the game world are generally the simplest to implement, but tend to feel disconnected.
Housing placement strategies:
- users will use the capability to block off areas of the game world with houses
- users will place houses in places designers really would prefer they not
- often results in endless streets of houses clustered around desirable locations
- no artistic control
- Favors the first adopters, as later waves of customers play the game the available locations become less and less desirable
- It is possible to run out of plots
- Needs to support a mechanic to free up plots
- Maintains some artistic control
- Seamless World technology or via normal "travel" with a loading screen.
- Shares characteristics with Game Area Housing Plots, but allows for the possibility to use instancing to scale and produce additional housing sites.
- Neighborhoods often feel a bit disconnected from the game areas, but less so than "Not Placed"
- Scales exceptionally well since there is no limit based on geometry.
- Strong feeling of disconnect from the game areas
- Scales exceptionally well
- Moderate feeling of disconnect from game areas
Can players customize (user generated content) their house?
Without a driving game play purpose for housing, housing will be an uninteresting feature to most players unless they have the capability to customize the house to suit their personal taste allowing claim to the holy grail of MMO features "User Generated Content". Unfortunately, users often have little to no sense or taste or may be directly out to break your game. Most housing systems that allow users to generate their own content place restrictions on the precise types of changes that are allowed to maintain a valid space for game play.
The ability to customize a house comes in many variations:
- Maintains some control over object placement ensuring interior remains a functional game space.
- Maintains some control over object placement ensuring interior remains a functional game space.
- Provides more variation by allowing users to select different "floor plans"
- High coolness factor
- Users can generate spaces that break game play. For example, a user could build a wall of sofas to trap players arriving in the area.
- Necessitates a more complex UI exposing capabilities similar to the Editing Client.
Can other players enter another player's house?
One reason to include player housing is to provide a setting where a community of users can gather (e.g. the user's friends). This does lead to a number of issues in how others are allowed to utilize the house.
Things to consider:
- Can other users enter the house when the owner is offline?
- Can people authorized by the owner make changes, or use features of a house?
- What happens if you log off in another user's home?
- How do other users enter the house? (this is particularly important when houses are located in area instances that are not game areas).
For Instanced Housing, how many users should an area server handle?
An implementation of instanced housing is to spin up an area instance for each user, which works but as a consequence the overhead for a single user's house on the server is extremely high. An empty area server uses approximately 15MB of RAM and is physically a separate process, when you multiply that by 1000 users in their houses you are now talking about a minimum 15GB of RAM, multiple physical servers and 1000 processes any one of which is doing essentially nothing at all the majority of the time. Users in player housing typically impose virtually zero load on the CPU of the area server so the CPUs for those machines serving player housing are mostly wasted.
Consequently, for instance housing it makes sense to look for ways to have an Area Server serve many users simultaneously. In fact, in the average design you could expect a single area server of being capable of hosting thousands to tens of thousands of players simultaneously in their houses.
How are customizations made and communicated to users?
Users familiar with working in HeroBlade and accustomed to the ease at which you can edit the world may assume they can simply drop a player into and edit instance and empower them to make changes. HeroEngine, like most game engines, makes a distinction between dynamic data and static data. Because HeroEngine is so fluid to work with, it is easy to not see this distinction. But rest assured... it is there.
HeroEngine presumes that you are going to have a team of professional developers working on an MMO. They will be building game, world, adding assets, etc. And what it does is provides an extremely dynamic and collaborative space for them to do this. It takes care of most of the details of keeping things in sync (which is a big technical challenge, by the way).
It further assumes that once you have a basic world, game play, and assets... this becomes the foundation of your game and game world. Essentially the "static" part. I put static in quotes because nothing is truly static in HE, but it does have the concept of "this is the basic way things are" and can optimize for that. Then anything that is different is a delta from that baseline.
So How does Editing Work?
When you are in an edit instance of an area, every change you make is adding/remove objects from the world (nodes in the GOM) on the server, or changing their properties (like position, scale, or whatever). That hierarchy of nodes is essentially the scene graph of the area. And the server persists that to the DB as you work.
But of course the client really needs this info too in order to... say... render anything. So every change made on the server is also reflected back down to attached clients so that new objects appear, and changes happen for everyone at (more or less) that same instant.
Now consider a big area that's been in constant development for a few days. It's got hundreds or thousands of nodes, and a quadzillion little tweaks and changes. For those who've been attached to the area the whole time, this is not important as they get every change as it happens. But what about someone who logs into that area after all this editing has taken place?
That person's client needs to be "brought up to date" with that current state of the world. The server essentially batches up all these edit operations and streams them down to the attaching client. This can be a metric ton of stuff, in this example, and take quite awhile.
In reality, HeroEngine does a lot of work to optimize this by consolidating edits and removing redundancy and obsolete edits as much as possible But still... even in couple hours of hard-core editing by a few people... it's a lot!
I mentioned that it has to "catch up"... but catch up from where?
So this is the other aspect of things. An edit instance does one other cute thing... it creates what we call a "DAT file", which is essentially an image of current state of an area (all fo the nodes that make it up, and their properties, etc.). It's really more than one file, but can be thought of as one for the purposes of this discussion. The DAT file is written out to the Repository every so often. Because the file is in the repository it benefits from the whole dynamic update on-demand thing for clients automagically.
So, when you go to a play instance of an area, the first thing a client does is load the DAT file for the area. And due to the magic of the repository, that file (in a typical scenerio) is already available locally in the client's local repository cache. If it isn't, or has been changed, it fetches it from the server just like anything else. But in a typical MMO situation, the DAT files for all of the areas are fairly static and thus always available in the LRC so the client can just immediately spin up an area on the client form that information and... go.
This does not mean that you cannot achieve a dynamic world that is "edited" by player actions. But what it means is that you need to create specific systems for manipulating the world (i.e., player oriented tools) and for persisting this data in a way that makes sense for your application. The nature of this last bit will depend on exactly what you are trying to do.
For example, are players editing a common environment, or is it specific to that player.
In either case, the basic concepts of adding objects, moving them around, and so forth is not done through the normal edit process (as explained above). But rather is done by creating systems for the creation and manipulation of them.
Ok, So Tell Me How Do I Build Customization Tools?
With a source license, and a willingness to fork the code, you can reuse some of the features already present for developer world editing to be useful for player world editing (things like the gizmos and such). A great deal of this is all in the C++ client (even though it is never used in the Player client). The C# generally just expresses the visual UI and behavior that surrounds the C++ (though some of that can be pretty sophisticated, as you'd expect with an engine of this nature).
But the way in which you express world editing (and the persisting of that) from players is not going to be the same code path nor technique used for editing the game world as a developer. You'd essentially build systems around spec oracles to be able to define game objects that can be manipulated by users.
If you further want to allow users to add their own content... like adding new meshes and/or textures... then you will need to do some additional work to tie in systems for this sort of thing. The tools as they exist for this are developer oriented. You would need to build end-user tools for uploading to the repository (not that hard) but the whole issue of limiting what a user can do, approval or other such things would be code you'd need to bring to the party.
Lets look at a typical implementation of player housing where you can choose the furniture/objects in the home and perhaps their location as well (an implementation by the way that requires no source code changes). There are several necessary structures the system must supply:
- Furniture/Object Definition - This can be as simple as a list of valid FQNs for meshes in the repository, through something more complex using the Spec system to define and edit the allowed objects
- DataStore - The customizations made by the player must be persisted in some fashion
- Rendering - It must be possible to communicate the customizations to clients "in" the house so the client can render them
The definition of game system objects in HeroEngine commonly uses the Spec system and it is reasonable to use it in this case as well. This provides a convenient and flexible way of defining the objects as well as minimizing the volume of data that will need to be communicated to a client so it can render appropriately.
While you could store the information about a player's customized house as a part of the account or character node hierarchies, we strongly favor instead the use of the Arbitrary Root Node mechanism to create a root node that is in charge of persisting information about the player's customizations. The advantages of this style of datastore are many among which:
- Data stored as part of an arbitrary root node hierarchy
- is available at any time from HSL, regardless of whether the player is logged in
- does not need to be loaded when the "owner" connects, rather it can be deferred until it is needed.
- can be loaded in a different area than the one the "owner"'s node hierarchies are currently loaded. This allows you to load the data and perform necessary setup prior to the arrival of a player in a "housing area".
So far, we have defined a specification for our furniture and the means by which our user customizations are persisted. Now its time to talk about how to use the data persisted in that datastore to get the client to actually render the customized house instead of an empty one. Prior to a player entering their house, we will load the arbitrary root node hierarchy representing the players house and construct a replication group which includes all of the nodes containing information about the house that need to be communicated to the client. The client of the player entering the house is then specified as a destination of that replication group resulting in that information being transmitted to the client.
Since we choose to use the Spec system to define our furniture, the actual data sent to the client can be extremely lightweight containing the ID of the specification used and a location at which the object should be placed. When the object is introduced to the client, it requests its specification be loaded to retrieve the FQN and any other immutable information and then factories a renderable object (HBNode) via the Prop bucket mechanics placing the object as specified by the replicated data.
Now, its important to note that this is a simplified description of what would happen as it ignores the asynchronous nature of many of these operations.
How do you return the user to the area from which they entered their house?
_playerConnection node stores information about the Area Instance in which the connection was previously loaded. However, there is an additional complication in that the area in which the player was previously loaded may no longer be running requiring your code to decide what the appropriate behavior is for this situation. One choice might be to spin up a new play instance, but this can quickly become inefficient at scale and it divides up your player population as they exit their houses.
If your game has a concept of "Common Areas" such as the multiple city instances used in City of Heroes, then you have probably already implemented a service (a.k.a System area to handle the management of them). In a design like this, you would query the system area to ask which common area should be the destination.
And of course, you always have the option of presenting the user with a UI to choose from a list of appropriate options.
For the purposes of this sample architecture, the following design goals were chosen:
- house is accessible even when the owner is not online
- houses are shared by an all characters of an account
- houses are not placed in Game Areas, but rather exist independently from game area.
- housing areas can be instanced to scale with load
- house customization is limited to choosing between N options for various furniture "slots"
- furniture is represented as a compact data representation using the specs
Our design calls for the owner to be capable of choosing from a selection of objects to place in various furniture "slots". Models (.GR2s) are referenced by their FQN which is ultimately a string path in which to locate the file in the Repository. Ideally we do not want to transmit those strings to a user when entering a home to properly construct the interior. Fortunately, the Spec system provides the ideal framework for defining all kinds of data including the data for our furniture.
From a data structure point of view, we need the immutable spec (
FurnitureSpec) to store information that does not change such as:
We also need another class that is factoried by a Furniture spec for addition to the owner's housing data (see HouseARN below), this second object is a "FurnitureObject" which derived from the
SpecDerivedObject parent class and stores any mutable data and the specKey.
- Mutable data
This division of data between the spec and an object factoried by the spec ensures a minimum of duplicated data and makes the amount of data that must be replicated to the client for the representation of a particular furniture object very small (consisting primarily of the specKey). When the furniture is introduced to the client, we simply request the corresponding furniture spec to retrieve whatever immutable information we need (FQN of the model primarily).
An argument could be made in some designs that the furniture stored in the
HouseARN does not actually need to be an object at all, but rather could be stored as simply the specKey in a map of furnitureSlot to specKey of object occupying that slot. This would be marginally more efficient, but it is a more constrained design that imposes some limits (such as not having a sensible place to store mutable data for a particular piece of furniture). Our general commendation is the extremely minor inefficiency of having an object is more than mitigated by the increased flexibility it allows and when taken into consideration with the recommended storage of housing data in an Arbitrary Root Node (see below) becomes really a non-issue.
A quick example of an instance where a furniture object might have mutable data would be a Cola Machine that has a limited number of a variety of different soft drinks. The user is allowed to order a drink and purchase refills to their Cola Machine when it runs out.
The design goals:
- house is accessible even when the owner is not online
- houses are shared by an all characters of an account
...will influence our choice for the datastore for player housing. There are four potential options for the datastore of housing information that would allow us to satisfy the goals; external datastores, Repository, System areas, and Arbitrary Root Nodes. If you've read the earlier portions of this page you may recall that we recommend the use of Arbitrary Root Nodes.
External Datastores can be used by implementing a HeroScript Extension Plugin that extends HSL to communicate with (for example) an external database that keeps track of player housing information. While an acceptable design, we generally feel utilization of the mechanics supported by the HeroEngine directly is preferred and splitting your data into an external datastore increases the complexity of your architecture overall.
Repository could be used to store files containing the serialized representation of a player house, but there are a couple reasons that we feel generally disqualify using the repository in this way; the repository does not support deletion of files by HSL (at least currently), separates the housing data from the owner's node hierarchy.
System areas are likely a part of the implementation of player housing as a routing mechanic for getting players to their houses and back from them and consequently there is some appeal to storing housing data in one. The disadvantages here are that all of the housing data for all players would be loaded (unless your design also uses arbitrary root nodes) and again the data is separated from the player (though depending on the design, this could also be an advantage).
Arbitrary Root Nodes have a number of characteristics that make them ideally suited for this type of data storage; can be loaded independently of the owning root node hierarchy (i.e. the player), loading can be deferred until needed, can be loaded in a different GOM than the owning root node hierarchy, can "belong" to account or character root nodes.
As I have decided to use Arbitrary Root Nodes owned by the account root node, I've decided to use Glomming to add a
HousePossessor class to account nodes that own homes. This will provide the interface for working with the home Arbitrary Root Node (hereafter referred to as the
HouseARN) and maintain a reference to the ID of the arbitrary root node created to store the account's housing data.
HouseARN will be the root of a hierarchy of nodes representing the various customizations in the owner's home and will maintain a map of furnitureSLOT to furnitureItem located there. Furniture objects will be hard associated to the
HouseARN and will primarily consist of a specKey pointing to a furniture specification which will include data such as name/description/FQN.
When a player is about to enter a house, a replication group is created if needed and the
HouseARN is added as the primary node and all
FurnitureObjects are added as replicated nodes. The player's client is now added as a destination for the replication group causing the objects to be serialized and transmitted to the client. Upon receipt of that serialized data, the client reconstructs the objects and generates a number of events on the primary node of the replication (in our case the
Now we are ready to visualize the furniture in our house...ALMOST
From the general outline above you may recall that it was leaving out a lot of asynchronous steps, well its now time to talk about some of them.
Using replication we have gotten to the point where we have objects both on server and client representing the furniture in our house. You may recall however, we are using the spec system to define our furniture and the data we actually have consists pretty much of a
specKey. On the client, specs are cached in the Local Repository Cache(LRC) and do not exist in memory until you request them which is asynchronous as an
I/O operation. So in order to retrieve the
FQN from the spec, we need to request each of the specs for our furniture objects be loaded.
There are several issues to consider:
- Housing Area Layout
- Character Positioning
- Other Players in the House
Housing Area Layout
First a definition:
- floor plan
- For the purposes of this design, a floor plan is the set of assets that make up the rough architectural and built-in elements of a "house". These elements can be indoor and/or outdoor in nature as needed.
For the sake of efficiency, it probably makes sense to make Housing Areas that serve multiple different floorplans at once. This will allow us to spin up additional housing areas on demand which can serve a variety of users with different housing floor plans simultaneously.
The basic idea for this design is that we lay out the rough architectural elements of a house as a "floor plan" via normal area editing done by one of your game's level designers. The various floor plans are laid out in a non-overlapping fashion near the origin (i.e. 0,0,0).
It is very likely that most game designs could fit all of the floor plans for their game in a single area, the only major consideration here is probably how many assets the client will have to load so there is a probably a trade-off point where it makes sense to have additional housing areas to accommodate new floor plans depending on the art assets involved. It is of course a good idea to have floor plans that use the same assets/textures to be grouped in the same housing area.
When a player travels to a housing area, the game code needs to position them in an appropriate spot within confines of a floor plan.
Arrival paths (or some other mechanic) should be established in each of the floor plans at an appropriate location for the player to enter the home. When using paths, they should be named or otherwise identified in some way so that code can choose the appropriate path for a user entering a particular home. The positioning of a player arriving in an area is done by the $ACCOUNT system node and you will need to override and extend that to understand the specialized behavior your Housing Area uses.
Player customizations to the existing floor plans which serve as the rough architectural elements and built-in assets for the house will be done using a Prop bucket. This process is started (and is highly asynchronous) during the introduction of the "furniture objects" to the client by replication, during which the object requests the load of its specification (if not already loaded) so that it can identify the FQN for the art asset it needs to instantiate. Upon receiving notification that the spec is loaded, the furniture object then needs to request the asynchronous load of the Asset (
AddAssetSpecToPropBucketCB() by a prop bucket created to handle house data which we will name "PlayerHousingPropBucket". Once the asset is loaded in the prop bucket, the object needs to request the instantiation of an HBNode using that asset and then position it properly within the user's floor plan.
The Replication Tutorial serves as an example for much of the code necessary for this implementation.
Normally other player's characters are introduced to a client when that client's character becomes "aware" of them via the Spatial Awareness System. This behavior would be wrong in our Housing Areas because we are utilizing the same physical location (i.e. floor plan 3) for potentially many players where each player is conceptually in his/her own house. Consequently, we need to adjust the behavior introducing players to each other to only introduce players to each other when they are located in the same house.
Awareness thus changes from a volume to a conceptual idea of who is in "Bob's House", causing people in "Bob's House" to add themselves to each others replication groups as client destinations.
Housing Area Management
Assuming you have sufficient users to warrant multiple Housing Area Instances or you have sufficient floor plans that you need multiple Housing Areas (and corresponding instances), some external mechanism needs to determine when to spin up/down additional instances and handle determination of which instance should serve a particular user's house. This service (A Housing System Area) also needs to know in which area instance a particular house is loaded so it can direct other occupants to the same area instance that is currently serving the house.