Anonim

AIMBOT 2.0

В эпизоде ​​1 Новой игры 2, около 9:40, есть кадр кода, который написала Нене:

Вот он в текстовой форме с переведенными комментариями:

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); } } } 

После выстрела Умико, указав на цикл for, сказала, что причина сбоя кода в том, что существует бесконечный цикл.

Я действительно не знаю C ++, поэтому не уверен, правда ли то, что она говорит.

Из того, что я вижу, цикл for просто перебирает debufs, которые в настоящее время есть у Actor. Если у Актера не будет бесконечного количества дебафов, я не думаю, что это может стать бесконечным циклом.

Но я не уверен, потому что единственная причина, по которой есть фрагмент кода, заключается в том, что они хотели поместить сюда пасхальное яйцо, верно? Мы бы только что сделали снимок задней части ноутбука и услышали бы, как Умико говорит: «О, у вас там бесконечный цикл». Тот факт, что они действительно показали какой-то код, заставляет меня думать, что каким-то образом этот код является своего рода пасхалкой.

Будет ли код действительно создавать бесконечный цикл?

8
  • Возможно, полезно: дополнительный снимок экрана, на котором Умико говорит, что "Это было вызов той же операции снова и снова ", что может не отображаться в коде.
  • Ой! Я этого не знал! @AkiTanaka, подводная лодка, которую я смотрел, говорит "бесконечный цикл"
  • @LoganM Я не совсем согласен. Дело не только в том, что у OP есть вопрос об исходном коде, взятом из аниме; Вопрос OP касается конкретного заявления, сделанного о исходный код персонажа из аниме, и есть ответ, связанный с аниме, а именно: «Crunchyroll сделал глупость и неправильно перевел строку».
  • @senshin Я думаю, вы читаете то, о чем хотите, чтобы вопрос был задан, а не то, о чем на самом деле спрашивают. Вопрос предоставляет некоторый исходный код и спрашивает, генерирует ли он бесконечный цикл как реальный код C ++. Новая игра! это художественное произведение; нет необходимости, чтобы код, представленный в нем, соответствовал реальным стандартам. То, что Умико говорит о коде, более авторитетно, чем любые стандарты или компиляторы C ++. В верхнем (принятом) ответе не упоминается какая-либо информация во вселенной. Я думаю, что можно задать вопрос по теме с хорошим ответом, но, как говорится, это не так.

Код не бесконечный цикл, но это ошибка.

Есть две (возможно, три) проблемы:

  • Если дебафов нет, урон вообще не будет нанесен
  • Чрезмерный урон будет нанесен, если дебуфов больше 1
  • Если DestroyMe () немедленно удаляет объект, а есть еще m_debufs для обработки, цикл будет выполняться над удаленным объектом и уничтожать память. У большинства игровых движков есть очередь уничтожения, чтобы обойти это и многое другое, так что это может не быть проблемой.

Нанесение повреждений должно быть вне петли.

Вот исправленная функция:

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); } m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); } } 
12
  • 15 Мы на проверке кода? : D
  • 4 поплавка отлично подходят для здоровья, если вы не набираете больше 16777216 HP. Вы даже можете установить здоровье на бесконечность, чтобы создать врага, которого вы можете поразить, но не умрет, и провести атаку с одним убийством с использованием бесконечного урона, который все равно не убьет персонажа с бесконечным HP (результат INF-INF равен NaN), но убьет все остальное. Так что это очень полезно.
  • 1 @cat По соглашению во многих стандартах кодирования m_ префикс означает, что это переменная-член. В этом случае переменная-член DestructibleActor.
  • 2 @HotelCalifornia Я согласен, есть небольшой шанс ApplyToDamage работает не так, как ожидалось, но в приведенном вами примере я бы сказал ApplyToDamage также необходимо переработать, чтобы потребовать передачи оригинала sourceDamage также, чтобы он мог правильно вычислить дебаф в этих случаях. Чтобы быть абсолютным педантом: на этом этапе информация об уроне должна быть структурой, которая включает исходный урон, текущий урон и природу повреждений, а также, если дебафы имеют такие вещи, как «уязвимость к огню». По опыту, это не заставит себя долго ждать любой игровой дизайн с дебафами.
  • 1 @StephaneHockenhull хорошо сказано!

Код не создает бесконечного цикла.

Единственный способ сделать цикл бесконечным - это если

debuf.ApplyToDamage(resolvedDamage); 

или же

DestroyMe(); 

должны были добавить новые элементы в m_debufs контейнер.

