суббота, 20 сентября 2014 г.

Space Engineers документация по API

Статья носит ознакомительный характер, и не претендует на истину в последней инстанции.
По мере изучения API я буду пополнять информацию.

И так доступ к игровому миру и ее механике мы получаем через класс MyAPIGateway.
Который в себе содержит следующие  интерфейсы:

         IMyCubeBuilder CubeBuilder;
         IMyEntities Entities;
         IMyMultiplayer Multiplayer;
         IMyPlayerCollection Players;
         IMySession Session;
         IMyTerminalActionsHelper TerminalActionsHelper;
         IMyUtilities Utilities;

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

Рассмотрим каждый.

1) CubeBuilder - содержит методы и объекты для управления процессов создания блоков.

 public interface IMyCubeBuilder
    {
        bool BlockCreationIsActivated { get; }
        bool CopyPasteIsActivated { get; }
        bool FreezeGizmo { get; set; }
        bool IsActivated { get; }
        bool ShipCreationIsActivated { get; }
        bool ShowRemoveGizmo { get; set; }
        bool UseSymmetry { get; set; }
        bool UseTransparency { get; set; }

        void Activate();
        void ActivateShipCreationClipboard(MyObjectBuilder_CubeGrid grid, VRageMath.Vector3 centerDeltaDirection, float dragVectorLength);
        void ActivateShipCreationClipboard(MyObjectBuilder_CubeGrid[] grids, VRageMath.Vector3 centerDeltaDirection, float dragVectorLength);
        bool AddConstruction(IMyEntity buildingEntity);
        void Deactivate();
        void DeactivateBlockCreation();
        void DeactivateCopyPaste();
        void DeactivateShipCreationClipboard();
        IMyCubeGrid FindClosestGrid();
        void StartNewGridPlacement(MyCubeSize cubeSize, bool isStatic);
    }

2) Entities - этот интерфейс я думаю будет интересней всего для создателей модов. Он содержит в себе методы для создания и управления всеми подконтрольными нам сущностями игрового мира, сенсоры, двигатели, орудия, кнопки и т.д.

    public interface IMyEntities
    {
        event Action OnCloseAll;
        event Action<IMyEntity, string, string> OnEntityNameSet;
        event Action<IMyEntity> OnEntityRemove;

        void AddEntity(IMyEntity entity, bool insertIntoScene = true);
        IMyEntity CreateFromObjectBuilder(MyObjectBuilder_EntityBase objectBuilder);
        IMyEntity CreateFromObjectBuilderAndAdd(MyObjectBuilder_EntityBase objectBuilder);
        IMyEntity CreateFromObjectBuilderNoinit(MyObjectBuilder_EntityBase objectBuilder);
        void EnableEntityBoundingBoxDraw(IMyEntity entity, bool enable, VRageMath.Vector4? color = null, float lineWidth = 0.01f, VRageMath.Vector3? inflateAmount = null);
        bool EntityExists(string name);
        bool Exist(IMyEntity entity);
        bool ExistsById(long entityId);
        VRageMath.Vector3? FindFreePlace(VRageMath.Vector3 basePos, float radius, int maxTestCount = 20, int testsPerDistance = 5, float stepSize = 1f);
        List<IMyEntity> GetElementsInBox(ref VRageMath.BoundingBox boundingBox);
        void GetEntities(HashSet<IMyEntity> entities, Func<IMyEntity, bool> collect = null);
        List<IMyEntity> GetEntitiesInAABB(ref VRageMath.BoundingBox boundingBox);
        List<IMyEntity> GetEntitiesInSphere(ref VRageMath.BoundingSphere boundingSphere);
        IMyEntity GetEntity(Func<IMyEntity, bool> match);
        IMyEntity GetEntityById(long entityId);
        IMyEntity GetEntityByName(string name);
        void GetInflatedPlayerBoundingBox(ref VRageMath.BoundingBox playerBox, float inflation);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1);
        List<IMyEntity> GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1, bool ignoreVoxelMaps, bool volumetricTest);
        IMyEntity GetIntersectionWithSphere(ref VRageMath.BoundingSphere sphere, IMyEntity ignoreEntity0, IMyEntity ignoreEntity1, bool ignoreVoxelMaps, bool volumetricTest, bool excludeEntitiesWithDisabledPhysics = false, bool ignoreFloatingObjects = true, bool ignoreHandWeapons = true);
        bool IsInsideVoxel(VRageMath.Vector3 pos, VRageMath.Vector3 hintPosition, out VRageMath.Vector3 lastOutsidePos);
        bool IsInsideWorld(VRageMath.Vector3 pos);
        bool IsNameExists(IMyEntity entity, string name);
        bool IsRaycastBlocked(VRageMath.Vector3 pos, VRageMath.Vector3 target);
        bool IsSelectable(IMyEntity entity);
        bool IsSpherePenetrating(ref VRageMath.BoundingSphere bs);
        bool IsTypeHidden(Type type);
        bool IsTypeSelectable(Type type);
        bool IsVisible(IMyEntity entity);
        bool IsWorldLimited();
        void MarkForClose(IMyEntity entity);
        void RegisterForDraw(IMyEntity entity);
        void RegisterForUpdate(IMyEntity entity);
        void RemapObjectBuilder(MyObjectBuilder_EntityBase objectBuilder);
        void RemapObjectBuilderCollection(IEnumerable<MyObjectBuilder_EntityBase> objectBuilders);
        void RemoveEntity(IMyEntity entity);
        void RemoveFromClosedEntities(IMyEntity entity);
        void RemoveName(IMyEntity entity);
        void SetEntityName(IMyEntity IMyEntity, bool possibleRename = true);
        void SetTypeHidden(Type type, bool hidden);
        void SetTypeSelectable(Type type, bool selectable);
        bool TryGetEntityById(long id, out IMyEntity entity);
        bool TryGetEntityByName(string name, out IMyEntity entity);
        void UnhideAllTypes();
        void UnregisterForDraw(IMyEntity entity);
        void UnregisterForUpdate(IMyEntity entity, bool immediate = false);
        float WorldHalfExtent();
        float WorldSafeHalfExtent();
    }

