Forcing movement of the mouse cursor when i shoot and pushing enemies

Hello,
I’ve been learning unity and programming for that matter for the past week and yesterday i have encountered 2 problems that i just couldn’t solve on my own or with the help of available materials on the internet so, here i am.

  1. basically, im trying to force movement of mouse cursor whenever i shoot to imitate recoil of the gun in a 2D, top-down shooter game.
    the movement im trying to get has to be different for each gun, so probably a customizable “recoil” value.

  2. Another problem - i want to push enemies a little bit back whenever they are being hit by a projectile with speed higher than X. I’ve tried doing that with RigidBody2D but it didnt work :frowning: Probably has to do something with projectile code(last one here)

My current example codes:

Enemy code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class enemy : MonoBehaviour
{
    public float health;

public void TakeDamage(float damage) {

health -= damage;
if (health <=0)
{
    Destroy(gameObject);
}

}
    // Start is called before the first frame update
    void Start()
    {
       
    }

    // Update is called once per frame
    void Update()
    {
       
    }
}

Player code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using MilkShake;


public class player : MonoBehaviour
{
        [Header("Movement speed")]
    public int movementSpeed;
        [Header("Movement speed while shooting")]
    public int movementSpeed2;
        [Header("Movement speed while zoomed in")]
    public int movementSpeed3;
    public int rotationSpeed;
    private Rigidbody2D rb;
    private Vector2 moveAmount;
    private Animator anim;

    
    private float _horizontalInput = 0;
    private float _verticalInput = 0;



    // Start is called before the first frame update
    void Start()
    {
        anim = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>();
  
    }

    // Update is called once per frame
    void Update()
    {



    Vector2 moveInput = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
    // movement speed while shooting
    if (Input.GetKey(KeyCode.Mouse0)){
    rb.AddForce(moveInput.normalized * movementSpeed2);
    }
    //movement speed while zoomed in
        if (Input.GetKey(KeyCode.Mouse1)){
    rb.AddForce(moveInput.normalized * movementSpeed3);
    }
    //movement speed
    else{
    rb.AddForce(moveInput.normalized * movementSpeed);
    }
    //animation
  if (moveInput != Vector2.zero) {
      anim.SetBool("isWalking", true);
    }
       else {
           anim.SetBool("isWalking", false);
           
        }
    }
    private void FixedUpdate()
    {
rb.MovePosition(rb.position + moveAmount * Time.fixedDeltaTime);
   
    }

}

Gun code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using MilkShake;
public class kar98k : MonoBehaviour
{
             [Header("Game objects")]
    public GameObject projectile;
    //fire from barrel
    public GameObject muzzleflash;
    public Transform shotPoint;
            [Header("Rate of fire")]
    // rate of fire of a gun
    public float rateOfFire;
    //timeBetweenShooting = shotTime, timeBetweenShots = rateoffire;
    private float shotTime;
    public float timeBetweenShooting;
         [Header("Spread settings")]
    public float spread;
    public float spreadmovews;
    public float spreadmovead;
             [Header("Reload settings")]
    public float reloadTime;
             [Header("Is full auto?")]
    public bool allowButtonHold;
             [Header("Ammo settings")]
    public int bulletsLeft, bulletsShot;
    public int magazineSize, magazineSizefull, bulletsPerTap;
             [Header("Other settings")]
            
    public float range;
    public TextMeshProUGUI text;
    bool shooting, readyToShoot, reloading;
    private float spreadx;
    private float spreadm;

//animation
    public Animator animator;

//shaker
public Shaker CamShake;
public ShakePreset Kar98kRecoil;


private void Awake()
{
readyToShoot = true;
}
    // Start is called before the first frame update
    void Start()
    {
    }

