/r/unity_tutorials

Photograph via snooOG

Tutorials for the Unity game engine! Share a tutorial that’s helped you, or that you’ve created and think will help others!

/r/unity_tutorials

51,502 Subscribers

0

Nuevo Canal de Unity

Hola, estoy creando un nuevo canal de YouTube sobre Unity! donde pienso subir videos tutoriales de como crear juegos si quieren pueden suscribirse gracias!

https://www.youtube.com/channel/UCdzxBQfPH1gdDqZQUe0th7A

0 Comments
2024/05/02
15:41 UTC

5

Grid based level editor

"This tool is designed to be used in grid-based 2D puzzle games. It has a user-friendly interface where users can view, edit, and reset the current state of the level in the editor."

source code: https://github.com/berkencami/grid-based-level-editor

1 Comment
2024/05/01
19:36 UTC

3

UI Elements Drag drop and swap

0 Comments
2024/05/01
15:54 UTC

5

Learn the basics of unity.

unity basics | Learn unity beginner to advanced https://youtu.be/6NF8SLq6bHE

1 Comment
2024/05/01
01:39 UTC

7

Useful unity tips

1 Comment
2024/04/30
11:11 UTC

1

Unity Volume Slider - Easy Tutorial (2023)

0 Comments
2024/04/29
12:12 UTC

28

Optimizing Graphics and Rendering in Unity: Key aspects and practical solutions

Introduction

Rendering plays a critical role in creating visually appealing and interactive game scenes. However, inefficient utilization of rendering resources can lead to poor performance and limitations on target devices. Unity, one of the most popular game engines, offers various methods and tools to optimize rendering.

Last time we considered optimizing C# code from the viewpoint of memory and CPU. In this article, we will review the basic principles of rendering optimization in Unity, provide code examples, and discuss practical strategies for improving game performance.

This article has examples of how you can optimize a particular aspect of rendering, but these examples are written only for understanding the basics, not for use in production

Fundamentals of rendering in Unity

Before we move on to optimization, let's briefly recap the basics of rendering in Unity. You can read more about the rendering process in my past article.

https://preview.redd.it/z9566z5bhexc1.png?width=800&format=png&auto=webp&s=ddfa6224c8470cf7379694d22bbfcdb2e7e065ba

Graphics pipeline

Unity uses a graphics pipeline to convert three-dimensional models and scenes into two-dimensional images. The main stages of the pipeline include:

  • Geometric transformation: Convert three-dimensional coordinates to two-dimensional screen coordinates.
  • Rendering: Defining visible objects and displaying them on the screen.
  • Shading: Calculating lighting and applying textures to create the final image.
  • Post-processing: Applying effects after rendering is complete, such as blurring or color correction.

Rendering components

The main components of rendering in Unity include:

  • Meshes: Geometric shapes of objects.

  • Materials: Parameters that determine the appearance of an object, including color, textures, and lighting properties.

  • Shaders: Programs that determine how objects are rendered on the screen.

Optimization of rendering

Optimizing rendering in Unity aims to improve performance by efficiently using CPU and graphics card resources. Below we'll look at a few key optimization strategies:

  • General Rendering Optimizations;
  • Reducing the number of triangles and LODs;
  • Culling (Frustrum, Occlusion);
  • Materials and Shaders Optimization;
  • Resources Packing;
  • Lighting Optimization;
  • Async Operations;
  • Entities Graphics;
  • Other Optimizations;

Let's get started!

General Rendering Optimizations

Depending on which rendering engine you have chosen and the goals you are pursuing - you should make some adjustments to that engine. Below we will look in detail at the most necessary options using HDRP as an example (but some of them are valid for URP and Built-In as well).

Graphics Setup (Project Settings -> Graphics)

https://preview.redd.it/npi77q1fhexc1.png?width=690&format=png&auto=webp&s=8c3179bb77069ea8c796207f6b4cbb7edf77a954

Optimal Settings for Graphics Setup:

  • Default Render Pipeline - uses for HDRP / URP / Custom SRP Default Asset Setup;
  • Lightmap Modes - use only important for you mode. If you don't use mixed or realtime lights - disable modes here;
  • Fog Modes - use only important for you fog settings. Disable unused features.
  • Disable Log Shader Compilation to increase building time;
  • Enable Camera-Relative Lights and Camera Culling;
  • Setup Rendering Tires for Built-In (especially shader quality and rendering path);

Depending on how you use shaders, you may need to configure Forward or Deferred Rendering. The default setting in Unity is mostly Forward Rendering, but you can change it to Forward and in some cases it will speed up the rendering process by several times.

Quality Settings (Project Settings -> Quality)

https://preview.redd.it/enwph52ihexc1.png?width=681&format=png&auto=webp&s=f573f66ef96698512c29489820c6ad281e48dbfe

Optimal Settings for Quality Setup:

  • Disable V-Sync at low-end and mobile devices;
  • Change Textures Global MipMap Limit for low-end devices to half-resolution or lower;
  • Reduce particles raycast budget for low-end devices to 64-128 pts;
  • Disable LOD cross-fade for low-end devices;
  • Reduce Skinned Mesh Weights for low-end devices;

Additional Rendering Settings (Project Settings -> Player)

https://preview.redd.it/r64sheikhexc1.png?width=672&format=png&auto=webp&s=cf5139dabd87e596d06555600b2d0b86260c6fd6

Optimal Settings for Quality Setup:

  • Set default fullscreen mode as Exclusive Fullscreen;
  • Set Capture Single Screen as enabled (disable rendering for multi-monitors);
  • Disable Player Log;
  • Set Color Space to Gamma (Linear for HDRP);
  • Set MSAA fallback to Downgrade;
  • Set DirectX 12 API as default for Rendering (especially if you need to use Ray Tracing);
  • Enable GPU Skinning and Graphics Jobs;
  • Enable Lightmap Streaming;
  • Switch Scripting backend to IL2CPP;
  • Use Incremental GC;

Render Pipeline Setup (HDRP Asset)

https://preview.redd.it/bcn7jp1nhexc1.png?width=800&format=png&auto=webp&s=dbabb0b01e7f2243ce4e92b1c540246a7fecbcc7

Now let's look at Settings in HDRP Asset:

  • Use lower Color Buffer Format;
  • Disable Motion Vectors at low-end devices;
  • Setup LOD Bias for different Quality Modes;
  • Play with different rendering distance and quality levels for decals, shadows etc.;
  • Enable Dynamic Resolution for low-end Devices (like FRS, DLSS etc);
  • Enable Screen Space Reflections or use Baked Reflections for low-end devices;

