Галактика сплющена по вертикали, поскольку я подключил второй монитор и не настроил графический планшет на правильное взаимодействие с ним. В результате он стал воспринимать в два раза большую по ширине рабочую область при такой же ее высоте. Чувствительность по вертикали стала меньше, чем по горизонтали. Пока до меня это дошло, галактику я уже дорисовал.
Прошло уже больше недели с тех пор, как я создал проект на XGM, и я подумал, что можно было бы рассказать в общих чертах о том, чем я тут занимаюсь. Здесь будет довольно много кода и совсем чуть-чуть общих слов о прогрессе разработки в конце поста.

Меня зовут гирвел и я трачу слишком много времени на код

Когда проект разрабатывает художник, ему всегда будет, что показывать - результат его разработки обычно выглядит вполне себе опрятно и внешне очень похож на игру. У меня же результат выглядит как-то так:
Это, к слову, должна была быть ракета со спейс марином в ней.

Между тем, большую часть времени я трачу на написание кода. Поэтому мне хотелось бы немного рассказать о внутренностях игры и совсем чуть-чуть поныть о том, как я мучился с документацией юнити. Все это будет происходить на поучительном примере написания системы SpacePhysics, состоящей из всего двух классов.
Кстати, пишу я на C#, потому что скрипты для Unity все пишут на C#. А еще потому, что я могу только в C#.

SpacePhysics - система, которая имитирует космическую физику. Работает она на основе Rigidbody2D (стандартная система Unity, делающая из двумерного объекта твердый двумерный объект) и позволяет выводить объекты на орбиты друг друга и заставлять их кружиться по красивым траекториям.
То, что при написании любого объектно-ориентированного кода надо делать в первую очередь - рисовать UML-диаграмму и заранее планировать, как все будет работать. У SpacePhysics окончательная диаграмма выглядит так:
UML диаграмма что это
UML диаграмма - диаграмма, отображающая структуру кода.
Существует некий стандарт UML, можно записывать в диаграммы всю информацию подряд и проводить время в прочих бессмысленных перфекционистских занятиях, но я предпочитаю делать небольшие диаграммы с короткими пояснениями, где указываются только публичные члены для экономии времени.
Между тем, это тот вариант, который был достигнут методом проб и ошибок за три дня и к которому я пришел, перепробовав несколько вариантов реализации. Мораль такова - стройте диаграммы и планируйте код заранее, пусть C# в сочетании с Visual Studio и позволяет переписывать огромные куски кода без проблем. А еще читайте документацию, пусть она большей частью и не русская, но если она не про Quaternion, то понять можно, а идея переписывать все системы самому не такая хорошая.

Так вот, есть класс GlobalPhysicsController, который производит все расчеты, а также содержит гравитационную константу G, которая и близко не соответствует G в реальном мире во славу геймплея. Еще он имеет список всех объектов, подверженных космической физике, и с определенной частотой в функции события FixedUpdate() прикладывает к ним силу, рассчитываемую по формуле ниже, с помощью метода <Rigidbody2D>.AddForce()
F = (G * m1 * m2) / dist ^ 2
Он наследует из MonoBehaviour'а и прикрепляется к объекту космоса, который существует только ради GlobalPhysicsController'а и ничего другого не делает.
Таким образом, общая идея:
class GlobalPhysicsController : MonoBehaviour
{
	public const G = 3e-3f;
	
	protected static List<GameObject> spaceObjects;
	
	protected virtual void FixedUpdate()
	{
		// code
	}
}
FixedUpdate() является встроенной функцией события, поэтому всегда является protected virtual, чтобы можно было написать класс-наследник и перегрузить функцию события.

Второй класс - LocalPhysicsController. Он содержит информацию о массе объекта (mass), которая может быть отрицательной (поскольку в дальнейшем это будет использовано как геймплейная фича), которая постоянно конфликтует с массой Rigidbody2D. Я слишком ленивый, чтобы искать решение этой проблемы.
Также класс содержит информацию о объектах, находящихся на орбите владельца LocalPhysicsContoller'а, и объекте, на орбите которого находится он сам. Этого нет в диаграмме, они появились уже после ее составления и редактировать ее я мне опять же лень.
И еще есть метод Orbite(), который выводит объект-владелец на орбиту любого другого объекта, но обычно это делается прямо из Unity.
Прикрепляется к любому объекту, делая его восприимчивым к космической физике, опять же через наследование из MonoBehaviour
class LocalPhysicsController : MonoBehaviour
{
	public float mass;
	
