zhora41, не уверен, что сейчас (спустя >2 лет с момента ответа) я бы стал делать так же, но ООП ведь само по себе не дает ответа на то, как "записать данные в файл". Особенно сложные данные, образующие нетривиальный граф зависимостей.
Под .NET есть множество способов сериализации объектов, в том числе поддерживающих циклические ссылки и другие сложные случаи. Можно сделать более гибкое сохранение на связке Entity Framework + SQLite, например. Или LiteDb.
JSON это не кульбит, а просто формат хранения данных. Можно в xml, можно в бинарном виде, суть не поменяется.
То, что я тогда обозвал "прокси", скорее нужно было назвать слоем хранения данных. Набор POCO классов, образующих собственно модель данных приложения, которые могут быть автоматически сохранены или восстановлены с диска. В каких-то сценариях можно в них также поместить и собственно логику приложения, в более комплексных случаях лучше выделить отдельный слой для этого с маппингом на слой данных. Подходы бывают разные, что в C#, что в других языках.
shadowmerc, переделывать все равно придется, без опыта с первого раза нормально редко получается сделать. У меня, по крайней мере.
На современных (и даже не совсем) смартфонах трехмерные игры работают достаточно быстро. Кроме того, на этапе прототипирования можно использовать обычные 3D модели (это просто удобнее, чем работать со спрайтами и их анимацией, по крайней мере если не очень опытный художник), если производительности будет не хватать - рендерить модели (например, в blender) в спрайты и заменять их модели внутри игры на получившиеся 2D спрайты.
В зависимости от архитектуры игры и связности логики с визуальным представлением объектов (если там какая-нибудь физика будет, например), переход может быть как почти безболезненным, так и весьма трудозатратным.
Непосредственно опыта таких манипуляций не имею, так что не могу точно предсказать подводные камни.
Попробовал .NET Core + SignalR (специальная версия либы для core), но пока не удалось заставить unity подключить .net core клиентскую библиотеку, а попробовать новый core, конечно, хочется. Впрочем, технологий под .net много, что-нибудь да найдется.
А статью от TheShock читал, выделение непосредственно логической модели игры как отдельной сборки действительно структурирует код (но в некоторых местах компонентный подход unity может быть удобнее, чем наследование).
Спасибо за развернутый ответ, пока его читал и "обрабатывал" вроде какие-то вещи в голове улеглись)
Примерно так я и хочу сделать, сервер полностью авторитарный. Локальную симуляцию я рассматривал исключительно как средство для достижения следующих целей:
Минимально трудоемкий способ сохранения на клиенте уже пришедшей информации о мире. Т.е. в любом случае, мне нет смысла обновлять информацию о планетах постоянно, следовательно клиент должен ее запоминать и не запрашивать каждый раз. Локальная симуляция может естественным образом выступать как структурированный контейнер для имеющейся у клиента информации о мире.
Автоматическая интерполяция/экстраполяция. Клиент знает, по каким законам существуют объекты во вселенной, следовательно может очень точно предсказывать ситуацию в своей области на основании имеющихся данных, пока не прилетит обновление с сервера. Или пока он не запросит у сервера выполнение команды ("хочу лететь туда" и т.д.) и с сервера не придет ответ с обновлениями. Доверия клиенту, конечно, никакого нет и получает он только то, о чем должен знать.
Т.е. так или иначе, какая-то локальная "симуляция" все равно есть, это подразумевают пункты 5-7 - клиенту нужно строить какую-то свою модель мира, чтобы понимать, как ее отрисовывать.
Вопрос только в том, писать ли эту модель независимо от серверного кода, или пытаться использовать тот же код, что крутится на сервере. Предполагаю, что этот вариант работает, пока сервер простой и легкий (ранние стадии разработки) и начнет создавать проблемы на поздних.
UNet видимо использовать не буду, пока попытаюсь в сервер ASP.NET с SignalR.
Правда, у игрока может быть множество кораблей и они, возможно, могут продолжать автоматическое функционирование, когда игрок оффлайн, да и масштабы очень сильно разнятся - я пытаюсь сделать относительно реалистичный мир, т.е. расстояния могут различаться на порядки (например, между звездными системами) и чанки довольно расплывчатыми становятся. Но вот выгрузка статичных объектов (астероидов каких-нибудь с неизменной траекторией) может стать полезной
А вот по предсказанию движений - с одной стороны, если на клиенте полная симуляция (т.е. локальный сервер, в каком-то смысле), то интерполяция работает автоматически - пока некий корабль не меняет тягу, его нет нужды обновлять, он будет лететь на клиенте точно так же, как и на сервере, в пределах погрешности.
Меня скорее беспокоит вопрос, шарить ли код сервера с клиентом, или писать одновременно "полноценную" серверную симуляцию и похожую, но другую, для клиента? Первый вариант менее трудоемок с одной стороны, с другой - любой хак на клиенте (то же "незнание" внутренней структуры чужого корабля, обработка ссылок на несуществующие для клиента объекты, возможно) будет существовать и в серверном коде, что плохо. К тому же, на клиенте уж точно нет БД, к которой возможно в процессе обращается сервер. Вот как этот вопрос решать не знаю, то ли дублировать логику, то ли делать код сервера универсальным и усложненным.
Первое предположение - внутренний вызов search просто ничего не делает, т.к. результат его выполнения нигде не используется. Возможно должно быть что-то вроде
Denis Gaydak: я еще исхожу из того, что вещи, которые я рисую, еще страшнее кода, который я пишу) Поэтому вдохновляюсь максимально простыми и информативными интерфейсами (вот еще пример из Orbiter), но пытаюсь все же не дойти до уровня тайлинговых wm. Вот и получаю подобную жуть, но для прототипа, наверное, терпимо.
Вообще, лучший интерфейс, который я когда-либо видел (на мой взгляд), в Elite: Dangerous. Распределенные по кокпиту панели очень органично вписываются в игру, к тому же могут очень быстро вызываться и управляться только с клавиатуры, без мыши. Впрочем, такой вариант подходит для космосима, но невозможен в какой-нибудь стратегии.
Однако, спасибо большое за ссылку, эвристическая оценка метода без непосредственной реализации и проверки оказалась неверна)
Метод показался слишком трудоемким (нарезать спрайты, заставить их неразрывно масштабироваться), совершенно не подумал о том, что он уже в самом движке организован.
И все же жаль, что в unity нет возможности работать с векторной графикой (хотя для этого возможно стоит смотреть в сторону альтернативных UI фреймворков вроде Noises GUI или Coherent).
Собственно, синглтон как один из вариантов я и рассматриваю. Наиболее простой выход из ситуации, конечно, но все же в чистом виде он подразумевает, что объект никогда не будет уничтожен, а в моем случае загрузка из сохранения это как раз уничтожение старого мира и создание нового, что в синглтоне делать нежелательно (хотя, конечно, можно, но не хотелось бы оставлять возможность объектов старого мира обратиться к новому, "думая", что он свой).
Кроме того, в unit-тестах желательно создавать изолированный чистый мир (напоролся на то, что сейчас они банально перестают проходить после запуска игры в редакторе, т.к. создаваемый ими объект имеет тот же guid, что и тот, что был в игре. Конечно, легко исправить, но плохо же).
Так using, пусть даже новый C#6, можно использовать либо для более лаконичного вызова статических методов (using static System.Math), либо, как обычно, для псевдонимов типов
using Stream = System.IO.Stream
А для this.Hardpoint это же не static ни разу, такое только макросы в C++ дадут провернуть, наверное)
Ну using/using static это все же способ создать псевдонимы для типов, а не для вызова экземплярных свойств.
Впрочем, спасибо, пока эту строчку вызовов написал, понял, что практически всегда до мира можно по крайней мере "добраться" по цепочке свойств и закешировать при необходимости, т.ч. особой проблемы, наверное, и нет.
Если вопрос был про то, что означала строка this.Harpoint...., то это все обычные свойства (называются просто также как и предоставляемые типы, для лаконичности). Ну, оборудование знает про отсек, в котором оно установлено, отсек знает в каком корабле он находится, а уж корабль знает в каком он, собственно, мире. Цепочка, на мой взгляд, логичная, но уж больно длинная.
Я использую некое подобие ModelView паттерна, т.ч. скрипты/gameobject'ы использую как View/Controller или что-то в этом роде. Поэтому большая часть кода вообще не является скриптами и знать не знает ни про какие GameObject'ы.
Не совсем понял про неуспевшие создаться объекты. При загрузке основной сцены игровой мир (набор систем, кораблей и т.д.) либо уже существует (т.е. эти статические классы инициализированы), либо будет создан перед инициализацией чего угодно другого (десериализован или сгенерирован, не важно). Т.ч. со временем создания проблемы и нет, есть проблема того, что, допустим, если какое-нибудь оборудование раньше могло получить событие старта хода как
WorldCtl.TurnStarted
то теперь ему придется делать что-то вроде
this.Hardpoint.Spacecraft.WorldContext.WorldCtl.TurnStarted
Вот думаю, как это слегка подсократить. Кроме идеи с интерфейсом ничего не приходит в голову.
А для скриптов, конечно, все проще - в какой-нибудь общедоступный контроллер закинуть свойство и готово.
А я его толком и не видел, т.к. ни в чем серьезном пока не участвовал) Буду копать современные подходы, я то думал, что какой-нибудь WPF с его сотнями событий это современно (хотя какой-нибудь INotifyPropertyChanged с его строковыми свойствами положительных мыслей о прогрессе не вызывает...).
Под .NET есть множество способов сериализации объектов, в том числе поддерживающих циклические ссылки и другие сложные случаи. Можно сделать более гибкое сохранение на связке Entity Framework + SQLite, например. Или LiteDb.
JSON это не кульбит, а просто формат хранения данных. Можно в xml, можно в бинарном виде, суть не поменяется.
То, что я тогда обозвал "прокси", скорее нужно было назвать слоем хранения данных. Набор POCO классов, образующих собственно модель данных приложения, которые могут быть автоматически сохранены или восстановлены с диска. В каких-то сценариях можно в них также поместить и собственно логику приложения, в более комплексных случаях лучше выделить отдельный слой для этого с маппингом на слой данных. Подходы бывают разные, что в C#, что в других языках.