Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Inverting Position on One Axis

Discussion in 'Scripting' started by PresidentPorpoise, Mar 2, 2017.

  1. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    Hello, I have been trying to achieve something for a long time and I am getting desperate. I have an object attached to my player sprite that marks the location that a projectile spawns at when the player clicks to shoot. THe problem is, when I used FlipX to flip the player sprite, the object does not also go to the other side of the player, so the projectiles spawn in the wrong place.

    Using C#, is there any way I could change transform.position.x to become negative and vice versa in order to make it "flip" with the player? I have tried doing this on my own but nothing I do apparently works with transform.position.

    Also, is there a better way to achieve this? Thanks in advanced.

    Player Movement Script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class PlayerMovement : MonoBehaviour
    6. {
    7.     public float maxSpeed;
    8.     public float jumpSpeed;
    9.     public float checkRadius;
    10.     public bool onGround;
    11.     public bool facingRight;  
    12.  
    13.     private Rigidbody2D playerRB;
    14.     private Animator playerAnim;  
    15.     private SpriteRenderer rend;
    16.     public Transform groundChecker;
    17.     public LayerMask groundLayer;
    18.  
    19.     private void Start()
    20.     {
    21.         playerRB = gameObject.GetComponent<Rigidbody2D>();
    22.         rend = GetComponent<SpriteRenderer>();
    23.         playerAnim = GetComponent<Animator>();
    24.         facingRight = true;
    25.         onGround = true;
    26.     }
    27.  
    28.     private void Update()
    29.     {
    30.         //if the player is on the ground and the spacebar has been pressed, the player jumps
    31.         if (onGround == true && Input.GetAxis("Jump") > 0)
    32.         {
    33.             onGround = false;
    34.             playerAnim.SetBool("Grounded", onGround);
    35.             playerRB.AddForce(new Vector2(0, jumpSpeed));
    36.         }
    37.  
    38.     }
    39.  
    40.     private void FixedUpdate()
    41.     {
    42.         //sets grounded to true if the overlapCircle overlaps something on the layer set as groundLayer
    43.         onGround = Physics2D.OverlapCircle(groundChecker.position, checkRadius, groundLayer);
    44.  
    45.         float move = Input.GetAxis("Horizontal");
    46.  
    47.         playerAnim.SetBool("Grounded", onGround);
    48.  
    49.         playerRB.velocity = new Vector2(move * maxSpeed, playerRB.velocity.y);
    50.         playerAnim.SetFloat("speed", Mathf.Abs(move));      
    51.  
    52.         if (move < 0 && facingRight)
    53.         {
    54.             turn();
    55.         }
    56.         else if (move > 0 && !facingRight)
    57.         {
    58.             turn();
    59.         }
    60.  
    61.     }
    62.  
    63.     void turn()
    64.     {
    65.         facingRight = !facingRight;
    66.         rend.flipX = !rend.flipX;
    67.     }
    68.  
    69. }
    70.  
    Projectile Script:
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class WizardProjectile : MonoBehaviour {
    6.  
    7.     private float speed;
    8.     public GameObject projectileSpawn;
    9.     public GameObject player;
    10.     public PlayerMovement playMov;
    11.     public BoxCollider2D col;
    12.     private Animator anim;
    13.     private SpriteRenderer rend;
    14.  
    15.     private void Awake()
    16.     {
    17.         projectileSpawn = GameObject.Find("Wizard Projectile Spawn");
    18.     }
    19.  
    20.     private void Start ()
    21.     {
    22.         //defining variables
    23.         speed = 15f;
    24.         player = GameObject.Find("Player");
    25.         col = GetComponent<BoxCollider2D>();
    26.         transform.position = projectileSpawn.transform.position;
    27.         rend = GetComponent<SpriteRenderer>();
    28.         anim = GetComponent<Animator>();
    29.  
    30.        //makes projectile face the same direction as the player
    31.        rend.flipX = player.GetComponent<SpriteRenderer>().flipX;
    32.     }
    33.  
    34.     private void Update()
    35.     {      
    36.  
    37.         // Will need to change all of this in order for the projectile to follow where the cursor clicked.
    38.         if (GetComponent<SpriteRenderer>().flipX == true)
    39.         {
    40.             transform.Translate(Vector2.left * Time.deltaTime * speed);
    41.         }
    42.         else if (GetComponent<SpriteRenderer>().flipX == false)
    43.         {
    44.             transform.Translate(Vector2.right * Time.deltaTime * speed);
    45.         }
    46.  
    47.         Destroy(gameObject, 2);
    48.     }
    49.  
    50.     private void OnTriggerEnter2D (Collider2D col)
    51.     {
    52.         anim.SetBool("Hit", true);
    53.         Destroy(gameObject);
    54.     }
    55. }
    56.  
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,816
    Let me assume a few things:

    1. your wizard moves around
    2. you want the notion of "the other side of your wizard" to be on the other side of him regardless of where he moves

    Therefore, one possibility is to make the spawn position a child gameobject of the wizard, so that it goes along with it.

    Another option is to make TWO of those invisible weapon spawns, one for normal and one for flipped, on each side of your wizard, as children of the wizard.

    In fact, if you do that then you can place them precisely on either side of the wizard, then use their positions to initialize the projectile on the correct side of the wizard.
     
  3. Jared_P

    Jared_P

    Joined:
    Apr 15, 2014
    Posts:
    12
    I'm away from my pc right now so what I could suggest for the mean time is that you can play safe by using "transform.localScale" to flip the object. This way all necessary things such as the collider, will be flipped also.
    For example:
    Code (CSharp):
    1. if (Input.GetAxis (horizontalAxis) < 0) {
    2.                         scale.x = -1;
    3.                         transform.localScale = scale;
    4.                     } else if (Input.GetAxis (horizontalAxis) > 0) {
    5.                         scale.x = 1;
    6.                         transform.localScale = scale;
    7.                     }
    BTW, I'll post my possible solution for your problem when I'm home...
     
  4. RoughSpaghetti3211

    RoughSpaghetti3211

    Joined:
    Aug 11, 2015
    Posts:
    1,695
    Could you use Unity transformation matrix to invert it ?
     
  5. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    The first thing you need to do is to keep a reference to your spawner object (Wizard Projectile Spawn) in your PlayerMovement object. As @Kurt-Dekker suggested, I'm assuming that this object is a child of the wizard. Which means that its local position can be easily flipped.

    Once you have a reference to this object in your PlayerMovement script, then in turn(), you can probably simply negate its X position:
    Code (csharp):
    1. spawnerObject.localPosition = new Vector3(-spawnerObject.localPosition.x, spawnerObject.localPosition.y, spawnerObject.localPosition.z);
    ~~~~

    Now let's talk about those GameObject.Find's. (You can fix your main problem first and come back to this after). GameObject.Find is possibly the worst function in Unity's API - there is ALWAYS another way to accomplish what you want, and that way will be objectively better than GameObject.Find. Worse, it gets people in bad programming habits.

    In your case, you're using it twice to find the player, and an object that is logically linked to the player. Let's solve the first one first, and then watch how easy the second one is to solve.

    There are several alternate ways for you to get a reference to the player. I see two obvious ones here: A singleton, and calling a function from the player to the instantiated projectile in the first place. The latter is undoubtedly the "correct" technique, but it'd require a significant restructuring of your code. The singleton is easier, but will limit you in the future a bit (specifically, if you ever decide you want multiple wizards, e.g. for multiplayer, you'll have to do some significant rewriting). I'll start with the singleton for now just because it will slide easily into your existing code.

    Singletons are appropriate when you have one of something at a time. One menu interface, one level in play, one end boss, etc. In this case, you have one player object. In PlayerMovement, add this:
    Code (csharp):
    1. public static PlayerMovement instance;
    2. void Awake() {
    3. instance = this;
    4. }
    And there's your singleton. You now have a pointer to your PlayerMovement object, accessible from anywhere in your code, simply through PlayerMovement.

    You can now replace "player" in your projectile script with PlayerMovement.instance.

    And if you've done what I suggested above, and created a reference in your PlayerMovement script to the projectile spawn point, you can get to that via PlayerMovement.instance.spawnerObject.
     
    PresidentPorpoise likes this.
  6. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    The spawn object is a child of the wizard, but it does not flip with the wizard's sprite since I am using FlipX in the sprite renderer. I like your idea of 2 spawns and I very much may try it out. Thanks!
     
  7. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    Can you elaborate on the function way of going about this? Unfortunately I am a newbie with 2D, programming, and Unity in general and even more unfortunately I plan on making this a two player game where both players in a match may end up using the "Wizard" character and most likely will be spitting projectiles all over the place. Thanks for your advice so far, it has been useful.
     
  8. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    Sure! To start, can I see your current projectile-creating code? I don't see it in PlayerMovement - I assume there's another script handling projectiles?
     
    PresidentPorpoise likes this.
  9. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    Thanks! Here is the projectile-creating script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class playerShoot : MonoBehaviour {
    6.  
    7.     void Update ()
    8.     {
    9.         if (Input.GetMouseButtonDown(0))
    10.         {
    11.             GameObject.Instantiate((GameObject)Resources.Load("Wizard Projectile"));
    12.         }
    13.     }
    14. }
    15.  
    As you can tell, the script handling behavior and spawn location of the projectiles are on the projectiles themselves (code listed in original post), but I should probably handle location in the spawning script now that I think about it.
     
  10. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    Yeah, that's the general idea. When you instantiate an object, you can "catch" it: (also, here's some slightly better ways to instantiate stuff from resources)
    Code (csharp):
    1. GameObject spawned = GameObject.Instantiate(Resources.Load<GameObject>("Wizard Projectile") );
    From there, you can get a reference to the projectile script with spawned.GetComponent<WizardProjectile>(), and you should be able to take anything player-dependent you're doing in that Start() and move it to this function.

    Sidenote: Keep this caching of GetComponent on the projectile script, though - and move it to Awake, which will get executed before the code after Instantiate gets executed. To elaborate, if you have this:
    Code (csharp):
    1. GameObject foo = GameObject.Instantiate(foo);
    2. Debug.Log(foo.GetComponent<FooScript>().testValue);
    3. //in FooScript
    4. public string testValue = "unset";
    5. void Awake() {
    6. testValue = "Awake";
    7. }
    8. void Start() {
    9. testValue = "Start";
    10. }
    It will be "Awake" that gets logged; Start will be run after that code, but before the frame is rendered.

    ~~~~

    How this relates to your other thing: Your playerShoot script should have a reference to your PlayerMovement (presumably on the same object), and that's the reference it should use to set anything relevant (such as position). So, for example,
    Code (csharp):
    1. spawned.transform.position = myPlayerMovement.spawnerObject.transform.position;
    And if you set your projectile's playMov as well, then you get another bonus: when your projectile collides with a wizard, you can check
    Code (csharp):
    1. if (collider.GetComponent<PlayerMovement>() != playMov) {
    2. //hurt them
    3. }
    4. else {
    5. // this is the wizard that fired me
    6. }
    For multi-wizard gameplay, that's your biggest advantage.

    And with these changes, you should be able to avoid both GameObject.Find and having singletons for your wizard.
     
    PresidentPorpoise likes this.
  11. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    Sorry if I ask some stupid questions, but I am having some trouble understanding this. What do you mean by keep this caching of GetComponent on the projectile script? I am pretty new to instantiating. To my understanding, I should put something like "GameObject spawned = GameObject.Instantiate(Resources.Load<GameObject>("Wizard Projectile") );" on the playerShoot script, but how do I keep the GetComponent caching on the Projectile script?

    Edit: I have gotten the projectile to spawn, but it does not spawn in the correct position and I get these errors: https://gyazo.com/0141c5d5fd055984beaa657c95d982d3

    Do you know how to fix this?

    playerShoot script:

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class playerShoot : MonoBehaviour {
    7.  
    8.     PlayerMovement myPlayerMovement;
    9.     WizardProjectile myProjectile;
    10.     GameObject spawned;
    11.  
    12.     private void Awake()
    13.     {
    14.         myPlayerMovement = gameObject.GetComponent<PlayerMovement>();
    15.     }
    16.     private void Start()
    17.     {
    18.         myProjectile = spawned.GetComponent<WizardProjectile>();
    19.         spawned.transform.position = myPlayerMovement.projectileSpawn.transform.position;
    20.         spawned = GameObject.Instantiate(Resources.Load<GameObject>("Wizard Projectile"));
    21.     }
    22.  
    23.     void Update ()
    24.     {
    25.         if (Input.GetMouseButtonDown(0))
    26.         {
    27.             spawned = GameObject.Instantiate(Resources.Load<GameObject>("Wizard Projectile"));
    28.             myProjectile.rend.flipX = gameObject.GetComponent<SpriteRenderer>().flipX;        
    29.         }
    30.     }
    31. }
    32.  
    33.  
    Wizard Projectile Script:

    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class WizardProjectile : MonoBehaviour {
    7.  
    8.     private float speed;
    9.     public GameObject projectileSpawn;
    10.     public PlayerMovement playMov;
    11.     public BoxCollider2D col;
    12.     private Animator anim;
    13.     public SpriteRenderer rend;
    14.  
    15.     private void Awake()
    16.     {
    17.         //projectileSpawn = GameObject.Find("Wizard Projectile Spawn");
    18.     }
    19.  
    20.     private void Start ()
    21.     {
    22.         //defining variables
    23.         speed = 15f;
    24.         col = GetComponent<BoxCollider2D>();
    25.         rend = GetComponent<SpriteRenderer>();
    26.         anim = GetComponent<Animator>();
    27.     }
    28.  
    29.     private void Update()
    30.     {    
    31.  
    32.         // Will need to change all of this in order for the projectile to follow where the cursor clicked.
    33.         if (GetComponent<SpriteRenderer>().flipX == true)
    34.         {
    35.             transform.Translate(Vector2.left * Time.deltaTime * speed);
    36.         }
    37.         else if (GetComponent<SpriteRenderer>().flipX == false)
    38.         {
    39.             transform.Translate(Vector2.right * Time.deltaTime * speed);
    40.         }
    41.  
    42.         Destroy(gameObject, 2);
    43.     }
    44.  
    45.     private void OnTriggerEnter2D (Collider2D col)
    46.     {
    47.         anim.SetBool("Hit", true);
    48.         Destroy(gameObject);
    49.     }
    50. }
    51.  
    52.  
     
    Last edited: Mar 3, 2017
  12. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    What I mean is actually just as you have it in your latest script. "caching of GetComponent" refers to stuff like "col = GetComponent<BoxCollider2D>()". However, you should put that stuff in Awake rather than Start (that's half of the reason for your errors).

    You've got stuff a little mixed up in playerShoot. Lines 18 and 19 should be in your update, in between when you spawn it and when you set its direction, and you shouldn't be Instantiating in Start. (this is the other half of the reason for your errors)

    That should fix your errors. You want the chain of actions to be (when the button is pressed):

    1) Instantiate the projectile (which causes Awake to be run immediately)
    2) Projectile sets all of its own references (col, rend, anim) [which is why you want these things in Awake]
    3) GetComponent<WizardProjectile>
    4) Set position and flipX [this requires rend to be set]
     
    PresidentPorpoise likes this.
  13. PresidentPorpoise

    PresidentPorpoise

    Joined:
    Nov 6, 2016
    Posts:
    37
    Thanks for the information. I ended up figuring this out on my own surprisingly after I came back to it after a break and I fixed those issues. I am on another issue, though. In player movement, how do I avoid using GameObject.Find? Here is where I use it in the script right now:
    Code (CSharp):
    1. private void Awake()
    2.     {
    3.         rightProjectileSpawn = GameObject.Find("Right Projectile Spawn"); //both are children of the player
    4.         leftProjectileSpawn = GameObject.Find("Left Projectile Spawn"); //that this script is attatched to
    5.     }
    6.  
     
  14. StarManta

    StarManta

    Joined:
    Oct 23, 2006
    Posts:
    8,745
    Most likely with a public Transform reference - just drag the objects into their slot in the inspector. If that doesn't work for whatever reason, transform.Find("Whatever") is better than GO.find - it's both faster and will only find objects by that name within a known spot on the hierarchy, which will allow them to work correctly with multiple spawned wizards.