	protected GameObject orbiteParent;
	protected List<GameObject> orbiteChildren;
	
	public void Orbite(gameObject orbiteParent)
	{
		// code
	}
}

Если кому то интересно, вот код обоих классов.
код
GlobalPhysicsController
using UnityEngine;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Systems.SpacePhysicsSystem
{
    [AddComponentMenu("Physics 2D/Global Phycics Controller")]
    public class GlobalPhysicsController : MonoBehaviour
    {
        // cache:
        static List<Rigidbody2D> soRigidbodies2D = new List<Rigidbody2D>();
        static List<Transform> soTransforms = new List<Transform>();
        static List<LocalPhysicsController> soPhysics = new List<LocalPhysicsController>();

        // constants:
        /// <summary>
        /// Gravitational constant
        /// </summary>
        public const float G = 1e-3f;

        // fields:
        public static List<GameObject> spaceObjects = new List<GameObject>();

        public static void AddSpaceObject(GameObject spaceObject)
        {
            spaceObjects.Add(spaceObject);

            soRigidbodies2D.Add(spaceObject.GetComponent<Rigidbody2D>());
            soTransforms.Add(spaceObject.GetComponent<Transform>());
            soPhysics.Add(spaceObject.GetComponent<LocalPhysicsController>());
        }

        public static void RemoveSpaceObject(GameObject spaceObject)
        {
            spaceObjects.Remove(spaceObject);

            soRigidbodies2D.Remove(spaceObject.GetComponent<Rigidbody2D>());
            soTransforms.Remove(spaceObject.GetComponent<Transform>());
            soPhysics.Remove(spaceObject.GetComponent<LocalPhysicsController>());
        }

        void FixedUpdate()
        {
            for (int i2 = 0; i2 < soTransforms.Count; i2++)
            {
                for (int i = 0; i < soTransforms.Count; i++)
                {
                    if (soTransforms[i].position == soTransforms[i2].position)
                        continue;

                    var v = soTransforms[i].position - soTransforms[i2].position;

                    float l = v.magnitude;
                    v.Normalize();

                    v *= G * soPhysics[i].mass * soPhysics[i2].mass / Mathf.Pow(l, 2);

                    soRigidbodies2D[i2].AddForce(v, ForceMode2D.Force);
                }
            }
        }
    }
}
LocalPhysicsController
using UnityEngine;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Systems.SpacePhysicsSystem
{
    [AddComponentMenu("Physics 2D/Local Phycics Controller")]
    public class LocalPhysicsController : MonoBehaviour
    {
        // cache:
        Rigidbody2D thisRigidbody2D;
        Transform thisTransform;

        // fields:
        public float mass;

        public GameObject orbiteParent;
        public List<GameObject> orbiteChildren;

        public void Orbite(GameObject orbiteParent, bool calledByParent = false)
        {
            if (!calledByParent)
            {
                this.orbiteParent = orbiteParent;
            }
            orbiteParent.GetComponent<LocalPhysicsController>().orbiteChildren.Add(gameObject);

            var d = orbiteParent.transform.position - thisTransform.position;
            var v = Mathf.Sqrt(GlobalPhysicsController.G * orbiteParent.GetComponent<LocalPhysicsController>().mass / d.magnitude);
            var v2 = orbiteParent.GetComponent<Rigidbody2D>().velocity + new Vector2(d.normalized.y * v, -d.normalized.x * v);

            thisRigidbody2D.velocity += v2;
            
            foreach (var c in orbiteChildren)
                c.GetComponent<LocalPhysicsController>().Orbite(orbiteParent, true);
        }

        public void Orbite()
        {
            Orbite(orbiteParent);
        }

        void Start()
        {
            if (orbiteParent != null)
                Orbite();

            thisRigidbody2D = GetComponent<Rigidbody2D>();
            thisTransform = GetComponent<Transform>();

            thisRigidbody2D.gravityScale = 0;

            orbiteChildren = new List<GameObject>();
            
            GlobalPhysicsController.AddSpaceObject(gameObject);
        }

        void Destroy()
        {
            GlobalPhysicsController.RemoveSpaceObject(gameObject);
        }
    }
}

