I am making a game with turn-based combat.
For the combat i am pulling between 1-3 monsters and then placing them. The monsters are GameObjects.
I then put the monsters in a List so i can check their position from left-right. (left being index 0 and right being index 2 if there is 3 monsters ofc)
My only problem is getting the index of the GameObject..
This bit of code is on my CombatMainController.cs
void createMonster(){
float numberOfMobs = Random.Range (1, 4);
print (numberOfMobs);
if (numberOfMobs > 2) {
monsterStartpointX = -4f;
}
else if (numberOfMobs > 1) {
monsterStartpointX = -2f;
}
else {
monsterStartpointX = 0f;
}
for (int i = 0; i < numberOfMobs; i++) {
randomMonster = Random.Range(0, monsters.Count);
GameObject monster = Instantiate(monsters[randomMonster], Vector3.up, Quaternion.identity) as GameObject;
aliveMonsters.Add(monster);
Vector3 temp = monster.transform.position;
temp.x = monsterStartpointX;
temp.y = monsterStartpointY;
monster.transform.position = temp;
monsterStartpointX += 4f;
}
}
public void removeMonster(GameObject gameObject){
print (gameObject);
aliveMonsters.Remove(gameObject);
}
print (gameObject) returns: Goblin(Clone) (UnityEngine.GameObject)
UnityEngine.MonoBehaviour:print(Object) - which is the GameObject that is being clicked on.
But aliveMonsters.Remove(gameObject); does not remove said GameObject.
This is the code that is attached to the monster
void OnMouseUp(){
OnClickGUI = !OnClickGUI;
//Destroy (gameObject);
}
void OnGUI(){
if (OnClickGUI) {
Vector3 V = Camera.main.WorldToScreenPoint(transform.position);
if (GUI.Button(new Rect(V.x - 50,Screen.height - V.y,100,30),"Attack")){
takenDamage();
}
}
}
void takenDamage(){
combatController.removeMonster (gameObject);
}
I have no idea what i'm doing wrong and hope you can help me or atleast point me in a better direction.
On MSDN you can read this about the List.Remove Method:
If type T implements the IEquatable<T> generic interface, the equality comparer is the Equals method of that interface; otherwise, the default equality comparer is Object.Equals.
If neither IEquatable<T> is implemented by the game object nor Object.Equals is overridden then reference equality is used for the comparison and this one will never fail if the given object is really in the list. This is the default behavior of Object.Equals.
I could imagine that either a faulty Object.Equals override or IEquatable<T> implementations exists that makes the Remove method fail.
Another possibility is that you try to remove a clone of the game object. In that case reference equality cannot work! You could fix it by overriding the Object.Equals method and to make it compare object IDs. Or you could remove like this:
int index = aliveMonsters.FindIndex(m => m.ID == monster.ID);
if (index >= 0 ) {
aliveMonsters.RemoveAt(index);
}
But in any case, make sure that there is really a problem with the list and not just with the game logic. The object might be removed from the list but not from the game.
Could you do something like:
public void removeMonster(GameObject gameObject){
print (gameObject);
GameObject objToRemove = allMonsters.DefaultIfEmpty(null)
.FirstOrDefault(f=>f.obj.ID=gameObject.ID);
if (objToRemove!=null)
aliveMonsters.Remove(objToRemove);
else
throw new Exception("That monster isn't in the list.")
}
Related
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
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();
}
I've been working on a platformer game in Unity, and I have a lot of different types of horizontal movement, all of which are executed with the Mathf.SmoothDamp function. Everything in my game works as intended, but after trying to clean up some cluttered code tonight I ran into some issues with part of smoothdamp's functionality, specifically its requirement to use the ref keyword.
Mathf.SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime)
The way my code has been structured for weeks is that I have a function that sets my horizontal velocity based on different if statements and their associated bools, but this has caused me to have to rewrite many blocks of code very similarly, with the only minor differences usually being the ref float currentVelocity and smooth time variables.
public void SetHorizontalForce(float x)
{
if (State.IsJumpBoosted && xForce != 0) //Smooths out horizontal movement force when jump boosted left or right (also defines air control when horizontally jump boosted)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x + xForce, ref velocityXSmoothingLeft, Parameters.xForceJumpSmoothing);
}
else if (State.Launch)
{
velocity.x = Mathf.SmoothDamp(velocity.x, Abilities.LaunchVelocity, ref launchVelocitySmoothing, Abilities.LaunchSmoothing);
}
else
{
if (smoothXForceMovement) //Smooths out horizontal boost movement when landing, so the character doesn't stop abruptly
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref velocityXSmoothing, Parameters.xForceLandSmoothing);
}
if (PlayerInput.DirectionalInput.x != 0) //Otherwise move as normal
{
if (PlayerInput.DirectionalInput.x > 0)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref velocityXSmoothingLeft,
(State.IsCollidingBelow) ? Parameters.accelerationTimeGrounded : Parameters.accelerationTimeAirborne);
}
else if (PlayerInput.DirectionalInput.x < 0)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref velocityXSmoothingRight,
(State.IsCollidingBelow) ? Parameters.accelerationTimeGrounded : Parameters.accelerationTimeAirborne);
}
}
}
}
This seems unnecessarily redundant, so I wanted to change my function to take in parameters:
public void SetHorizontalForce(float x, float velocityXSmoothing, float smoothTime)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref velocityXSmoothing, smoothTime);
}
I'd then call this method as needed in other parts of my code, subbing in the necessary variables to make everything work properly again.
public void Launch()
{
CharacterController2D.SetHorizontalForce(airplaneVelocity, launchVelocityXSmoothing, launchSmoothTime);
StartCoroutine(LaunchResetTimer());
}
This looks like it should work, but no matter how many different ways I tried to rewrite my code nothing ever worked the same again; in fact, my character was barely able to move at all. It wasn't until I removed the float velocityXSmoothing parameter and hard set it in my SetHorizontalForce function that things started feeling better.
However, if each distinct kind of movement doesn't have its own ref float currentVelocity value, SmoothDamp appears to "cheat," and doesn't respond to the smooth time as you'd expect. (Just to clarify, I found that having separate smoothing times for moving left and right caused those values to lerp by the smooth time almost exactly; using a single smoothing float caused the values to lerp faster, and did not feel as responsive to input.)
Am I using ref incorrectly? Is it even possible to use it as a parameter like this? I also tried adding ref in SetHorizontalForce's smoothing parameter, but this did not work.
Edit: short answer:
do
private float _velocityXSmoothing = 10f; // You will need to set that to a value that suits you, I used 10f arbitrarily
public void SetHorizontalForce(float x, float smoothTime)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref _velocityXSmoothing, smoothTime);
}
Long answer
This will require some understanding of the c# language.
The ref keyword means that the value is passed by reference. Reference type objects in c# are passed by reference by default, meaning that if you pass them as argument and edit them, the edit won't disappear when the function returns. On the other hand, value types objects (like struct and primitive types such as float) are passed by value by default, meaning that the value is copied. the ref keyword is used to force a primitive type to be passed by reference. Look at the example below:
private void Start()
{
float val = 1f;
Debug.Log("start with: " + val)
FuncByValue(val); // This will print val + 3, but not change val in the Start method
Debug.Log("after FuncByValue: " + val)
FuncByRef(val); // This will print val + 10, and the change affects val also in this Start method
Debug.Log("after FuncByRef: " + val)
}
private void FuncByValue(float val) // here, val is a COPY of the argument. like another variable with the same value
{
val = val + 3;
Debug.Log("by value: " + val)
}
private void FuncByRef(ref float val) // Here, val is exactly the same variable as the argument
{
val = val + 10;
Debug.Log("by ref: " + val)
}
In this example, the FuncByRef function will change the value of vall, so the output will be
start with: 1
by value: 4
after FuncByValue: 1
by ref: 11
after FuncByRef: 11
So back to your code, when you do:
public void SetHorizontalForce(float x, float velocityXSmoothing, float smoothTime)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref velocityXSmoothing, smoothTime);
}
You pass a velocity by ref, meaning that the SmoothDamp function will modify is. This is noted in the doc of SmoothDamp:
currentVelocity The current velocity, this value is modified by the function every time you call it.
The reason for this is that SmoothDamp expect you to keep the modified value so you can give it back the next time you call SmoothDamp so it continues with the effect. In your code, since your function gets the velocity by value, you don't keep the value that was modified by SmoothDamp. What you can do to make it work, instead of using a ref, is saving the velocityXSmoothing as a class variable so you keep it between each call:
private float _velocityXSmoothing = 10f; // You will need to set that to a value that suits you, I used 10f arbitrarily
public void SetHorizontalForce(float x, float smoothTime)
{
velocity.x = Mathf.SmoothDamp(velocity.x, x, ref _velocityXSmoothing, smoothTime);
}
Now the _velocityXSmoothing will be kept between each call and will be properly updated by the SmoothDamp function
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.
-I cannot change int values created in main using a class function-
I am making a game and I have a function called sideCollision that checks if the player is touching a pictureBox,and if the player is touching the pictureBox,an integer called Score is incremented by 1.
Here is the function:
public void sideCollision(Control player,Control pointRadius,Control topPipe,Control bottomPipe,Control gameOver,int force,bool timer,int score,int pointRadiusCounter)
{
if (player.Right > pointRadius.Left && player.Left < pointRadius.Right - player.Width / 2 && player.Bottom > pointRadius.Top)
{
if (timer == true && pointRadiusCounter == 0)
{
pointRadiusCounter = 1;
score++;
}
}
}
The function detects the player touching the wall,but it does not increment the score by 1. I also have a message in my main saying "score has not been assigned to,and will always have its default value of 0".
I need to know how to change the score value using the function,because the function isn't changing the value of Score
You shouldn't use pass-by-reference in situations like this. Instead, you could in this situation use something like this.
private void SomeMethod()
{
var score = 0;
var pointRadiusCounter = 0;
if (CollisionOccurred(...))
{
score++;
pointRadiusCounter++;
}
...
...
...
}
public bool CollisionOccurred(Control player,Control pointRadius,Control topPipe,Control bottomPipe,Control gameOver,int force,bool timer)
{
if (player.Right > pointRadius.Left && player.Left < pointRadius.Right - player.Width / 2 && player.Bottom > pointRadius.Top)
{
if (timer == true && pointRadiusCounter == 0)
{
return true;
}
}
return false;
}
Forgive me for this being a little bit offtopic but this might help you avoiding problems like the one you have at the moment:
I would consider to think about what variables need to be passes as a
parameter and what variables could also be accessible from all your
classes because their value will, whether it is changed or not, be the
same at any point of your code (for example lifepoints of the player,
score of the player, position of the player).
Then you could transfer them into a own class (for this example the
"player" class), make those variables attributes of this class, and
make those accessible for your other classes via making the attributes
public static or an Singleton-class if there is just 1 player in
the game.
Passing an amount of variables as high as you do to
sideCollision(...) can cause a lot of trouble when your project
becomes bigger. Arranging and prioritizing your variables, methods,
classes etc. can help you to prevent this. So take your time and think
about what is important and where you need it and stuff like that.
int is a value type. That means it's passed by-value and inside the function you have a copy of your original variable. You can solve this by passing it by-reference explicitly:
public void sideCollision(Control player,Control pointRadius,Control topPipe,Control bottomPipe,Control gameOver,int force,bool timer, ref int score, ref int pointRadiusCounter)
{
if (player.Right > pointRadius.Left && player.Left < pointRadius.Right - player.Width / 2 && player.Bottom > pointRadius.Top)
{
if (timer == true && pointRadiusCounter == 0)
{
pointRadiusCounter = 1;
score++;
}
}
}