Client/Server rendering

Started by
2 comments, last by RedBeard101 8 years, 3 months ago

Hi All,

Just a couple questions about how I should approach rendering my client terminals for my game engine I am making.

Here's my current scope:

2D game engine. and I am designing an authoritative server, with a hash-grid where all game objects find themselves in buckets. Clients report their input every frame, and go through a lock-step to get their position from the server, along with their actions.

The clients have access to all of the map data for rendering, but do not have access to of the GameObjects. How should the server communicate which objects to draw?

I want the server to create a list of objects to draw, based on the clients position in the hashgrid, and pass it to the client. Can I pass a list of pointers? or do I have do pass a list of unique object IDs? It seems like passing all the objects over the connection would be pretty heavy on bandwidth.

I realize I may be leaving out some important details, and I'd be happy to try to give more information, but if anyone has any input or reference to documentation, I would appreciate it.

Thanks!

Advertisement

Since all the client handles is user input, rendering and sound output I'd have the server send each client something like this:


struct DrawCall
{
    Vector2 position,
    int Layer,
    uint SpriteID
}

The renderer in the client would then pull the sprite texture from it's assets by ID and draw it in the given location.

Please note that I never tried myself at a 2D engine so this suggestion is probably not really the optimal solution

This should probably be in the Multiplayer and Networking section. There are a few ways to handle what you're describing.

Assuming you have a hash grid with uniform cell size then you just need to use the player's current camera on the server to collect all the entities in range. That just requires iterating through the cells and performing a look-up in the hash grid. Then for all the cells that exist in the hash grid union the entities into a list. This list represents all the entities the client can see. I have a tutorial on spatial partitioning and the queries here. That said the basic query function would look like this in C#:


public HashSet<IGridEntity> Query(AxisAlignedBoundaryBox aabb)

{
    var entities = new HashSet<IGridEntity>();
 
    var startX = (int)(aabb.MinX / CellSize);
    var endX = (int)(aabb.MaxX / CellSize);
    var startY = (int)(aabb.MinY / CellSize);
    var endY = (int)(aabb.MaxY / CellSize);
 
    for (var x = startX; x <= endX; ++x)
    {
        for (var y = startY; y <= endY; ++y)
        {
            Cell cell;
            if (grid.TryGetValue(y * dimensions + x, out cell))
            {
                entities.UnionWith(cell.Entities);
            }
        }
    }
    return entities;

}

Now once you have all the entities you have to tell the player about them. In the past I've advocated using a full and delta state pattern. That is for every client you store an array of all the entities you've told the client about and then on every message after that you simply tell them what's changed. If you do that then you'll have an array that's empty when the client connects. We can refer to this array as the "known entities" array.

Depending on how you're building packets you'd want to add all the information about the new entities that aren't in the array (so that the client can recreate it). This would include things like the entity type. You're right that you'll want to give all the entities a unique id. You'll use this id to refer to the client entity later. If the entity is already known by the client (it's in the known entities array) then you just need to send a delta packet with things that have changed, like position. You can use bools to track changed items in the entity then after sending data to all the clients just set the bool to false.

That's all there is to it. Every tick of the server query for the entities in the camera then check the known entities and either add new entity data to the packet or write a delta entity data. The client has its own array of known entities so if it receives an entity id it's never seen it knows it needs to deserialize the full state and if it already knows about the entity then it'll deserialize a delta for the entity.

The two tutorials I linked go into things a lot more including how to forget entities. There are a lot of optimizations, but this is a fairly simple technique especially for a lock-step game. (It forms the basis though for a lot of technique for drastically reducing bandwidth though).

Thank you guys very much for your quick replies, I will definitely check out the links you posted. Storing an array of previously known entities seems to be a very workable solution, as I was having trouble visualizing what state information would be going over the network. This should start me in the right direction, thanks!

This topic is closed to new replies.

Advertisement