Также немного о проделанной работе

  • Написана собственная простенькая система анимации, более удобная и логичная
  • Можно очень удобно отслеживать действия игрока (нажатия кнопок, мышь, ...)
  • Написана система юнитов
  • Персонаж уверенно передвигается в космосе, стреляет с анимацией по стенам сгустками плазмы, которые от них рикошетят и, если сильно постараться, с десятой попытки убивают игрока
  • Ракету на больших скоростях сильно трясет. Это вышло случайно, но выглядит очень реалистично.

Если вас заинтересовал проект, можете подписаться на проект в соцсетях:
  • ВКонтакте - короткие частые новости разработки, идеи, куски сценария
  • Twitter - все подряд, каждый второй твит имеет отношение к проекту

Я не знаю, насколько интересной и понятной получилась статья, поэтому буду ряд любому фидбеку. Мне очень важно знать, насколько интересным получился лог, что вышло занудно, где лучше было бы быть посерьезнее.
`
ОЖИДАНИЕ РЕКЛАМЫ...
2
29
8 лет назад
2
Я смотрю, у тебя тут есть связанные данные. Почему бы не объеденить их во что-то одно целое и потом использовать его, упростив в дальнейшем написание кода и работу с ним. Заодно и оптимизировать доступ к этим данным, как для обычного обращения, так и для удаления, если использовать хеш таблицу.
// добавление
	soRigidbodies2D.Add(spaceObject.GetComponent<Rigidbody2D>());
	soTransforms.Add(spaceObject.GetComponent<Transform>());
	soPhysics.Add(spaceObject.GetComponent<LocalPhysicsController>());
// удаление
	soRigidbodies2D.Remove(spaceObject.GetComponent<Rigidbody2D>());
	soTransforms.Remove(spaceObject.GetComponent<Transform>());
	soPhysics.Remove(spaceObject.GetComponent<LocalPhysicsController>());
Например:
// Объявляем класс (можно даже внутренний)
private class SpaceObjectMetaData {
	public Rigidbody2D Rigidbody {get; set;}
	public Transform Transform {get; set;}
	public LocalPhysicsController PhysicsController {get; set;}
}

Dictionary<SpaceObject, SpaceObjectMetaData> spaceObjects = new Dictionary<SpaceObject, SpaceObjectMetaData>();

// При добавлнии
	if (!spaceObjects.ContainsKey(spaceObject)) {
		spaceObjects.Add(spaceObject, new SpaceObjectMetaData {
			Rigidbody = spaceObject.GetComponent<Rigidbody2D>(),
			Transform = spaceObject.GetComponent<Transform>(),
			PhysicsController = spaceObject.GetComponent<LocalPhysicsController>()
		});
	}
// При удалении
	if (spaceObjects.ContainsKey(spaceObject)) {
		spaceObjects.Remove(spaceObject);
	}
// При обращении
	if (spaceObjects.ContainsKey(spaceObject)) {
		var metadata = spaceObjects[spaceObject];
	}
// или в цикле
	foreach (var spaceObjectPair in spaceObjects) {
		var spaceObject = spaceObectPair.Key,
		var metadata = spaceObjectPair.Value;
		// ....
	}
И тогда, если тебе потребуется добавить новую метаинформацию для объекта поребуется поменять только 1 место (инициализация метаданных) и в остальных местах, где она используется сразу же будет доступ не ней.
0
21
8 лет назад
Отредактирован girvel
0
Бонус вдогонку
pic
#indev
Опрос: Как вы думаете, насколько нормально смотрится мыльный фон?
1. 
норм
2. 
не норм
Загруженные файлы
0
23
8 лет назад
Отредактирован SomeFire
0
girvel, без содержимого вся картинка - фон. Хз как будет выглядеть во время игры.
0
21
8 лет назад
0
girvel:
Опрос: Как вы думаете, насколько нормально смотрится мыльный фон?
Если речь о размыливании пикселей/интерполяции и подобном то не очень.
Чтобы оставить комментарий, пожалуйста, войдите на сайт.