0
\$\begingroup\$

I am making an FPS game in Unity, trying to keep myself to OOP principles as much as I can but I found dead-end.

I want to add recoil to my game, now logically thinking the amount of recoil should be stored in the Weapon instances, but the recoil itself should be done by the Camera. The problem here, it doesn't make sense for the weapon to have a dependency towards the Camera, I think this could cause trouble down the line. I thought of making an Action (observer pattern), where the Weapon is a broadcaster while the Camera is an observer.

The problem here is there will be multiple players, so the Camera instance shall know if it's their Player who made the shot (the weapon would broadcast it's Player reference as well), so in order to do this, every Camera should store a reference to their own Player which again brings the problem I described with the gun. The Camera shouldn't have a reference to its player, for example, if I later want to add the ability to spectate a game, this would cause problems.

  • So the Player can have a Camera dependency
  • The Camera shouldn't have any dependencies
  • The Weapon can have a Player dependency

How would one go about implementing this recoil, how could I avoid the problems I've described?

\$\endgroup\$
1
\$\begingroup\$

One simple method is to add a UnityEvent. Here I've chosen to put it on the character controller, so I don't need to rewire it each time the player picks up a new weapon.

public class FPSController : MonoBehaviour {
    [System.Serializable]
    public class RecoilEvent : UnityEvent<Vector3> {}

    public RecoilEvent onRecoil;

    // In Unity 2020+, you can skip the class definition and just write:
    // public UnityEvent<Vector3> onRecoil;

    Weapon _currentWeapon;

    // Code for handling player input and movement goes here...

    void Fire() {
        Vector3 recoil = _currentWeapon.Fire();

        // Pass the recoil information along to anything that needs to react.
        // (Could include camera, reticle UI, character animation, even health!)
        if(onRecoil != null)
            onRecoil.Invoke(recoil);
    }
}

Now weapons just have to honour a contract that, when fired, they report back the recoil that results from that firing. I'm using a simple Vector3 here, but it could be a struct with more sophisticated recoil parameters, or even a reference to a ScriptableObject asset that you use to tune your various recoil profiles.

public class Weapon : MonoBehaviour {

    // Do ammo/cooldown management
    // (or delegate that responsibility to its own component)...

    public Vector3 Fire() {
        // Do the shooty bits...

        // Compute the appropriate recoil effect as needed.
        Vector3 recoil = GetRecoilVector();

        return recoil;
    }
}

Now your camera controller can expose a public method to anything that wants to apply a jerk to the camera:

public class CameraControl : MonoBehaviour {

    public void ApplyImpulse(Vector3 impulse) {
        // I'm agnostic as to the source of this impulse,
        // this method will just push the camera around.
        // So now you can re-use it for explosions, melee hits, etc.
    }
}

Note that there's no coupling here between the weapon and anything else. The weapon doesn't know who uses its recoil, and the camera doesn't know where the impulse came from, or even what "recoil" is. Neither the weapon nor camera need to know about RecoilEvent - as long as they both speak a common language (here, Vector3), they can communicate.

You can wire up this connection in data at design time, say if you have a "local player avatar" prefab that has the character, weapon, and camera all together.

Wiring up event in inspector

Or it can be wired up at runtime by the code that spawns the local player avatar and their camera:

FPSController avatar = Instantiate(localPlayerAvatarPrefab, spawnPosition, spawnOrientation);
FPSCamera camera = Instantiate(cameraPrefab);

camera.AttachToCharacter(avatar);
avatar.onRecoil.AddListener(camera.ApplyImpulse);

// This can of course include multiple listeners, if you have multiple systems that
// care about recoil, but don't all live inside the same prefab.
avatar.onRecoil.AddListener(fpsHUD.OnShoot);
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.