Why does my Enum parameter goes back to default value? - c#

I have created 4 levels, but I'm stuck in the first two because my enum Level keeps going back to default=level_1.
So I'm stuck between levels 1 and 2. Why does my parameters go back to initial/default value?
This is true as well for any other parameters (int) I've tried.
I have already refreshed the scenes in the build setting.
//the enum parameter I use
enum Level { Level_1, Level_2, Level_3, Level_4 };
Level currentLevel = Level.Level_1;
//the loading next scene function
private void LaodNextScene()
{
switch (currentLevel)
{
case Level.Level_1:
{
print("loading level 2");
currentLevel = Level.Level_2;
SceneManager.LoadScene("Level_2");
break;
}
case Level.Level_2:
{
print("loading level 3");
currentLevel = Level.Level_3;
SceneManager.LoadScene("level_3");
break;
}
case Level.Level_3:
{
print("loading level 4");
currentLevel = Level.Level_4;
SceneManager.LoadScene("level_4");
break;
}
case Level.Level_4:
{
print("loading level 4 again");
SceneManager.LoadScene("level_4");
break;
}
default:
print("invalid level");
break;
}
}
//the loading first scene function
private void LaodFirstScene()
{
currentLevel = Level.Level_1;
SceneManager.LoadScene(currentLevel.ToString());
}
I expect to be able to move forward in my levels, but I'm stuck in the first two:
When I die - level 1 restarts.
When I win - level 1 goes to level 2, however level 2 restarts itself.

Within the switch statement you set currentLevel to the next level, then you load a new scene. I suspect that when the new scene loads currentLevel = Level.Level_1 is called again and resets currentLevel
You can try and replace that line to something like currentLevel = (Level)SceneManager.GetActiveScene().buildIndex;
or you can make a Game Manager that keeps track of the current level with the singleton approach. It's a bit more complicated but probably a more elegant solution.
If you want to know more about singletons:
https://www.youtube.com/watch?v=CPKAgyp8cno
https://www.studica.com/blog/how-to-create-a-singleton-in-unity-3d
https://gamedev.stackexchange.com/questions/116009/in-unity-how-do-i-correctly-implement-the-singleton-pattern

Related

How to simplify animation transaction

In 2D scene, I have 3 idle states and 3 walk states (each idle/walk state has 3 direction sub state).
I know I can make transactions in animator.
My question is, how to simplify transaction? For example, idle_front state has 5 transactions to other states (idle_back, idle_right, walk_front, walk_back, walk_right), now if I want to add an attack state, also with 3 direction sub state, the transaction lines makes me crazy.
My solution
I have tried using animator.Play() to play animation, but when switch to attack state, there is a problem.
The invocation of idle state play is in FixedUpdate() event function, after response to the attack key press event (to play attack animation), only little frames of attack is played, it is soon replaced by idle animation.
How can I implement similar effect of a trigger, to play the whole attack animation? Or is there any way to simplify transactions, and continue to use a trigger?
Example code is here
private void FixedUpdate()
{
if (moveInput.y == 0)
{
switch (moveInput.x)
{
case < 0:
animator.SetInteger(Direction, (int) MovingDirection.Left);
animator.Play("player_walk_right");
spriteRenderer.flipX = true;
break;
case > 0:
animator.SetInteger(Direction, (int) MovingDirection.Right);
animator.Play("player_walk_right");
spriteRenderer.flipX = false;
break;
}
}
else
{
switch (moveInput.y)
{
case < 0:
animator.SetInteger(Direction, (int) MovingDirection.Down);
animator.Play("player_walk_front");
break;
case > 0:
animator.SetInteger(Direction, (int) MovingDirection.Up);
animator.Play("player_walk_back");
break;
}
}
}
void OnFire()
{
switch (animator.GetInteger(Direction))
{
case (int) MovingDirection.Up:
animator.Play("player_attack_back");
break;
case (int) MovingDirection.Down:
animator.Play("player_attack_front");
break;
case (int) MovingDirection.Left:
animator.Play("player_attack_right");
spriteRenderer.flipX = true;
break;
case (int) MovingDirection.Right:
animator.Play("player_attack_right");
spriteRenderer.flipX = false;
break;
}
}
You just need to stop moving while the attack animation is playing. To know when the animation is end, one way is using events https://docs.unity3d.com/Manual/AnimationEventsOnImportedClips.html
First you need to add an event near the end of the animation, let's name it OnAttackEnd.
NOTE! To ensure the script can receive the event, the script and the animator must be attached on the same GameObject.
In the script add a boolean field to indicate if attack anim is playing and the OnAttackEnd callback.
bool _isAttackAnimPlaying;
void OnAttackEnd()
=> _isAttackAnimPlaying = false;
In OnFire method, set the boolean field to true, and in FixedUpdate method check the value.
void OnFire()
{
switch (animator.GetInteger(Direction))
{
case (int) MovingDirection.Up:
animator.Play("player_attack_back");
_isAttackAnimPlaying = true;
....
}
}
private void FixedUpdate()
{
if(!_isAttackAnimPlaying)
{
.....
}
}

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!

Unity C# Getting info from Switch cases and using it in unity

Im making a game for school and Im trying to get this data from the switch:
public void planets()
{
switch(PlanetType)
{
case planetType.Desert:
Debug.Log("Desert");
maxFuel = 500;
break;
case planetType.Ruined:
Debug.Log("Ruined");
maxFuel = 1500;
break;
case planetType.Lush:
Debug.Log("Lush");
maxFuel = 3000;
break;
}
}
per scene for that specific planet as the maxFuel in the slider in that scene
I have the enum and that stuff, but for some reason in Unity, it only takes the input max fuel in unity and doesnt want to take the max fuel from the switchcase. Any quick fixes?

