I have this PlayerPrefsUpdate.cs for my levels menu:
using UnityEngine;
public class PlayerPrefsUpdate : MonoBehaviour {
public int PlayerLevel = 0;
private int LastLevel = -1;
private void Start() {
PlayerLevel = PlayerPrefs.GetInt("PlayerLevel");
}
private void Update() {
if (LastLevel != PlayerLevel) {
PlayerPrefs.SetInt("PlayerLevel", PlayerLevel);
LastLevel = PlayerLevel;
}
}
}
If i play for example 4 levels, 5 levels are unlocked and everything is fine.
But if i play again the level 1, the levels are locked again. How can i do ?
Instead of checking if the last level you've played is not the PlayerLevel check that it is greater instead.
if (LastLevel > PlayerLevel) {
PlayerPrefs.SetInt("PlayerLevel", PlayerLevel);
LastLevel = PlayerLevel;
}
The naming you have is a bit confusing, so it might be the other way around. But you want to check if the level you just played is greater than the biggest you've played so far. Furthermore, you really should not need to have this inside the Update function as I would expect it would not change before completing a new level and returning to the menu screen.
If the problem is that that the PlayerPrefs' PlayerLevel variable is the only thing you persists, you might want to check that you are increasing it when you update it:
int progressedToLevel = PlayerPrefs.GetInt("PlayerLevel");
if (currentLevel > progressedToLevel) {
PlayerPrefs.SetInt("PlayerLevel", currentLevel);
}
I succeded with this code in GameManager.cs :
public void CompleteLevel ()
{
int savedPlayerLevel = PlayerPrefs.GetInt("PlayerLevel");
int newPlayerLevel = Mathf.Max(PlayerLevel, savedPlayerLevel);
PlayerPrefs.SetInt("PlayerLevel", newPlayerLevel);
completeLevelUI.SetActive(true);
}
Related
so i'm trying to do a little game on unity . And i did a script , when you press left click on an object , it open a canvas and another one and then it add +1 to i , like that when i found every object . (so when i=3) it shows the end screen. But when i go in game mode , when i press left click it doesnt add the number , like every object have his same i , but i dont understand they have the same function no ?
And sometimes 2 of 3 works, i dont know if thats clear to understand.
using UnityEngine;
using UnityEngine.UI;
public class ActionObjet : MonoBehaviour
{
public Image image1;
public Image image2;
public Image image3;
public int i = 0;
void Start()
{
image1.enabled = false;
image2.enabled = false;
image3.enabled = false;
}
void Update()
{
if (Input.GetKeyDown("space"))
{
if (image1.enabled == true)
{
i = i + 1;
print(i);
image2.enabled = !image2.enabled;
image1.enabled = !image1.enabled;
FindObjectOfType<AudioManager>().Play("bruit_recuperer_objet");
}
}
}
void OnMouseDown()
{
if (image1.enabled == false)
{
image1.enabled = !image1.enabled;
}
}
}
https://i.stack.imgur.com/sOSJH.png
So I found the solution , I'm a bit sad that I wasted 5 hours just for that but yea..
I just changed the
public int i=0
into
static int i=0;
This is because each gameObject with this script has its own instance of all the variables. A static variable's value is universal to all instances of the same class so any one of them can update it for all of them when marked static.
I am trying to play my animator's animation 5 times. That is after every animation that ends, it has to replay it for another time. How do I do this?
public Animator anim;
void Start ()
{
StartCoroutine(PlayAnimInterval(5));
}
private IEnumerator PlayAnimInterval(int n)
{
while (n > 0)
{
anim.Play("wave", -1, 0F);
--n;
//yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length); //Returns 1 which is wrong
}
}
Use the for loop in IEnumerator to solve the problem. Also make sure you enter the layer number correctly. Here the wave state is repeated 5 times in layer zero.
private IEnumerator PlayAnimInterval(int n)
{
for (int i = 0; i < n; i++)
{
anim.Play("wave", 0, 1);
yield return new WaitForSeconds(anim.GetCurrentAnimatorStateInfo(0).length);
}
}
How to detect Animator Layer?
The animator layer is an array. By adding each layer in the layers section, its index is also added. You can find its code below.
Repeat State Info with Behaviour
In this method you can solve the problem of repetition in any state. Just create a behavior like the one below and just add the number of repetitions. This method also works independently of the layer.
public class Repeat : StateMachineBehaviour
{
public int repeatTime;
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
if (repeatTime <= 0) return;
repeatTime--;
animator.Play(stateInfo.fullPathHash);
}
}
I am currently working on a game where to characters are having a conversation. There are a few dialogue options, but the conversation trees are quite simple. A key aspect is that the player is able to interrupt the other party mid-sentence and change the course of the conversation.
To preface, I have learned all I know from youtube and I've been getting by on increasingly complex if-statements, so I'm trying something new here.
So I did my first attempt using what I know: Invoke and if-statements.
public class SubtitleSystem : MonoBehaviour
{
public float subtitleTimeBuffer;
public string[] dialogue;
public string[] specialDialogue;
public float[] subTiming;
public float[] specialSubTiming;
public AudioClip[] diaClips;
public Text sub;
AudioSource player;
int subNum;
int specialSubNum;
// Start is called before the first frame update
void Start()
{
player = gameObject.GetComponent<AudioSource>();
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.E))
{
if (sub.enabled == false) {
SubSystem();
}
}
}
void SubSystem()
{
if(subNum < dialogue.Length)
{
if (subNum != 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = dialogue[subNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
subNum++;
}
else if (subNum == 1)
{
player.clip = diaClips[subNum];
player.Play();
sub.text = specialDialogue[specialSubNum];
sub.enabled = true;
Invoke("DisableText", subTiming[subNum] + subtitleTimeBuffer);
Invoke("SpecialSub", specialSubTiming[specialSubNum]);
}
} else
{
Debug.Log("No more dialogue");
}
}
void DisableText()
{
sub.enabled = false;
}
void SpecialSub()
{
sub.text = dialogue[subNum];
subNum++;
}
This was functional, though clunky and very work intensive for the person that has to find the individual timing for each line of dialogue and manually break up a subtitle line if it was too long.
Another big problem was that it was impossible to fully interrupt dialogue, because Invoke was called and would run regardless of what I pressed. Maybe I could add some kind of bool condition to prevent that, but the project I'm working on will have a few hundred lines of dialogue, so I need to come up with something where I don't have to type in every line in the inspector and find the manual timing.
This is where it becomes murky for me as I am unfamiliar with a lot of these methods.
An obvious solution to the Invoke problem would be to use Coroutines. These can be interrupted and I could even have a check for input inside a loop to let a player interrupt.
IEnumerator SubtitleRoutine()
{
while (DialogueIsPlaying)
{
//Display Subtitles
if(Input.GetButton("Interrupt button"){
//interrupt
}
yield return null;
}
//Wait for next piece of dialogue
}
Something like that is what I'm imagining.
The next problem is tying dialogue to some kind of system so I can pull the correct piece of audio and display the correct subtitles. In my first attempt this was simple enough because the four pieces of test audio I created were short sentences, but if a character speaks for longer it would be tedious to break up the dialogue manually.
Alternatively I thought about breaking the audio files up into "subtitle-length" so every audio file had a subtitle string directly associated with it, but this seems inefficient and troublesome if dialogue needs to change down the line.
So I thought if I could somehow create a class, that contained all the information needed, then my coroutine could pull in the correct dialogue using it's id (perhaps an integer) and plug in all the information from the object into my coroutine.
So something like this:
public class dialogue
{
//Int ID number
//Audiofile
//Who is speaking
//Length
//Subtitle String 1
//Subtitle String 2
//Subtitle String 3
// etc
}
IEnumerator SubtitleRoutine(dialogue)
{
while (DialogueIsPlaying)
{
//Display Subtitles - divide number of subtitle string by Length and display each for result.
if (Input.GetButton("Interrupt button"){
//interrupt audio and subtitles - stop the coroutine
//set correct dialogue Int ID for next correct piece of dialogue and start coroutine with new dialogue playing.
}
yield return null;
}
//Wait for next piece of dialogue
}
Though this is all outside of what I know, from what I've been able to read and understand, this seems like it might work.
So my question is:
Is this approach going to work?
If so, where should I look for ressources and help to teach me how?
If not, what methods should I look at instead?
Thank you so much for your help!
So what I understand is that you want to make an interruptable dialog system. You chose the coroutine approach which is a great choice, but you're not using it to its full potential. When you use StartCoroutine(IEnumerator _enumerator); you'll get a coroutine class back. If you store it, you can later use StopCoroutine(Coroutine _routine); to stop it. So you won't have to use a while loop or if statements to check interrupting.
Hope this will help you. If it doesn't I'll send some code.
After receiving some help from a coding mentor I found a system that works using a custom class as a datatype and coroutines to display subtitles with correct timing.
The names of the variables are in Danish but the code works in Unity without issues.
using System.Collections.Generic;
using UnityEngine;
public class Undertekster
{
public int id;
public AudioClip audioFile;
public float length;
public string[] subtitles;
public bool isMonk;
public SubSystem subsys;
public Undertekster(int id, int dialogClipNummer, float length, string[] subtitles, bool isMonk, SubSystem subsys)
{
this.subsys = subsys;
this.id = id;
this.audioFile = subsys.dialogClip[dialogClipNummer];
this.length = length;
this.subtitles = subtitles;
this.isMonk = isMonk;
}
}
Notice that use another script when constructing the class to make use of Monobehavior. That way I can assign the correct audiofile to each line of dialogue using an array created in the inspector. The proper way would probably be to look for the file somehow, but that's beyond me.
Next is the subtitle system. For demonstration you hit space in-game to start dialogue and hit F to interrupt. The "subtitles" are Debug.log in the console, but you can easily tie them to a Text object in the UI.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class SubSystem : MonoBehaviour
{
[Header("DialogClip")]
public AudioClip[] dialogClip;
[Header("Indstillinger")]
public float subtitleBuffer;
public AudioSource munk;
public AudioSource salamander;
List<Undertekster> alleUndertekster = new List<Undertekster>();
int currentDialogueNumber;
Undertekster currentDia;
float timeDivided;
void Start()
{
currentDialogueNumber = 1;
LoadDialog();
}
public void LoadDialog()
{
alleUndertekster.Add(new Undertekster(1, 0, dialogClip[0].length, new string[] { "Kan du huske mig?" }, true, this));
alleUndertekster.Add(new Undertekster(2, 1, dialogClip[1].length, new string[] { "Øh...", "Lidt..." }, false, this));
alleUndertekster.Add(new Undertekster(3, 2, dialogClip[2].length, new string[] { "Jeg er din nabo din idiot!" }, true, this));
alleUndertekster.Add(new Undertekster(4, 3, dialogClip[3].length, new string[] { "Shit!" }, false, this));
}
IEnumerator PlayNextDialogue()
{
int count = 0;
while (munk.isPlaying || salamander.isPlaying)
{
ShowSubtitle(count);
yield return new WaitForSeconds(timeDivided + subtitleBuffer);
count++;
yield return null;
}
//yield return new WaitForSeconds(subtitleBuffer);
currentDialogueNumber++;
Debug.Log("Coroutine is stopped and the current dialogue num is " + currentDialogueNumber);
StopCoroutine(PlayNextDialogue());
}
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown(KeyCode.F))
{
InterruptDialogue();
}
if (Input.GetKeyDown(KeyCode.Space))
{
StartDialogue();
}
}
public void StartDialogue()
{
currentDia = alleUndertekster.Find(x => x.id == currentDialogueNumber);
timeDivided = currentDia.length / currentDia.subtitles.Length;
if (currentDia.isMonk)
{
munk.clip = currentDia.audioFile;
munk.Play();
} else if (!currentDia.isMonk)
{
salamander.clip = currentDia.audioFile;
salamander.Play();
}
StartCoroutine(PlayNextDialogue());
}
public void InterruptDialogue() {
StopCoroutine(PlayNextDialogue());
munk.Stop();
salamander.Stop();
currentDialogueNumber++;
StartDialogue();
}
public void ShowSubtitle(int i)
{
if(i <= currentDia.subtitles.Length - 1)
{
Debug.Log(currentDia.subtitles[i]);
} else
{
return;
}
}
}
I chose to put all the dialogue classes into a list so it was easily searchable for the id-numbers. It might have been better to use a Dictionary, but this worked for me and that was good it enough for this project.
With this system, my manuscript writer can put in every line of dialogue with its associated audioclip in the LoadDialog() function and determine who is speaking and in how many pieces the subtitles should be broken into to fit on the screen. They are then displayed one after the other while the audio is playing.
This probably isn't the best solution in the world, but I hope it works for whoever might need it - plus I learned a ton.
I have a method that is supposed to check a player's HP and then perform some logic based on if the number is greater than 0, or less than or equal to 0.
The method works but if I type it in the code and then change the hp value of a player it won't do anything until I type in the method name again. Then it will display the correct information.
At first I thought of using some kind of loop instead of a method. If I am correct, that means I'd have to have put curly braces around all my code that needs to get checked (which means basically around the whole code and I don't want that). Same with IF statement - even if I put it at the beginning of the code I'd still have to put curly braces around all the code.
Then I thought of a method which I already mentioned. And as I said - it does what it's supposed to do but only if I paste it multiple times into the main code.
Now, my question - is there ANY way to make the method "repeat itself" constantly or, I don't know, start at some place in the code and remain active?
Here is the method:
static void Status()
{
if (Player.playerHealth <= 0)
{
Player.isDead = true;
Console.WriteLine("You are dead!");
}
else if(Player.playerHealth > 0)
{
Player.isDead = false;
Console.WriteLine("You are not dead!");
}
}
I would be really grateful for any help.
You can define playerHealth as a property. That way, any time anyone changes it, you can make some code fire, including the check that you want.
class Player
{
protected int _playerHealth = 0;
public int PlayerHealth
{
set
{
_playerHealth = value;
if (_playerHealth == 0)
{
isDead = true;
Console.WriteLine("You are dead!");
}
}
get
{
return _playerHealth;
}
}
Now you don't even need to call Status... the logic will occur automatically whenever the player's health is modified.
var player = new Player();
player.PlayerHealth = 0; //Automatically triggers message
Correct me if I'm wrong but you want to call a method everytime the player's life changes? It looks like you should use an event.
public class HealthEventArgs : EventArgs
{
public int Health { get; set; }
public HealthEventArgs(int health)
: base()
{
this.Health = health;
}
}
public class Player
{
public event EventHandler<HealthEventArgs> LifeChanged;
int _Health;
public int Health
{
get => _Health;
set
{
_Health = value;
LifeChanged?.Invoke(this, new HealthEventArgs(_Health));
}
}
}
Then in your code it would look something like that
Player player = new Player();
player.LifeChanged += (o, e) =>
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};
With that mechanism in place, everytime the health of a player changes the event will fire and you will have access to the player and its health and can act accordingly.
If you're not confortable with lambda expressions, the code above can also be written as such
Player player = new Player();
player.LifeChanged += PlayerLifeChanged;
public void PlayerLifeChanged(object o, HealthEventArgs e)
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};
I am making a maze game in unity 3d where the least time taken to complete the level is the highscore. Is there a way to save the highscore using Playerprefs? Scripting is done in C#.
You can make it easier using Get/Set Assessors.
public class GameManager: MonoBehavior {
...
public float HighScore {
get {
return PlayerPrefs.GetFloat("HIGHSCORE", 0);
}
set {
if(value > HighScore) {
PlayerPrefs.SetFloat("HIGHSCORE", value);
}
}
}
...
public void GameOver() {
HighScore = currentScore;
}
}
You can first put this check in your homescript in awake function:
if(!PlayerPrefs.HasKey("HighestScore"))
{
PlayerPrefs.SetString("HighestScore", "keep some big number");
}
Or alternatively can use (Preferably)
if(!PlayerPrefs.HasKey("HighestScore"))
{
PlayerPrefs.SetFloat("HighestScore", 100000f);
}
Then when the particular level ends and you need to save the score
Compare the existing PlayerPref with the current Player score.
For example for float
if(PlayerPrefs.GetFloat("HighestScore")>=current_score)
{
PlayerPrefs.SetFloat("HighestScore",current_score);
}
Hope this helps !
Thanks I got the answer! Just needed to check if the first round had already taken place or not.
// Start is called before the first frame update
void Start()
{
HighScore = score;
WinScore.text = PlayerPrefs.GetFloat("HighScore").ToString();
}
// Update is called once per frame
void Update()
{
if(PlayerPrefs.GetFloat("HighScore") == 0f)
{
PlayerPrefs.SetFloat("HighScore", (float)HighScore);
WinScore.text = HighScore.ToString();
}
if (HighScore < PlayerPrefs.GetFloat("HighScore")){
PlayerPrefs.SetFloat("HighScoreEasy", (float)HighScore);
WinScore.text = HighScore.ToString();
}
}