C# & Unity : Pass reference by value? - c#

I'm new to C# and Unity, and here I am tweaking and creating my first minigame.
Here is the problem:
I've got a little cube, that moves. I've implemented a method that checks the next position before making a move.
The method receives as parameters the current cube position, and the direction:
public bool okToMove(Transform playerCurrentPosition , int directionIndex)
{
Transform playerNextPosition = playerCurrentPosition;
playerNextPosition.Translate(toDirection(directionIndex));
if (playerNextPosition.position.x > 1 ||
playerNextPosition.position.x < -1 ||
playerNextPosition.position.y > 1 ||
playerNextPosition.position.y < -1)
return false;
else
return true;
}
Then, I call the method
public void movePlayer(int directionIndex)
{
if ( okToMove(gameObject.transform, directionIndex) )
transform.Translate(toDirection(directionIndex));
}
The problem is that the cube makes 2 moves at once. This is because of
transform.Translate(toDirection(directionIndex));
and
playerNextPosition.Translate(toDirection(directionIndex));
that is called from okToMove method. Unity or C# sees playerNextPosition as the real cube, and not somekind of temporary copy that only exists inside the method.
So why is my gameObject.transform being passed as a reference and not by value? How can I make it work?
Thanks in advance and sorry for my noobiness.

You are passing reference to Transform and then moving it with translate in "okToMove", best way is to make a copy of Vector3, just change your "okToMove" like this.
public bool okToMove(Transform playerCurrentPosition , int directionIndex){
Vector3 playerNextPosition = playerCurrentPosition.position;
playerNextPosition += toDirection(directionIndex);
if (playerNextPosition.x > 1 ||
playerNextPosition.x < -1 ||
playerNextPosition..y > 1 ||
playerNextPosition.position.y < -1)
return false;
else
return true;
}
Transform is component attached to each gameObject and it holds values for position, rotation and scale, so your "playerCurrentPosition" is not copy of position but rather reference to Transform (not a copy).

Create a new GameObject that is a copy of yours original, and use its transform to make your calculations. (This answer is originally from the Unity forums). The official documentation tells me you can use Object.Instantiate to create a clone of a GameObject.
In C#, objects have always their reference passed as value, so simply reassign won't do it. See this related question.

Objects in C# are passed by reference. If you want to copy an object, implement the ICloneable interface which has the method Clone(). You will need to copy the object yourself and return it from this method.

Related

Passing the cycle from the beginning to the end (Or vice versa)