3) Multiplayer - данный интерфейс предоставляет нам скудные возможности по получению информации о сетевой игре.

    public interface IMyMultiplayer
    {
        bool IsServer { get; }
        bool MultiplayerActive { get; }
        ulong MyId { get; }
        string MyName { get; }
        IMyPlayerCollection Players { get; }
        ulong ServerId { get; }

        bool IsServerPlayer(IMyPlayer player);
    }

4) Players - немаловажный интерфейс, благодаря которому скоро появятся NPC в игре стараниями моддеров. Предоставляет возможность добавлять, убивать, удалять игроков.

     public interface IMyPlayerCollection
    {
        DictionaryReader<long, MyPlayerInfo> AllPlayers { get; }
        int Count { get; }

        IMyPlayer this[ulong steamUserId] { get; }

        void Add(IMyPlayer player);
        long AddNewNpc(string npcName);
        bool Exists(ulong steamUserId);
        void ExtendControl(IMyEntity entityWithControl, IMyEntity entityGettingControl);
        IMyPlayer GetPlayerControllingEntity(IMyEntity entity);
        void GetPlayers(List<IMyPlayer> players, Func<IMyPlayer, bool> collect = null);
        bool HasExtendedControl(IMyEntity firstEntity, IMyEntity secondEntity);
        void KillPlayer(long playerId);
        void ReduceControl(IMyEntity entityWhichKeepsControl, IMyEntity entityWhichLoosesControl);
        void Remove(IMyPlayer player);
        void Remove(ulong steamUserId);
        void RemoveControlledEntity(IMyEntity entity);
        void RevivePlayer(long playerId);
        void SendControlledEntities(ulong sendTo);
        SerializableDictionary<long, ulong> Serialize();
        void SetControlledEntity(ulong steamUserId, IMyEntity entity);
        void TryExtendControl(IMyEntity entityWithControl, IMyEntity entityGettingControl);
        bool TryGetPlayer(ulong steamUserId, out IMyPlayer player);
        bool TryReduceControl(IMyEntity entityWhichKeepsControl, IMyEntity entityWhichLoosesControl);
        bool TrySetControlledEntity(ulong steamUserId, IMyEntity entity);
        void UpdateFromPlayer(long playerId);
        void UpdateFromPlayers();
        void UpdateFromSession(List<MyObjectBuilder_Checkpoint.PlayerItem> playersFromSession);
    }

