Force on Character Controller (Knockback)

Hi, I’m working on a pretty big project in which this is rather important. I’m using a Character Controller component with a customized movement script. It doesn’t have a Rigidbody component attached to it, so I thought that excludes physics.

However, what I want to accomplish is… That when the player is hit by an projectile, it gets knocked back, depending on the speed and mass of the projectile. I already have the projectile and all, I just need some headstart on scripting these physics-like things without having to use a rigidbody.

Anyone any suggestions?

You can add the script below to each character (CharacterController is required!). When a bullet hits the character, you must get this script (let’s call it ImpactReceiver.js) and call the function AddImpact passing the impact direction (the bullet direction, for instance) and the force you want to apply.

var mass = 3.0; // defines the character mass
var impact = Vector3.zero;
private var character: CharacterController;

function Start(){
  character = GetComponent(CharacterController);
}

// call this function to add an impact force:
function AddImpact(dir: Vector3, force: float){
  dir.Normalize();
  if (dir.y < 0) dir.y = -dir.y; // reflect down force on the ground
  impact += dir.normalized * force / mass;
}

function Update(){
  // apply the impact force:
  if (impact.magnitude > 0.2) character.Move(impact * Time.deltaTime);
  // consumes the impact energy each cycle:
  impact = Vector3.Lerp(impact, Vector3.zero, 5*Time.deltaTime);
}

