Skip to main content

Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
TagsGame Engines

Nice work with the character sketch! And great choice with Unity. I've tried Unreal too and it felt a bit overwhelming, both in terms of performance drain and in terms of learning curve.

Also regarding the ranged system, you might be able to solve the issue by manually aiming at the target in front of you (if one is found), and otherwise just throwing it straight forward. You can detect targets in front of you regardless of elevation.

You can do this by parenting a BoxCollider (or any other collider of choide) to your character, making it a trigger, excluding all layers except the one where your targets exist. Also, make it really tall to deal with the elevation issues. Make it as long as the range the character has. 

Add a new script to track targets, let's say, TargetManager to the character. This script has a OnTriggerEnter and OnTriggerExit callbacks which add valid targets to a list. On Update() you can pick the target from the list based on some criteria (closest, unobstructed, etc), save it to some variable, let's say GameObject target. Careful how you write this one, because it might have some performance concerns. Make it run on a less-frequent Coroutine if it does cause performance issues.

Then, on your object throw script, you check if you targetManager.target != null and then use this handy phisics calculation to tell exactly what force you need to apply to the throwable to make it land exactly on your target position:


using System;
using UnityEngine;
namespace Game.ProjectileSystem
{
    public enum BallisticTrajectory
    {
        // Highest arc to hit from above
        Max,
        // Most direct arc to hit straight on
        Min,
        // Somewhere in between
        LowEnergy
    }
    public class Ballistics
    {
        public static Vector3 CalculateVelocityToLaunchToCameraDirection(
          GameObject camera, 
          Transform source,
          float speed, 
          LayerMask layerMask, 
          float raycastRange = 15f
        )`{
            // aim from camera infinitely far a our target
            var ray = new Ray(camera.transform.position, camera.transform.forward);
            if (Physics.Raycast(ray, out var hit, raycastRange, layerMask, QueryTriggerInteraction.Ignore))
            {
                return CalculateVelocity(source, hit.point, speed).normalized * speed;
            }
            else
                return CalculateVelocity(
                  source, 
                  (ray.origin + ray.direction * raycastRange), speed
                ).normalized * speed;
        }
        /// <summary>
        /// Returns the velocity needed to hit a target from a certain position with a certain speed. This 3d vector can also
        /// be used as the look direction for a projectile by using Quaternion.LookRotation().
        /// </summary>
        /// <returns>The 3D velocity.</returns>
        public static Vector3 CalculateVelocity(Transform source, Vector3 target, float speed, BallisticTrajectory trajectoryType = BallisticTrajectory.Min)
        {
            speed = Mathf.Clamp(speed, 0, speed);
            Vector3 toTarget = target - source.position;
            // Set up the terms we need to solve the quadratic equations.
            float gSquared = Physics.gravity.sqrMagnitude;
            float b = speed * speed + UnityEngine.Vector3.Dot(toTarget, Physics.gravity);
            float discriminant = b * b - gSquared * toTarget.sqrMagnitude;
            // Check whether the target is reachable at max speed or less.
            if (discriminant < 0)
            {
                // Target is too far away to hit at this speed.
                // Abort, or fire at max speed in its general direction?
                return toTarget.normalized * speed;
            }
            float discRoot = Mathf.Sqrt(discriminant);
            // Highest shot with the given max speed:
            float T_max = Mathf.Sqrt((b + discRoot) * 2f / gSquared);
            // Most direct shot with the given max speed:
            float T_min = Mathf.Sqrt((b - discRoot) * 2f / gSquared);
            // Lowest-speed arc available:
            float T_lowEnergy = Mathf.Sqrt(Mathf.Sqrt(toTarget.sqrMagnitude * 4f / gSquared));
            float T;
            switch (trajectoryType)
            {
                case BallisticTrajectory.Max:
                    T = T_max;
                    break;
                case BallisticTrajectory.Min:
                    T = T_min;
                    break;
                case BallisticTrajectory.LowEnergy:
                    T = T_lowEnergy;
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(trajectoryType), trajectoryType, null);
            }
            // Convert from time-to-hit to a launch velocity:
            return toTarget / T - Physics.gravity * T / 2f;
        }
    }
}

If you're curious where I got this function, the answer is a mix of physics knowledge and some experimentation plus some ChatGPT for translating said knowledge to C#. 

If you're curious where I use this function, you can check out my game Recolonizer, which is out now as a browser game on open beta! I plan on fully releasing it to Steam and iOS in mid-2025! It is what drives the direction of the cannonballs, as they are all physics projectiles.

In any case, good luck on your project! Hope this helped a bit :)

TY!!! And good luck with your project as well!
To be fair, that's more of a design choice than a technical issue. We discussed it later and came to term that a short ranged range attack could also work. Suffice to say that we're still testing everything. We've made an projectile template so it's easy to make other types of projectile upon it.

I personally love to nerd about physics in game, though currently there are other mechanics that still has to be made. Temporary upgrade is one of them and I can see a ballistic projectile reaching different height level being one potential upgrade.

Thanks again for your input!

Ah, good. Often complex solutions can be replaced by better design. And indeed, nerding about physics is like 45% of the reason my project even exists lol

Looking forward to future updates!