Creating a Custom Pixel Art Font with Aseprite and FontForge

I am writing this blog post to document the method I use to transition pixel art from a raster format into a functional vector font. This workflow bridges Aseprite and FontForge to make the font creation process fast and efficient.

First, the drawing phase takes place in Aseprite. You need to determine a specific line standard, and all necessary characters must be drawn based on this standard using a grid-based system. For this project, each character is designed at a 16px size.

Making Custom Pixel Art Font

To extract these characters individually, export the entire canvas as a single PNG file. Next, create a new file in Aseprite and implement this PNG using the 'Import Sprite Sheet' option.

Once the sheet is sliced, export the characters individually using the 'Export As' menu:

  • Format: char_.svg 
  • Pixel Scale: 64px

Note: The pixel scale is set to 64px because the original characters are 16px. Scaling is necessary before moving to the vector stage.

Now, open FontForge to compile the font. Go to the 'Font Info' section:

  • Set the Em Size value to 1024. 
  • Set the encoding to ISO 10646-1 (Unicode, BMP).

Set Em Size on FontForge

Next, select the relevant character slots in the software one by one and import your corresponding SVG files. After all characters are imported, select all of them and navigate to:

  • Element -> Validation -> Validate...

The most likely errors you will encounter here will be related to overlapping paths or non-integer values. To resolve this, keep all characters selected and apply these two operations sequentially:

  • Element -> Overlap -> Remove Overlap 
  • Element -> Round -> To Int

If there are some characters that remain unresolved, double-click to open them specifically, apply the same operations directly inside, and save. There will inevitably be some unresolvable validation errors purely due to the blocky design of pixel art; you can safely ignore those.

Align Character on FontForge


After the geometry is fixed, the line space of the characters needs to be configured. This requires opening each character individually and applying the necessary narrowing adjustments to the left and right bearings. While making these edits, you can test the font's appearance and spacing dynamically by opening:

  • Metrics -> New Metrics Window

Once all spacing adjustments are completed, navigate to File -> Generate Fonts... Select TrueType from the options and generate your font as a .ttf file. Your custom pixel art font is now ready to be used.

Devamını Oku »

Wireless Debugging on Android Devices

I have published my application named 'Coğrafya İnteraktif Harita', which is one of the mobile applications I developed over the last two weeks. 

I am writing this blog post to document the fast debugging method I use during development. I mean, this is not exactly a secret, but it might serve as useful information for those who are unaware of it or haven't realized it yet.

Under normal circumstances, debugging through the Android simulator provided by Android Studio or standalone emulators can be painful depending on your environment. In contrast, debugging directly on a real Android phone is highly fast and comfortable. Of course, this situation might also vary depending on the size of the application. Anyway, I remember that in the past, I had to connect my phone to the computer to debug; because the connection was constantly dropping—whether due to the USB cable itself or the computer's erratic behavior—the debugging process turned into a complete ordeal for me and really put me off. After a long time, when I returned to mobile development for a small application, I joyfully experienced that there was actually no need for this cable situation at all. The solution is 'Wireless Debugging'. Back then, my old Android device probably did not feature such a function, so I couldn't benefit from this blessing. Anyway, without further ado, let's see how Wireless Debugging is done using ADB.

Note: Both your computer and your Android device must be connected to the same Wi-Fi network

First, let's open the command prompt on your Windows computer:

C:\Users\gurbu>adb devices
* daemon not running; starting now at tcp:5037
* daemon started successfully
List of devices attached
First, I performed a routine check to see if any device was connected, and it appears that none are. Now, let's open the Android phone; you will likely access the required settings section on your phone in a similar manner.

Wireless Debugging on Android Devices

Open the Wireless Debugging.
Wireless Debugging on Android Devices
Make sure 'USB debugging' and 'Wireless debugging' are selected. Additionally, press and hold 'Wireless debugging' to open the following page:
Wireless Debugging on Android Devices

The sections that concern us here are the 'IP address & Port' section and the 'Pair device with pairing code' section. First, click on 'Pair device with pairing code':

Wireless Debugging on Android Devices
Now, I want to pair the devices according to the information provided above, as shown in the command prompt below:

