Ваша проблема в том, что вы используете static
переменных. static
точно такие же, как и глобальные переменные. Единственное отличие состоит в том, что вам нужно получить к ним доступ через имя класса.
Так что же это означает? Когда вы загружаете свою игру и когда загружается сам класс, создаются и инициализируются статические переменные. В вашем случае TotalZombies
установлено на 5
, а timeLeft
на 25f
.
Но эти переменные сохраняются и никогда не инициализируются повторно, пока работает ваша игра. Даже если вы сделаете Application.LoadLevel
эти переменные и их значения сохраняются.
Это означает, что если вы измените эти переменные и перезагрузите уровень, TotalZombies
и timeLeft
по-прежнему будут иметь свои последние значения.
Из-за этого я призываю никогда не использовать static
переменных. Они легко внедряют трудно обнаруживаемые ошибки. Давайте предположим простое исправление вашего кода.
Вы дополнительно добавляете инициализацию в свой метод Start()
. Например, в вашем классе Status
вы добавляете.
void Start() {
TotalZombies = 5;
timeLeft = 25.0f;
}
В вашем случае это может полностью решить проблему, но вы также можете сказать, что это просто случайность или удача.
В Unity не существует порядка, в котором вызывается Start()
. Например, все еще может случиться так, что метод Start
в вашем классе Generate
вызывается первым при загрузке сцены. Если вы использовали Status.TotalZombies
или Status.timeleft
в Start
для инициализации чего-либо в Generate
, у вас все еще есть ошибка, заключающаяся в том, что ваша инициализация неверна, поскольку она использует переменные из предыдущего запуска. Проблема в том, что иногда Unity может сначала выполнить Status.Start()
перед Generate.Start()
, а иногда и наоборот. Это привело бы к ошибке, которая возникает только sometimes
и которую чрезвычайно сложно отладить.
Если бы вы знали вышеизложенное, вы также могли бы поместить свою инициализацию в метод Awake
. Потому что методы Awake
будут вызываться перед любым методом Start
. Так что это будет лучшее решение.
Но существуют и другие проблемы. Например, давайте рассмотрим ваш метод Generate.Update()
. Например, вы напрямую выполняете Status.timeLeft -= Time.deltaTime;
в своем методе Update
. Но когда, например, в вашей игре есть несколько GameObjects
с компонентом Generate
, это означает, что timeLeft
будет уменьшено несколько раз в одном кадре. Если у вас есть два компонента Generate
, это означает, что ваше время будет истекать в два раза быстрее.
Таким образом, даже установка инициализации в Start
или Awake
может исправить некоторые ошибки, но у вас все еще будут другие проблемы с static
s
Это причина, по которой я рекомендую вообще не использовать static
. Итак, как решить эту проблему? Вместо статического вы должны создать атрибуты класса. И, кроме того, вы должны сделать все свои атрибуты доступными только для вашего собственного класса. Это также влияет на другой код. Например, вы больше не могли уменьшить атрибут timeLeft
с Generate
. Звучит как недостаток, но заставляет задуматься о том, как правильно изменить timeLeft
. В вашем случае вы не очень хотите, чтобы любой класс отовсюду мог изменить timeLeft
. Это время, которое следует постоянно сокращать, и сокращать его в несколько раз просто ошибка. Результат того есть. Ваш класс Status
должен изменить только timeLeft
в Update
. То же самое касается TotalZombies
. Было бы лучше просто иметь такой метод, как IncrementTotalZombies
и DecrementTotalZombies
, вместо того, чтобы делать Status.TotalZombies++
и так далее. Например, ваш класс Status
теперь должен выглядеть так:
public class Status : MonoBehaviour {
public int TotalZombies { get; private set; }
public float TimeLeft { get; private set; }
void Awake() {
this.TotalZombies = 5;
this.TimeLeft = 25f;
}
void Update() {
this.TimeLeft -= Time.deltaTime;
}
public void IncreaseTotalZombies() {
this.TotalZombies++;
}
public void DecreaseTotalZombies() {
if ( this.TotalZombies <= 0 ) {
throw new ApplicationException("Cannot decrease TotalZombies. Already 0. Possible Bug in your code.");
}
this.TotalZombies--;
}
}
Теперь IncreaseTotalZombies
или DecreaseTotalZombies
звучат как накладные расходы, но здесь вы можете сделать много дополнительных проверок. Например, проверьте, не становится ли счетчик никогда меньше нуля. Потому что когда это происходит, у вас где-то в коде есть ошибка. Например, случайно увеличить TotalZombies на два или где-то еще уменьшить его на два и так далее. Вы также можете реализовать атрибут MaxTotalZombies
, который гарантирует, что вы никогда не получите больше зомби, как определено. И если это произойдет, он выдаст исключение, указывающее на ваш код непосредственно там, где это произошло.
Также легче выявить ошибки. Потому что увеличение его два раза подряд выглядит неправильно.
status.IncreaTotalZombies();
status.IncreaTotalZombies();
где следующий код может выглядеть правильно
Status.TotalZombies += 2;
Но если вы сделаете вышеуказанные изменения, вы увидите, что ваш текущий Status.TotalZombies
больше не будет работать. Вы также должны изменить способ получения экземпляра вашего класса Status
. Для этого предположим, что вы создали GameObject в Unity с именем Status
. Затем в свой класс Generate
вы должны добавить следующее.
private Status status;
void Awake() {
this.status = GameObject.Find("Status").GetComponent<Status>();
}
Теперь вы можете заменить Status.TotalZombies++
и так далее на status.IncreaseTotalZombies()
. Если вы просто хотите получить значения, вы все равно можете написать status.TimeLeft
, но установка значения status.TimeLeft -= Time.deltaTime
теперь вызовет ошибку. И вам больше не нужно его устанавливать, потому что это поведение, которое класс Status
уже обрабатывает в своем методе Update
.
Кроме того, в вашем классе Generate
у вас был такой код.
Application.LoadLevel("level 2");
Status.TotalZombies=10;
Status.timeLeft=59.0f;
Это не сработало, как ожидалось. Потому что, когда вы вызываете Application.LoadLevel()
, вызывается ваша новая сцена, а строки за ней никогда не вызывались. Вы можете исправить это, изменив порядок.
Status.TotalZombies=10;
Status.timeLeft=59.0f;
Application.LoadLevel("level 2");
Потому что ваш статус, где static
значение сохраняется при загрузке. Но весь подход по-прежнему не очень хорош. Проблема в том, что вы жестко задаете значения в своем коде. И, похоже, вам нужно разное количество Зомби и Времени для каждого уровня. Если вы хотите, вы можете просто добавить атрибуты в свой класс Status
, которые инициализируют ваши переменные, и эти переменные можно установить через вашу среду разработки Unity. Например, добавьте следующие атрибуты к вашему классу Status
.
public int _StartZombies = 5;
public float _StartTime = 25f;
Если вы добавите это в свой класс Status
сейчас, в вашей IDE появятся два текстовых поля с именами Start Zombies
и Start Time
. В этих полях теперь вы можете указать, сколько Зомби или сколько стартового времени должно быть у вашего уровня. Значения по умолчанию — 5
и 25
для этих значений. Но эти значения не применялись при загрузке вашего уровня. Чтобы также применять эти значения при загрузке вашего уровня, измените метод Awake
на.
void Awake() {
this.TotalZombies = this._StartZombies;
this.TimeLeft = this._StartTime;
}
Теперь this.TotalZombies
и this.TimeLeft
всегда получают значения, которые вы настроили в своей среде IDE. Единственное, что вам сейчас нужно сделать, это написать.
Application.LoadLevel("SomeLevel");
И вы просто можете настроить количество зомби и время через вашу IDE! Это также означает, что теперь у вас есть повторно используемые компоненты. И вы настраиваете вещи там, где они принадлежат!
Вы также описали, что вам нужны разные условия для загрузки нового уровня. Например, если пользователь может убить всех зомби за определенное время, он сразу переходит на уровень 3 вместо уровня 2 и так далее. Итак, как вы можете добавить это, не создавая множество специальных классов?
Сначала вам нужен отдельный класс, который просто хранит данные. В вашем случае вам нужно конкретное время и определение, какой уровень загружается. Итак, вы можете написать что-то вроде этого.
[System.Serializable]
public class LoadLevelData {
public float TimeLeft;
public string LoadLevel;
}
Но, на мой взгляд, эта логика относится к классу Status
, поэтому сейчас вы добавляете в этот класс следующее.
public LoadLevelData[] _NextLevels;
Как только вы добавите это в свой код. В Unity IDE вы увидите «Следующие уровни» с «Курсором». Теперь вы можете развернуть этот курсор, и появится поле Size
. Теперь вы можете, например, записать в него 2, и это даст вам Element 0
и Element 1
. Таким образом, Unity дает вам возможность создавать массив объектов, и вы можете создавать столько записей, сколько хотите, из IDE с любыми значениями, которые вы хотите!
Теперь вы можете написать метод LoadNextLevel
таким образом.
public void LoadNextLevel() {
foreach ( var level in this._NextLevels ) {
if ( level.TimeLeft > this.TimeLeft ) {
Application.LoadLevel(level.LoadLevel);
}
}
}
Теперь вы можете настроить в Unity IDE
Element 0:
Time Left -> 20
Next Level -> "Level 3"
Element 1:
Time Left -> 10
Next Level -> "Level 2"
Вам нужно только позвонить status.LoadNextLevel()
, когда ваша игра закончится. И вы можете настроить все из IDE. Также обратите внимание. Порядок, в котором вы заполняете массив _NextLevel, важен. В этом случае «Оставшееся время» -> 20 должно стоять перед «10».
person
David Raab
schedule
08.11.2014