Search Unity

Mobile - Screen Touch Shooting Problem

Discussion in 'Scripting' started by Watchers, Sep 27, 2016.

  1. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    Hi everyone,
    amateur dev here, me and a colleague are developing a mobile multiplayer minigame with 2 players moving around (think isometric / 3D scene with the camera looking at a 45degree angle) and we 've been having trouble implementing the shooting mechanic.

    The basis is that you touch the screen and the bullet will go from the player to the point in the screen (and hopefully we find a way for the bullet to go beyond that point).

    So basically you touch the screen and the bullet flies towards that direction.

    Currently we are using (unsuccessfully) the design of our ground being a collection of mini plane objects stuck together and we have implemented a script that uses a raycast so we can get the x and z position of the certain mini object so we can take that position and move our bullet towards that x and z position.

    We have successfully used the MoveTowards function and the bullet does fly to the direction and stops there.

    Right now we have the problem of any time we touch the screen - all bullets fly towards that direction(not a multiplayer problem, the bullet script mover has an issue).
    After fiddling around now we get random offsets on the x or z axis after we touch something, or it gives us a wrong value and we cannot understand why (Keep in mind we might touch the same place and it might give an error value then after spamming it , it will give the correct value every time).

    I will submit our code below, some insight would be wonderful!

    Thanks

    Script References
    BulletMover.cs is attatched to the bullet prefab.
    PlayerController.cs is attatched to the player prefab.
    GetShootingPos.cs is attatched to the camera which is a child of the player prefab.

    P.S: I am certain it is something simple that we just cannot find due to inexperience.

    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class GetShootingPos : MonoBehaviour {
    5.     Camera camera;
    6.  
    7.     private RaycastHit hit;
    8.     private GameObject myObject;
    9.     public  Vector3 posx;
    10.     public static Vector3 shootpos;
    11.  
    12.     void Start() {
    13.         camera = GetComponent<Camera>();
    14.     }
    15.  
    16.     void Update() {
    17.         if (Input.touchCount > 0) {
    18.  
    19.             foreach (Touch touch in Input.touches) {
    20.  
    21.  
    22.                 if (Input.touchCount == 2 && touch.fingerId==1) {
    23.                     Ray ray = camera.ScreenPointToRay (touch.position);
    24.                     //Debug.DrawRay (camera.transform.position, touch.position, Color.green);
    25.                     if (Physics.Raycast (ray, out hit)) {
    26.                         //Debug.Log (hit.transform.position.x);
    27.                         posx = hit.transform.position;
    28.                         posx.y = 0.0f;
    29.                         shootpos = posx;
    30.                     }
    31.  
    32.  
    33.                 }
    34.             }
    35.         }
    36.  
    37.  
    38.     }
    39.  
    40.  
    41.     public static Vector3 getShootPos()
    42.     {
    43.  
    44.         return shootpos;
    45.     }
    46. }
    47.  
    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class BulletMover : MonoBehaviour {
    5.     public float speed;
    6.  
    7.     void Update () {
    8.        Vector3.MoveTowards(transform.position,PlayerController.mover[0],Time.deltaTime*speed  )    ;
    9.     }
    10. }
    11.  

    Code (CSharp):
    1.  
    2. public class PlayerController : NetworkBehaviour {
    3.  
    4.  
    5.  
    6.  
    7.     //==== FOR SHOOTING ======
    8.     public GameObject shotprefab;
    9.     public  Transform shotSpawn;
    10.     public float fireRate;
    11.     private float nextFire;
    12.     public float bulletspeed;
    13.  
    14.  
    15.     //experimental
    16.     public static Vector3[] mover = new Vector3[1];
    17. // the mover vector3 is an array because we were fiddling with the idea of using arrays
    18. // and the elements for specific bullets (stupid i know)
    19.  
    20.  
    21.     private void Update ()
    22.     {
    23.         GetNetworkSendInterval ();
    24.  
    25.  
    26.  
    27.         if (!isLocalPlayer)
    28.             return;
    29.    // 2 fingers on screen, the first finger uses a joystick
    30.         if (( Input.touchCount == 2 ) && Time.time > nextFire) {
    31.             nextFire = Time.time + fireRate;
    32.             CmdFire();
    33.        
    34.         }
    35.  
    36.  
    37.  
    38.     }
    39.  
    40.      
    41.     [Command] // <--server
    42.     void CmdFire()
    43.     {
    44.  
    45.  
    46.  
    47.          
    48.         mover [0] = GetShootingPos.getShootPos ();
    49.         Debug.Log (mover[0]); // this will sometimes give us stupid values and it is unknown why
    50.         // create the bullet object from the bullet prefab
    51.  
    52.         var bullet = (GameObject)Instantiate(shotprefab, shotSpawn.position, shotSpawn.rotation);
    53.          // BULLETMOVER SCRIPT WORKS HERE ^
    54.      
    55.         //spawn the bullet on the clients
    56.         NetworkServer.Spawn(bullet);
    57.  
    58.         // when the bullet is destroyed on the server it will automaticaly be destroyed on clients
    59.         Destroy(bullet, 5.0f);    
    60.     }
    61.  
     
    Last edited: Sep 27, 2016
  2. chubbspet

    chubbspet

    Joined:
    Feb 18, 2010
    Posts:
    1,220
    Be careful when using static variables in things like position. For example, line 10 of the first script might be a problem - the fact that it is static means that if you change it's value anywhere, it will apply everywhere.
     
    Watchers likes this.
  3. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    I see... that actually might explain a lot.
    How do I go about changing values from script to script without using static methods or variables? Creating an object of a scriptB class didn't seem like a good idea... Am I wrong? How would I go about it?

    Thanks for the reply!
     
  4. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Is the GetShootingPos script attached to anything else? By that i mean you only have one instance of it.. that one instance being on the camera that is the child of the player. If so then the fact that its static isn't your bug. (not that its good practice, I'll show below a better way) but if you only have one instance of this script, then we can rule out multiple copies all sharing that one variable being your bug.

    A better way to get the shooting position:
    Code (CSharp):
    1. Vector3 shootPos;
    2.  
    3. public Vector3 ShootPos
    4. {
    5.       get {return shootPos;}
    6.       // uncomment this if you want others to be able to change shootPos
    7.       // set { shootPos = value;}
    8. }
    Then other objects would need one of two things either:
    Code (CSharp):
    1. // You assign this in the editor
    2. public GameObject shootPosObject;
    or you tag your GameObject that has the ShootPos on it with a tag like "ShootPos"
    then the code would do this to get the GameObject it needed:
    Code (CSharp):
    1. GameObject shootPosObject = GameObject.FindWithTag("ShootPos");
    Once you have a handle on the GameObject this code will get your ShootPos variable:
    Code (CSharp):
    1. GetShootingPos shootScript = shootPosObject.GetComponent<GetShootingPos>();
    2. mover[0] = shootScript.ShootPos;
    This will work for getting a variable on any GameObject to any other GameObject.

    You should use this same idea for PlayerController and mover

    Now you say this:
    It looks to me like thats what you coded. Though I'll admit I'm not 100% sure what this line is saying as I have no experience at all with mobile devices or Input.Touch:
    Code (CSharp):
    1. if (Input.touchCount == 2 && touch.fingerId==1) {
    But It looks like your saying when you touch (and drag?) the screen set a ShooterPosition.
    Then your PlayerController code copies that Shooter Position into mover[0]
    Then every bullet look to mover[0] in its update and moves towards it.

    So I might be a little off on what condition shooterPos is being set, but once your GetShooterPos script sets it, all the bullets immediately fly towards that spot.

    If your trying to get each bullet to fly towards the spot shooterPos was at the moment that bullet was created you need to do this:
    Code (CSharp):
    1. public class BulletMover : MonoBehaviour {
    2.     public float speed;
    3.  
    4.     Vector3 myTarget;
    5.  
    6.     // I'm going to assume you've Tagged the PlayerController
    7.     // with "PlayerController" and changed mover[0]  to NOT be
    8.     // static and has a Property called Mover (with the get/set
    9.     //  method I showed above
    10.     void Start()
    11.     {
    12.              GameObject playerController = GameObject.FindObjectWithTag("PlayerController");
    13.               PlayerController pcScript = playerController.GetComponent<PlayerController>();
    14.                myTarget = pcScript.Mover;
    15.     }
    16.  
    17.     void Update () {
    18.        Vector3.MoveTowards(transform.position,myTarget,Time.deltaTime*speed  )    ;
    19.     }
    20. }
    Now each bullet only finds its target one time when its instantiated, and each bullet has a different target based on where the finger was when you created it.
     
    Watchers likes this.
  5. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    takatok,

    This is a great reply. I did indeed code it this way and I knew what was wrong then I tried to make an array that would store the positions which was a horrible idea.

    I was just stuck and needed some simple insight which is EXACTLY what you gave me (I admit I was so mental blocked that I started working on other stuff like hazards and power-ups, and funnily I use <GetComponent> to work around with stuff from other scripts there, lol)

    Two very valuable guidance tips that will no doubt fix my problem with the shooting mechanic.

    Thanks a lot and I will make sure to post working code.

    Any more feedback from you or others is greatly appreciated.

    P.S: The idea of having multiple mini planes was put forward by my colleague. I think there is a way to get touch to world 3d coordinates built in the Unity API so keep that in mind.
     
  6. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    Alright I fixed my coding issues with the above tips and it works almost perfeclty.

    There is an issue in my code... the mover variable always has the previous value, as in the bullet will move with the previous touch value.

    I am thinking of introducing delay to my BulletMover script in order for it to take the correct value.

    I am not sure if this is the problem and some insight would be helpful.
    I will be researching for it now...

    Also, is there a way to move an object towards a location and beyond that? MoveTowards will only move it till the position I am touching on my screen. Any ideas?

    Thanks a lot and once I fix the Mover issue I shall post my working code.
     
  7. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    This is because every frame Unity calls all your scripts Update functions. However, there is not way to force Unity to call them in any particular order. So whats happening is on the frame that this is true:
    Code (CSharp):
    1. if (( Input.touchCount == 2 ) && Time.time > nextFire) {
    Unity is calling PlayerController first. Its getting ShooterPos and instantiating a new bullet. ShooterPos is still set to the pos the *LAST* time the above condition was true. Now Unity calls GetShooterPos Update, and ShooterPos is set to the correct value. We can fix this by adding a Coroutine to PlayerController to wait one frame before making our bullet. That way we know GetShooterPos Update gets called and has the correct value:
    Code (CSharp):
    1.  if (( Input.touchCount == 2 ) && Time.time > nextFire) {
    2.            StartCoroutine(FireBullet());
    3.   }
    4.          
    5.  
    6. IEnumerator FireBullet()
    7. {
    8.           yield return null;  // Wait one Frame
    9.           nextFire = Time.time + fireRate;
    10.           CmdFire();
    11. }
     
  8. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    takatok,

    thank you very much. I came to the very same conclusion but I wasn't sure of if I was right or not. I 've used CoRoutines for other stuff in my code to bypass this but I couldn't find the specific point to do it here in order to get it right.

    While I researched i found there's a built-in way in Unity to fix this, the Script Execution Order in Edit->Project Settings.

    I used that and everything works perfectly now - but I am not sure if it's better to do that rather than using a custom script Co-Routine like the one I have in my other stuff or the one you 've showcased above.

    Thank you very much for the re-assurance and insight, you 've helped tons.

    I 've also just posted something new in Unity Questions/Answers, you can check it out if you wish

    http://answers.unity3d.com/questions/1249109/moving-an-objectbullets-towards-a-vector-position.html

    Good day!
     
  9. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    Also, you have a very small bug in your code, by having the

    nextFire = Time.Time + fireRate;

    inside the IEnumerator 2 bullets spawn.

    bug free like so:


    Code (CSharp):
    1.  if (( Input.touchCount == 2 ) && Time.time > nextFire) {
    2.            nextFire = Time.time + fireRate;
    3.            StartCoroutine(FireBullet());
    4.   }
    5.      
    6. IEnumerator FireBullet()
    7. {
    8.           yield return null;  // Wait one Frame      
    9.           CmdFire();
    10. }

    It works perfectly this way. I think I ll use this one because I don't know exactly how the Script Execution Order works.

    Thank you tons!
     
  10. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    I think I'd avoid using this. Waiting one frame to execute code that is dependant on all other Updates being called is very easy to do, and totally unnoticeable for the User. No one is going to notice a 0.02 ->0.0333s delay.

    Its also easier to comment and have others understand what your doing. If you are working with a partner or team, they may not know you have an execution order set, so it may not be obvious that certain updates are being fired in a certain order.

    Depending on Script Execution Order to make sure these things are ordered correctly could get messy if you start using it on lots and lots of scripts with multiple interactions between each other. That being said, in your case with just 2 scripts that have to be ordered is obviously very simple and a reasonable approach as well.
     
    Watchers likes this.
  11. Watchers

    Watchers

    Joined:
    Sep 27, 2016
    Posts:
    8
    Posting Working Code for anyone who might stumble upon this thread looking for answers

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Networking;
    3. using System.Collections;
    4.  
    5. public class PlayerController : NetworkBehaviour {
    6.  
    7.     //==== FOR SHOOTING ======
    8.     public GameObject shootPosObject;
    9.     public GameObject shotprefab;
    10.     public  Transform shotSpawn;
    11.     public float fireRate;
    12.     private float nextFire;
    13.     public float bulletspeed;
    14.     public  Vector3 mover ;
    15.     //==== FOR SHOOTING ======
    16.  
    17.     private void Update ()
    18.     {
    19.         GetNetworkSendInterval ();
    20.         deltaTimer += (Time.deltaTime - deltaTimer) * 0.1f; // for fps display    
    21.      
    22.         if (!isLocalPlayer)
    23.             return;
    24.  
    25.         if ((Input.GetButton("Fire2") || Input.touchCount == 2 ) && Time.time > nextFire) {
    26.             nextFire = Time.time + fireRate;
    27.             StartCoroutine (FireBullet ()); // waiting so we get the correct value on BulletMover
    28.         }
    29.  
    30.     }
    31.  
    32.     IEnumerator FireBullet()
    33.     {
    34.         yield return null;  // Wait one Frame
    35.         CmdFire();
    36.     }
    37.  
    38.      
    39.     [Command] // <--server
    40.     void CmdFire()
    41.     {
    42.         GetShootingPos shootScript = shootPosObject.GetComponent<GetShootingPos> ();
    43.  
    44.         mover = shootScript.ShootPos;      
    45.         // create the bullet object from the bullet prefab
    46.         var bullet = (GameObject)Instantiate(shotprefab, shotSpawn.position, shotSpawn.rotation);
    47.         //spawn the bullet on the clients
    48.         NetworkServer.Spawn(bullet);
    49.         // when the bullet is destroyed on the server it will automaticaly be destroyed on clients
    50.         Destroy(bullet, 5.0f);    
    51.     }
    52.  
    53. }


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class GetShootingPos : MonoBehaviour {
    5.     Camera camera;
    6.  
    7.     private RaycastHit hit;
    8.     private GameObject myObject;
    9.     public  Vector3 posx;
    10.     public  Vector3 shootpos;
    11.  
    12.     void Start() {
    13.         camera = GetComponent<Camera>();
    14.     }
    15.  
    16.     void Update() {
    17.         if (Input.touchCount > 0) {
    18.  
    19.             foreach (Touch touch in Input.touches) {
    20.  
    21.  
    22.                 if (Input.touchCount == 2 && touch.fingerId==1) {
    23.                     Ray ray = camera.ScreenPointToRay (touch.position);
    24.                     //Debug.DrawRay (camera.transform.position, touch.position, Color.green);
    25.                     if (Physics.Raycast (ray, out hit)) {
    26.                         //Debug.Log (hit.transform.position.x);
    27.                         posx = hit.transform.position;
    28.                         posx.y = 0.0f;
    29.                         shootpos = posx;
    30.                     }
    31.  
    32.  
    33.                 }
    34.             }
    35.         }
    36.  
    37.  
    38.     }
    39.  
    40.  
    41.     public  Vector3 ShootPos
    42.     {
    43.         get { return shootpos;}
    44.  
    45.         //set { ShootPos = value; }
    46.     }
    47. }
    48.  


    Code (CSharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class BulletMover : MonoBehaviour {
    5.     public float speed;
    6.     // Use this for initialization
    7.     Vector3 myTarget;
    8.     void Start () {      
    9.         GameObject playerController = GameObject.FindGameObjectWithTag ("Player");
    10.         PlayerController pcScript = playerController.GetComponent<PlayerController>();
    11.         myTarget = pcScript.Mover;      
    12.  
    13.     }
    14.  
    15.  
    16.  
    17.     // Update is called once per frame
    18.     void Update () {
    19.         transform.position = Vector3.MoveTowards(transform.position,myTarget,Time.deltaTime*speed);
    20.     }
    21. }
    22.  
     
  12. takatok

    takatok

    Joined:
    Aug 18, 2016
    Posts:
    1,496
    Great! glad its working.