C:\Users\gurbu>adb pair 192.168.1.102:44749
Enter pairing code: 611794
Successfully paired to 192.168.1.102:44749 [guid=adb-XXXXXXXXXXX-XXX]
I have completed the pairing process; once paired, your computer should already appear listed under the 'paired devices' list.

Wireless Debugging on Android Devices

Now we can connect to this Android device from our computer wirelessly. This time, we enter the IP and port information written under the 'IP address & Port' section. Note: This is not the IP address and port that we used during pairing; take a close look at my screenshots.
C:\Users\gurbu>adb connect 192.168.1.102:32979
connected to 192.168.1.102:32979
It appears that we have successfully connected; let's check the devices list once again:
C:\Users\gurbu>adb devices
List of devices attached
192.168.1.102:32979     device
adb-xxxxxxxxxxxxx._adb-tls-connect._tcp       device
Our device has now been successfully connected to the computer wirelessly for debugging.

We can also see that the device is visible and ready to be used on Visual Studio.
Wireless Debugging on Android Devices Visual Studio
Devamını Oku »

Starting The Basic RTS Game Project Structure

First, let's start by slowly building the project structure. I mentioned that I'll be using Monogame. I previously set up a Monogame template in VS 2022, so I can create a project directly. You can take look from this link(Setting up Visual Studio on Windows | MonoGame) I created LdgRpg project as monogame desktop cross-platform project.

I executed and I can see cornflowerblue color filled display. First, I want to add my character. Therefore, I will create an entities folder and add a class called Character. In this class, I will define general properties of this character, such as its position, speed, and size.

Of course, I'll have a general character pool. Therefore, I'll create a manager class to manage this character collection collectively. I also want to assign GUIDs to my characters; this might be useful later.

Normally, Monogame offers a class like Game1, but I want to build my own separate structure. Of course, I will use this structure within the Game1 class. I'm creating my own game manager. This manager will be the hub for managing all other managers.
protected override void LoadContent()
{
    _spriteBatch = new SpriteBatch(GraphicsDevice);
    gameManager = new GameManager();
    // TODO: use this.Content to load your game content here
}
Now we need to add some ridiculous character texture. I'll create a texture manager structure for this. Because managing textures one by one is too much trouble. And let's draw it:
And there it is my little ugly man right there. That's enough for now.
Devamını Oku »

Let's develop an RPG game

After a long time, I'm decided to back this useless blog and revived again. Well, too much changes happened in the tech. You know AI-era(or hype?) thing, idk. Honestly, after these changes, I feel like content creation has not really mattered anymore. So, at least the kind of content I produce doesn't really matter anymore. People can now create games with simple concepts using just a single prompt. Or they can say "hey teach me that how can i make this game step by step?" to an AI. That's why that's blog going to be more personal about my amateur game development journey. My posts won't contain as much source code as before. This will be a blog where I mostly write about my own process and my observations during this process. Briefly, it won't be like a tutorial. 

 Let's get to the main point. I've decided to make a simple rgp game. Honestly, I don't have much of an idea how i am going to proceed right now, but it will be become clear as I progress, I guess. But I think I'm going to use Monogame framework. I don't want to mess around with a game engine. Of course, I'll use AI to help with this process, but I don't plan on doing anything too AI-generated in the game itself. I think I'll only be able to generate a few basic codes. On the game art side, I'm planning to do pixel art drawing. But that doesn't mean I'll only use pixel art assets. I'll also create and use designs in Blender. I'll use Aseprite for pixel art. I think it would be a good idea to use the power of a blender, as well. After developing the game, I want to release it on a platform, but I am not entirely sure whether it will be mobile or desktop. Right now, I'm leaning towards mobile. 

This post will also serve as the homepage for this development process, so I plan to list the other posts here. The posts are the following:

Devamını Oku »

Service Provider in .NET MAUI

namespace Temptor.Helpers
{
    public static class ServiceProvider
    {
        public static TService GetService<TService>()
        {
            return Current.GetService<TService>();
        }

