and ok, maybe you’re just mixing stuff together, because you’re considering the way the player is looking, so camera comes to you as a logical piece of this puzzle, but let’s start from the beginning. first off, I will assume you’re doing a 3D game, from the sound of it.
no, you absolutely don’t need a camera (I mean, for the logic of reactive shooting). but ok, we can use a camera as a state holder, if you’d like – the state being the looking direction. be mindful that you could just as well keep this state completely independently of any camera, and then update the camera to reflect this – just saying.
okay, so you keep the camera together with the player, it’s best to nest it then in the hierarchy, keep it as a child of player. when you shoot, you instantiate a bullet, assign it a forward vector, and some other parameters, maybe slight deviation in trajectory, maybe you give it a color, maybe a lifetime, hit points, all that jazz, and finally velocity. the prefab you use for a bullet has a script on it, so we don’t care about the bullet in this code, we only care about its kickback, right?
well, it’s very simple. the kickback of the bullet is a force that acts opposite to its velocity, and should be relatively small-ish because the mass of the bullet is many times smaller than the mass of the player. but if you want the game to possess an arcade flavor, you could make the strength of this force configurable.
let’s assume you have a working character controller, to start off with something.
Unity’s standard character controller comes with several features: mouse look, WASD, ground detection, jumping, and slippery contact to avoid getting stuck into walls and low ceilings, if I remember correctly.
to instantiate a bullet (I’d recommend reusing a prefab from a pool) you do something like this in player’s class
void Shoot() {
MyBullet bullet = GameObject.Instantiate(...).GetComponent<MyBullet>(); // position, rotation, etc go here
bullet.ConfigureAndRun(...); // other parameters go here, including velocity
}
then you have the bullet itself
using System;
using UnityEngine;
public class MyBullet : MonoBehaviour {
[NonSerialized] public int index;
[NonSerialized] public int hitPoints;
[NonSerialized] public Color color;
[NonSerialized] public Vector3 direction;
[NonSerialized] public float lifetime;
[NonSerialized] public float velocity;
public void ConfigureAndRun(int index, Vector3 direction, float velocity, float lifetime, int hp, Color color) {
this.index = index; // this is only for book-keeping, especially if reused from a pool of several pre-instantiated bullets
this.hitPoints = hp;
this.color = color;
this.direction = direction;
this.lifetime = lifetime;
this.velocity = velocity;
enabled = true;
}
void Update() {
transform.localPosition += velocity * direction * Time.deltaTime;
lifetime -= Time.deltaTime;
if(lifetime <= 0f) Kill();
}
// you need some OnCollision functions, as usual, to register hitting enemies and walls
public void Kill() {
enabled = false;
GameObject.Destroy(this.gameObject);
}
}
Now we can flesh out Shoot in Player with more stuff
using UnityEngine;
public class Player : MonoBehaviour {
[SerializeField] GameObject myBulletPrefab;
[SerializeField] [Range(0f, 20f)] float kickbackStrength = 10f;
[SerializeField] [Range(0f, .98f)] float kickbackDampening = 0.976f;
Vector3 _kickback;
public void Shoot(float kickbackStrength) {
// first you come up with the exact direction in which the player is shooting
// you want to keep this
var shootingDirection = deviateFromDirection(
origin: transform.position,
dir: transform.forward,
distance: 1f,
radius: 0.2f
);
// this object should not be parented to anything
// you need to make a bullet prefab that has MyBullet attached to it
// then link that prefab to 'myBulletPrefab' in the inspector
MyBullet bullet = GameObject.Instantiate(
original: myBulletPrefab,
position: transform.position,
rotation: transform.forward // (this isn't entirely true, but let's roll with this for now)
).GetComponent<MyBullet>();
bullet.ConfigureAndRun(
index: 0,
direction: shootingDirection, // now we send this to the bullet, to make it travel the right path
velocity: 10f,
lifetime: 5f,
hp: 10,
color: Color.red
);
// finally, you apply this direction as a force that affects the player
// for this we use _kickback vector as an accumulator
_kickback -= shootingDirection * kickbackStrength; // notice the minus, this is because kickbacks go in the opposite direction
}
// imagine a small circular target in front of the player, it is at some distance and has a radius
// the bullet path will randomly deviate from the center so that it always lands within this circle
// if you intend to have more than 5 bullets per frame coming off from multiple agents, then
// this can be made significantly faster, but in the general case this will suffice (it's still fast)
Vector3 deviateFromDirection(Vector3 origin, Vector3 dir, float distance, float radius) {
var c = origin + distance * dir;
var q = Quaternion.FromToRotation(Vector3.back, dir);
var p = c + q * (Vector3)(radius * Random.insideUnitCircle);
return (p - origin).normalized;
}
}
Now, alongside the usual stuff associated with character controller you additionally process this kickback in the Player’s update
void Update() {
_kickback *= kickbackDampening; // a cheap trick to progressively diminish this "force" until it's almost zero (keep it sufficiently larger than 0, but never at 1 or more)
transform.localPosition += _kickback * Time.deltaTime;
// regular player control goes here
// depending on where exactly you capture user input, you want to call Shoot() when some button is pressed
}
If you’ve made the standard shooting stuff before, hopefully this is enough to get you started.
(I’m also sure I’ve made an error somewhere and you’ll probably have to tweak the values until it resembles something of use, but the concept is here.)
(You also need to make some sort of shooting cooldown so that you can’t launch gazillion bullets with a short hold of a button)