Camera Optimization

https://preview.redd.it/hcreft2phexc1.png?width=734&format=png&auto=webp&s=a95120ed3518b966236d4ae05c2d0405a6eac019

Now let's look at Camera Setup:

  • Use lower Clipping Planes for low-end devices;

  • Allow Dynamic Resolution with Performance Setup at low-end devices;

  • Use Culling masks and Occlusion Culling;

Reducing the number of triangles and LODs

The fewer triangles in a scene, the faster Unity can render it. Use simple shapes where possible and avoid excessive detail. Use tools like LOD (levels of detail) and Impostors to automatically reduce the detail of objects at a distance.

LOD (level of detail) is a system that allows you to use less detailed objects at different distances.

https://preview.redd.it/h1h2sfirhexc1.png?width=661&format=png&auto=webp&s=9351cbc85e7fca77f673c92fd57b9f008527204f

Impostors is a system that bakes a highly polygonal object to display as sprites, which can also be useful on the course. Unlike regular Billboards, Impostors look different from different angles, just like a regular 3D model should.

https://preview.redd.it/cvlox57thexc1.png?width=800&format=png&auto=webp&s=0fa0671cddf1c93a0d7c09b8cf7933ba1bbc7820

You can also reduce the number of triangles on the fly if you want to create your own clipping conditions. For example you can use this component for runtime mesh processing.

Culling (Frustrum, Occlusion)

Culling objects involves making objects invisible. This is an effective way to reduce both the CPU and GPU load.

In many games, a quick and effective way to do this without compromising the player experience is to cull small objects more aggressively than large ones. For example, small rocks and debris could be made invisible at long distances, while large buildings would still be visible.

Occlusion culling is a process which prevents Unity from performing rendering calculations for GameObjects that are completely hidden from view (occluded) by other GameObjects. When rendering rather large polygonal objects (for example, in-door or out-door scenes) not all vertices are actually visible on the screen. By not sending these vertices for rendering, you can save a lot on rendering speed with Frustrum Culling.

https://preview.redd.it/9f5ggomvhexc1.png?width=697&format=png&auto=webp&s=d913a556bbacca701f7d9ab575ecc32f52a8c56f

In Unity has its own system for Occlusion Culling, it works based on cutoff areas.

To determine whether occlusion culling is likely to improve the runtime performance of your Project, consider the following:

  • Preventing wasted rendering operations can save on both CPU and GPU time. Unity’s built-in occlusion culling performs runtime calculations on the CPU, which can offset the CPU time that it saves. Occlusion culling is therefore most likely to result in performance improvements when a Project is GPU-bound due to overdraw.
  • Unity loads occlusion culling data into memory at runtime. You must ensure that you have sufficient memory to load this data.
  • Occlusion culling works best in Scenes where small, well-defined areas are clearly separated from one another by solid GameObjects. A common example is rooms connected by corridors.
  • You can use occlusion culling to occlude Dynamic GameObjects, but Dynamic GameObjects cannot occlude other GameObjects. If your Project generates Scene geometry at runtime, then Unity’s built-in occlusion culling is not suitable for your Project.

https://preview.redd.it/gqegzoixhexc1.png?width=776&format=png&auto=webp&s=29e78f94f4ba95d976c2fc1217c06aea05968da1

For an improved Frustrum Culling experience, I suggest taking a library that handles it using Jobs.

Materials and Shaders optimization

Materials and Shaders can have a significant impact on performance. The following things should be considered when working with materials:

  • Use as few textures as possible, where possible bake your sub textures such as Ambient into Diffuse. Also keep an eye on texture sizes.
  • Where possible, use GPU Instancing and Material Variants
  • Use the simplest shaders with the minimum number of passes.
  • Use shader LOD to control simplicity of your material in runtime.
  • Use simple instructions in shaders and avoid complex mathematical operations.

Write LOD-based shaders for your project:

Shader "Examples/ExampleLOD"
{
    SubShader
    {
        LOD 200

        Pass
        {                
              // The rest of the code that defines the Pass goes here.
        }
    }

    SubShader
    {
        LOD 100

        Pass
        {                
              // The rest of the code that defines the Pass goes here.
        }
    }
}

Switching Shader LOD at Runtime:

Material material = GetComponent<Renderer>().material;
material.shader.maximumLOD = 100;

Complex mathematical operations
Transcendental mathematical functions (such as pow, exp, log, cos, sin, tan) are quite resource-intensive, so avoid using them where possible. Consider using lookup textures as an alternative to complex math calculations if applicable.

Avoid writing your own operations (such as normalize, dot, inversesqrt). Unity’s built-in options ensure that the driver can generate much better code. Remember that the Alpha Test (discard) operation often makes your fragment shader slower.

Floating point precision
While the precision (float vs half vs fixed) of floating point variables is largely ignored on desktop GPUs, it is quite important to get a good performance on mobile GPUs.

Resources Packing

Bundling textures and models reduces the number of calls to the disk and reduces resource utilization. There are several options for packaging resources in the way that is right for you:

  • Using Sprite Packer for 2D Sprites and UI Elements;
  • Using Baked Texture atlases in 3D Meshes (baked in 3D Editors);
  • Compress Textures using Crunched Compression with disabling unused mipmaps;
  • Using Runtime Texture Baking;

// Runtime Texture Packing Example
Texture2D[] textures = Resources.LoadAll<Texture2D>("Textures");
Texture2DArray textureArray = new Texture2DArray(512, 512, textures.Length, TextureFormat.RGBA32, true);
for (int i = 0; i < textures.Length; i++)
{
    Graphics.CopyTexture(textures[i], 0, textureArray, i);
}

Resources.UnloadUnusedAssets();

Also, don't forget about choosing the right texture compression. If possible, also use Crunched compression. And of course disable unnecessary MipMaps levels to save space.

Disable invisible renders

Disabling rendering of objects behind the camera or behind other objects can significantly improve performance. You can use culling or runtime disabling:

// Runtime invisible renderers disabling example
Renderer renderer = GetComponent<Renderer>();
if (renderer != null && !renderer.isVisible)
{
    renderer.enabled = false;
}

Lighting and Shadow Optimization

