Бесплатно по России

С 10:00 до 21:00

С 10:00 до 21:00

Заполнить бриф

Онлайн заявка

#

Методы оптимизации кода на примере River Raft

Редакция Appfox

Редакция Appfox

Время чтения: 2 минуты

Оптимизация является очень важным этапом при разработке игры. Каждая игра нуждается в оптимизации. Игра RiverRaft - это сложный симулятор сплава по рекам и целевая платформа это Android, где не у всех есть достаточно мощные устройства. В игре присутствует динамическая вода. Объект который моделирует поведение воды в игре, повторяя его физику. Так же скрипт, который моделирует изменение меша плоскости, эмулируя волны. Скрипт, который отвечает за физику плавающего объекта по волнам.

И так, приступим. Первый же метод – это избавление от лишних вычислений в методе Update. Так как Update выполняет просчет каждый кадр и это слишком часто для сложных вычислений, что само по себе влечет уменьшение качества кадров в секунду.

Рассмотрим это все на примере скрипта Floating Object. Здесь большую часть значений мы вычисляем в методе Start. И сохраняем их в памяти. Тем самым мы избавляемся от нежелательного «деления каждый кадр».

void Start()
{
  objectVolume = this.rigidbody.mass / this.density;
  voxelsCountForEachAxis = Mathf.RoundToInt(1f / this.normalizedVoxelSize);

  _bounds = this.collider.bounds;
  voxelHeight = _bounds.size.y * this.normalizedVoxelSize;
  voxelHeightHalf = voxelHeight * 0.5f;
  voxelOneHeightDel = (float)1 / (float)voxelHeight;
}

protected virtual void FixedUpdate()
{
  if (this.water != null && this.voxels.Length > 0)
  {
    CalculateMaxBuoyancyForce();

    float submergedVolume = 0f;

    for (int i = 0; i < this.voxels.Length; i++)
    {
      Vector3 worldPoint = this.transform.TransformPoint(this.voxels[i]);

      float waterLevelSum = 0;
      waterCollides.ForEach(x => { waterLevelSum += x.GetWaterLevel(worldPoint); });
      float waterLevel = waterLevelSum / waterCollides.Count;
      float deepLevel = waterLevel - worldPoint.y + voxelHeightHalf; // How deep is the voxel

      // 0 - voxel is fully out of the water, 1 - voxel is fully submerged        
      float submergedFactor = Mathf.Clamp(deepLevel * voxelOneHeightDel, 0f, 1f);
      submergedVolume += submergedFactor;

      Vector3 surfaceNormal = Vector3.zero;
      waterCollides.ForEach(x => { surfaceNormal += x.GetSurfaceNormal(worldPoint); });
      surfaceNormal = surfaceNormal / waterCollides.Count;

      Vector3 newRotation = Vector3.zero;
      Quaternion surfaceRotation = Quaternion.FromToRotation(newRotation, surfaceNormal);
      surfaceRotation = Quaternion.Slerp(surfaceRotation, Quaternion.identity, submergedFactor);

      Vector3 finalVoxelForce = surfaceRotation * (forceAtSingleVoxel * submergedFactor);
      this.rigidbody.AddForceAtPosition(finalVoxelForce, worldPoint);

      Debug.DrawLine(worldPoint, worldPoint + finalVoxelForce.normalized, Color.blue);
    }

    submergedVolume = submergedVolume * voxelLength; // 0 - object is fully out of the water, 1 - object is fully submerged

    this.rigidbody.drag = Mathf.Lerp(this.initialDrag, this.dragInWater, submergedVolume);
    this.rigidbody.angularDrag = Mathf.Lerp(this.initialAngularDrag, this.angularDragInWater, submergedVolume);
  }
}

Естественно мы избавились от лишних вычислений, которые нам не нужны, а именно от waterCollides, newRotation, waterLevel

Входные и выходные параметры для этих выражений имеют значение = 0. Но все же вычисления происходят и этот ноль нужно как-то получить. Поэтому логично было бы избавиться от этих вычислений.

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

private void CalculateMaxBuoyancyForce()
{
  Vector3 maxBuoyancyForce = this.water.Density * objectVolume * -Physics.gravity;
  forceAtSingleVoxel = maxBuoyancyForce * voxelLength;
}

Так мы сумеем повысить фпс на 10 пунктов.

Скрипт WaterVolume. Так же избавляемся от лишний вычислений. Тем самым повышаем фпс еще на 5 пунктов.

public float GetWaterLevel(Vector3 worldPoint)
{
  Vector3[] meshPolygon = this.GetSurroundingTrianglePolygon(worldPoint);

  if (meshPolygon != null)
  {
    Vector3 planeV1 = meshPolygon[1] - meshPolygon[0];
    Vector3 planeV2 = meshPolygon[2] - meshPolygon[0];
    Vector3 planeNormal = Vector3.Cross(planeV1, planeV2).normalized;

    if (planeNormal.y < 0f)
    {
      planeNormal *= -1f;
    }

    // Plane equation
    float yOnWaterSurface = (-(worldPoint.x * planeNormal.x) - (worldPoint.z * planeNormal.z) + Vector3.Dot(meshPolygon[0], planeNormal)) / planeNormal.y;

    return yOnWaterSurface;
  }

  return this.transform.position.y;
}

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

protected virtual void Update()
{
  for (var i = 0; i < this.vertices.Length; i++)
  {
    var vertex = this.baseVertices[i];

    if (speed != 0 && height != 0)
    {
      vertex.y += Mathf.Sin(Time.time * this.speed + this.baseVertices[i].x + this.baseVertices[i].y + this.baseVertices[i].z) * chacheValue;
    }

    this.vertices[i] = vertex;
  }

  this.mesh.vertices = this.vertices;
  this.mesh.RecalculateNormals();
}

При проведении этих действий мы повысили фпс в общем на 25 пунктов. В итоге имеем толкьо при этих изменениях уже достаточных для игры 40 фпс. Но этого не достаточно. В следующей статье будет рассказано о оптимизации локации для повышения фпс.

В студии AppFox можно подать заявку на программирование, создание игр или заказать разработку приложений https://appfox.ru/ и получить бесплатную консультацию по ценам и услугам.

Автор

Редакция Appfox

Редакция Appfox