Showing posts with label Update. Show all posts
Showing posts with label Update. Show all posts

Wednesday, November 27, 2024

Combat Scene Update

 Lots of progress has been made on multiple fronts.

The combat Scene now properly loads a level(different from a map) and tracks mouse movement. It also Is able to load dialogue once specified conditions are met. I've also got the scaffolding for Adding camera controls and overlays to highlight parts of the map, though it isn't quite presentable.



Here we see a sample dialogue from dialogue manager playing once the map is loaded

Then general workflow has the battle load up everything. Data specific to the scenario is pulled from a scenario file and stored in a map node, which has wrapped code to access it. The debug console does not like accessing instantiated nodes, so this allows me to access the map indirectly. I'll likely need to do something similar for when I add units.

The scenario file is a node, so I'll be relying on virtual functions to add in dialogues, victory conditions, and anything else a scripted battle may need.


I've also developed a couple of tools to assist in making sprites. Character sprites(I'm calling them actors as they will also control movement and actions) will be one of two types: component actors and sprite actors.

Sprite actors will be straight forward sprites. They are a single loaded texture that can play animations. ComponentActors are made of multiple sprites in order to facilitate customization. Change armor? The sprite will reflect that.

I am using the Time elements Character Core Set and the expansion, both by FinalBossBlues. The pack does come with a character generator that can generate spritesheets for you, but that would mean I would have to prebake every sprite.

What I've done instead is create the Sprite Slicer too. 


It is easy to use. I'm using some of the frame lists that FinalBossBlues uses for his animations. So the walk cycle will look the same as the pre generated one. If I wanted to change a frame, I just need to change the array entry.

Once The animations are done, I then run the script, which will generate each animation in all 4 direction and store them in an animation library. I can then load that animation library into the Actor class and it will have access to all the animations. The actor also already has a premade animation tree to handle transitions.


    public void CreateAnimation(Array<int> frames, int direction, string name, bool loop)
    {       
        Animation animation = new Animation();
        int ind = animation.AddTrack(Animation.TrackType.Value);
        animation.TrackSetPath(ind, ".:frame_coords");
        animation.ValueTrackSetUpdateMode(ind, Animation.UpdateMode.Discrete);
        if (loop)
            animation.LoopMode = Animation.LoopModeEnum.Linear;
        animation.Length = (frames.Count) * speed;
        for (int i = 0; i < frames.Count; i++)
        {


            Vector2I frame = new Vector2I(frames[i], direction);
            GD.Print("frame " + frame);
            int index = animation.TrackInsertKey(ind, speed * i, new Vector2(frames[i], direction));

        }

        animationLibrary.AddAnimation($"{name}_{IntToDir(direction)}", animation);

    }

This is the core logic. It creates the empty animation and creates a track for the sprite sheet.. It then adds each frame, spaced out by a speed variable defined earlier in the code. This means all animations will run at the same rate.

Finally, it adds the animation to the library created previously. 

The spritesheets provided by FinalBossBlues each have 4 rows of animations, each one representing the sprite in one direction, so it is easy to just run the code 4 times, once for each row.

I purchased these assets, but I'm not sure if I am allowed to post one as an example.

I've also mostly finished a tool to use these spritesheets to generate sprites.



This one runs in engine and allows me to see a sprite with different pieces from different directions. It does still need a way to actually export the animation and preview animations, but the core of it is working. 