Good afternoon guys, such a question, I'm doing an array with brute force. I can't understand why assigning a value from the last element of the FrezeTree array to the BufferObject works in the Start() method, but this does not happen in Update.
So.
At the start of the scene, there is a search for all objects with the Tree tag (This is a temporary and debag solution):
FrezeTree = GameObject.FindGameObjectsWithTag("Tree");
Then, this list is passed to the array:
public GameObject[] FrezeTree;
Next, I'm trying to pass one element from the array to another object (This happens in the Start() method):
foreach (GameObject gm in FrezeTree)
{
BufferObject = gm;
}
But it turns out that only the last element is transmitted (Because of My own crookedness, since I don't know how to fix it yet). Why do I need a Buffer Object? From it I get the X position of the object, which I use for other purposes. The idea is to transfer one element from the array to the object, and when the object ceases to exist (Gets the null status), it goes to the element above / below (No matter in what order). Yes, I know that in what I have given above and I do not feel that I am trying to make a transition or skip an element from the array. I found various solutions on the great Internet, but the result was always the same, gets the last element and does not choose another one. That's why I turned here.
As I understand you want to
set one closest target object
wait until it is destroyed
go to the next closest target object
You could do this by simply checking whether the BufferObject is already set and still alive like e.g.
using System.Linq;
...
public GameObject BufferObject;
private void Update ()
{
// This means BufferObject is either not assigned yet or was destroyed
if(!BufferObject)
{
BufferObject = FindClosestTarget();
}
}
private GameObject FindClosestTarget ()
{
// Get all currently existing trees
// since this happens only once in a while it should be okey to repeat this call
return GameObject.FindGameObjectsWithTag("Tree")
// order them by distance
.OrderBy(gm => (transform.position - gm.transform.position).sqrMagnitude)
// get the first one or null if there was none
.FirstOrDefault();
}
This way it would also take trees that are spawned later into account.
If you rather want to cache this array only once you can still do this like
private GameObject [] trees;
public GameObject BufferObject;
private void Start ()
{
trees = GameObject.FindGameObjectsWithTag("Tree");
}
private void Update ()
{
// This means BufferObject is either not assigned yet or was destroyed
if(!BufferObject)
{
BufferObject = FindClosestTarget();
}
}
private GameObject FindClosestTarget ()
{
// First filter out any null or destroyed objects
return trees.Where(t => t)
// Then order the rest by distance
.OrderBy(gm => (transform.position - gm.transform.position).sqrMagnitude)
// Then pick the first one or null if there isn't any
.FirstOrDefault();
}
See
Linq Where
Linq OrderBy
Linq FirstOrDefault
implicit UnityEngine.Object.bool operator
In general, I figured out how to implement everything myself. So, the code:
public GameObject[] FrezeTree; //Array of all objects in the scene
private void Start()
{
FrezeTree = GameObject.FindGameObjectsWithTag("Tree"); //Finding all objects with a tag (I do not recommend using tags due to the lack of code navigation)
}
private GameObject FindClosestTarget()
{
return False True.Where(t => t).FirstOrDefault(); //Assigning and sorting an array with objects from the scene, thanks #derHugo
}
void Update()
{
BufferObject = FindClosestTarget(); //will be Assigned the next element of the array, in case the current will become "null"
if (BufferObject == null)
{
Debug.Log("debug for (BufferObject == null)"); //arbitrary code in the case that any object on the stage left.
}
}
P.S. This code is perfect for creating a simple AI for NPCs in your game. It's not perfect, but at least something is better than nothing :) Thanks #derHugo

Set z transform position of an instantiated GameObject sprite in unity

Is there an easy (shorter) way to set the z transform position of an instantiated game object (sprite) in unity? I want to set each instance to 2 for now.
Here's the one line of code I am using, I just wanted to set the z position and it seems cumbersome to do it this way - ignore the first line as I am only including it to demonstrate how the GameObject has been instantiated:
GameObject laser = Instantiate(laserPrefab, transform.position, Quaternion.identity) as GameObject;
laser.transform.position = new Vector3(laser.transform.position.x, laser.transform.position.y, 2);
Thanks in advance!
There is no simpler way of doing this, but you can create method to make your code assigning new position shorter. For example, creating method like this:
Vector3 SetZ(Vector3 vector, float z)
{
vector.z = z;
return vector;
}
Would let you to set new object's position like this
laser.transform.position = SetZ(laser.transform.position, 2);
You have to use method, because trying to do it directly, using property would result in compile time error.
transform.position.z = 2;
Results in error: CS1612: Cannot modify the return value of 'Transform.position' because it is not a variable
There are some extension methods on Vector3 that might make it a bit more easy to read. But I don't think it will get much shorter than what you wrote:
Like Vector3.MoveTowards
https://docs.unity3d.com/ScriptReference/Vector3.MoveTowards.html
But the short answer might be this: No.
A Vector(2/3) is a struct. In this case its made safe for the user by unity and made fully readonly, as you can't really assign values to a struct, because that would result in a new instance of the struct. So you cannot do assignments like laser.transform.position.z = 2. You will always have to create a new Vector.

How to replace element in MeshRenderer.sharedMaterials

We are using a object from the asset store that makes usage's of a mesh renderer with 32 materials in it. We need to implement functionality that makes it possible to replace 1 single material in the list, and keep the rest.
I have tried several things, but I keep struggeling with the materials list, that is why I decided to ask you guys for help.
public Material TestMaterial;
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
}
}
}
The above code is how I kinda want to use it.
The TestMaterial object is not null, it is selected from the Unity Editor, so that is fine.
Could someone give me some insight?
Thanks in forward!
from Renderer.sharedMaterials:
Note that like all arrays returned by Unity, this returns a copy of materials array. If you want to change some materials in it, get the value, change an entry and set materials back.
You have to do exactly that. Store the materials in a temporary variable, change entries and write the entire array back to sharedMaterials when done:
var materials = meshRenderer.sharedMaterials;
for(int i = 0; i < materials.Length; i++)
{
if(!string.Equals(materials[i].name, "replaceableMat") continue;
materials[i] = TestMaterial;
}
meshRenderer.sharedMaterials = materials;
The reason in the background is that sharedMaterials is not a field but a property.
Since the assignment of sharedMaterial[i] is not an assignment to the whole property what it does is just replacing that entry in a temporary array but not actually assigning it back to the Renderer component.
Only by assigning a value to the entire property actually makes the property execute it's setter and writes the array back to the Renderer component.
This should work.
void Update()
{
for (int i = 0; i < meshRenderer.sharedMaterials.Length; i++)
{
if (meshRenderer.sharedMaterials[i].name == "replaceableMat")
{
// Replace with TestMaterial
renderer.sharedMaterials[i] = TestMaterial
}
}
}

how to remove a specific game object (that is in an array) from a list?

my goal with the code is to have multiple skulls appear on screen for comparison. Depending on number of skulls on screen (1-6) they change position and size, Ive done this through the use of an array of gameObjects (the skulls) and a list that can keep track of which skull is in which "position".
The code works if you are adding, but runs into problems when you remove the object. It "works" on RemoveAt only if you remove the GameObjects in the same order that they were added. IE: The list is always removing element 0(first item in list) rather than the specific gameObject that is instigating the RemoveAt function.
While I have found many answers on how to remove specific individual gameObjects, They dont work since my gameObjects are in an array- I cant simply say remove gameObject name. Which leads me to my question of how to remove a specific gameObject[] from a list?
either remove or removeAll run these two errors:
error CS1502: The best overloaded method match for System.Collections.Generic.List<UnityEngine.GameObject>.RemoveAll(System.Predicate<UnityEngine.GameObject>)' has some invalid arguments.
Error CS1503: Argument#1' cannot convert int' expression to typeSystem.Predicate'
Here are my two scripts for all this:
Script on each bird skull named birdController:
public int myBirdID;
public int orderInList;
//bool to keep track of object active or not
public bool whoAmIReally;
//potentially redundant; allows game object to see if hes on or not
public GameObject IAmMe;
// Use this for initialization
void Start () {
//fixingIntOrder();
}
public void OnEnable(){
//when object is on check bool
whoAmIReally = true;
Debug.Log("IM ON");
}
public void OnDisable(){
//when object is turned off uncheck bool, change list value to 0
//**this wont change actual list order or value in cubeplacement script- my hope is that it might have- likely missing a step somewhere
whoAmIReally = false;
orderInList = 0;
Debug.Log("IMOFF");
}
Script on empty with all of my array and list info named cubePlacement:
public int numberSkullOnScreen;
public bool[] skullOn;
public GameObject[] allSkulls;
public GameObject listHolder;
public List<GameObject> birdsOnScreen = new List<GameObject>();
//declaring the script to avoid NullReferenceEcxeption where I reference it
public birdController _birdController;
// Use this for initialization
void Start () {
//only way I can get my list to work: have an object act as a placeholder onStart
birdsOnScreen.Add(listHolder);
}
//turning on and off the skulls
public void toggleBirdSkull(int mySkull){
if (skullOn[mySkull] == false){
//set specific gameObject active
allSkulls[mySkull].SetActive(true);
//make skull on bool true
skullOn[mySkull] = true;
//add one to the number on screen
numberSkullOnScreen++;
//reference int orderInList from Bird controller script
allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList = numberSkullOnScreen;
//add skull to list when turned on THIS WORKS YAY
birdsOnScreen.Add(allSkulls[mySkull]);
//Run function to place skulls
placementSkulls();
}
else{
allSkulls[mySkull].SetActive(false);
skullOn[mySkull] = false;
numberSkullOnScreen--;
//remove skull from list when turned off-- THIS DOESNT WORK...
birdsOnScreen.RemoveAt(allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList);
//Run function to place skulls based on the int number skulls on screen
placementSkulls();
}
}
Have you tried to remove objects using Remove method?
Like this:
birdsOnScreen.Remove (allSkulls[mySkull]);
Your birdsOnScreen.RemoveAt version also looks correct.
If you want to use RemoveAll method, you should pass predicate, which returns bool, eg:
birdsOnScreen.RemoveAll (bird => { return bird == allSkulls[mySkull]; } );
I would suggest to use Remove in your case, since you know the object you're removing.
Read more here: https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.list-1.remove?view=netframework-4.7.2
Also, try to increase your numberSkullOnScreen after you assign its value to the object's property:
allSkulls[mySkull].gameObject.GetComponent<birdController>().orderInList = numberSkullOnScreen;
numberSkullOnScreen++;
This way you won't need to use a placeholder anymore, because your indices will be correct.
I guess you're looking for a similar function like in an List. You could use this:
public static void RemoveAt<T>(ref T[] arr, int index) {
for (int a = index; a < arr.Length - 1; a++)
{
arr[a] = arr[a + 1];
}
Array.Resize(ref arr, arr.Length - 1);
}
or if you know the Object:
public static T[] RemoveObjectArray<T> (this T[] arr, T ObjToRemove) {
int numIdx = System.Array.IndexOf(arr, ObjToRemove);
if (numIdx == -1) return arr;
List<T> tmp = new List<T>(arr);
tmp.RemoveAt(numIdx);
return tmp.ToArray();
}

Array.FindIndex Always Got Null in unity C#

I don't know why my script below in unity c# always got null ?
public class WeatherControl : MonoBehaviour {
public GameObject Rain;
public int[] RainTime = new int[]{6,7,8,9,10,18,19,20,21,22,16};
int day;
System.DateTime dates;
// Use this for initialization
void Start () {
dates = System.DateTime.UtcNow;
day = (int) dates.Day;
//day = 16;
Debug.Log ("DAY : " + day);
int posRain = System.Array.FindIndex (RainTime, x => x.Equals(16));
Debug.Log ("POS RAIN : " + posRain);
if (posRain >= 0) {
Rain.SetActive (true);
} else {
Rain.SetActive (false);
}
}
}
my variable int posRain always return -1 Even at array there is value contain it. the variable day contain 16. I put it manually and type 16 too. But always return -1.
I don't know why.
I have try this too :
int posRain = System.Array.IndexOf (RainTime, day);
That's always return -1 too.
I have tested it at online C# Tester here : https://csharppad.com/
It works at it return 10.
But in unity c# editor it is different always return -1.
Could someone explain what is going on ?
Thanks
Dennis
Your array is public, so it's serialized by Unity. Unity will set the array to the value you gave it in inspector, overriding the one you declared in the code. If you don't need to access the array from the inspector, you should use [NonSerialized] attribute. If you do need to access it from inspector, you should also edit it from there.
Finally Got The ANSWER....
And finally this is a little thing we should remember. Sometimes this can make us onfused.
Once you update the array value at script. Don't forget to resetting or reattach the script that contain array value in the inspector. When you resetting or reattach the script all the value array size will update.
For example :
in script you have an array :
public int[] RainTime = new int[]{6,7,8,9,10,18,19,20,21,22};
This is not include 16 in array. Now you run the code in editor.
In the inspector you will see that array size contain value of all RainTime array value.
Now back to the script update the array value at :
public int[] RainTime = new int[]{6,7,8,9,10,18,19,20,21,22,**16**};
You have put 16 at last array. Now Run the code in editor. And see in the inspector array size value, Value 16 is not contain at array in the inspector even you have update it via script.
What you should you is to Reset The value from inspector OR reattact the script from inspector. It will update the value and now array in the inspector now contain value 16 too.
Thanks

Categories