Saturday, November 23, 2024

Level Scene


 In order to make a tactical rpg, there needs to be a grid. Godot has a built in tile editor. For the purposes of testing(and maybe published, who knows) I'll be using the Grasslands 4 Seasons 16x16 Tileset by ElvGames. I purchased it as part of a humble bundle pack, but you can purchase it here


I've used this tileset to create a random setup that will work for our purposes. Just a random field.




Two of the tileset atlases

Another one
Some objects that are availible
The result

Each Level will also have a collision layer deciding whether or not a tile is walkable

There is nothing super complicated in the code. I did get some of the formulas from  GDQuests tactical movement series found here. The GitHub is found here I personally prefer c#, so some conversion and tweaking was done. The code itself is released under the MIT License


The ready function makes sure to hide the collision layer unless that feature is disabled.

    public override void _Ready()
    {
        base._Ready();
        if (HideCollision)
        {
            Visible = false;
        }
    }

This is for when a tile is represented as a object in a 1d array(or needs to be

    //Converts a 2d coordinate to an array index
    public int ToIndex(Vector2I tileCoordinate)
    {
        return (int)(tileCoordinate.X + GetMapSize().X * tileCoordinate.Y);
    }
    //converts an array index to a 2d coordinate
    public Vector2I IndexToCell(int index)
    {
        int mapWidth = GetMapSize().X;
        int x = index % mapWidth;
        int y = index / mapWidth;
        return new Vector2I(x, y);

    }

Boundary checking code is as follows:

     public bool IsWalkableTile(Vector2I position)
    {
        return CollisionLayer.GetCellSourceId(position) != -1;
    }

    public Vector2I ClampTile(Vector2I tileCoordinate)
    {
        int X = tileCoordinate.X;
        int Y = tileCoordinate.Y;
        Vector2I mapSize = GetMapSize();
        if (X < 0)
            X = 0;
        if (X > mapSize.X)
            X = mapSize.X;
        if (Y < 0)
            Y = 0;
        if (Y > mapSize.Y)
            Y = mapSize.Y;
        return new Vector2I(X, Y);
    }

    public bool IsInBounds(Vector2I tileCoordinate)
    {
        int X = tileCoordinate.X;
        int Y = tileCoordinate.Y;
        Vector2I mapSize = GetMapSize();
        if (X < 0 || Y < 0)
            return false;
        if (X > mapSize.X || Y > mapSize.Y)
            return false;
        return true;
    }

Getting the coordinates of a tile and the tile at a set of coordinates is also important

    public Vector2I PixelToTile(Vector2I pixelCoordinates)
    {
        return (Vector2I)new Vector2(Mathf.Floor(pixelCoordinates.X / GetResolution()), Mathf.Floor(pixelCoordinates.Y / GetResolution()));
    }

    //Returns the pixel coordinates of the tile based off the center of the tile
    public Vector2I TileToPixel(Vector2I TileCoordinates)
    {
        Vector2I halfSize = new Vector2I(GetResolution() / 2, GetResolution() / 2);
        return TileCoordinates * GetResolution() + halfSize;
    }



I also have not tested this code out yet, but beyond typos, the logic should be good.

Oh, I also installed a developer console addon. I think I'm addicted to plug ins.







This is the Developer Console for AssetLib by HamsterByte. You can find it here

It is licensed under MIT. I have not done anything with it yet, but it surely will be good for testing. Right?


Below is the majority of the file as I have it right now. I'll eventually be using a github(why am I not already!? Seriously) but this will do for now.

namespace Data.Enum;

public enum LevelResolution
{
    Undefined = 0,
    R16x16 = 16,
    R32x32 = 32,
    R48x48 = 48
}


namespace Data.Enum;
[GlobalClass]
public partial class Level : Node2D
{
    [Export]
    LevelResolution resolution;
    [Export]
    bool HideCollision = false;
    TileMapLayer CollisionLayer { get { return (TileMapLayer)FindChild("Collision"); } }
    Array<TileMapLayer> Layers
    {
        get
        {
            Array<TileMapLayer> layers = new Array<TileMapLayer>();
            Array<Node> children = GetChildren();
            foreach (Node item in children)
            {
                if (item.GetType() != typeof(TileMapLayer))
                    continue;
                if (item == CollisionLayer)
                    continue;

                layers.Add((TileMapLayer)item);
            }
            return layers;
        }
    }

    #region ConsoleCommands

    [ConsoleCommand]
    public int GetResolution()
    {
        return (int)resolution;
    }
    [ConsoleCommand]
    public Vector2I GetMapSize()
    {
        Vector2I foundSize = Vector2I.Zero;

        Array<TileMapLayer> layers = Layers;
        foreach (TileMapLayer layer in layers)
        {
            Rect2I rect = layer.GetUsedRect();
            if (rect.Size.X > foundSize.X)
                foundSize = new Vector2I(rect.Size.X, foundSize.Y);
            if (rect.Size.Y > foundSize.Y)
                foundSize = new Vector2I(foundSize.X, rect.Size.Y);
        }
        return foundSize;
    }
    [ConsoleCommand]
    public Vector2I GetPixelMapSize()
    {
        Vector2I mapSize = GetMapSize();
        return mapSize * GetResolution();
    }

    [ConsoleCommand]
    public Vector2I MouseToTile()
    {
        Vector2I mousepos = (Vector2I)GetLocalMousePosition();
        return PixelToTile(mousepos);
    }

    public Vector2I MouseToPixel()
    {
        Vector2I mousepos = (Vector2I)GetLocalMousePosition();
        Vector2I mouseTile = PixelToTile(mousepos);
        Vector2I tilePixel = TileToPixel(mouseTile);
        return tilePixel;

    }
    [ConsoleCommand]
    public Vector2I PixelToTile(int pixelX, int pixelY)
    {
        return PixelToTile(new Vector2I(pixelX, pixelY));
    }

    [ConsoleCommand]
    public Vector2I PixelToTile(Vector2I pixelCoordinates)
    {
        return (Vector2I)new Vector2(Mathf.Floor(pixelCoordinates.X / GetResolution()), Mathf.Floor(pixelCoordinates.Y / GetResolution()));
    }

    [ConsoleCommand]
    public Vector2I TileToPixel(int TileX, int TileY)
    {

        return TileToPixel(TileX, TileY);
    }

    [ConsoleCommand]
    public bool IsWalkableTile(int TileX, int TileY)
    {
        return IsWalkableTile(new Vector2I(TileX, TileY));
    }
    [ConsoleCommand]
    public Vector2I ClampTile(int TileX, int TileY)
    {
        return ClampTile(new Vector2I(TileX, TileY));
    }
    [ConsoleCommand]
    public bool IsInBounds(int TileX, int TileY)
    {
        return IsInBounds(new Vector2I(TileX, TileY));
    }
    #endregion
    //Returns the pixel coordinates of the tile based off the center of the tile
    public Vector2I TileToPixel(Vector2I TileCoordinates)
    {
        Vector2I halfSize = new Vector2I(GetResolution() / 2, GetResolution() / 2);
        return TileCoordinates * GetResolution() + halfSize;
    }

    public bool IsWalkableTile(Vector2I position)
    {
        return CollisionLayer.GetCellSourceId(position) != -1;
    }

    public Vector2I ClampTile(Vector2I tileCoordinate)
    {
        int X = tileCoordinate.X;
        int Y = tileCoordinate.Y;
        Vector2I mapSize = GetMapSize();
        if (X < 0)
            X = 0;
        if (X > mapSize.X)
            X = mapSize.X;
        if (Y < 0)
            Y = 0;
        if (Y > mapSize.Y)
            Y = mapSize.Y;
        return new Vector2I(X, Y);
    }

    public bool IsInBounds(Vector2I tileCoordinate)
    {
        int X = tileCoordinate.X;
        int Y = tileCoordinate.Y;
        Vector2I mapSize = GetMapSize();
        if (X < 0 || Y < 0)
            return false;
        if (X > mapSize.X || Y > mapSize.Y)
            return false;
        return true;
    }

    //Converts a 2d coordinate to an array index
    public int ToIndex(Vector2I tileCoordinate)
    {
        return (int)(tileCoordinate.X + GetMapSize().X * tileCoordinate.Y);
    }
    //converts an array index to a 2d coordinate
    public Vector2I IndexToCell(int index)
    {
        int mapWidth = GetMapSize().X;
        int x = index % mapWidth;
        int y = index / mapWidth;
        return new Vector2I(x, y);

    }
    public override void _Ready()
    {
        base._Ready();
        if (HideCollision)
        {
            Visible = false;
        }
    }


}

Some methods are overloaded to allow versions that work with console commands

I'll be leaving out includes and such, but it should be easy to get this code to work in your own project.