Some examples:

  • When shooting with raycast (weapon script):


    if (Physics.Raycast(ray, hit)){
    if (hit.rigidbody){ // it the object hit is a rigidbody, add force:
    hit.rigidbody.AddForce(ray.direction * force);
    } else { // but if it’s a character with ImpactReceiver script, add impact:
    var script: ImpactReceiver = hit.gameObject.GetComponent(ImpactReceiver);
    if (script) script.AddImpact(ray.direction, force);
    }

  • When applying explosion force (explosion script):


    colliders = Physics.OverlapSphere (explosionPosition, explosionRadius);
    for (var hit in colliders) {
    if (hit.rigidbody){ // if it’s a rigidbody, add explosion force:
    hit.rigidbody.AddExplosionForce(explosionPower, explosionPosition, explosionRadius, 3.0);
    } else { // but if it’s a character with ImpactReceiver, add the impact:
    var script: ImpactReceiver = hit.transform.GetComponent(ImpactReceiver);
    if (script){
    var dir = hit.transform.position - explosionPosition;
    var force = Mathf.Clamp(explosionPower/3, 0, 15);
    script.AddImpact(dir, force);
    }
    }
    }

i searched for this, found it , and converted to c# :slight_smile:
thx aldonaletto, its really helpful and amazing

using UnityEngine;

using System.Collections;
    
    public class ImpactReceiver : MonoBehaviour {
    	float mass = 3.0F; // defines the character mass
    	Vector3 impact = Vector3.zero;
    	private CharacterController character;
    	// Use this for initialization
    	void Start () {
    		 character = GetComponent<CharacterController>();
    	}
    	
    	// Update is called once per frame
    	void Update () {
    	 // apply the impact force:
     	 if (impact.magnitude > 0.2F) character.Move(impact * Time.deltaTime);
     		 // consumes the impact energy each cycle:
     		 impact = Vector3.Lerp(impact, Vector3.zero, 5*Time.deltaTime);
    	}
    	// call this function to add an impact force:
    	public void AddImpact(Vector3 dir, float force){
    	  dir.Normalize();
    	  if (dir.y < 0) dir.y = -dir.y; // reflect down force on the ground
    	  impact += dir.normalized * force / mass;
    	}
    }

If you like to keep number of scripts on gameobjects to a minimum, like me, then you can use this script.

I’ve modified hamcav’s script to a more centralized approach.

using UnityEngine;

using System.Collections.Generic;

public class ImpactReceiver : MonoBehaviour
{
	static Dictionary<GameObject, List<Vector3>> forcesOnGameObjects = new Dictionary<GameObject, List<Vector3>>();

	// Update is called once per frame
	void Update()
	{
		List<GameObject> gameObjectsToRemove = new List<GameObject>();

		//our shit
		foreach (KeyValuePair<GameObject, List<Vector3>> gameObjectToBeMoved in forcesOnGameObjects){
			GameObject target = gameObjectToBeMoved.Key;

			if (target == null) {
				gameObjectsToRemove.Add(gameObjectToBeMoved.Key);
				continue;
			}	

			List<Vector3> impacts = gameObjectToBeMoved.Value;
			Vector3 finalImpact = Vector3.zero;
			foreach (Vector3 impact in impacts) finalImpact += impact;

			if (finalImpact.magnitude > .2f)	{
				target.GetComponent<CharacterController>().Move(finalImpact * Time.deltaTime);
			}

			for (int i = 0 ; i < impacts.Count ; i++){
				forcesOnGameObjects[gameObjectToBeMoved.Key] <em>= Vector3.Lerp(impacts_, Vector3.zero, 5 * Time.deltaTime);_</em>

* }*
* }*
* foreach (GameObject gameObjectToRemove in gameObjectsToRemove){*
* forcesOnGameObjects.Remove(gameObject);*
* }*
* }*

* public static void AddImpactOnGameObject(GameObject target, Vector3 impact)*
* {*
* // add object with impact to tuple*
* if (!forcesOnGameObjects.ContainsKey(target))*
* {*
* List existingImpacts = new List();*
* existingImpacts.Add(impact);*
* forcesOnGameObjects.Add(target, existingImpacts);*
* }*
* else*
* {*
* List existingImpacts = forcesOnGameObjects[target];*
* existingImpacts.Add(impact);*
* forcesOnGameObjects[target] = existingImpacts;*
* }*
* }*
}

I made it affect your character locally and got rid of the magnitude thing which might have been a mistake or something but here it is.

To use put this script on your thing with the character controller (probably your player) and when you are going to trigger it use

ImpactReceiver.AddImpactOnGameObject(//the game object you put the script on//, new Vector3(x value, y value, z value);

_

using UnityEngine;

using System.Collections.Generic;

public class ImpactReceiver : MonoBehaviour
{
    static Dictionary<GameObject, List<Vector3>> forcesOnGameObjects = new Dictionary<GameObject, List<Vector3>>();

    // Update is called once per frame
    void Update()
    {
        List<GameObject> gameObjectsToRemove = new List<GameObject>();

        //(their) our (☭) shit
        foreach (KeyValuePair<GameObject, List<Vector3>> gameObjectToBeMoved in forcesOnGameObjects)
        {
            GameObject target = gameObjectToBeMoved.Key;

            if (target == null)
            {
                gameObjectsToRemove.Add(gameObjectToBeMoved.Key);
                continue;
            }

            List<Vector3> impacts = gameObjectToBeMoved.Value;
            Vector3 finalImpact = Vector3.zero;
            foreach (Vector3 impact in impacts) finalImpact += impact;


            finalImpact = finalImpact.x * transform.right + transform.forward * finalImpact.z;
                target.GetComponent<CharacterController>().Move(finalImpact * Time.deltaTime);

            for (int i = 0; i < impacts.Count; i++)
            {
                forcesOnGameObjects[gameObjectToBeMoved.Key] <em>= Vector3.Lerp(impacts_, Vector3.zero, 5 * Time.deltaTime);_</em>

}
}
foreach (GameObject gameObjectToRemove in gameObjectsToRemove)
{
forcesOnGameObjects.Remove(gameObject);
}
}

public static void AddImpactOnGameObject(GameObject target, Vector3 impact)
{
// add object with impact to tuple
if (!forcesOnGameObjects.ContainsKey(target))
{
List existingImpacts = new List();
existingImpacts.Add(impact);
forcesOnGameObjects.Add(target, existingImpacts);
}
else
{
List existingImpacts = forcesOnGameObjects[target];
existingImpacts.Add(impact);
forcesOnGameObjects[target] = existingImpacts;
}
}
}