        public static IServiceProvider Current
        {
            get 
            {
                #if WINDOWS10_0_17763_0_OR_GREATER
                    return MauiWinUIApplication.Current.Services;
                #else
                    return null;
                #endif
            }
        }
    }
}
No need to use constructor injection for any service if necessary:
namespace Temptor.Models
{
    public class Property
    {
        public PropertyService ropertyService;

        ...

        public Property() 
        {
            this.PropertyService = Helpers.ServiceProvider.GetService<PropertyService>();

            ...
        }
    }
}
Devamını Oku »

Shader Codes

 Circle Drawing:

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float2 center = float2(0.5, 0.5);

    float distance = length(coords - center);

    float radius = 0.4;

    float4 color = (distance < radius) ? float4(0, 0, 0, 1) : float4(1, 1, 1, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

 sin and cos

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{

    float amplitude = 0.9;
    float frequency =55.0;
    
    // vertical
    float sinValue = amplitude * sin(coords.x * frequency);

    // horizontal
    float cosValue = amplitude * cos(coords.y * frequency);

    float4 color = float4(sinValue, 0, cosValue, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

 Basic Animation:

Pass the time parameter from update method with these code lines that float time = (float)gameTime.TotalGameTime.TotalMilliseconds;, and then effect.Parameters["time"].SetValue(time);.

float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float amplitude = 0.9;
    float frequency =55.0;
    
    // vertical
    float sinValue = amplitude * sin((coords.x + time * 0.5) * frequency);

    float4 color = float4(sinValue, 0, 0, 1);

    return color;
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    return float4(coords.x, 0.0, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    return float4(0.0, coords.y, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    return float4(uv.x, uv.y, 0.0, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    return float4(0.0, distance, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance -= 0.7;

    distance = abs(distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance -= 0.7;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10)/10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);


    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10 + (time * 0.005))/10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    // make (0,0) origin
    float2 uv = (coords - float2(0.5, 0.5)) * float2(2,-2);
    
    uv *= 4;
    uv = frac(uv);
    uv -= 0.5;

    // length function calculate the magnitude of the vector. length(vec(0,0)) = 0, length(vec(0,1)) = 1    
    float distance = length(uv);

    distance = cos(distance * 10 + (time * 0.005)) / 10;

    distance = abs(distance);

    //distance = step(0.1, distance)
    distance = smoothstep(0.0, 0.2, distance);

    return float4(distance, 0, distance, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}
float time;

static const float PI = 3.14159;

float2x2 rotate3d(float angle) {
    return float2x2(cos(angle), -sin(angle), sin(angle), cos(angle));
}

float4 PixelShaderFunction(float2 coords: TEXCOORD0) : COLOR0
{
    float2 p = coords * 2 - float2(1,1);

    float temp = (time * 0.005) * PI;

    p = mul(rotate3d(temp), p);

    float t = 0.075 / abs(0.7 - length(p));

    float vl = 0.2 * (sin(time) + 3.0);
    float3 temp2 = float3(t, t, t) * float3(vl, p.y * 0.8, 3.0);

    return float4(temp2, 1.0);
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

Devamını Oku »

A UI library for MonoGame - Devlog #1

I've been working on my own ui library for a while. Progress is a bit slow because I don't have much time. I can say it's more of a project I pursue for hobby purposes. 

There are some reasons why I am doing such a project:

  • I can work on projects in Monogame, so I might need some UI support.
  • I couldn't find a UI library in Monogame that truly meets my requirements. (There are some issues for ImGUI in Monogame)
And other things that I haven't thought of yet.

So far in the project, I'm progressing with two elements: Component and Panel. The components will be placed inside the panels. Yes, simply like that. I don't want it to be more complex for now. I have created elements such as panel, button, and scrollbar. It's functioning adequately in its basic form. In fact, it seems possible to achieve a responsive outcome.

An example image:


The project will not be open source for now. Additionally, my GitHub page has become quite cluttered.

In the next stage, I plan to add basic elements such as horizontal scrollbar, label, text box, grid etc.. I also need to create a suitable structure for handling and capturing events.

Let's create a todo list:
  • Label
  • TextBox
  • Handling Events
  • Horizontal Scrollbar
  • z-order (for collision ex.)
  • Grid (??)
  • ImageButton component (~)

Devamını Oku »