All Lights can be rendered using either of two methods:

  • Vertex lighting calculates the illumination only at the vertices of meshes and interpolates the vertex values over the rest of the surface. Some lighting effects are not supported by vertex lighting but it is the cheaper of the two methods in terms of processing overhead. Also, this may be the only method available on older graphics cards.
  • Pixel lighting is calculated separately at every screen pixel. While slower to render, pixel lighting does allow some effects that are not possible with vertex lighting. Normal-mapping, light cookies and realtime shadows are only rendered for pixel lights. Additionally, spotlight shapes and point light highlights look much better when rendered in pixel mode.

Lights have a big impact on rendering speed, so lighting quality must be traded off against frame rate. Since pixel lights have a much higher rendering overhead than vertex lights, Unity will only render the brightest lights at per-pixel quality and render the rest as vertex lights.

Realtime shadows have quite a high rendering overhead, so you should use them sparingly. Any objects that might cast shadows must first be rendered into the shadow map and then that map will be used to render objects that might receive shadows. Enabling shadows has an even bigger impact on performance than the pixel/vertex trade-off mentioned above.

So, let's look at general tips for lighting performance:

  • Disable lights when it not visible;
  • Do not use realtime lightings everywhere;
  • Play with shadow distance and quality;
  • Disable Receive Shadows and Cast Shadows where it not used. For example - disable Cast Shadowing for roads and shadow casting at landed objects;
  • Use vertex lights for low-end devices;

Simple example of realtime lights disabling at runtime:

Light[] lights = FindObjectsOfType<Light>();
foreach (Light light in lights)
{
    if (!light.gameObject.isStatic)
    {
        light.enabled = false;
    }
}

Async Operations

Try to use asynchronous functions and coroutines for heavy in-frame operations. Also try to take calculations out of Update() method, because they will block the main rendering thread and increase micro-frizz between frames, reducing your FPS.

// Bad Example
void Update() {
    // Heavy calculations here
}

// Good Example
void LateUpdate(){
    if(!runnedOperationWorker){
        RunHeavyOperationHere();
    }
}

void RunHeavyOperationHere() {
    // Create Async Calculations Here
}

Bad Example of Heavy Operations:

// Our Upscaling Method
public void Upscale() {
    if(isUpscaled) return;

    // Heavy Method Execution
    UpscaleTextures(() => {
        Resources.UnloadUnusedAssets();
        OnUpscaled?.Invoke();
        Debug.Log($"Complete Upscale for {gameObject.name} (Materials Pool): {materialPool.Count} textures upscaled.");
    });

    isUpscaled = true;
}

private void UpscaleTextures(){
    if(!isUpscaled) Upscale();
}

Good Example of Heavy Operation:

// Our Upscaling Method
public void Upscale() {
    if(isUpscaled) return;

    // Run Heavy method on Coroutine (can be used async instead)
    StopCoroutine(UpscaleTextures());
    StartCoroutine(UpscaleTextures(() => {
        Resources.UnloadUnusedAssets();
        OnUpscaled?.Invoke();
        Debug.Log($"Complete Upscale for {gameObject.name} (Materials Pool): {materialPool.Count} textures upscaled.");
    }));

    isUpscaled = true;
}

private void UpscaleTextures(){
    if(!isUpscaled) Upscale();
}

Entities Graphics

If you using ECS for your games - you can speed-up your entities rendering process using Entities Graphics. This package provides systems and components for rendering ECS Entities. Entities Graphics is not a render pipeline: it is a system that collects the data necessary for rendering ECS entities, and sends this data to Unity's existing rendering architecture.

https://preview.redd.it/5m2tltnfiexc1.png?width=604&format=png&auto=webp&s=4ca25e6478edcd8c24f7c261486492da4e5f2844

The Universal Render Pipeline (URP) and High Definition Render Pipeline (HDRP) are responsible for authoring the content and defining the rendering passes.

https://docs.unity3d.com/Packages/com.unity.entities.graphics@1.0/manual/index.html

Simple Usage Example:

public class AddComponentsExample : MonoBehaviour
{
    public Mesh Mesh;
    public Material Material;
    public int EntityCount;

    // Example Burst job that creates many entities
    [GenerateTestsForBurstCompatibility]
    public struct SpawnJob : IJobParallelFor
    {
        public Entity Prototype;
        public int EntityCount;
        public EntityCommandBuffer.ParallelWriter Ecb;

        public void Execute(int index)
        {
            // Clone the Prototype entity to create a new entity.
            var e = Ecb.Instantiate(index, Prototype);
            // Prototype has all correct components up front, can use SetComponent to
            // set values unique to the newly created entity, such as the transform.
            Ecb.SetComponent(index, e, new LocalToWorld {Value = ComputeTransform(index)});
        }

        public float4x4 ComputeTransform(int index)
        {
            return float4x4.Translate(new float3(index, 0, 0));
        }
    }

    void Start()
    {
        var world = World.DefaultGameObjectInjectionWorld;
        var entityManager = world.EntityManager;

        EntityCommandBuffer ecb = new EntityCommandBuffer(Allocator.TempJob);

        // Create a RenderMeshDescription using the convenience constructor
        // with named parameters.
        var desc = new RenderMeshDescription(
            shadowCastingMode: ShadowCastingMode.Off,
            receiveShadows: false);

        // Create an array of mesh and material required for runtime rendering.
        var renderMeshArray = new RenderMeshArray(new Material[] { Material }, new Mesh[] { Mesh });

        // Create empty base entity
        var prototype = entityManager.CreateEntity();

        // Call AddComponents to populate base entity with the components required
        // by Entities Graphics
        RenderMeshUtility.AddComponents(
            prototype,
            entityManager,
            desc,
            renderMeshArray,
            MaterialMeshInfo.FromRenderMeshArrayIndices(0, 0));
        entityManager.AddComponentData(prototype, new LocalToWorld());

        // Spawn most of the entities in a Burst job by cloning a pre-created prototype entity,
        // which can be either a Prefab or an entity created at run time like in this sample.
        // This is the fastest and most efficient way to create entities at run time.
        var spawnJob = new SpawnJob
        {
            Prototype = prototype,
            Ecb = ecb.AsParallelWriter(),
            EntityCount = EntityCount,
        };

        var spawnHandle = spawnJob.Schedule(EntityCount, 128);
        spawnHandle.Complete();

        ecb.Playback(entityManager);
        ecb.Dispose();
        entityManager.DestroyEntity(prototype);
    }
}

Profiling

And of course, don't optimize graphics blindly. Use Unity profiling tools like Profiler to identify rendering bottlenecks and optimize performance.

For example - create your profiler metrics for heavy calculations:

Profiler.BeginSample("MyUpdate");
// Calculations here
Profiler.EndSample();

