If-else statement for changing levels - c#

I have a got a function for changing levels in my game. In the game, the first level changes to the second level correctly. But the second level changes to the first level too, then count = 4. How can I fix this? Here is my code:
public void CheckLevelCompletion() {
if ((count == 4) && (levelOneCompleted == false)) {
img.sprite = levelCompletedImage;
count = 0;
levelOneCompleted = true;
SceneManager.LoadScene("FifthGameSecondLevel");
}
else if ((count == 6) && (levelOneCompleted == true))
{
img.sprite = levelCompletedImage;
count = 0;
levelTwoCompleted = true;
SceneManager.LoadScene("FifthGameThirdLevel");
}
}

I don't know your code steps, but maybe it is this situation, I think.
If scene reloaded, your game object in previous scene is destroyed.
So levelOneCompleted is always false.
It is why SceneManager.LoadScene("FifthGameThirdLevel"); does not run.
If it is right, I recommend levelOneCompleted make public and set true in second level game object.

Related

PlayerPrefs not saving unlocked levels

I used this code to try and make buttons in my level select screen not interactable if the level has not been completed. By default, I want to set level one's button to be unlocked so I used this:
[SerializeField] Button[] levelBUttons;
private void Start()
{
int levelCurrentlyAt = PlayerPrefs.GetInt("levelCurrentlyAt", 2);
for (int i = 0; i < levelBUttons.Length; i++)
{
if(i+2 > levelCurrentlyAt)
{
levelBUttons[i].interactable = false;
}
}
}
The problem is when I press play, level one's button is also not interactable
The reason I have set the int to 2 and not 1 is that in the build settings the scene order is 1) Start Screen, 2) Level Select Screen 3) Level One.
In the inspector, level one's button is also the first button element in the button array.
I changed the for loop to this and it seems to work:
int levelAt = PlayerPrefs.GetInt("levelAt", 1);
for (int i = 0; i < levelBUttons.Length; i++)
{
if(i+1>levelAt)
{
levelBUttons[i].interactable = false;
}
}
But is this not wrong because level one has a build index of 2 and 1, I am supposed to use i+1 and not i+2.
Not sure I fully understand your question, but you might be getting confused on the order. The first scene in build order will have index of 0, as that's how indexing in most languages works. Thus level 1 will have index of 1, unless you have 2 scenes before it in the build menu. Hope I could help!

Snake Program stops running without exception

