Making Modules

From Procedural Objects
Jump to navigation Jump to search
    I18n.png  Language En flag.jpg English Fr flag.png Français Es flag.png Español De flag.jpg Deutsch Nl flag.png Nederlands Ru flag.jpg Русский Ko flag.png 한국어 Zh flag.png 简体中文 Ja flag.png 日本語 Th flag.png ภาษาไทย


Objects created with the mod can have modules attached to them. The Procedural Objects mod allows the creation of custom module types to extend the features of the objects. This article details the API provided with the mod. It is assumed that you know the basics of C# programming and Modding for Cities:Skylines.

Custom PO module types are made of two key components :

  • A POModuleType instance that provides general information about the custom module type (Name, max count on map, etc.)
  • A custom class inherited from POModule that provides the behaviour for the modules.

Examples of source codes:

Creating a POModuleType

The core of a custom PO module type is just a standard Cities:Skylines mod. Make sure to add ProceduralObjects in the project references. Using .NET version 3.5 may also prevent Reflection issues.

This also allows OnSettingsUI(UIHelperBase helper) to be implemented for general settings of the modules (not shown below).

The example below initiates a "BirdFlocksModule" type.

using UnityEngine;
using ICities;
using ProceduralObjects;

namespace BirdFlocksPOModule
{
    public class BirdFlocksPOMod : LoadingExtensionBase, IUserMod
    {
        // Standard C:S mod setup.
        public string Name { get { return "Bird Flocks Module"; } }
        public string Description { get { return "Bird flocks module for Procedural Objects"; } }

        private POModuleType moduleType;

        public override void OnCreated(ILoading loading)
        {
            base.OnCreated(loading);
            if (moduleType == null)
            {
                moduleType = new POModuleType()
                {
                    Name = Name,
                    Author = "Simon Royer",
                    help_URL = "http://wwww.mywebsite.com/birdflockshelp", // the URL to open when Help button is clicked (optional)
                    TypeID = "BirdFlocksModule",  // This is the unique id of the type. It should remain the same throughout updates.
                    ModuleType = typeof(BirdFlocksModule), // This defines the POModule class as the behaviour class for the modules.
                                                           // of course you have to create a BirdFlocksModule class which inherits
                                                           // from POModule (see below)
                    maxModulesOnMap = 100, // set to zero for no limit
                    keepOpenWhenLeaveObj = false // set to true if you want the module window to remain opened when leaving the 
                                                 // parent object edit mode
                };
            }
            if (!ProceduralObjectsMod.ModuleTypes.Contains(moduleType))
                ProceduralObjectsMod.ModuleTypes.Add(moduleType);  // Add the POModuleType to Procedural Objects' supported types list.
        }
        public override void OnReleased()
        {
            base.OnReleased();
            if (moduleType == null)
                return;
            if (ProceduralObjectsMod.ModuleTypes.Contains(moduleType))
                ProceduralObjectsMod.ModuleTypes.Remove(moduleType);
        }
    }
}

Custom class inherited from POModule

The behaviour of the module type is defined inside the custom class inherited from the POModule class.

namespace BirdFlocksPOModule
{
    public class BirdFlocksModule : POModule
    {
        // POModule can have custom variables and methods.
        // ProceduralObjects does not use [Serializable]. Implement GetData() and LoadData() for saving.
        public List<BirdAgent> birds;
        public int BirdsCount;
        public Mesh birdMesh;
        public Material birdMaterial;
        private bool switchBackCalculation;
        private float cohesion = 5f, avoidance = 2f, alignment = 3f, speed = 2f, visionDist = 35f, reactivity = .7f;

        public override void OnModuleCreated(ProceduralObjectsLogic logic)
        {
            base.OnModuleCreated(logic);
            // initialize the customization window size. The position doesn't matter.
            window = new Rect(0, 0, 325, 175);
            // do stuff here...

            // this method should not include variable initilization (such as "cohesion = 5f" in this case) because
            // this method is also called after loading, so that would screw up the loaded data. Instead default
            // values should be set as shown above.
        }
        public override void UpdateModule(ProceduralObjectsLogic logic, bool simulationPaused, bool layerVisible)
        {
            // UpdateModule() is run in Update() when the module is enabled. 
            for (int i = 0; i < birds.Count; i++)
            {
                var bird = birds[i];
                bird.Render(birdMesh, birdMaterial);  // Render calls must be located in UpdateModule().
                if (!simulationPaused)
                    bird.CalculateMovement(birds, cohesion, avoidance, alignment, speed, visionDist, reactivity);
            }
        }
        public override void DrawCustomizationWindow(int id)
        {
            // the base.DrawCustomizationWindow(id) call provides basic window features (close button, drag window...)
            base.DrawCustomizationWindow(id);
            GUI. .... // draw UI stuff here using the UnityEngine.GUI class. See the ProceduralObjects.UI.GUIUtils class 
                      // for extended UI elements (separators, etc)
        }

