Advertisement

Isometric Layering with Heights like Stronghold Crusader

Started by December 26, 2019 12:53 AM
9 comments, last by Alberth 4 years, 10 months ago

Hello Guys,
i started to write my own Game like Stronghold Crusader in the Framework Monogame. I got literally all work, but one think i don't know how to handle. So i add here first my question and than i give you a insight to my Project:

How can i get my Objects that are infront of my tile don't get drawn Over by the Tile? Or someone have a idea how Stronghold handle it to get the Fake 3d look with the Heights involved, is there a technique?

In my Game i use a 2d Isometric Diamond Map. And for the different Heights i use different Layers. So if the height is for example 0 the Layer is 0, for 5 = Layer 1,10 = Layer 2, ...

The Problem with this Method is that the Objects infront are drawn over by the Tiles behind(because of Higher Layer).

I do like to have it similar to Stronghold Crusader, but don't understand how they did it. First i know if i change the Height of a Tile they draw a Image Behind the Tile like that:

And ingame that looks like that:

SideView:

Also i saw this Strange behavior that the first Soldier after the changed Tile in Height go a Layer underneath the Soldier afterwards,So the first one will be drawn over by second one, which is not correct?.

Sideview:

How can i approach the heights on my Isometric Map without ordering all Tiles + Objects by the y Position? I don't want to Order all of it because of 2 main reasons:

  1. Better Performance because of less Gpu calls(Spritesheet usage, Initiating on GPU)
  2. I don't need Ordering of the List by y value at all, that save me also most of the Time

GamePreview:

Here is also some Code, if you need to know something to answer the Question just ask me. :)

MapCode:

 protected override void Update(float elaspedTime, ref Map component)
        {

            #region CameraViewChanged

            ref Camera cameraData = ref _camera.Get<Camera>();



            if (cameraData.ZoomChanged)
            {
                ColumnsToDraw = cameraData.VisibleArea.Width / tileWidth + 5;
                RowsToDraw = cameraData.VisibleArea.Height / (int)tileHeightHalf + 6;
                cameraData.ZoomChanged = false;
            }

            #endregion

            #region Reset Layer
            for (int i = 0; i < _layer.Length; i++)
            {
                _layer[i].Get<Layer>().End = 0;
                _layer[i].Get<Layer>().Start = 0;
                _layer[i].Get<Layer>().ActualCount = 0;
            }
            #endregion

            #region Calculate Tile/Object IDS in View
            var start = ScreenToWorldPosition(new Point(cameraData.VisibleArea.X, cameraData.VisibleArea.Y));
            ref Map mapStruct = ref _map.Get<Map>();
            int tiley;
            int tilex;
            start.X -= 3;
            start.Y += 2;
            int k = 0;
            int actualLayer;
            for (int x = 0; x < RowsToDraw; x++)
            {
                tiley = start.Y;
                tilex = start.X;

                for (int y = 0; y < ColumnsToDraw; y++)
                {
                    var tile = tiley * component.Size.X + tilex;
                    if (tiley < 0) break;
                    if (tilex < 0)
                    {
                        tilex++;
                        tiley--;
                        continue;
                    }
                    if (tile < 0 || tile > _tiles.Length - 1)
                    {
                        tiley--;
                        tilex++;
                        continue;
                    }
                    tiley--;
                    tilex++;
                    actualLayer = _tiles[tile].Layer * 2;
                    if (_tiles[tile].IsVisible)
                    {
                        ref Layer layerTile = ref _layer[actualLayer].Get<Layer>();
                        layerTile.End++;
                    }
                    ref var layer = ref _layer[actualLayer + 1].Get<Layer>();
                    _quadrantGrid.GetEntitysCountOnTile(tile, ref layer);
                    if (_tiles[tile].Object.HasValue)
                    {
                        layer.End++;
                    }
                    k++;
                    _idsToDraw[k] = tile;
                }
                start.Y += (x + 1) % 2;
                start.X += x % 2;
            }
            #endregion

            #region Calculate Layer Start and End for Tile/Object Array

            int numberTiles = 0;
            int numberObjects = 0;
            for (int i = 0; i < _layer.Length / 2 - 1; i++)
            {
                numberTiles += _layer[i * 2].Get<Layer>().End;
                ref var layer = ref _layer[(i + 1) * 2].Get<Layer>();
                layer.Start = numberTiles;
                layer.ActualCount = numberTiles;

                numberObjects += _layer[i * 2 + 1].Get<Layer>().End;
                ref var layerObject = ref _layer[(i + 1) * 2 + 1].Get<Layer>();
                layerObject.Start = numberObjects;
                layerObject.ActualCount = numberObjects;
            }

            #endregion

            #region SetVisible Objects/Tiles to Array that will be drawn
            int layerID;
            for (int i = 0; i < k; i++)
            {
                layerID = _tiles[_idsToDraw[i]].Layer * 2;

                if (_tiles[_idsToDraw[i]].IsVisible)
                {
                    ref var layerTiles = ref _layer[layerID].Get<Layer>();
                    ref MapTile tileData = ref mapStruct.TilesinView[layerTiles.ActualCount].Get<MapTile>();
                    tileData = _tiles[_idsToDraw[i]];
                    layerTiles.ActualCount++;

                }
                ref var layerObjects = ref _layer[layerID + 1].Get<Layer>();
                _quadrantGrid.SetEntitysVisibleOnKey(_idsToDraw[i], ref layerObjects, ref mapStruct);

                if (_tiles[_idsToDraw[i]].Object.HasValue)
                {
                    mapStruct.EntitiesInView[layerObjects.ActualCount] = _objects[_tiles[_idsToDraw[i]].Object.Value];
                    layerObjects.ActualCount++;
                }
            }
            #endregion
}

