- Home /
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?
Could alter the players transformation.position and use a ray to check if anything is behind them.
Would it be feasible to temporarily disable the character controller?
Answer by aldonaletto · Apr 21, 2012 at 01:49 PM
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); } } } ...
And what if I want the script to be called OnTriggerEnter, I have an explosion which is a sphere with Trigger collision that automatically grows in size.
This is good stuff. +1 from me. It should have more votes I$$anonymous$$O.
I think it might be appropriate to simplify things a bit since you can just work with the force vector and not split out a normalized vector and its magnitude.
I recommend Danzou marks this as answered.
@s_guy, you're right: I actually used the force vector in my own scripts - it's simpler, and additionally allows to call AddImpact via Send$$anonymous$$essage.
Thanks @aldonaletto ! This is super helpful for me!
This works except that sometimes the force applied on Y gets glitchy where the object isn't thrown into the air with the intended value, like barely gets lifted off the ground by 1 inch and sometimes it's too strong. I'm not sure what's the cause but it seems dir.normalized is one possible cause as its value fluctuates depending on the positioning of the target and force source.
Answer by hamcav · Sep 04, 2013 at 11:08 PM
i searched for this, found it , and converted to c# :) 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;
}
}
Answer by Swibo · Oct 07, 2017 at 08:35 AM
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][i] = Vector3.Lerp(impacts[i], Vector3.zero, 5 * Time.deltaTime);
}
}
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<Vector3> existingImpacts = new List<Vector3>();
existingImpacts.Add(impact);
forcesOnGameObjects.Add(target, existingImpacts);
}
else
{
List<Vector3> existingImpacts = forcesOnGameObjects[target];
existingImpacts.Add(impact);
forcesOnGameObjects[target] = existingImpacts;
}
}
}
Answer by SlavCabbag · Jul 17, 2021 at 02:03 PM
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][i] = Vector3.Lerp(impacts[i], Vector3.zero, 5 * Time.deltaTime);
}
}
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<Vector3> existingImpacts = new List<Vector3>();
existingImpacts.Add(impact);
forcesOnGameObjects.Add(target, existingImpacts);
}
else
{
List<Vector3> existingImpacts = forcesOnGameObjects[target];
existingImpacts.Add(impact);
forcesOnGameObjects[target] = existingImpacts;
}
}
}