5) Session - данный интерфейс позволяет работать с текущей игровой сессией и ее параметрами ( творческий ли режим, максимальное кол-во игроков и т.д.)

    public interface IMySession
    {
        float AssemblerEfficiencyMultiplier { get; }
        float AssemblerSpeedMultiplier { get; }
        bool AutoHealing { get; }
        uint AutoSaveInMinutes { get; }
        IMyCameraController CameraController { get; }
        bool CargoShipsEnabled { get; }
        bool ClientCanSave { get; }
        IMyControllableEntity ControlledObject { get; }
        bool CreativeMode { get; }
        string CurrentPath { get; }
        string Description { get; set; }
        TimeSpan ElapsedPlayTime { get; }
        bool EnableCopyPaste { get; }
        MyEnvironmentHostilityEnum EnvironmentHostility { get; }
        DateTime GameDateTime { get; set; }
        float GrinderSpeedMultiplier { get; }
        float HackSpeedMultiplier { get; }
        float InventoryMultiplier { get; }
        bool IsCameraAwaitingEntity { get; set; }
        short MaxFloatingObjects { get; }
        short MaxPlayers { get; }
        bool MultiplayerAlive { get; set; }
        bool MultiplayerDirect { get; set; }
        double MultiplayerLastMsg { get; set; }
        string Name { get; set; }
        float NegativeIntegrityTotal { get; set; }
        MyOnlineModeEnum OnlineMode { get; }
        string Password { get; set; }
        IMyLocalPlayer Player { get; }
        float PositiveIntegrityTotal { get; set; }
        float RefinerySpeedMultiplier { get; }
        bool ShowPlayerNamesOnHud { get; }
        bool SurvivalMode { get; }
        bool ThrusterDamage { get; }
        string ThumbPath { get; }
        TimeSpan TimeOnBigShip { get; }
        TimeSpan TimeOnFoot { get; }
        TimeSpan TimeOnJetpack { get; }
        TimeSpan TimeOnSmallShip { get; }
        IMyVoxelMaps VoxelMaps { get; }
        bool WeaponsEnabled { get; }
        float WelderSpeedMultiplier { get; }
        ulong? WorkshopId { get; }
        string WorldID { get; set; }

        void BeforeStartComponents();
        void Draw();
        void GameOver();
        void GameOver(MyTextsWrapperEnum? customMessage);
        MyObjectBuilder_Checkpoint GetCheckpoint(string saveName);
        MyObjectBuilder_Sector GetSector();
        Dictionary<string, byte[]> GetVoxelMapsArray();
        MyObjectBuilder_World GetWorld();
        bool IsPausable();
        void RegisterComponent(MySessionComponentBase component, MyUpdateOrder updateOrder, int priority);
        bool Save(string customSaveName = null);
        void SetAsNotReady();
        void Unload();
        void UnloadDataComponents();
        void UnloadMultiplayer();
        void UnregisterComponent(MySessionComponentBase component);
        void Update(float timeInSeconds);
        void UpdateComponents();
    }

6) TerminalActionsHelper - еще не разобрался, но думаю это как то связано с терминалами в игре..

    public interface IMyTerminalActionsHelper
    {
        void GetActions(Type blockType, List<ITerminalAction> resultList, Func<ITerminalAction, bool> collect = null);
    }

7) Utilities - интерфейс утилит, тут мы имеем доступ к чату, уведомлениям, и к неким хранилищам данных, мне еще предстоит разобраться с этим.

    public interface IMyUtilities
    {
        IMyConfigDedicated ConfigDedicated { get; }

        event MessageEnteredDel MessageEntered;

        string GetTypeName(Type type);
        TextReader ReadFileInGlobalStorage(string file);
        TextReader ReadFileInLocalStorage(string file, Type callingType);
        void SendMessage(string messageText);
        void ShowMessage(string sender, string messageText);
        void ShowNotification(string message, int disappearTimeMs = 2000, MyFontEnum font = MyFontEnum.White);
        TextWriter WriteFileInGlobalStorage(string file);
        TextWriter WriteFileInLocalStorage(string file, Type callingType);
    }


