- Home /
Why do I need to move target position into new Vector3, and can not use the Vector3 that holds the target position directly?
With the help of a script I found on here, I was able to move a butterfly sprite to a new random location. However, I need to first load the x and y values of the new location Vector3 into another Vector3.x and .y, otherwise it does not work. Logically I would think that I can use directly the randomly defined targetPosition Vector3. I would like to understand what is wrong in my logic.
I have the 2 scripts below. Script 1 works, but is not logical to me. Script 2 should work in my opinion but does not.
The way it works: I have a butterfly sprite in my world At Start, I select a random point on a sphere and make this my targetPosition Then in Update, I check the current location of the butterfly and move it towards the targetPosition. In script 1 this works but I first put the random target location into Vector3 targetPos and then assign targetPos.x to targetPosition.x and same for y. In script 2 I put the random location directly into targetPoistion and looking at the debug.log, the x and y values are not zero.
Could someone please explain what is wrong in my logic of script 2 and explain how I should move my butterfly properly to the targetPosition?
Thanks!
Script1: using UnityEngine; using System.Collections;
public class ButtMove : MonoBehaviour {
private Vector3 targetPosition;
private Vector3 targetPos;
public void Start(){
**Vector3 targetPos = transform.position + Random.insideUnitSphere * 10;**
**targetPosition.x = targetPos.x;**
**targetPosition.y = targetPos.y;**
**targetPosition.z = 0f;**
Debug.Log ("targetposition = " + targetPosition.x + " - " + targetPosition.y);
}
public void Update(){
float speed = 2; //move towards targetPosition with this speed
Vector3 currentPosition = this.transform.position; //position of butterfly
//Debug.Log ("currentposition= " + currentPosition);
//first, check to see if we're not yet close to the target
if (Vector3.Distance (currentPosition, targetPosition) > .1f) {
Vector3 directionOfTravel = targetPosition - currentPosition;
//now normalize the direction, since we only want the direction information
directionOfTravel.Normalize ();
//scale the movement on each axis by the directionOfTravel vector components
this.transform.Translate (
(directionOfTravel.x * speed * Time.deltaTime),
(directionOfTravel.y * speed * Time.deltaTime),
(directionOfTravel.z * speed * Time.deltaTime),
Space.World);
}
}
}
Script2 only difference to script 1 is in Start():
public void Start(){
**Vector3 targetPosition = transform.position + Random.insideUnitSphere * 10;
targetPosition.z = 0f;**
Debug.Log ("targetposition = " + targetPosition.x + " - " + targetPosition.y);
}
Answer by Bunny83 · Sep 16, 2015 at 10:21 PM
Well, if the code is exactly as you wrote it your problem is that you define a new local variable inside Start with the same name as your member variable of the class.
So instead of:
Vector3 targetPosition = transform.position + Random.insideUnitSphere * 10;
you have to use
targetPosition = transform.position + Random.insideUnitSphere * 10;
Bunny, thanks for your short but correct and clear answer.
I have been fiddling around with my code and the first suggestions made here, but still could not get it working.
I just changed my code as you suggested, and it works fine. Simplified my code and no error message anymore.
Thanks!
Answer by RudyTheDev · Sep 15, 2015 at 08:32 PM
To answer the question about modifying transform's position: you cannot directly assign .z
of transform.position
, because Vector3
is a struct
and .position
is a property.
Here is an example to demonstrate how writing fields and properties works with structs and classes:
public class FieldAndPropertyModify : MonoBehaviour
{
private Vector3 structField;
private Vector3 StructProperty { get; set; }
private MyVector classField;
private MyVector ClassProperty { get; set; }
private void Start()
{
structField.z = 3f; // fine
// StructProperty.z = 3f; ** NOT fine **
classField.z = 3f; // fine
ClassProperty.z = 3f; // fine
}
private class MyVector { public float z; }
}
Firstly, struct
is a value type. This means it returns a copy when you read it:
Vector3 first = new Vector3(1, 2, 3);
Vector3 second = first;
second.z = 10f;
Debug.Log(first.z); // 3f
Secondly, accessing StructProperty
returns a copy. While I used a { get; set; }
shorthand, here is what the compiler "sees":
private Vector3 _structProperty;
private Vector3 StructProperty { get { return _structProperty; } set { _structProperty = value; } }
So when you read transform.position
(which is a get
method), you receive a copy of Vector3
. You can then modify this struct to your needs. Then you pass it back into transform.position
(which is a set
method with a value
parameter), which stores a copy again. But accessing .z
from reading a property simply accesses the value of the copy and assigning a new value to it that just gets "lost" makes no sense.
See Bunny83's answer for the issue in the source code itself.
Edit: rewrote and scrapped incorrect info, thanks Bunny83
Are you sure Vector3 is an immutable struct? I change the x, y and z members of Vector3s all the time, just try this code and it'll print "2" in the console:
void Awake() {
Vector3 p = new Vector3(0f,0f,0f);
p.z = 2f;
Debug.Log (p.z);
}
I always tought the reason for what the OP is asking was that Transform.position is a property that returns a new vector3 struct each time you get it, and the actual Vector3 used internally by the Transform component is private.
That is correct. Struct's aren't immutable, they act more like value types ins$$anonymous$$d of reference types. @DiegoSLTS is exactly right. Transform.position
is a property so when you access it you're getting a copy of the struct, so when you change a field on it you're changing that field on the copy.
So when a struct is a property you have to replace the entire struct to change any of the values. It's kind of confusing and not particularly intuitive unless you understand more of the inner workings of C#.
And I should point out that it doesn't work this way because Unity decided it should, but this is part of the .NET specification.
You are right, yes. I over-simplified it a lot and I rephrased my answer per your guys' feedback to be more technically correct rather than "friendly". $$anonymous$$y example should have been clearer too. Yeah, structs are only immutable in a certain sense and you can reassign a value, because it makes a copy under the hood. And even though structs are still objects in .NET, they get treated as value types and so "immutable". Unity .position is indeed a property so not auto-copyable as it uses a method to write that doesn't directly translate to "auto-copyable" struct.
$$anonymous$$, Thank you very much for the eleborate explanation. This really helps me understand why and how to do it.
While the third paragraph is more or less correct, the rest is completely wrong. Also it doesn't address the actual code in the question where he doesn't try to change a component of transform.position but of a true variable which is perfecly fine.
You're right, I didn't answer the exact issue of OP's code as I didn't look at it carefully. I would say your answer deserves acceptance in that regard. Seeing that error now makes sense about what the actual problem was. I assumed OP was trying to use transform.position.z
directly and the code posted is an attempt to work around it. (I also see a copy-paste error in my second code example.) Would you care to elaborate about the other bits being wrong? I realize they are way simplified, but is it completely wrong? I rather edit the answer with your feedback than leave it wrong forever.
Well, targetPosition.z = 3f;
doesn't make a new copy of the Vector3 variable. It will actually replace only the z value of the struct. That line would translate to
ldarg.0
ldflda valuetype [UnityEngine]UnityEngine.Vector3 Butt$$anonymous$$ove::targetPosition
ldc.r4 3
stfld float32 [UnityEngine]UnityEngine.Vector3::z
"ldarg.0" (load agrument 0) loads the "this" reference onto the stack.
"ldflda" (load field address) will place a pointer on the stack that points to the specified field for the given object
"ldc.r4 3" (load constant) simply places the value "3f" on the stack. "r4" specifies the format (r4==float, r8==double, i4==int, ...)
"stfld" (store field) directly stores the value 3 in the specified field, in this case the z field of the Vector3 instance.
When you assign a Vector3 value as a whole to another one it is always copied and never referenced, but not when you access it's member fields.
Your explanation of the "property problem" was correct. Properties are not fields and therefore you can't get a reference / pointer. When you read a property the get method returns a copy of the actual Vector3 value. So there's no way to reference an actual field. The position property actually doesn't have a field in managed code. It actually calls a method in native C++ code which returns the position. Likewise when you assign a Vector3 to the property the set method also is a native method which actually changes the position.
Your last example is a bit confusing an seems to overcomplicate the actual "calculation". As long as you use a temp variable everything should work just fine. You just can't directly set a component of a value-type-property. That applies to all properties that have a value type. Such as $$anonymous$$aterial.color, Light.color, Transform.position / .rotation / .localScale / .eulerAngles /.forward / ....
Your answer
Follow this Question
Related Questions
Move character by X units on the left 1 Answer
Multiple Cars not working 1 Answer
Distribute terrain in zones 3 Answers
C# Question Regarding"new" keyword and Vector3 type 2 Answers