Это кажется маловероятным. И если бы это было так, программа могла бы аварийно завершить работу из-за изменения контейнера во время итерации.

Программа, скорее всего, выйдет из строя из-за вызова DestroyMe(); который предположительно уничтожает текущий объект, который в данный момент выполняет цикл.

Мы можем думать об этом как о мультфильме, в котором «плохой парень» пилит ветку, чтобы «хороший парень» упал вместе с ней, но слишком поздно понимает, что находится не на той стороне разреза. Или мидгаардская змея, поедающая собственный хвост.


Я также должен добавить, что наиболее частым признаком бесконечного цикла является то, что он замораживает программу или делает ее не реагирующей. Это приведет к сбою программы, если она повторно выделяет память или делает что-то, что в конечном итоге делится на ноль, или тому подобное.


На основании комментария Аки Танаки:

Возможно, полезно: дополнительный снимок экрана, на котором Умико говорит, что «он снова и снова вызывает одну и ту же операцию», что может не отображаться в коде.

«Он снова и снова вызывал одну и ту же операцию» Это более вероятно.

При условии, что DestroyMe(); не предназначен для вызова более одного раза, это с большей вероятностью вызовет сбой.

Способ решить эту проблему - изменить if для чего-то вроде этого:

 if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; } 

Это приведет к выходу из цикла, когда DestructibleActor будет уничтожен, убедившись, что 1) DestroyMe метод вызывается только один раз и 2) не накладывайте баффы без толку, если объект уже считается мертвым.

2
  • 1 Выход из цикла for при состоянии здоровья <= 0 определенно является лучшим решением, чем ожидание завершения цикла для проверки состояния.
  • Думаю, я бы наверное break вне цикла, и тогда вызов DestroyMe()на всякий случай

С кодом есть несколько проблем:

  1. Если нет дебафов, повреждений не будет.
  2. DestroyMe() название функции звучит опасно. В зависимости от того, как это реализовано, это может быть или не быть проблемой. Если это просто вызов деструктора текущего объекта, заключенного в функцию, тогда возникает проблема, так как объект будет уничтожен в середине выполнения кода. Если это вызов функции, которая ставит в очередь событие удаления текущего объекта, тогда нет проблемы, поскольку объект будет уничтожен после того, как он завершит свое выполнение, и сработает цикл событий.
  3. Фактическая проблема, которая, кажется, упоминается в аниме, «Он снова и снова вызывал одну и ту же операцию» - она ​​вызовет DestroyMe() так долго как m_currentHealth <= 0.f и осталось еще несколько дебаффов для повторения, что может привести к DestroyMe() вас вызывают несколько раз, снова и снова. Цикл должен остановиться после первого DestroyMe() вызов, потому что удаление объекта более одного раза приводит к повреждению памяти, что, вероятно, приведет к сбою в долгосрочной перспективе.

Я не совсем уверен, почему каждый дебафф забирает здоровье, а не только один раз, а эффекты всех дебаффов применяются к начальному полученному урону, но я предполагаю, что это правильная логика игры.

Правильный код будет

// the calculation of damage when attacked void DestructibleActor::ReceiveDamage(float sourceDamage) { // apply debuffs auto resolvedDamage = sourceDamage; for (const auto& debuf:m_debufs) { resolvedDamage = debuf.ApplyToDamage(resolvedDamage); m_currentHealth -= resolvedDamage if (m_currentHealth <= 0.f) { m_currentHealth = 0.f; DestroyMe(); break; } } } 
3
  • Я должен отметить, что, поскольку я писал распределители памяти в прошлом, удаление той же самой памяти не должно быть проблемой. Он также может быть избыточным. Все зависит от поведения распределителя. Мой просто действовал как связанный список низкого уровня, поэтому «узел» для удаленных данных либо становился свободным несколько раз, либо повторно удалялся несколько раз (что соответствовало бы перенаправлению избыточных указателей). Но хороший улов.
  • Double-free - это ошибка, которая обычно приводит к неопределенному поведению и сбоям. Даже если у вас есть собственный распределитель, который каким-то образом запрещает повторное использование одного и того же адреса памяти, double-free - это неприятный код, поскольку он не имеет смысла, и на вас будут кричать статические анализаторы кода.
  • Конечно! Я не создавал его для этой цели. Некоторым языкам просто требуется распределитель из-за недостатка функций. Нет нет нет. Я просто сказал, что авария не гарантируется. Определенные классификации проектов не всегда терпят крах.