    // Update is called once per frame
    void Update()
    {
  float spreadx = Random.Range(-spread, spread);

             //rozrzut w ruchu
            if (Input.GetKey(KeyCode.W) || Input.GetKey(KeyCode.S) || Input.GetKey(KeyCode.A) || Input.GetKey(KeyCode.D))
   
        spreadm = Random.Range(-spread, spread) * spreadmovews;
        else spreadm = spreadx;
                


                  
// weapon
        Vector2 direction = Camera.main.ScreenToWorldPoint(Input.mousePosition) - transform.position;
        float angle = Mathf.Atan2(direction.y, direction.x) * Mathf.Rad2Deg;
        Quaternion rotation = Quaternion.AngleAxis(angle - 90, Vector3.forward);
        transform.rotation = rotation;
                MyInput();
//angle, spread
        Quaternion bulletSpread = Quaternion.Euler(new Vector3(0, 0, spreadm));



            if (Time.time >= shotTime) {
        if (readyToShoot && shooting && !reloading && magazineSize > 0){
            bulletsShot = bulletsPerTap;
            Shoot();
                Instantiate(projectile, shotPoint.position, rotation * bulletSpread);
                Instantiate(muzzleflash, shotPoint.position, transform.rotation);
                shotTime = Time.time + rateOfFire;
            }
       
        }
    //}

    }
    private void MyInput ()
    {
        //shooting full auto or not
                if (allowButtonHold) shooting = Input.GetKey(KeyCode.Mouse0);
        else shooting = Input.GetKeyDown(KeyCode.Mouse0);

if (Input.GetKeyDown(KeyCode.R) && bulletsLeft > magazineSize && !reloading){
     Reload();
     }

}
//----------------------------------- S H O O T I N G -----------------------------------------
   
public void Shoot ()
{
//sound
    AudioSource audio = gameObject.AddComponent<AudioSource>();
    audio.PlayOneShot((AudioClip)Resources.Load("kar98ks"));
//camera shake, recoil
CamShake.Shake(Kar98kRecoil);

    readyToShoot = false;
            magazineSize--;
            bulletsShot--;
                Invoke("ResetShot", timeBetweenShooting);
             if(magazineSize > 0 && bulletsShot > 0)
                 {
                    Invoke("Shoot", rateOfFire);
                        //animation
                          animator.SetTrigger("attack1");
                 }
                    //auto reload
    if(magazineSize == 0 && bulletsLeft > 0){
    Reload();
    }

       
}
private void ResetShot()
    {
        readyToShoot = true;
    }
    private void Reload()
    {
        reloading = true;
        Invoke("ReloadFinished", reloadTime);
    }
    private void ReloadFinished()
    {
        magazineSize = magazineSizefull;
        bulletsLeft = bulletsLeft - magazineSizefull;
        reloading = false;
    }
}

Projectile code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class N9mm : MonoBehaviour
{
   [Header("Speed settings")]
   // speed of the bullet
    public float SpeedLossFactor; //has to be below 1
    public float SpeedIncreaseFactor;
    public float SpeedMax;
    private float speedDamageFactor; //speed incease by each update
    public float speed = 50;

    [Header("Damage Settings")]

    public float damageMin;
    public float damageMax;
    private float damage;
    private int damageAfterPen;

     [Header("Other Settings")]
     public float lifeTime = 5;
           [Header("Is armor piercing?")]
     public float arp;
    // lifetime of a bullet
//sound

    // Start is called before the first frame update
    void Start()
    {

      //bullet dissapears
      Destroy(gameObject, lifeTime); 
    }

    // Update is called once per frame
    void Update()
    {
           

      if(speed < SpeedMax && speed > 40){
      speed = speed + SpeedIncreaseFactor;
      }
      speedDamageFactor = speed - 50;
        transform.Translate(Vector2.up * speed * Time.deltaTime);


    }
void DestroyProjectile() {
  Destroy(gameObject);
}
    private void OnTriggerEnter2D(Collider2D collision){

      float damage = Random.Range(damageMin, damageMax) + speedDamageFactor;
      if (collision.tag == "enemy" && speed > 60 && arp == 0)
      {
        // if speed is above 60 and armor piercing = 0, the object thats been hit is pushed.
        collision.GetComponent<enemy>().TakeDamage(damage);
        speed = speed * SpeedLossFactor;
      } if (collision.tag == "enemy" && speed > 60 && arp == 1) {
        // if speed is above 60 and armor piercing = 1, the bullet goes through the target without pushing him
        collision.GetComponent<enemy>().TakeDamage(damage);
        speed = speed * SpeedLossFactor;
      }
      if (collision.tag == "enemy" && speed <= 60) {
        // if bullet speed is below 60 the target wont be pushed back.
        collision.GetComponent<enemy>().TakeDamage(damage);
        speed = speed * SpeedLossFactor;
      }
            if(collision.tag == "objects"){
              collision.GetComponent<objects>().TakeDamage(damage);
        speed = speed * SpeedLossFactor;
      }
      if(collision.tag == "ally"){
        speed = speed * SpeedLossFactor;
      }
              if (speed <= 25){
        DestroyProjectile();
      }

    }
}