Additional Optimization Tips

So, let's take a look at an additional checklist for optimizing your graphics after you've learned the basic techniques above:

  • Keep the vertex count below 200K and 3M per frame when building for PC (depending on the target GPU);
  • If you’re using built-in shaders, pick ones from the Mobile or Unlit categories. They work on non-mobile platforms as well, but are simplified and approximated versions of the more complex shaders;
  • Keep the number of different materials per scene low, and share as many materials between different objects as possible;
  • Set the Static property on a non-moving object to allow internal optimizations like Static Batching. Or use GPU Instancing;
  • Only have a single (preferably directional) pixel light affecting your geometry, rather than multiples;
  • Bake lighting rather than using dynamic lighting. You can also bake normal maps and lightmaps directly into your diffuse textures;
  • Use compressed texture formats when possible, and use 16-bit textures over 32-bit textures. Use Crunch Compression;
  • Avoid using fog where possible;
  • Use Occlusion Culling, LODs and Impostors to reduce the amount of visible geometry and draw-calls in cases of complex static scenes with lots of occlusion. Design your levels with occlusion culling in mind;
  • Use skyboxes or planes with sprite to “fake” distant geometry;
  • Use pixel shaders or texture combiners to mix several textures instead of a multi-pass approach;
  • Avoid Heavy calculations in Update() method;
  • Use half precision variables where possible;
  • Minimize use of complex mathematical operations such as pow, sin and cos in pixel shaders;
  • Use fewer textures per fragment;

Let's summarize

Optimizing rendering is a rather painstaking process. Some basic things - such as lighting settings, texture and model compression, preparing objects for Culling and Batching, or UI optimization - should be done already during the first work on your project to form your optimization-focused work pipeline. However, you can optimize most other things on demand by profiling.

And of course thank you for reading the article, I would be happy to discuss various aspects of optimization with you.

You can also support writing tutorials, articles and see ready-made solutions for your projects:

My Discord | My Blog | My GitHub | Buy me a Beer

BTC: bc1qef2d34r4xkrm48zknjdjt7c0ea92ay9m2a7q55
ETH: 0x1112a2Ef850711DF4dE9c432376F255f416ef5d0

1 Comment
2024/04/29
11:10 UTC

33

Bad Apple but it's a 172800 Entities in Unity ECS (180 FPS!) 🔥Would you like me to prepare tutorial about this? Let me know in comments! ❤️

8 Comments
2024/04/27
21:01 UTC

2

Blender második lecke: Ritka tempó

1 Comment
2024/04/27
12:47 UTC

8

How to Make a Save & Load System in Unity. I have realized that the JSON system doesn't work well with the New Input System, so I also had to add the part of fixing the character controller bug in Unity.

1 Comment
2024/04/26
13:15 UTC

7

Cinemachine camera tutorial

0 Comments
2024/04/25
13:41 UTC

1

Extend Your Unity Game with Discord Activities using SvelteKit

0 Comments
2024/04/24
04:06 UTC

2

When you create a material, it defaults to having all of the standard shader's 27 saved properties. When you switch to a different shader, those saved properties stick around and clutter up your material. This can make copying materials slow. There's a great editor script to quickly fix this.

0 Comments
2024/04/23
21:09 UTC

4

Help with my game

Hi guys, I’m trying to make a 2d platformer roguelike with art by penusbmic and I need to finish a big part of it by mid june. My primary goals are making the character movement and actions feel decent, decent enemies and one boss at the end of the game play. I don’t want to make it big yet. The problem is I’m not sure which tutorials to follow, I’ve watched a couple but none of them seem to be the ones I need. Could you guys maybe lend me a hand if you know some good tutorial and direct me to them? Also, do you think it’s a realistic goal, in the time given? This game is basically all I need to do for the next two months so I can work on it 5-6 hours a day.

8 Comments
2024/04/22
19:12 UTC

1

(spanish video) How to use trigger in Collider 2D in Unity in just 6 minutes

0 Comments
2024/04/22
14:30 UTC

4

Mastering 3D Graphics: Unveiling the Secrets Behind Stunning Visuals | Math in Game Development

Check out my latest video in the series about Importance of Math in game development.
This video gives a brief overview of how math plays a important role in 3D graphics.
https://youtu.be/q7oYF3pl7jk?si=jtTQpeD0DvgxdrEK

0 Comments
2024/04/20
19:06 UTC

5

Shader optimization!

0 Comments
2024/04/20
16:42 UTC

18

Optimizing CPU Load in C#: Key Approaches and Strategies

Introduction

Hi everyone, last time we already touched upon the topic of optimizing code in C# from the point of view of RAM usageIn general, efficient use of computer resources such as the central processing unit (CPU) is one of the main aspects of software development. This time we will talk about optimizing CPU load when writing code in C#, which can significantly improve application performance and reduce power consumption, which is especially critical on mobile platforms and the web. In this article, we will consider several key approaches and strategies for optimizing CPU load in the C# programming language.

https://preview.redd.it/jb8r6thokevc1.png?width=1280&format=png&auto=webp&s=3b6dba76f9e853a5d039a7682aa881d3a05d21e8

Using Efficient Algorithms

One of the most important aspects of CPU load optimization is choosing efficient algorithms. When writing C# code, make sure that you use algorithms with minimal runtime complexity. For example, when searching for an element in a large array, use algorithms with O(log n) or O(1) time complexity, such as binary search, instead of algorithms with O(n) time complexity, such as sequential search.

https://preview.redd.it/1d2qwylpkevc1.jpg?width=405&format=pjpg&auto=webp&s=bee268b792e4870661c0f8104cce92bf2bf62de8

Search Algorithms

Linear Search - also known as the sequential search algorithm. A simple search algorithm checks each element in a collection until the desired value is found. Linear search can be used for sorted and unsorted collections, but it is useful for small collections.

public static int LinearSearch(int[] arr, int target) {
    for (int i = 0; i < arr.Length; i++)
        if (arr[i] == target)
            return i;

    return -1;
}

Binary Search - is a more efficient search algorithm that divides the collection in half at each iteration. Binary search requires the collection to be sorted in ascending or descending order.

public static int BinarySearch(int[] arr, int target) {
    int left = 0;
    int right = arr.Length - 1;

    while (left <= right){
        int mid = (left + right) / 2;

        if (arr[mid] == target)
            return mid;
        else if (arr[mid] < target)
            left = mid + 1;
        else
            right = mid - 1;
    }

    return -1; // target not found
}

