Оптимизация является очень важным этапом при разработке игры. Каждая игра нуждается в оптимизации. Игра 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/ и получить бесплатную консультацию по ценам и услугам.