This code feels a bit messy, but here is the breakdown

    [Export]
    string AssetsFolder = "Folder containing spritesheets goesHere";
    [Export]
    Array<string> Components = ["Each subfolder goes here];

This shows the script where to find the spritesheets.

System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<int, List<Component>>> Folders = [];

This is a bit of a monster of a datastructure definition.

The Inner Dictionary correlates Each Sprite type with the different colors it has available. For example, The first type of of hair contains hair1.png, but it also has color variations hair1_c1.png, hair1_c2.png, etc.

The outer dictionary then stores all of the sprite types with its category(hair, weapon, etc.).


After some boiler plate, we then make a spinner setup(I called it a SpriteMakerComponentPicker) for each category. It updates the texture when either picker is changed.

Some components have a 0 item index (bottom0.png), so instead of getting the spritesheet by the value of the spinner directly, I have to use spinner's value to retrieve the key that we need.

componentPicker.PartUpdated += (value) =>
            {
                if (value == -1)
                {
                    CustomizedActor.SetTexture(comp, null);
                    return;
                }
                int color = componentPicker.GetColorValue();
                int key = ComponentListByIDDictionary.Keys.ToArray()[value];
                List<Component> components = ComponentListByIDDictionary[key];
                Component component = components[componentPicker.GetColorValue()];
                Texture2D tex = GetTexture(comp, componentPicker.GetIDValue(), component);
                CustomizedActor.SetTexture(comp, tex);
            };
            componentPicker.ColorUpdated += (value) =>
            {
                if (value == -1)
                {
                    CustomizedActor.SetTexture(comp, null);
                    return;
                }
                int color = componentPicker.GetColorValue();
                List<Component> components = ComponentListByIDDictionary[value];
                int key = ComponentListByIDDictionary.Keys.ToArray()[value];
                Component component = components[componentPicker.GetColorValue()];
                Texture2D tex = GetTexture(comp, componentPicker.GetIDValue(), component);
                CustomizedActor.SetTexture(comp, tex);
            };

At this point, I can likely combine the events into one.


public struct Component
    {
        public string category;
        public int id;
        public int color;
    }

Component is a basic data structure used to help find the spritesheet's filename. everything follows the format

    categoryX.png or categoryX_cY.png, 

except weapons, which swap category with a weapon type,

I've left a lot of code out, but if you are interested in seeing a more complete version let me know.



Currently I have the following short term goals:

  1. Make the SpriteMakerTool Export to file and get loaded by the Actor class
  2. Give functionality to the actor class. Perhaps I'll go back to calling it a sprite and abstract movement code.




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.


Mechanics Planning 1

    When I develop my game, I'll be using the Godot Engine. I know this will be a tactical rpg, with grid based movement. I want characters to have jobs and skill trees. That by itself already gives us a lot of things to make.

    Here is what I am thinking currently. I'll likely make a page that has an expanded version of this labeled game design document. Some of this is obvious, but will be useful for turning everything into code.

Combat:

    This is the core of the game. The combat system must be able to figure out who's turn it is, allow the player to move and use at least one action of their turn, and  make the ai move and take at least one action on their turn. It must also figure out if the combat is a victory, defeat, or at a point where a character must speak.

    When moving, there will need to be grid based movement, likely using A* path finding. However, that only figures out movement from point A to point B. There also needs to be systems for targeting area of effect style spells.

    Spells will need to have targeting systems, and the ai will need to be able to prioritize and decide which spell to use.

    Spells and attacks(which are also spells) need have their effects scaled on the caster and targets stats.

    Combat also needs you to deploy characters.

    Having a list of squares per combat sequence that the player can put a character on. This will not be based on the map, as the map may end up being reused. Instead, I'll have a Battle file/resource/collection that defines what goes on and scripts each individual battle. 

Party management scene:

    This will be where you look at your entire party, it will allow you to select individual characters and manage them. It should also allow you to view your inventory and general progress in the game.

A character management scene:

    This will be where a character can be equipped and customized. They will be able to select what job they are using,  choose what equipment they are using, unlock new jobs, and progress their skill trees in their active jobs. 

    I still am not sure if I will allow respeccing, have respeccing being limited or cost something, or restrict it entirely. Additionally, while the job a character has will effect the characters stats, I may make it so that the stats are adjusted on a character changes jobs. Otherwise, they will have a base stat growth, and a bonus based on their class(es) on level up. More to come

Jobs

    Jobs will be the core of how a character is customized. Each character will have a selection of jobs available as soon  as you control them.  Each job will have a skill tree that progresses as you continue to use the job. Some skills will Unlock Spells, others will unlock passive bonuses. Others will increase a characters Feat Points(see below) New jobs may be unlocked by taking skills.

    Some jobs may be temporarily unlocked by equipment.

    Each character will always have a primary job.  They may also unlock a secondary job. The primarily job will determine the characters stats and what equipment a character can use. Both the primary and secondary job will determine what spells a character can cast. 

Feats

    Feats are passive bonuses. Once a feat skill is unlocked it becomes available to equip. Each Feat costs Feat Points to equip. The cost is individual to each feat. As long as it is unlocked, a feat can always be equipped, regardless of class.

Equipment

    Each character will have equipment slots. Each slot is assigned to one type of equipment. By default, a character will have 1 Weapon slot ,1 Offhand slot,  1 Armor slot, and 3 accessory slot.
Some characters may have more or less slots. Some feats may also  adjust equipment slots
Equipment may take up more than 1 slot. For example, a two handed sword may take up a weapon slot and an offhand slot.

    In addition to the slots that a piece of equipment takes up, each piece of equipment will have a category, This is used both for sorting items, and to determine which jobs/characters with certain feasts can use the equipment. A mage would normally not be able to use a katana.

    Some feats may add extra effects with some weapons

Stats

    Every character will have the following stats:

    Health: if it reaches 0, a character is dead
    Mana: Used as a casting resource
    Barrier: Acts as a 
    Strength: Used to deal physical Damage
    Magic: Used to deal magical damage
    Proficiency: Increases the damage done with mechanical attacks. reduces damage done to a barrier
    Dexterity: Used to deal damage with ranged attacks
    Defense: Used to reduce physical damage
    Will: Used to reduce magical damage
    Reflex: Effects chance to dodge attacks
    Luck: increases chance for critical hits
    Speed: Effects turn order
    Move: Effects how far a character can move

Story and Progression. 

    I'm still thinking about the story. I don't have a budget, so I will likely not voice acting or external cutscenes. This means most of it will be told through in game cutscenes or in the middle of battle., Either way I 'll need a dialogue system.


    Specifically I'll be using the Dialogue Manager plug in by NathanHoad. This is released under the the MIT license, which makes it great for both commercial and non commercial use. I'll be creating a test scene later on to make sure it works like I want it to.




Saturday, November 16, 2024

Laying down the foundations

 Welcome! Right now everything is a bit barebones, but so are most new projects,

    So why are we here? I get distracted easily, and having plans, goals and accomplishments in writing is a new way for me to try to ground myself. Acting as marketing for anything I decide to sell doesn't hurt either.

I'll get straight to the first goal: This blog will start with two projects: This blog itself.  Not just posting in it, but improving it over time. In the long run, I'd like to create something in Laravel, or use it as an excuse to teach  myself .Net using my own designs and adding features I've coded myself.  Moving it off of bloggrer.com once I've got some Domain name shenanigans taken care of. I may end up moving to Wordpress in the interim between posting here and on my own site. Of course, I'll be keeping an eye on the legal situation there before starting that.

My second goal is the real project, I want to make a game. Specifically a tactical RPG. I love the idea of creating my own combat system on a grid, 3D  or 2D. Creating a flexible spell system that allows me to easily create new character classes. It has been a dream project of mine for many years, and each time I find myself getting distracted and losing track of what I am working on. I think having something in writing to show what I need to do will be perfect for getting past that hurdle.

I may attempt to create some basic art myself, but I have never been an artist despite many attempts. I will be relying on premade assets and potentially working with a team for visuals and sound(and maybe story) Prepare for  a lot of MS paint and minimal photo-editing.

Other than being a dev log, I'll be posting anything coding or computer science related I may learn. I want this to be educational for readers. I've always loved learning about programming design patterns, algorithms, and  anything that can add on to my toolset.