UNet Multiple Character Solution

(This post is originally from the wiki I wrote for my recent game project Runner or Dinnerhttps://github.com/stormouse/horror-story-alpha/wiki/UNet-Multiple-Character-Solution)

Dead ends

  • Add components according to the character type after a playerPrefab is spawned: Unity does not support adding NetworkBehaviour to a spawned GameObject
  • Bind every possible component to playerPrefab and disable some of them according to the character type: Too hard to organize component relationships on that prefab. Dirty component references.

Solution

Unreal Method

Let playerPrefab be a ‘player controller’,and the characters are Pawns/Characters in Unreal concepts. A player controller can possess a character.

Pros: this method is universal, easily comprehensible, fully-controlled

Cons: you have to implement possess, and native helper components like NetworkTransform and NetworkAnimator cannot help you anymore.

P.S: I haven’t think over how to implement this feature, will research on Unreal’s implementation further.

Spawn-time Replacement (Our Way!)

If your game has a lobby scene, a traditional way to share information between lobbyPlayer and gamePlayer is to customize OnLobbyServerSceneLoadedForPlayer

// class NetworkLobbyPlayer
public override bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer,
                                                       GameObject gamePlayer);

This callback runs when the server notices a client has finished loading game scene. By default it spawns a game player prefab and transfer the control from lobbyPlayer to gamePlayer. But we can ignore the existing gamePlayer and create a new character for this client, which requires ReplacePlayerForConnection

public static bool ReplacePlayerForConnection(Networking.NetworkConnection conn,
                                              GameObject player, 
                                              short playerControllerId);

From server’s view, clients are just connections. We can get a client’s connection from a NetworkIdentity that is spawned for that client, for us that is, the lobbyPlayer.

var clientConn = lobbyPlayer.GetComponent<NetworkIdentity>().connectionToClient;

Then we spawn a new character prefab for the client to ‘possess’.

// instantiate on server side
var newPlayer = Instantiate(characterPrefab);

// tell all clients to spawn (auto-synced)
NetworkServer.Spawn(newPlayer);

// destroy auto-created gamePlayer                      
NetworkServer.Destroy(gamePlayer);

No need to worry about synchronization problem. The Spawn method of NetworkServer will ensure that every client scene will create a same copy when they are ready.

Finally, we return false for OnLobbyServerSceneLoadedForPlayer to prevent transfer of player control, because we have destroyed the game player and given the control to newPlayer by replacement.

How to Build a Heatmap for Tactic AI : Attempt 2

After a little bit more thinking I changed the propagation function from

Heat(neighbor) += Heat(this) * decay / numberOfOpenNeighbors

to

Heat(neighbor) = Max(Heat(neighbor), Heat(this) - numOpenNeighbors * decay)

And now the decay factor is the valve for linear decreasing speed.

Intuitively you can see the ‘heat’ is decreasing linearly relative to ‘distance’ from heat source. But remember, we want a 1/distance^2 like curve for it, therefore we apply a post-processing step to the temperature we got for each grid. That is:

(1) Distance(grid) = Heat(source) - Heat(grid)
(2) Factor(grid) = 1 / (Distance(grid) * Distance(grid) + 1)
(3) Factor(grid) = Factor(grid) - 1 / (Heat(source) * Heat(source) + 1)
(4) Heat(grid) = Heat(source) * Factor(grid)

By applying step (2), we generated a 1/distance^2 like function. (The +1 term on the denominator is to avoid divide by zero at source point.) But you can see that even a grid originally with Heat = 0 will be assigned with a positive factor. Step (3) is responsible for eliminating those residues.

Here I present the test results:

Compared with the result of last post, the result of this new mathematical model yields more reasonable area of influence, as well as easier-to-tune heatmap parameters.

Hope it helps you somehow!

PS: this algorithm takes 1~2 ms to run on my Unity3D scene, with grid map size ~60*40, which is acceptable for my current need, but not as good as expected. Later post I might talk about my experiments on optimizing the algorithm.


			

How to Build a Heatmap for Tactic AI : Attempt 1

I’ve been doing experiments on game AI, tactic team AI to be specific. And currently I’m looking forward to make use of a heatmap.

My idea is to make a grid map and assign heat value (or temperature) on grids so that it represents value of some property that can be reasoned about by an agent. An example would be to represent the potential danger. Think of a group of defenders who wants to defend a room with two entrance from waves of zombies. The area close to the two entrances are most dangerous, therefore have highest temperature in the heatmap. In addition to those two points, places nearby should also be ranked more dangerous than any other places. And a corner of the room could be a safer place to hold (although it’s not always true).

However I’d like the heatmap to have some extra characteristics. Namely, I want the emission of the heat satisfy this constraint:

The narrower the space is, the harder for the heat to go away.

It makes sense because it’s most dangerous if you are trapped in a small cage or a narrow corridor with a ruthless zombie than in a open space, where you can run and turn freely.

My first implementation attempt is to use Breadth-First Search to spread the heat and decrease the energy as it goes farther and farther. And to satisfy the requirement above, I made the decrease factor negatively related to the number of open spaces (neighbor grid without obstacles) next to a grid. For instance, let’s say first there’s a heat source somewhere in the grid field, and 5 out of it’s 8 neighbors are obstacles. And those 3 open neighbors will share all the energy the heat source emits. On the other hand, if all 8 neighbors of some heat source are open, the heat emit will be evenly distributed among them, therefore each neighbor receives less energy.

Intuitively this method tends to keep energy in small room and makes heat in narrow hallways to travel further before the energy becomes insignificant, than in a wide open space. However, the experiment didn’t work out well.

In the experiment I use the number of grids a heat source can influence to indicate whether this model makes sense. In common sense, a heat source with the same initial energy should influence same number of grids regardless of geometry, assuming the obstacles are made of insulation materials. But this naive model cannot guarantee it. Also, exponentially decreasing energy feels wrong. You can see the results from the image below. (Depth of green-ish color indicates the energy. Two heat sources are on the same row, different columns of the field)

exponential_decreasing_by_open_neighbors
Heat in narrow spaces does influence further, but the area of influence is unproportional.

Next step I’ll figure out how to preserve the area of influence on any geometry, and try to invite some 1/distance^2 like factor into the function because it feels more natural.