I am trying to program snake in console. After a random amount of steps the snake takes, a new GameObject spawns (e.g. an apple that makes the snake grow or a mushroom which makes the snake faster).
I create a random gameObject (on random (left, top) coordinates and then I need to check if that gameObject spawns on top of a snakeBodyPart or on top of another gameObject. If that is the case, I create a new gameObject and try the same again. Only if it doesn´t collide with another gameObject or the Snake, it will spawn.
snakeElemnts = a list, which has all the snake- body-parts;
gameObjects = a list with all existing gameObjects;
If the noCollisionAmount == the amount of snakeElements and GameObjects, there should be no collision and the new GameObject should spawn.
Unfortunately at one point (early on), the snake just stops moving and nothing happens (no exception or anything).
I can´t debug because I have a KeyboardWatcher running, which checks if a key gets pressed. Therefore when I press break, I can only examine the keyboardwatcher.
Setting a breakpoint is not useful, because I never break when the problem arises.
if (this.randomStepNumber == 0)
{
int noCollision = 0; // this variable counts the amount of times there was no collision
GameObject validGameObject;
while (true)
{
validGameObject = this.snakeGameObjectFactory.Generate();
foreach (SnakeElements element in snakeElements)
{
if (validGameObject.XPosition != element.XPosition || validGameObject.YPosition != element.YPosition)
{
noCollision++;
}
else
{
break;
}
}
foreach (GameObject gameObject in gameObjects)
{
if (noCollision == snakeElements.Count) // if there was no collision of the new gameobject with an element of the snake, the gameobjects will get checked
{
if (validGameObject.XPosition != gameObject.XPosition || validGameObject.YPosition != gameObject.YPosition)
{
noCollision++;
}
else
{
break;
}
}
else
{
break;
}
}
if (noCollision == snakeElements.Count + gameObjects.Count) // if there was no collision at all, the check is ended
{
break;
}
else
{
noCollision = 0;
}
}
this.gameObjects.Add(validGameObject);
this.randomStepNumber = this.random.Next(10, 30);
}
Based on your comments that removing this block of code causes the problem to cease, I thought maybe it would be good to clean up the code a little and remove that potential infinite loop with the continue statement. Making use of a little LINQ Enumerable.Any and a do-while loop, we can simplify the logic of the above code. There isn't really a need to be counting collisions, since the purpose of the code is to create a new object iff a collision is detected. That means if we detect a collision with the snake, then we want to generate a new object, or if we detect a collision with another existing object, then we want to generate a new object. So instead of counting, we use the Any statement to check if there is any collision with the snake or an existing object. If there is, then generate a new object. If not, then use that object and continue. Note: In order to use the Enumerable.Any you will need to add a using statement for the namespace System.Linq.
if (randomStepNumber == 0)
{
GameObject validGameObject;
do
{
validGameObject = snakeGameObjectFactory.Generate();
}
while(snakeElements.Any(s => validGameObject.XPosition == s.XPosition && validGameObject.YPosition == s.YPosition) ||
gameObjects.Any(o => validGameObject.XPosition == o.XPosition && validGameObject.YPosition == o.YPosition));
gameObjects.Add(validGameObject);
randomStepNumber = random.Next(10, 30);
}
As a side note. I removed the this keyword as it appeared you were not using it in all cases in your code, and if you do not have to use it, it makes the code a little more readable because it is less wordy. If it needs to be added back because of variable collisions, then I would suggest renaming the variables as having member and local variables with the same name can also be confusing when trying to debug something.
Secondary note - here is a version of the code not using LINQ, in case that is not allowed for your homework.
if (randomStepNumber == 0)
{
GameObject validGameObject = null;
while(validGameObject == null)
{
validGameObject = snakeGameObjectFactory.Generate();
foreach(var snake in snakeElements)
{
if (validGameObject.XPosition == snake.XPosition &&
validGameObject.YPosition == snake.YPosition)
{
validGameObject = null;
break;
}
}
if (validGameObject != null)
{
foreach(var gameObject in gameObjects)
{
if (validGameObject.XPosition == gameObject.XPosition &&
validGameObject.YPosition == gameObject.YPosition)
{
validGameObject = null;
break;
}
}
}
}
gameObjects.Add(validGameObject);
randomStepNumber = random.Next(10, 30);
}
I think you may not understand how "continue" works.
if (noCollision > snakeElements.Count + gameObjects.Count)
{
continue;
}
you have that at the beginning of a while loop. If it's found true you will be stuck in an infinite loop.
secondly, you have a While(true) in there that will never let you save a valid result since it's outside the loop. You should replace the while with a do while and check for a valid result at the end of it, then loop if there was a collision.

How do I access specific raycast hits in a 'for' loop?

I have a loop that draws 4 raycasts from the bottom of my character, which are used to detect collision with objects. My issue is that if raycast #1 and raycast #4 are colliding with different objects they return the values from both objects.
I'd like to make it so that I look for a hit on raycast #4 only, and if that doesn't return a hit then I check raycast #3, etc. Once I have a hit I will check for the value of the object with which the raycast is colliding. I tried using RaycastHits[], but I believe this is intended to be used when you want to analyze multiple hits within all raycasts, not just a single raycast.
for (int i = 0; i < VerticalRayCount; i++)
{
Vector2 rayOrigin = (directionY == -1) ? RaycastOrigin.bottomLeft : RaycastOrigin.topLeft;
rayOrigin += Vector2.right * (VerticalRaySpacing * i + deltaMovement.x);
RaycastHit2D hit = Physics2D.Raycast(rayOrigin, Vector2.up * directionY, rayLength, collisionMask);
Debug.DrawRay(rayOrigin, Vector2.up * directionY * rayLength, Color.red);
if (directionY == -1)
{
if (directionX == 1)
{
if (i == VerticalRayCount -1)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
else if (i == 0)
{
if (hit)
{
if (currentPlatform != hit.collider.gameObject)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
}
}
}
}
}
}
I see two problems in your code:
In your for-loop currentPlatform gets allways overwritten by the last hit.
But since you check if(currentPlatform != hit.collider.gameObject) it will allways switch between the last two hits if there is more than one. This happens because it hits the first one and sets it. Than the second hit is not equal to the first one so it gets overwritten. So the next time the currentPlatform is still the second hit and gets again overwritten with the first hit of the new loop and always goes on like this.
To avoid the second remove those if(currentPlatform != hit.collidergameObject).
And you should add break; after a hit so the for loop doesn't continue with the other raycasts but rather exits the for loop immediately. So you get only one hit and don't overwrite them.
...
if(hit)
{
var platform = hit.collider.gameObject.GetComponent<IPlatform>();
currentPlatform = hit.collider.gameObject;
// here add break so the for loop is not continued
break;
}
...
On this way currentPlattform allways has the value of the first hit.