Space Engineers API C#

Я в кратце расскажу как писать собственные моды под игру Space Engineers на языке C# (Sharp).
Статья больше ориентирована на более менее опытных людей, знакомых с азами программирования на одном из объектно ориентированных языков.

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

Открываем видео от разработчиков: http://www.youtube.com/watch?v=gAh1bNfRLPw

По видео мы повторяем все до момента когда придет пора писать код.
Вот тут мы остановимся по подробнее.
По первых: убедитесь что вы импортировали все необходимые пространства имен как указано в видео, начала вашего кода будет выглядеть как то так:

using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Linq;
using Sandbox.ModAPI;
using Sandbox.Common;
using Sandbox.Common.ObjectBuilders;
using Sandbox.Common.Components;



Далее мы указываем пространство имен и имя нашего класса.

namespace Bot
{
    public class Bot : MySessionComponentBase
    {
    }
}


И тут разработчики предлагают нам на выбор несколько вариантов как можно активировать свой код.

There are several ways to this
You add handler on MyAPIGateway.Utils.MessageEntered and execute your code depending on player input to chat
You can subclass MySessionComponentBase, add Attribute and it will get loaded before world and then updated each frame.
You can subclass MyGameLogicComponent, add Attribute and it will get hooked to any object


1) Можно повесить обработчик события MyAPIGateway.Utils.MessageEntered (отправлено сообщение в чат) и по срабатыванию триггера этого события вызывать вашу функцию с кодом.
2) Можно расширить класс MySessionComponentBase, в таком случае ваш код может выполняться при каждой перерисовке, либо при загрузке единожды, что мы и сделаем в примере ниже.
3) И можно расширить класс MyGameLogicComponent который позволит привящать к любому игровому объекту. Об этом напишу если будут желающие.

И так представляю полную версию кода примитивного мода, который лишь дублирует наши сообщения в чате и выводит уведомление.

using Sandbox.ModAPI;
using Sandbox.Common;
using Sandbox.Common.ObjectBuilders;
using Sandbox.Common.Components;
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;
using System.Linq;

namespace Bot
{
    [Sandbox.Common.MySessionComponentDescriptor(Sandbox.Common.MyUpdateOrder.BeforeSimulation)]
    public class Bot : MySessionComponentBase
    {
        public override void BeforeStart()
        {
            base.BeforeStart();
            MyAPIGateway.Utilities.MessageEntered += new MessageEnteredDel((string messageText, ref bool sendToOthers) => onMessage(messageText));
        }

        private void onMessage(String messageText)
        {
            MyAPIGateway.Utilities.ShowMessage("Bot", "player say: " + messageText);
            MyAPIGateway.Utilities.ShowNotification("My notification!");
        }
    }
}


Давайте теперь разберем более подробно.
public class Bot : MySessionComponentBase - Мы расширили базовый класс

public override void BeforeStart() - переопределили базовый метод

MyAPIGateway.Utilities.MessageEntered += new MessageEnteredDel((string messageText, ref bool sendToOthers) => onMessage(messageText)); - И добавили обработчик событий, который при появлении сообщения в чате вызывает метод onMessage

Внутри метода onMessage мы обращаемся к 2 методам:
1) MyAPIGateway.Utilities.ShowMessage() - Пишет сообщение в чат, в качестве первого аргумента принимается имя отправителя (ник), второй аргумент - само сообщение.

2) MyAPIGateway.Utilities.ShowNotification() - Отображает текстовое уведомление на экране игрока.

Сохраняем полученный код, как bot.cs,
Заходим по пути - (У вас буква диска может быть другой)C:\Users\[Ваш пользователь]\AppData\Roaming\Space Engineers\
Создаем папку Mods, если ее еще нет, в ней создаем папки Bot\Data\Scripts\BotScript\ сюда кладем наш bot.cs
Полный путь у меня выглядит так:
C:\Users\sudoroot\AppData\Roaming\Space Engineers\Bot\Data\Scripts\BotScript\bot.cs
Запускаем игру, либо создаем новый мир, либо загружаем уже созданный, до загрузки вам надо лишь зайти в настройки этого мира - mods - и добавить наш мод.