any help would be appreciated.

Hey there,

First of all, apologies for not addressing your problems immediately but I think this is very important, especially since you are a beginner to programming and it would be bad for you to get some bad habits that make your code less understandable, maintainable and scalable.
I would delete the empty start and update methods (you can always add them later if needed) to make the code cleaner. Also, it is very important that you give your variables more meaningful names- so that you and other programmers you may work with/ask their assistance can easily understand the purpose of each variable. Using comments is good, and I see you use them often, but unless your variable is responsible for something really complex that is hard to briefly explain, you should never rely on comments or headers to explain the purpose of a variable. So better names for the variables in Player script would be:

  • movementSpeed > normalMovementSpeed
  • movementSpeed2 > shootingMovementSpeed
  • movementSpeed3 > zoomedInMovementSpeed

I’d always rather have long variable names, then ones that cause me to waste time (which could be a lot) trying to understand.

Also, it is widely accepted that classes names should start with a capital letter. Also, I would change N9mm to Bullet/Projectile.

Furthermore, you could have the same behaviour with a lot less if statements in your Projectile script (which would make the code a lot cleaner and easy to understand) if you nest the if statements. It is also more performant, you could check if the collision’s tag is enemy once instead of 3 times, and check the speed once if you add an else statement.

Now I will get to your problems:
I have never made this type of project myself, so I can’t guarantee it’ll work for you- but I can tell you what would be the first thing I’d try. I would make a sprite for the crosshair, with a rigidbody2d component (with linear drag) and a Crosshair script component. Of course I would also make the bullets move at the direction of the crosshair gameobject instead of the mouse position, so Singleton is probably useful here.

The Crosshair script would have a bool variable which determines if crosshair position is currently being affected by recoil (name suggestion: isAffectedByRecoil), a float field that determines at which distance the crosshair should no longer be affected by recoil (naming this variable is harder so I probably would also add a comment for it, I suggest: distanceFromMouseForNoRecoil), and a float field which determined the movement speed of the crosshair to return to the mouse position when it is being affected by recoil (name suggestion: movementTowardsMouseSpeed), and right after player stops shooting until the cursor returns to mouse position.
In the FixedUpdate method I’d check if the crosshair’s position is currently being affected by recoil:

  • If so, I would gradually move it towards the mouse position according to our movementTowardsMouseSpeed field.

  • If the crosshair’s distance to our mouse position is smaller than distanceFromMouseForNoRecoil, it shall no longer be affected by recoil, so I’d set isAffectedByRecoil to false

  • Else, move the cursor to the exact position of the mouse

To set the gun’s recoil, I’d add an array of Vector2 (if you don’t know what arrays are, I strongly recommend you learn about them before continuing to read this comment, here is a video you can watch from a youtuber who also makes Unity tutorials you may find helpful:

)
I’d also add a float field that sets how much time it takes for the index of the recoil array to reset (so that if player shoots 2 bullets for example, after a specified time the force which we’ll add to the crosshair will be the force at index 0 instead of 2 the next time the player shoots). To make the recoil effect better, I would probably change the force according to the time the player took since the last shot if the index of the recoil we apply isn’t 0.

About your second issue (pushing the enemies backwards when hit), I haven’t seen a single line of code that adds force (which I assume is the best way to do it for your type of game) to the enemy’s rigidbody2d, sets its velocity or moves its position, so unless you forgot to share some lines of code, it obviously shouldn’t happen.

Hope this helps, you can message me back if you need to and I’ll try to reply (I am very busy nowadays so can’t promise I’ll be responsive).