        public override void GetData(Dictionary<string, string> data, bool forSaveGame)
        {
            // GetData() is where saving occurs. The data is saved as string Key/Value pair that are added as shown below.
            // If you need to save different variables depending on the purpose of the saving process (saving a module 
            // for a savegame or for an export),  read the forSaveGame variable to differentiate those cases.
            data.Add("birdsCount", BirdsCount.ToString());
            data.Add("cohesion", cohesion.ToString());
            data.Add("avoidance", avoidance.ToString());
            data.Add("alignment", alignment.ToString());
            data.Add("speed", speed.ToString());
            data.Add("visionDist", visionDist.ToString());
            data.Add("reactivity", reactivity.ToString());
        }
        public override void LoadData(Dictionary<string, string> data, bool fromSaveGame)
        {
            // LoadData() is where loading occurs. The data is loaded as a collection of string Key/Value pair that are
            // read as shown below. If you need to load different variables depending on the purpose of the loading process 
            // (loading a module from a savegame or from an export), read the fromSaveGame variable to differentiate those cases.
            // This example only uses float parsing but you can implement your own parsing processes.
            // Always check if the variable exists using an if statement as shown below. As a reminder, default values for
            // those variables must be declared at the top of the class, not in OnModuleCreated() or any method.
            if (data.ContainsKey("birdsCount"))
                BirdsCount = int.Parse(data["birdsCount"]);
            if (data.ContainsKey("cohesion"))
                cohesion = float.Parse(data["cohesion"]);
            if (data.ContainsKey("avoidance"))
                avoidance = float.Parse(data["avoidance"]);
            if (data.ContainsKey("alignment"))
                alignment = float.Parse(data["alignment"]);
            if (data.ContainsKey("speed"))
                speed = float.Parse(data["speed"]);
            if (data.ContainsKey("visionDist"))
                visionDist = float.Parse(data["visionDist"]);
            if (data.ContainsKey("reactivity"))
                reactivity = float.Parse(data["reactivity"]);
        }
    }
}

Inherited members

POModule inherited classes inherit the following members from POModule :

ProceduralObject parentObject The parent object on which the module is attached.
bool enabled The enabled state of the module.
bool showWindow The state of the customization window of the module.
Rect window The window rectangle. Initialize in OnModuleCreated() as shown above.
POModuleType ModuleType The ModuleType instance that has been set up in the mod (see above)

Implementable methods

POModule classes can implement the following methods. Not all of them need to be implemented, choose the ones you need.

void OnModuleCreated(ProceduralObjectsLogic logic) Invoked upon module creation in game, whether through Copy/paste, Creation or Loading. This method should take the enabled field into account, for it may be required upon cloning of the module. When used through Loading, LoadData() is called before OnModuleCreated().
void OnModuleRemoved(ProceduralObjectsLogic logic) Invoked upon parent object deletion or module removal.
void OnModuleEnabled(ProceduralObjectsLogic logic) Invoked when the module is enabled. Not called when the entire module type is enabled from the Modules Management window.
void OnModuleDisabled(ProceduralObjectsLogic logic) Invoked when the module is disabled. Not called when the entire module type is disabled from the Modules Management window.
void OnModuleWindowOpen(ProceduralObjectsLogic logic) Invoked when the module window is opened.
void OnModuleWindowClose(ProceduralObjectsLogic logic) Invoked when the module window is closed.
void OnParentPositionSet(ProceduralObjectsLogic logic, Vector3 oldPosition, Vector3 newPosition) Invoked when the parent object position is changed.
void OnParentRotationSet(ProceduralObjectsLogic logic, Quaternion oldRotation, Quaternion newRotation) Invoked when the parent object rotation is changed.
void OnApplyModelChange(Vertex[] vertices) Invoked when a change to the parent object model is applied.
void UpdateModule(ProceduralObjectsLogic logic, bool simulationPaused, bool layerVisible) Invoked in Update() (every frame) only if the module is enabled. This is where Render calls are made.
bool RenderParentThisFrame(ProceduralObjectsLogic logic) Invoked in Update() as a condition for rendering the parent PO of the module. If any module of a PO returns 'false' on a given frame then rendering for the parent PO is skipped on the next frame.
void LoadData(Dictionary<string, string> data) Initialize the module with the provided data set. This method is called before OnModuleCreated().
void GetData(Dictionary<string, string> data) Use data.Add(string,string) calls in GetData() to be used as Key/Value data pairs when loading with LoadData().
void DrawCustomizationWindow(int id) Window drawing function for a module (use UnityEngine.GUI methods). Uses the Rect window field. Define window size in OnModuleCreated() as shown in the example above. The base.DrawCustomizationWindow(id) call provides basic window features (close button, drag window...)
void OnGUI() Called inside the typical OnGUI() function, in parallel to the DrawCustomizationWindow() function described above, when the module window is open.
POModule Clone() Used upon cloning or copy/paste. If not implemented this will use object.MemberwiseClone(). Provide implementation if a deeper copy is required.