Вот такой небольшой и нехитрый код, позволил нам обратится к игровому API.
Если эта статья понравится, напишу более подробно и более детально.

среда, 2 апреля 2014 г.

Советы, которых я сам стараюсь придерживаться, и вам советую.


Не вредные советы.

На начальных этапах, на каком бы языке вы не писали, надо придерживаться ряда правил при написании кода.
Есть ряд практик, которые существуют уже давно, и на их базе я для себя простым языком составил список правил и их описание.

Данный список я буду пополнять.

  1. Код должен читаться без комментариев - Ваш код должен сам говорить о том, что он делает и как. Например не надо именовать переменные одним символом вроде a. Кроме случаев вроде цикла for(int i = 0; i < maxLength; i++), тут уместно использовать i в качестве итератора. В остальных случаях непонятно для чего ваша переменная и что в ней. Если переменная хранит в себе номер телефона, то назовите ее соответственно phoneNumber, или phone_number. Стиль оформления переменных меняется от языка к языку. Ваши функции и методы должны четко отображать в названии что они делают. То есть, если вы хотите сложить 2 числа в функции или методе, то назовите соответсвенно sum(int a, int b). Чтобы ваши коллеги понимали что данная функция\метод будет складывать числа что вы передаете в качестве аргументов. Так же это правило относится к "магическим числам", это когда у вас в коде используется нечто вроде: peopleCount / 4. Что это за цифра? Откуда она взялась? Создайте константу TABLE_SEATS (места за столом), давайте взглянем: peopleCount / TABLE_SEATS, вот теперь понятно что мы получим кол-во столов необходимое на заданное кол-во человек. 
  2. Функция или метод должен делать только одно действие, и ничего больше - соедините с первым правилом, и получится хороший код. Суть в том, что когда ваша функция или метод называется sum(int a, int b), то она должна только сложить переданные ей числа и вернуть\не вернуть результат, в зависимости от реализации. Если вы там же еще и поделите, отнимете, возведете в степень, тогда получается, что функция врет. Со временем вы и сами запутаетесь в том что делает ваша программа, не говоря уже о ваших коллегах. Если вам уж так необходимо в одной функции произвести несколько действий, то дайте более абстрактное название. Но лучше все таки разбить на несколько функций\методов.
  3. Не должно быть дублирующегося кода - бывают ситуации, когда надо в разных функциях делать одно и тоже, но с небольшими изменениями. Так вот не надо копировать код из одной функции\метода в другой. Вы плодите одинаковый код. Повторяющийся код должен быть вынесен в отдельный класс, метод\функцию, модуль. В зависимости от масштаба. Я скажу больше, иногда кажется что "ничего страшного тут всего 10 строк дублируется", а потом еще и тут...ну и тут..и тут...ну мысль мою вы поняли. Так вот не допускайте этого, заставляйте себя переписывать.
  4. Если функция\метод имеет более 2-х уровней вложения, значит надо вынести в отдельный метод\функцию часть логики - очень частый случай, когда люди называя метод\функцию абстрактным именем, например: "processData()" пишут там 500 строк кода. С уровнем вложенности в 3 и больше  (Уровень вложенности - это количество кода вложенного например в циклы ). Код рабочий, но из за большого уровня вложенности читать его и разбираться становиться тяжело. Поэтому руководствуясь 1 и 2 правилом - выносите часть логики в отдельные методы\функции, чтобы код был более читабельным.
  5. Минимальный уровень доступа - ко всем методам, и переменным класса доступ должен быть минимальный, не забывайте о инкапсуляции, ее придумали не зря. Просто приучите себя выставлять минимальный уровень доступа, пока вы в проекте одни, вероятно вы не допустите ошибок, и не перепишите значение переменной класса, которой не должны были. Или не вызовите метод класса, который вызовет исключение, потому что сначала должен был отработать другой метод. Но когда вы работаете в команде, такая ситуация может случиться. 
  6. 90% планирование, 10% кодирование - я понимаю, что в большинстве проектов это кажется невозможным. Но постарайтесь попробовать, вы увидите как сократилось количество ошибок, и как увеличилось понимание того, что вы собираетесь сделать.