DrawCode:

 protected override void Update(float elaspedTime, in Entity entity)
        {
            ref Layer layer = ref entity.Get<Layer>();
            ref Map map = ref _map.Get<Map>();
            if (layer.Object)
            {

                for (int i = layer.Start; i < layer.Start + layer.End; i++)
                {
                    ref var gameObjectEntity = ref map.EntitiesInView[i];
                    ref GameObject gameObject = ref gameObjectEntity.Get<GameObject>();
                    ref Transform transform = ref gameObjectEntity.Get<Transform>();
                    ref TextureShared mapTextureShared = ref gameObjectEntity.Get<TextureShared>();
                    _batch.Draw(texture: mapTextureShared.TextureSheet, position: transform.Position + transform.Offset, sourceRectangle: gameObject.Source, color: Color.White);

                }
                return;
            }

            for (int i = layer.Start; i < layer.Start + layer.End; i++)
            {
                ref var tile = ref map.TilesinView[i];
                ref MapTile mapTile = ref tile.Get<MapTile>();
                ref TextureShared mapTextureShared = ref tile.Get<TextureShared>();
                ref TileProperty tileProperty = ref _tilePropertys[mapTile.ID].Get<TileProperty>();
                _batch.Draw(texture: mapTextureShared.TextureSheet, position: mapTile.Position + tileProperty.Offset + mapTile.Offset, sourceRectangle: tileProperty.Source, color: Color.White);


            }

            }


Cant edit the Post so here is in game Image from Stronghold:

Advertisement

You're not saying what graphics API you're using, but I am guessing you're using polygons with textures on them while I think that game simply used sprites and drew the tiles in the reverse order in the Y direction. Maybe try to draw them from the highest Y value to the lowest? I don't think you need to sort them at all just reverse the order in which the tiles are drawn vertically so that they cover the tiles that are supposed to be behind.

@Hermetix I use the Monogame framework with Opengl, so yes im using polygons with textures on them. Hm if i do your approach with y value i lose many fps because of less buffering texure sheets with the polygons on the graphics card(Tiles and Objects need different texturesheets/textureatlas). For know i have many layers the a layer draws first the bottom Tiles and after the Objects, The Layer is diffrent from the offset values. But i think thats not really correct. Id' like to have performant draw calls, instead of change the input every second or third draw call to another Texture. Maybe i can put the Objects on the right Layer to get overdrawn or underneath a Tile? But therefore i dont have a good idea how to know on which Layer i should place the Object.

Perhaps first have something that works rather than something fast that fails? Once you have that, you can measure if it needs performance improvements. If it does and nothing more urgent needs work first, the working setup gives you information what is required for not breaking the display, so you can take that into account to improve the performance.

As a few ideas to consider, why are tiles and objects not on the same texture? There is no law against that afaik. There is also no law against copying eg tile images on several textures. As another option, perhaps you can first draw all tiles at a single y, then draw all objects at a single y?

The thing is i want to get it work with around 200'000 Soldiers, the simple way with sorting by y i know how to implement and did that before.One thing i did for know is i lookup if a tile has a offset or every Time only behind a Object, i add this Tiles than to the first Layer which get drawn first(Only Tiles). Afterwards i draw a Tiles and the Objects belonged to the Tile.
I cant combine tiles and Objects on the same Textures because the animation of the Objects will take the space and if i add tiles there the images get to big for the GPU.

Advertisement

Perhaps use the z-depth thing in the GPU then? I am not sure what you aim for. Do you want to simulate how stronghold does it, or do you want a working display? Letting the GPU deal with depth should solve your problems easily.

As stronghold is from 2002, they didn't have GPU limitations. So while your observed limitations are no doubt real, if you want a truthful simulation, you have added a new limitation that they didn't have.

Have you tried to make it work? I would suggest to do that first. Code such that it works even you have to switch textures more often than you like or do other slow stuff. Throw 200k soldiers on it, and see how it works. Measure the time. Then you know where you are, instead of (seemingly) just guessing it is not fast enough.

I made it work and have a workarround for the Tiles, because Graphic Cards by now can hold bigger Textures i can add my Tiles to the Object Tilesets and get so no swapping and the Fps i need, With the olf Method i got with 10k Units only 70 Fps.

Looks good!

This topic is closed to new replies.

Advertisement