I have never made one of these so I figured I’d try my hand at it. This is what I came up with for the actual auto-aiming engine.
The methodology is discussed in the source comments.
In my opinion it has a nice “magnetic” feel to it when the crosshairs go over a visible enemy.
Sorry I don’t have it packaged up in something public but I’ll see if I get some time this weekend to stick it in one of my public projects.
But meanwhile, it’s all laid out for you to enjoy here:
This is the engine script. It has a factory function to help you set it up properly at runtime. It is NOT intended to just be drag-dropped in the editor.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
// @kurtdekker - cheap and cheerful super-generic FPS aim assister
public class AutoAimAssist1 : MonoBehaviour
{
System.Func<Transform,IEnumerable<Vector3>> GetVisibleTargetLocations;
IAimAssistable AimAssistable;
float MaxVectorOffset;
// Factory method to attach to your stuff. Set this up with:
//
// - the viewpoint you are aiming from (Transform). This script is placed there too.
// - your mouse look script, which must implement IAimAssistable interface.
// - a function this script can call to say "enumerate me some enemies I can see and possibly target"
// - the maximum normalized vector offset (the sine of maximum off-boresight)
//
public static AutoAimAssist1 Attach(
Transform viewpoint,
System.Func<Transform,IEnumerable<Vector3>> GetVisibleTargetLocations,
IAimAssistable aimAssistable,
float maxVectorOffset)
{
var aaa = viewpoint.gameObject.AddComponent<AutoAimAssist1>();
aaa.GetVisibleTargetLocations = GetVisibleTargetLocations;
aaa.AimAssistable = aimAssistable;
aaa.MaxVectorOffset = maxVectorOffset;
return aaa;
}
void Update ()
{
var TargetLocations = GetVisibleTargetLocations( transform);
var MyPosition = transform.position;
var MyForward = transform.forward;
foreach( var targetLocation in TargetLocations)
{
var VectorToEnemy = targetLocation - MyPosition;
VectorToEnemy.Normalize();
// essentially the tangent vector between MyForward and the line to the enemy
var difference = VectorToEnemy - MyForward;
// how big is that offset along the sphere surface
float vectorOffset = difference.magnitude;
// if it is within our auto-aim MaxVectorOffset, we care
if (vectorOffset < MaxVectorOffset)
{
// transform it to local offset X,Y plane
var localDifference = transform.InverseTransformDirection( difference);
// normalize it to full deflection
localDifference /= MaxVectorOffset;
// scale it according to conical offset from boresight (strongest in middle)
float conicalStrength = (MaxVectorOffset - vectorOffset) / MaxVectorOffset;
localDifference *= conicalStrength;
// send it to the aim assist injection point
AimAssistable.InjectAutoAim( localDifference);
}
}
}
}
It knows how to call this type of an IAimAssistable
object:
using UnityEngine;
// implement this in your main mouse look controller to receive
// steering input from the AutoAimAssist script(s)
public interface IAimAssistable
{
void InjectAutoAim( Vector2 aim);
}
In your mouse look script, after all your other calculations are done, you would blend the Vector2 you received via InjectAutoAim() method, something like:
_mouseAbsolute += LastAimAssist;
LastAimAssist = Vector2.zero; // mark it as "processed"
You would choose scaling values to the above vector, since this is extremely dependent on your mouse movement implementation.
The auto-aimer also requires a function like this to retrieve visible enemies. You would provide this in your own enemy manager class:
IEnumerable<Vector3> GetVisibleTargetLocations( Transform viewpoint)
{
foreach( var e in Enemies)
{
if (e)
{
// TODO: include LOS by considering if viewpoint can even
// see this enemy, perhaps with raycast or other method.
yield return e.transform.position;
}
}
}