Getting an error for one of my Queues.. need help figuring it out

I'm working on a game in Unity that is played on a hex-board with units that you control. When a unit is selected, hexes around it are lit up, indicating (by color - Blue to move, orange to attack), that you can move to them, or attack whatever is on them. Here's a screenshot for reference:
Here is my function to show the hexes:
//breadth first search
//has built in range checker so you dont have to check the path length of every individual node
//works whether attackRange or moveRange is longer
public void showUnitsHexes(ArmyUnit unitScript, bool trueShowFalseHide)
{
//HIDING
if(trueShowFalseHide == false)
{
foreach(TileNode node in nodesToHide)
{
//change the mat back to the default white
comReader.changeNodeMat(node, CommandReader.hexMats.defaultt);
node.Hide();
}
nodesToHide.Clear();
return;
}
//SHOWING
//Only show if its your turn (return on enemy turn)
if ((comReader.playerNum == getWhichButtonAndPlayer.playerType.P1 && comReader.CurrPlayerTurn == 2)
|| (comReader.playerNum == getWhichButtonAndPlayer.playerType.P2 && comReader.CurrPlayerTurn == 1))
return;
#region SETUP
//these were originally parameters but it's easier to just set them in the funct itself
TileNode startNode = unitScript.node;
int attackRange = unitScript.attackRange;
int moveRange = unitScript.moveRange;
//safety-net
if (startNode == null || (attackRange < 1 && moveRange < 1))
return;
//how to know when you're done
int finishedRadius;
if (moveRange > attackRange)
finishedRadius = moveRange;
else
finishedRadius = attackRange;
#endregion
//get the list of nodes not to go over twice, and the queue of nodes to go through
List<TileNode> nodesPassedAlready = new List<TileNode>();
nodesPassedAlready.Add(startNode);
Queue<TileNode> nodesToGoToInCurrentRadius = new Queue<TileNode>();
Queue<TileNode> nodesToGoToInNextRadius = new Queue<TileNode>();
int currentRadius = 1;
bool finished = false;
//add the 6 nodes surrounding the initial node to the queue to start things off
foreach (TileNode n in startNode.nodeLinks)
nodesToGoToInCurrentRadius.Enqueue(n);
//while the queue is not empty...
while (finished == false)
{
//END CHECK
//if done in current radius, need to go to next radius
if (nodesToGoToInCurrentRadius.Count < 1)
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//...get the next node and...
TileNode n = nodesToGoToInCurrentRadius.Dequeue();
//...if there's no issue...
#region safety check
//...if the node is null, has already been shown, is inhabited by a non-unit, don't bother with it
if (n == null || nodesPassedAlready.Contains(n) || comReader.hexIsInhabited(n, false, true))
continue;
//...if is inhabited and outside of attackRange, or inhabited by a friendly unit, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
ArmyUnit currUnit = comReader.CurrentlySelectedUnit;
if (comReader.hexIsInhabited(n, true, true) && (currentRadius > currUnit.attackRange || comReader.unitIsMine(comReader.getUnitOnHex(n).GetComponent<ArmyUnit>())))
continue;
#endregion
//...1) show it
#region show nodes
//show as requested, add it to the list
//change the mat to whatever color is relevant: if n is inhabited and in attack range, color atkColor, otherwise color moveColor
if (comReader.hexIsInhabited(n, true, true) && currentRadius <= attackRange)
{
Debug.Log("attackRange: " + attackRange);
Debug.Log(n.name);
if (n.name.Equals("node345"))
Debug.Log("checked it");
comReader.changeNodeMat(n, CommandReader.hexMats.attack);
n.Show();
nodesToHide.Add(n);
}
//make sure hex is in moveRange. possible that it isnt, if attack range > moveRange
else if (moveRange >= currentRadius)
{
comReader.changeNodeMat(n, CommandReader.hexMats.move);
n.Show();
nodesToHide.Add(n);
}
//do not take n.show() out of those braces. If you do, it will sometimes show white nodes and you don't want to show them
#endregion
//...2) don't go over it a second time
nodesPassedAlready.Add(n);
//...and 3) add all surrounding nodes to the queue if they haven't been gone over yet AND ARE NOT ALREADY IN THE QUEUE
foreach (TileNode adjacentNode in n.nodeLinks)
{
#region safety check
//...if the node is null or has already been shown or is inhabited by a non-unit, don't bother with it
if (adjacentNode == null || nodesPassedAlready.Contains(adjacentNode) || comReader.hexIsInhabited(adjacentNode, false, true))
continue;
//...if is inhabited and outside of attackRange, don't bother with it
//NOTE: rather than combining this with the above if check, leave it separated (and AFTER) to avoid errors when n == null)
//currUnit already defined in the above safetycheck
if (comReader.hexIsInhabited(adjacentNode, true, true) && currentRadius > currUnit.attackRange)
continue;
#endregion
nodesToGoToInNextRadius.Enqueue(adjacentNode);
}
}
}
My problem is that when I set the attack range longer than the map's size (set to 30, map is 12x19), I get the error:
InvalidOperationException: Operation is not valid due to the current state of the object
System.Collections.Generic.Queue`1[TileNode].Peek()
Likewise, when I set the range to 17 or 18 -- enough to be able to attack the enemy base from where you see my unit in the picture -- the node for that base is never even looked at in the function, despite it being in attack range.
What does this error message mean? Where is my logic error? Sorry if this is sloppily written -- I'll be happy to answer any questions you may have. Thank you!
Is there any chance that nodesToGoToInNextRadius is empty?
Queue.Dequeue calls Queue.Peek underneath and throws InvalidOperationException when the queue is empty.
You assigning nodesToGoToInNextRadius to nodesToGoToInCurrentRadius:
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
and not checking again if there is anything in the queue.
if (nodesToGoToInCurrentRadius.Count < 1) // that's fine
{
if (currentRadius == finishedRadius)
{
finished = true;
continue;
}
else
{
currentRadius++;
//1. now you are swapping queues
nodesToGoToInCurrentRadius = nodesToGoToInNextRadius;
nodesToGoToInNextRadius = new Queue<TileNode>();
}
}
//2. and calling Dequeue on swapped queue without checking if it's empty.
TileNode n = nodesToGoToInCurrentRadius.Dequeue();

How to stop a looped instanced sound in c#

Well im trying to make a system which decides which car sound to play when i accelerate, keep driving, slow down and idle.
Pretty much what happens is it ignores my .stop command for my looped sounds and then creates another instance and dosn't stop the other. I have tried many different ways of declaring my instanced sound but it just moans about any other way.
SoundEffectInstance CarIdleLooped = CarIdle.CreateInstance();
CarIdleLooped.IsLooped = true;
CarIdleLooped.Volume = 0.2f;
SoundEffectInstance CarFastlooped = CarFastloop.CreateInstance();
CarFastlooped.IsLooped = true;
CarFastlooped.Volume = 0.6f;
if (pad_p1.Triggers.Right == 0 && CarIdling == false && CarIsDriving == false)
{
CarIdling = true;
CarIdleLooped.Play();
}
if (pad_p1.Triggers.Right > 0 && CarIdling == true)
{
CarIdling = false;
CarIdleLooped.Stop();
CarAccelerate.Play();
}

Categories

Resources