Interpolation search - is a variant of binary search that works best for uniformly distributed collections. It uses an interpolation formula to estimate the position of the target element.

public static int InterpolationSearch(int[] arr, int target) {
    int left = 0;
    int right = arr.Length - 1;

    while (left <= right && target >= arr[left] && target <= arr[right]) {
        int pos = left + ((target - arr[left]) * (right - left)) / (arr[right] - arr[left]);

        if (arr[pos] == target)
            return pos;
        else if (arr[pos] < target)
            left = pos + 1;
        else
            right = pos - 1;
    }

    return -1; // target not found
}

Jump search - is another variant of binary search that works by jumping ahead by a fixed number of steps instead of dividing the interval in half.

public static int JumpSearch(int[] arr, int target) {
    int n = arr.Length;
    int step = (int)Math.Sqrt(n);
    int prev = 0;

    while (arr[Math.Min(step, n) - 1] < target) {
        prev = step;
        step += (int)Math.Sqrt(n);

        if (prev >= n)
            return -1; // target not found
    }

    while (arr[prev] < target) {
        prev++;

        if (prev == Math.Min(step, n))
            return -1; // target not found
    }


    if (arr[prev] == target)
        return prev;

    return -1; // target not found
}

As you can see, there can be a large number of search algorithms. Some of them are suitable for some purposes, others for others. The fast binary search algorithm is most often used as a well-established algorithm, but this does not mean that you are obliged to use it only, because it has its own purposes as well.

Sorting Algorithms

Bubble sort - a straightforward sorting algorithm that iterates through a list, comparing adjacent elements and swapping them if they are in the incorrect order. This process is repeated until the list is completely sorted. Below is the C# code implementation for bubble sort:

public static void BubbleSort(int[] arr) {
    int n = arr.Length;
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

Selection sort - a comparison-based sorting algorithm that operates in place. It partitions the input list into two sections: the left end represents the sorted portion, initially empty, while the right end denotes the unsorted portion of the entire list. The algorithm works by locating the smallest element within the unsorted section and swapping it with the leftmost unsorted element, progressively expanding the sorted region by one element.

public static void SelectionSort(int[] arr) {
    int n = arr.Length;
    for (int i = 0; i < n - 1; i++) {
        int minIndex = i;
        for (int j = i + 1; j < n; j++) {
            if (arr[j] < arr[minIndex])
             minIndex = j;
        }

        int temp = arr[i];
        arr[i] = arr[minIndex];
        arr[minIndex] = temp;
    }
}

Insertion sort - a basic sorting algorithm that constructs the sorted array gradually, one item at a time. It is less efficient than more advanced algorithms like quicksort, heapsort, or merge sort, especially for large lists. The algorithm operates by sequentially traversing an array from left to right, comparing adjacent elements, and performing swaps if they are out of order.

public static void InsertionSort(int[] arr) {
    int n = arr.Length;
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
    }
}

Quicksort - a sorting algorithm based on the divide-and-conquer approach. It begins by choosing a pivot element from the array and divides the remaining elements into two sub-arrays based on whether they are smaller or larger than the pivot. These sub-arrays are then recursively sorted.

public static void QuickSort(int[] arr, int left, int right){
    if (left < right) {
        int pivotIndex = Partition(arr, left, right);
        QuickSort(arr, left, pivotIndex - 1);
        QuickSort(arr, pivotIndex + 1, right);
    }
}