Question On Sequential Attacks / Finishing Sequence Early

I'm working on a top down game where when you press the attack button multiple times there's a 3 hit sequence. I have the 3 hit sequence working just fine, but I have two problems I can't seem to find a solution to through my google fu.
When the sequence is finished, I don't have a good way to transition back to the idle state.
When you press the button once or twice, I'm not sure how I can tell whether you haven't pressed the button again in a long enough time to decide the sequence has been cancelled.
(I'm using a state machine I made for the character controller, and the states don't inherit from monobehavior so I don't have access to coroutines in this attack state)
Here is the code, I would really appreciate some feedback and help.
private string currentAttack = "Attack1";
public AttackingState(Character character, StateMachine stateMachine)
: base(character, stateMachine)
{
}
public override void Action()
{
if (Input.GetKeyDown(KeyCode.Space))
{
switch (currentAttack)
{
case "Attack1":
{
this.character.SetTrigger("Attack2");
this.currentAttack = "Attack2";
break;
}
case "Attack2":
{
this.character.SetTrigger("Attack3");
this.currentAttack = "Attack3";
break;
}
case "Attack3":
{
this.character.SetTrigger("Attack1");
this.currentAttack = "Attack1";
break;
}
default:
break;
}
}
if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
character.SetTrigger("AttackCancelled");
stateMachine.SetState<WalkingState>(new WalkingState(character, stateMachine));
currentAttack = "Attack1";
}
}
public override void OnStateEnter()
{
base.OnStateEnter();
character.SetTrigger("Attack1");
}
you could track a timestamp since the last press and do something like
// stores the last attack time
private float lastAttackTime;
// whereever you want to assign this value from
// The delay before the attack is reset and starts over from 1
private const float MAX_ATTACK_INTERVAL = 1f;
if (Input.GetKeyDown(KeyCode.Space))
{
// Time.time is the time in seconds since the app was started
if(Time.time - lastAttackTime > MAX_ATTACK_INTERVAL)
{
// start over
// mimics the attack3 since there you start over at 1
currentAttack = "Attack3";
}
lastAttackTime = Time.time;
switch (currentAttack)
{
...
}
}
In general instead of string I would recommend either a simple int or an enum instead.

Loading/unloading levels using SceneManager.LoadScene(int, LoadSceneMode.Additive) / SceneManager.UnloadSceneAsync(int); Deletes default level

Using additive loading in unity project, load a default level (Game manager) then run a method to select and activate additive levels (Lighting/Map). The game has rounds, and at the end of each round I run a method meant to unload the additive levels (Lighting/Map), however it deletes the default level as well.
Error upon crash: Display 1 No Cameras rendering
Have referenced the unity documentation pages;
https://docs.unity3d.com/ScriptReference/SceneManagement.UnloadSceneOptions.html
https://docs.unity3d.com/ScriptReference/SceneManagement.LoadSceneMode.html
Section for loading in level:
private IEnumerator RoundStarting ()
{
//Calling method meant to load in additive levels. Seems to work...
LoadMapForRound();
// As soon as the round starts reset the tanks and make sure they can't move.
ResetAllTanks ();
DisableTankControl ();
// Snap the camera's zoom and position to something appropriate for the reset tanks.
m_CameraControl.SetStartPositionAndSize ();
// Increment the round number and display text showing the players what round it is.
m_RoundNumber++;
m_MessageText.text = "ROUND " + m_RoundNumber;
// Wait for the specified length of time until yielding control back to the game loop.
yield return m_StartWait;
}
//Method meant to load in additive levels.
private void LoadMapForRound()
{
int LevelIndex = Random.Range(2, 4);
SceneManager.LoadScene(1, LoadSceneMode.Additive);
SceneManager.LoadScene(LevelIndex, LoadSceneMode.Additive);
//Debug.Log("SceneLoaded");
}
Section for unloading level:
private IEnumerator RoundEnding ()
{
// Stop tanks from moving.
DisableTankControl();
// Clear the winner from the previous round.
m_RoundWinner = null;
// See if there is a winner now the round is over.
m_RoundWinner = GetRoundWinner();
// If there is a winner, increment their score.
if (m_RoundWinner != null)
m_RoundWinner.m_Wins++;
// Now the winner's score has been incremented, see if someone has one the game.
m_GameWinner = GetGameWinner();
// Get a message based on the scores and whether or not there is a game winner and display it.
string message = EndMessage();
m_MessageText.text = message;
// Wait for the specified length of time until yielding control back to the game loop.
yield return m_EndWait;
//calling ethod meant to unload levels, but ends up unloading default level as well.
DestroyRoundMap();
}
//Method meant to unload levels
private void DestroyRoundMap()
{
SceneManager.UnloadSceneAsync(LevelIndex);
SceneManager.UnloadSceneAsync(1);
}
Unload additive levels (Light/Map) at rounds end, keep default level (0), instead the default level gets unloaded
and crashes the game.
Included local variable within the LoadMapForRound for the Global Variable to be equal to.
private void LoadMapForRound()
{
int mapChosen = Random.Range(2, 4); Solution Line
LevelLoaded = mapChosen;
SceneManager.LoadScene(LevelLoaded, LoadSceneMode.Additive);
SceneManager.LoadScene(1, LoadSceneMode.Additive);
//Debug.Log("SceneLoaded");
}
Got the idea for the solution from a separate thread on the same question. Thread here:
How do I unload additive scenes?

Categories

Resources