private static int Partition(int[] arr, int left, int right){
    int pivot = arr[right];
    int i = left - 1;

    for (int j = left; j < right; j++) {
        if (arr[j] < pivot) {
            i++;
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    int temp2 = arr[i + 1];
    arr[i + 1] = arr[right];
    arr[right] = temp2;
    return i + 1;
}

Merge sort - a sorting algorithm based on the divide-and-conquer principle. It begins by dividing an array into two halves, recursively applying itself to each half, and then merging the two sorted halves back together. The merge operation plays a crucial role in this algorithm.

public static void MergeSort(int[] arr, int left, int right){
    if (left < right) {
        int middle = (left + right) / 2;
        MergeSort(arr, left, middle);
        MergeSort(arr, middle + 1, right);
        Merge(arr, left, middle, right);
    }
}

private static void Merge(int[] arr, int left, int middle, int right) {
    int[] temp = new int[arr.Length];
    for (int i = left; i <= right; i++){
        temp[i] = arr[i];
    }

    int j = left;
    int k = middle + 1;
    int l = left;

    while (j <= middle && k <= right){
        if (temp[j] <= temp[k]) {
            arr[l] = temp[j];
            j++;
        } else {
            arr[l] = temp[k];
            k++;
        }
        l++;
    }

    while (j <= middle) {
        arr[l] = temp[j];
        l++;
        j++;
    }
}

Like search algorithms, there are many different algorithms used for sorting. Each of them serves a different purpose and you should choose the one you need for a particular purpose.

Cycle Optimization

Loops are one of the most common places where CPU load occurs. When writing loops in C# code, try to minimize the number of operations inside a loop and avoid redundant iterations. Also, pay attention to the order of nested loops, as improper management of them can lead to exponential growth of execution time, as well as lead to memory leaks, which I wrote about in the last article.

Suppose we have a loop in which we perform some calculations on array elements. We can optimize this loop if we avoid unnecessary calls to properties and methods of objects inside the loop:

// Our Arrays for Cycle
int[] numbers = { 1, 2, 3, 4, 5 };
int sum = 0;

// Bad Cycle
for (int i = 0; i < numbers.Length; i++) {
    sum += numbers[i] * numbers[i];
}

// Good Cycle
for (int i = 0, len = numbers.Length; i < len; i++) {
    int num = numbers[i];
    sum += num * num;
}

This example demonstrates how you can avoid repeated calls to object properties and methods within a loop, and how you can avoid calling the Length property of an array at each iteration of the loop by using the local variable len. These optimizations can significantly improve code performance, especially when dealing with large amounts of data.

Use of Parallelism

C# has powerful tools to deal with parallelism, such as multithreading and parallel collections. By parallelizing computations, you can efficiently use the resources of multiprocessor systems and reduce CPU load. However, be careful when using parallelism, as improper thread management can lead to race conditions and other synchronization problems and memory leaks.

So, let's look at bad example of parallelism in C#:

long sum = 0;
int[] numbers = new int[1000000];
Random random = new Random();

// Just fill random numbers for example
for (int i = 0; i < numbers.Length; i++) {
    numbers[i] = random.Next(100);
}

// Bad example with each iteration in separated thread
Parallel.For(0, numbers.Length, i => {
    sum += numbers[i] * numbers[i];
});

And Impoved Example:

long sum = 0;
int[] numbers = new int[1000000];
Random random = new Random();

// Just fill random numbers for example
for (int i = 0; i < numbers.Length; i++) {
    numbers[i] = random.Next(100);
}

// Sync our parallel computions
Parallel.For(0, numbers.Length, () => 0L, (i, state, partialSum) => {
    partialSum += numbers[i] * numbers[i];
    return partialSum;
}, partialSum => {
    lock (locker) {
        sum += partialSum;
    }
});

In this good example, we use the Parallel.For construct to parallelize the calculations. However, instead of directly modifying the shared variable sum, we pass each thread a local variable partialSum, which is the partial sum of the computations for each thread. After each thread completes, we sum these partial sums into the shared variable sum, using monitoring and locking to secure access to the shared variable from different threads. Thus, we avoid race conditions and ensure correct operation of the parallel program.

Don't forget that there is still work to be done with stopping and clearing threads. You should use IDisposable and use using to avoid memory leaks.

If you develop projects in Unity - i really recommend to see at UniTaks.

Data caching

Efficient use of the CPU cache can significantly improve the performance of your application. When working with large amounts of data, try to minimize memory accesses and maximize data locality. This can be achieved by caching frequently used data and optimizing access to it.

Let's look at example:

// Our Cache Dictionary
static Dictionary<int, int> cache = new Dictionary<int, int>();

// Example of Expensive operation with cache
static int ExpensiveOperation(int input) {
    if (cache.ContainsKey(input)) {
        // We found a result in cache
        return cache[input];
    }

    // Example of expensive operation here (it may be webrequest or something else)
    int result = input * input;

    // Save Result to cache
    cache[input] = result;
    return result;
}

In this example, we use a cache dictionary to store the results of expensive operations. Before executing an operation, we check if there is already a result for the given input value in the cache. If there is already a result, we load it from the cache, which avoids re-executing the operation and reduces CPU load. If there is no result in the cache, we perform the operation, store the result in the cache, and then return it.

This example demonstrates how data caching can reduce CPU overhead by avoiding repeated computations for the same input data. For the faster and unique cache use HashSet structure.

Additional Optimization in Unity

Of course, you should not forget that if you work with Unity - you need to take into account both the rendering process and the game engine itself. I advise you to pay attention first of all to the following aspects when optimizing CPU in Unity:

  1. Try to minimize the use of coroutines and replace them with asynchronous calculations, for example with UniTask.
  2. Excessive use of high-poly models and unoptimized shaders causes overload, which strains the rendering process.
  3. Use a simple colliders, reduce realtime physics calculations.
  4. Optimize UI Overdraw. Do not use UI Animators, simplify rendering tree, split canvases, use atlases, disallow render targets and rich text.
  5. Synchronous loading and on-the-fly loading of large assets disrupt gameplay continuity, decreasing its playability. Use async assets loading, for example with Addressables Assets.
  6. Avoiding redundant operations. Frequently calling functions like Update() or performing unnecessary calculations can slow down a game. It's essential to ensure that operations are only executed when needed.
  7. Object pooling. Instead of continuously instantiating and destroying objects, which can be CPU-intensive, developers can leverage object pooling to reuse objects.
  8. Optimize loops. Nested loops or loops that iterate over large datasets should be optimized or avoided when possible.
  9. Use LODs (Levels of Detail). Instead of always rendering high-poly models, developers can use LODs to display lower-poly models when objects are farther from the camera.
  10. Compress textures. High-resolution textures can be memory-intensive. Compressing them without significant quality loss can save valuable resources. Use Crunch Compression.
  11. Optimize animations. Developers should streamline animation as much as possible, as well as remove unnecessary keyframes, and use efficient rigs.
  12. Garbage collection. While Unity's garbage collector helps manage memory, frequent garbage collection can cause performance hitches. Minimize object allocations during gameplay to reduce the frequency of garbage collection.
  13. Use static variables. Use static variables as they are allocated on the stack, which is faster than heap allocation.
  14. Unload unused assets. Regularly unload assets that are no longer needed using Resources.UnloadUnusedAssets() to free up memory.
  15. Optimize shaders. Custom shaders can enhance visuals but can be performance-heavy. Ensure they are optimized and use Unity's built-in shaders when possible.
  16. Use batching. Unity can batch small objects that use the same material, reducing draw calls and improving performance.
  17. Optimize AI pathfinding. Instead of calculating paths every frame, do it at intervals or when specific events occur.
  18. Use layers. Ensure that physics objects only interact with layers they need to, reducing unnecessary calculations.
  19. Use scene streaming. Instead of loading an entire level at once, stream parts based on the player's location, ensuring smoother gameplay.
  20. Optimize level geometry. Ensure that the game's levels are designed with performance in mind, using modular design and avoiding overly complex geometry.
  21. Cull non-essential elements. Remove or reduce the detail of objects that don't significantly impact gameplay or aesthetics.
  22. Use the Shader compilation pragma directives to adapt the compiling of a shader to each target platform.
  23. Bake your lightmaps, do not use real-time lightings.
  24. Minimize reflections and reflection probes, do not use realtime reflections;
  25. Shadow casting can be disabled per Mesh Renderer and light. Disable shadows whenever possible to reduce draw calls.
  26. Reduce unnecessary string creation or manipulation. Avoid parsing string-based data files such as JSON and XML;
  27. Use GameObject.CompareTag instead of manually comparing a string with GameObject.tag (as returning a new string creates garbage);
  28. Avoid passing a value-typed variable in place of a reference-typed variable. This creates a temporary object, and the potential garbage that comes with it implicitly converts the value type to a type object;
  29. Avoid LINQ and Regular Expressions if performance is an issue;

Profiling and Optimization

Finally, don't forget to profile your application and look for bottlenecks where the most CPU usage is occurring. There are many profiling tools for C#, such as dotTrace and ANTS Performance Profiler or Unity Profiler, that can help you identify and fix performance problems.

In Conclusion

Optimizing CPU load when writing C# code is an art that requires balancing performance, readability, and maintainability of the code. By choosing the right algorithms, optimizing loops, using parallelism, data caching, and profiling, you can create high-performance applications on the .NET platform or at Unity.

And of course thank you for reading the article, I would be happy to discuss various aspects of optimization and code with you.

My Discord | My Blog | My GitHub | Buy me a Beer

0 Comments
2024/04/19
09:14 UTC

5

Create a new Unity Project for Legends of Learning

0 Comments
2024/04/19
01:29 UTC

3

Unity RPG Tutorial: bow Logic: Aiming Mechanics - Start Your 3D RPG Journey PART 32

0 Comments
2024/04/18
19:06 UTC

6

Extension Methods tutorial

0 Comments
2024/04/18
12:51 UTC

24

Memory Optimization in C#: Effective Practices and Strategies

Introduction

In the world of modern programming, efficient utilization of resources, including memory, is a key aspect of application development. Today we will talk about how you can optimize the resources available to you during development.

https://preview.redd.it/dudbl8jtssuc1.png?width=1280&format=png&auto=webp&s=d7c71a1a0109730ec66d8b68fc06bfa65c1f79f1

The C# programming language, although it provides automatic memory management through the Garbage Collection (GC) mechanism, requires special knowledge and skills from developers to optimize memory handling.

https://preview.redd.it/c0sasvxvssuc1.png?width=800&format=png&auto=webp&s=dac7eec7bbf2692643f679b9276282c2b15f8c92

So, let's explore various memory optimization strategies and practices in C# that help in creating efficient and fast applications.

Before we begin - I would like to point out that this article is not a panacea and can only be considered as a support for your further research. 

Working with managed and unmanaged memory

Before we dive into the details of memory optimization in C#, it's important to understand the distinction between managed and unmanaged memory.

Managed memory

This is memory whose management rests entirely on the shoulders of the CLR (Common Language Runtime). In C#, all objects are created in the managed heap and are automatically destroyed by the garbage collector when they are no longer needed.

https://preview.redd.it/jz1dt600tsuc1.png?width=800&format=png&auto=webp&s=eb5315c56a2ad098a926f902f2ac0372303af35e

Unmanaged memory

This is memory that is managed by the developer. In C#, you can handle unmanaged memory through interoperability with low-level APIs (Application Programming Interface) or by using the unsafe
and fixed
keywords. Unmanaged memory can be used to optimize performance in critical code sections, but requires careful handling to avoid memory leaks or errors.

Unity has basically no unmanaged memory and also the garbage collector works a bit differently, so you should just rely on yourself and understand how managed memory works on a basic level to know under what conditions it will be cleared and under what conditions it won't.

Using data structures wisely

Choosing an appropriate data structure is a key aspect of memory optimization. Instead of using complex objects and collections, which may consume more memory due to additional metadata and management information, you should prefer simple data structures such as arrays, lists, and structs.

Arrays and Lists

Let's look at an example:

// Uses more memory
List<string> names = new List<string>();
names.Add("John");
names.Add("Doe");

// Uses less memory
string[] names = new string[2];
names[0] = "John";
names[1] = "Doe";

In this example, the string[]
array requires less memory compared to List<string>
because it has no additional data structure to manage dynamic resizing.

However, that doesn't mean you should always use arrays instead of lists. You should realize that if you often have to add new elements and rebuild the array, or perform heavy searches that are already provided in the list, it is better to choose the second option.

Structs vs Classes

In my understanding, classes and structures are quite similar to each other, albeit with some differences (but that's not what this article will be about), they still have quite a big difference about how they are arranged in our application's memory. And understanding this can save you a huge amount of execution time and RAM, especially on large amounts of data. So let's look at some examples.

https://preview.redd.it/ra2ly944tsuc1.png?width=732&format=png&auto=webp&s=e7c2d49361af7652ea7594b9620975c75fcfe487

So, suppose we have a class with arrays and a structure with arrays. In the first case, the arrays will be stored in the RAM of our application, and in the second case, in the processor cache (taking into account some peculiarities of garbage collection, which we will discuss below). If we store data in the CPU cache, we speed up access to the data we need, in some cases from 10 to 100 times (of course, everything depends on the peculiarities of the CPU and RAM, and these days CPUs have become much smarter friends with compilers, providing a more efficient approach to memory management).

So, over time, as we populate or organize our class, the data will no longer be placed with each other in memory due to the heap handling features, because our class is a reference type and it is arranged more chaotically in memory locations. Over time, memory fragmentation makes it more difficult for the CPU to move data into the cache, which creates some performance and access speed issues with that very data.

// Class Array Data
internal class ClassArrayData
{
    public int value;
}

// Struct Array Data
internal struct StructArrayData
{
    public int value;
}

Let's look at the options of when we should use classes and when we should use structures.

When you shouldn't replace classes with structures:

  • You are working with small arrays. You need a reasonably big array for it to be measurable.
  • You have too big pieces of data. The CPU cannot cache enough of it, and it ends in RAM.
  • You have reference types like String in your Struct. They can point to RAM just like Class.
  • You don’t use the array enough. We need fragmentation for this to work.
  • You are using an advanced collection like List. We need fixed memory allocation.
  • You are not accessing the array directly. If you want to pass the data around to functions, use a Class.
  • If you are not sure, a bad implementation can be worse than just keeping to a Class array.
  • You still want Class functionality. Do not make hacky code because you want both Class functionality and Struct performance.

When it's still worth replacing a class with a structure:

  • Water simulation where you have a big array of velocity vectors.
  • City building game with a lot of game objects that have the same behavior. Like cars.
  • Real-time particle system.
  • CPU rendering using a big array of pixels.

A 90% boost is a lot, so if it sounds like something for you, I highly recommend doing some tests yourself. I would also like to point out that we can only make assumptions based on the industry norms because we are down at the hardware level.

I also want to give an example of benchmarks with mixed elements of arrays based on classes and structures (done on Intel Core i5-11260H 2.6 HHz, iteratively on 100 million operations with 5 attempts):

  • No Shuffle: Struct ( 115ms ), Class( 155ms )
  • 10% Shuffle: Struct ( 105ms ), Class( 620ms )
  • 25% Shuffle: Struct ( 120ms ), Class( 840ms )
  • 50% Shuffle: Struct ( 125ms ), Class( 1050ms )
  • 100% Shuffle: Struct ( 140ms ), Class( 1300ms )

Yes, we are talking about huge amounts of data here, but what I wanted to emphasize here is that the compiler cannot guess how you want to use this data, unlike you - and it is up to you to decide how you want to access it first.

Avoid memory leaks

Memory leaks can occur due to careless handling of objects and object references. In C#, the garbage collector automatically frees memory when an object is no longer used, but if there are references to objects that remain in memory, they will not be removed.

https://preview.redd.it/iueagfmztsuc1.png?width=512&format=png&auto=webp&s=f88d82b66e40cbef0a38395cc1eadf9b4f92b6cd

Memory Leak Code Examples

When working with managed resources such as files, network connections, or databases, make sure that they are properly released after use. Otherwise, this may result in memory leaks or exhaustion of system resources.

So, let's look at example of Memory Leak Code in C#:

public class MemoryLeakSample
{
    public static void Main()
    {
        while (true)
        {
            Thread thread = new Thread(new ThreadStart(StartThread));
            thread.Start();
        }
    }

    public static void StartThread()
    {
        Thread.CurrentThread.Join();
    }
}

And Memory Leak Code in Unity:

int frameNumber = 0;
WebCamTexture wct;
Texture2D frame;

void Start()
{
    frameNumber = 0;

    wct = new WebCamTexture(WebCamTexture.devices[0].name, 1280, 720, 30);
    Renderer renderer = GetComponent<Renderer>();
    renderer.material.mainTexture = wct;
    wct.Play();

    frame = new Texture2D(wct.width, wct.height);
}

// Update is called once per frame
// This code in update() also leaks memory
void Update()
{
    if (wct.didUpdateThisFrame == false)
        return;

    ++frameNumber;

    //Check when camera texture size changes then resize your frame too
    if (frame.width != wct.width || frame.height != wct.height)
    {
        frame.Resize(wct.width, wct.height);
    }

    frame.SetPixels(wct.GetPixels());
    frame.Apply();
}

There are many ways to avoid memory leak in C#. We can avoid memory leak while working with unmanaged resources with the help of the ‘using’ statement, which internally calls Dispose() method. The syntax for the ‘using’ statement is as follows:

// Variant with Disposable Classes
using(var ourObject = new OurDisposableClass)
{
    //user code
}

When using managed resources, such as databases or network connections, it is also recommended to use connection pools to reduce the overhead of creating and destroying resources.

Optimization of work with large volumes of data

When working with large amounts of data, it is important to avoid unnecessary copying and use efficient data structures. For example, if you need to manipulate large strings of text, use StringBuilder instead of regular strings to avoid unnecessary memory allocations.

// Bad Variant
string result = "";
for (int i = 0; i < 10000; i++) {
    result += i.ToString();
}

// Good Variant
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
    sb.Append(i);
}
string result = sb.ToString();

You should also avoid unnecessary memory allocations when working with collections. For example, if you use LINQ to filter a list, you can convert the result to an array using the

ToArray()

method to avoid creating an unnecessary list.

// Bad Example
List<int> numbers = Enumerable.Range(1, 10000).ToList();
List<int> evenNumbers = numbers.Where(n => n % 2 == 0).ToList();

// Good Example
int[] numbers = Enumerable.Range(1, 10000).ToArray();
int[] evenNumbers = numbers.Where(n => n % 2 == 0).ToArray();

Code profiling and optimization

Code profiling allows you to identify bottlenecks and optimize them to improve performance and memory efficiency. There are many profiling tools for C#, such as dotTrace, ANTS Performance Profiler and Visual Studio Profiler.

Unity has own Memory Profiler. You can read more about them here.

Profiling allows you to:

  • Identify code sections that consume the most memory.

  • Identify memory leaks and unnecessary allocations.

  • Optimize algorithms and data structures to reduce memory consumption.

Optimize applications for specific scenarios

Depending on the specific usage scenarios of your application, some optimization strategies may be more or less appropriate. For example, if your application runs in real time (like games), you may encounter performance issues due to garbage collection, and you may need to use specialized data structures or algorithms to deal with this problem (for example Unity DOTS and Burst Compiler).

Optimization with managed memory (unsafe code)

Although the use of unsafe
memory in C# should be cautious and limited, there are scenarios where using unsafe
code can significantly improve performance. This can be particularly useful when working with large amounts of data or when writing low-level algorithms where the overhead of garbage collection becomes significant.

// Unsafe Code Example
unsafe
{
    int x = 10;
    int* ptr;
    ptr = &amp;x;

    // displaying value of x using pointer
    Console.WriteLine(&quot;Inside the unsafe code block&quot;);
    Console.WriteLine(&quot;The value of x is &quot; + *ptr);
} // end unsafe block

Console.WriteLine(&quot;\nOutside the unsafe code block&quot;);

However, using

unsafe

code requires a serious understanding of the inner workings of memory and multithreading in .NET, and requires extra precautions such as checking array bounds and handling pointers with care.

Conclusion

Memory optimization in C# is a critical aspect of developing efficient and fast applications. Understanding the basic principles of memory management, choosing the right data structures and algorithms, and using profiling tools will help you create an efficient application that utilizes system resources efficiently and provides high performance.

However, don't forget that in addition to code optimization, you should also optimize application resources (for example, this is very true for games, where you need to work with texture compression, frame rendering optimization, dynamic loading and unloading of resources using Bundles, etc.).

And of course thank you for reading the article, I would be happy to discuss various aspects of optimization and code with you.

You can also support writing tutorials, articles and see ready-made solutions for your projects:

My Discord | My Blog | My GitHub | Buy me a Beer

3 Comments
2024/04/16
08:10 UTC

0

Trade Unreal for Unity Tutorials from a Humble Bundle

Anybody buy a recent humble bundle that came with "Unreal Engine 5: Creating a Car Racing Game," and they want to trade for the Godot or Unity tutorials (Awesome Tuts) that im not going to use from the current GameMasters Toolkit up on the site? I bought the wrong bundle, and got the wrong car racing tut. Will trade all unity or Godot tuts (so they don't go to waste) for the one unreal tut. Kay, let me know

0 Comments
2024/04/15
20:03 UTC

6

How to Animate Your Character in Unity 3D!

0 Comments
2024/04/15
13:07 UTC

7

This doesn't come up super often, but sometimes I want to replace a script on a GameObject with another one that has the same serialized fields and not lose the data in those fields. I didn't think it was possible but it turns out it is using the inspector "Debug" mode.

0 Comments
2024/04/14
20:45 UTC

16

Crucial aspect of game development is Player Movement 🔥 While handling movement for GameObjects is simple, it becomes more complex when dealing with ECS. However, with complexity comes efficiency and code cleanliness. ❤️Link to Full Tutorial in description!

3 Comments
2024/04/14
14:53 UTC

3

Arcade Bike Controller Tutorial

0 Comments
2024/04/13
16:38 UTC

5

Floating health bar: Screen-space UI or world-space UI? [in 1min]

1 Comment
2024/04/13
07:42 UTC

Back To Top