Profile image

Sky Modification and Replacement SPMOD Tutorial

110k hpgbproductions  3.7 years ago

This post documents how to modify and replace the SimplePlanes sky through modding. This guide assumes basic knowledge and literacy of Unity and C#.

Example GitHub repository with SPMOD. View the full scripts and download the SPMOD from there. Feel free to use the scripts shown here.

The repository contains:

  • Examples.cs script demonstrating editing TOD parameters with commands. Attach to persistent object.
  • Examples2.cs script demonstrating automatic application of a custom skybox. Attach to a child of custom map or map plugin.
  • Example Unity scene.
  • SkyModExamples.spmod.

The SPMOD contains:

  • ChangeSkyBrightness command that can be run in the main menu or sandbox.
  • "Skybox Replace Test" sandbox map. This map uses the "Cold Sunset" skybox in AllSky Free.

[ Open SPMODing ] This guide and my mod source code repositories are openly available and intended to help those that may have an interest in programming.

Contents

  1. Basics
  2. Sky References
  3. Sky Modification
  4. Sky Replacement

1. Basics

This section provides some basic technical information.

The SimplePlanes sky is controlled by the Time of Day (TOD) asset, of which documentation exists online.

Since TOD contains its own classes that aren't part of C# or Unity, it's not possible to access them without bundling them with the mod. Of course, this is not practical due to its price.

Luckily, C# has a feature called reflection, which allows us to use TOD types and classes without having to physically include them in the mod. The online documentation can be consulted to find out which parameters are available for editing.

While we are using reflection here, we do not need to use the SP Reflection Tools package.


2. Sky References

This section demonstrates how to get the main TOD objects for processing in Sections 3 and 4.

The first step is to get a reference to the TOD_Sky component, which is the master component and responsible for controlling others. At the same time, get the reference to its host GameObject. This will facilitate getting the other components.

Here are the first three fields we need to populate. Notice how we store the Type of TOD_Sky in addition to the reference to the actual instance of TOD_Sky.

// [Examples.cs ~ Lines 7 to 14]

// Sky Dome GameObject.
private GameObject SkyDome;

// Sky Dome "TOD_Sky" component type
private Type SkyComponentType;

// A reference to the actual component of type "TOD_Sky"
private object SkyComponent;

We can compare the Type name to any string, in this case the known TOD_Sky Component Type. This module finds one component of any Type, but should only be used if only one component of that Type exists in the scene.

Note: Even though we know the names of the Sky Dome GameObjects in the main menu and sandbox, we should rely on GameObject names as little as possible. Even though there are no more updates planned for the game, mods can introduce new objects with the same names.

// [Examples.cs ~ Lines 34 to 45]

// Get all components, and looks through them to find a TOD_Sky component
Component[] AllComponents = FindObjectsOfType<Component>();
foreach (Component c in AllComponents)
{
    if (c.GetType().Name == "TOD_Sky")
    {
        // Saves Sky Dome information
        SkyDome = c.gameObject;
        SkyComponentType = c.GetType();
        SkyComponent = c;
    }
}

If a different TOD component is required, it can be obtained by changing the compared Type. To get more than one component from this foreach block, add else if block(s). If you like switch statements, it works too.


3. Sky Modification

This section demonstrates getting Fields in TOD components.

Before starting, make a note of the location of each Field you want to modify in your mod. In this example, we will choose the Brightness parameter, found in the Type TOD_AtmosphereParameters. Like before, we store both the instance of the object, and its Type.

Note that "Attributes" as listed in the documentation are actually called Fields in C#, and Attributes are something different.

// [Examples.cs ~ Lines 16 to 23]

// "TOD_AtmosphereParameters" type found in "TOD_Sky"
private Type AtmosphereType;

// A reference to the object of type "TOD_AtmosphereParameters" in SkyComponent
private object Atmosphere;

// The brightness field defined in the "TOD_AtmosphereParameters" type
private FieldInfo BrightnessField;

Get the TOD_AtmosphereParameters Atmosphere Field in the TOD_Sky component, and hence get the Brightness Field.

// [Examples.cs ~  Lines 54 to 73]

// Get all fields in the TOD_Sky type,
// and looks through them to find the Atmosphere field
FieldInfo[] fields = SkyComponentType.GetFields();
foreach (FieldInfo field in fields)
{
    // also try (field.FieldType.Name == "TOD_AtmosphereParameters")
    if (field.Name == "Atmosphere")
    {
        AtmosphereType = field.FieldType;
        Atmosphere = field.GetValue(SkyComponent);

        // Get all fields in the TOD_AtmosphereParameters type,
        // and looks through them to find the Brightness field
        FieldInfo[] atmoFields = AtmosphereType.GetFields();
        foreach (FieldInfo atmoField in atmoFields)
        {
            if (atmoField.Name == "Brightness")
                BrightnessField = atmoField;
        }
    }
}

Once we have the Brightness Field and the object with that Field we want to modify, we can use SetValue() to write a value to it.

private void ChangeSkyBrightness(float brightness)
{
    BrightnessField.SetValue(Atmosphere, brightness);
}

Download the example SPMOD in the GitHub repository, and run the command ChangeSkyBrightness. Watch the sky's brightness change according to your entry, keeping in mind that 1.5 is the default value.


4. Sky Replacement

This section demonstrates how to remove the TOD system and add a custom skybox.

We have previously learnt that TOD_Sky is the main Component used in the TOD sky asset. However, to replace the sky, we must get the TOD_Components Component instead. This Component holds references to all GameObjects used by TOD to generate the sky at runtime.

Try to convert the Component-finding example code in Section 2! It should look like this.

// [Examples2.cs ~ Lines 19 to 29]

// Similar Component finder to the first example
Component[] AllComponents = FindObjectsOfType<Component>();
foreach (Component c in AllComponents)
{
    if (c.GetType().Name == "TOD_Components")
    {
        SkyDome = c.gameObject;
        SkyComponentHolderType = c.GetType();
        SkyComponentHolder = c;
    }
}

To remove the default TOD sky, all its GameObjects must be destroyed. Find all GameObject fields in the TOD_Components Component and destroy them. After that, destroy the Sky Dome itself to remove the TOD scripts. (While not strictly necessary, it will prevent NullReferenceExceptions from appearing in the dev console.)

// [Examples2.cs ~ Lines 36 to 51]

// Similar Field finder to the first example
FieldInfo[] fields = SkyComponentHolderType.GetFields();
foreach (FieldInfo field in fields)
{
    // Get all GameObjects referenced by TOD and destroy them
    if (field.FieldType.Name == "GameObject")
    {
        GameObject RefGameObject = (GameObject)field.GetValue(SkyComponentHolder);
        Debug.Log("Destroyed GameObject " + RefGameObject.name);
        Destroy(RefGameObject);
    }
}

// Finally destroy the Sky Dome itself to prevent errors from appearing in the dev console
Debug.Log("Destroyed GameObject " + SkyDome.name);
Destroy(SkyDome);

Change the skybox with your own, and set up the cameras to use it. From running tests in the dev console, Camera (MainCamera) has the lowest depth value, and so it is the one to be modified. However, due to the existence of multiple cameras tagged MainCamera in the sandbox, we cannot use Camera.main to get the proper camera.

In this example, the lowest Camera is selected by name, but it is also possible to use the depth value to find the Camera. The depth value of Camera (MainCamera) is 1.

// [Examples2.cs ~ Lines 53 to 64 (modified)]

// Replace the skybox
RenderSettings.skybox = SkyboxMaterial;

// Set up the Main Camera to use the skybox
MainCamera = GameObject.Find("MainCamera").GetComponent<Camera>();
MainCamera.clearFlags = CameraClearFlags.Skybox;
Debug.Log("Applied settings on Camera " + MainCamera.name);

When making your map, keep in mind that the Directional Light controlled by TOD is also destroyed by the skybox replacement script. You must add your own Directional Light.

  • Log in to leave a comment
  • Profile image
    12.3k GhostTeam2

    @hpgbproductions Okay, I'll try to find it again

    8 months ago
  • Profile image

    @GhostTeam2 i don't have TOD, i have only changed the options of TOD in SP

    8 months ago
  • Profile image
    12.3k GhostTeam2

    @hpgbproductions So can you share it with me or is there any other way to get it?

    8 months ago
  • Profile image

    @GhostTeam2 you can read and change the values that the devs use, but you cannot download TOD with this way

    8 months ago
  • Profile image
    12.3k GhostTeam2

    @hpgbproductions I mean, TOD assets can't be downloaded. I need to optimize some details according to the time in TOD assets But he seems to have been taken off the shelf.

    8 months ago
  • Profile image

    @GhostTeam2 see the code block [Examples.cs ~ Lines 34 to 45]
    1. get all components and store the result
    2. iterate through the list/array until you find the components with the correct type names
    3. store the type, and the object (you will need both)

    8 months ago
  • Profile image
    12.3k GhostTeam2

    I can't get the TOD component now. Can you teach me how to get it? This is very helpful for my new map lighting rendering optimization.

    8 months ago
  • Profile image
    9,018 BigBushy

    thanks mate, i was struggling to change the sky params because they kept resetting

    2.5 years ago
  • Profile image
    2,186 Chich2000

    OCD kicks in
    I absolutely need to be the tenth upvote and the 20th comment-

    2.9 years ago
  • Profile image

    @Suqingqing set sea level to null, it's one of the things in service provider

    3.3 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions Can the ocean be hidden? To see the underground world of SP, without the floating physics of the ocean, if it can be achieved, that would be great!!
    But unfortunately I can't C#......
    : (

    3.3 years ago
  • Profile image

    @Suqingqing you can put the script in the map plugin, but it cannot be loaded on android

    3.3 years ago
  • Profile image
    44.0k 1342791782

    I have been studying SP Sky according to Time of Day for a long Time

    3.3 years ago
  • Profile image
    44.0k 1342791782

    Can custom skyboxes be used in the map plugin?

    3.3 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions ok.

    3.5 years ago
  • Profile image

    @Suqingqing not sure what you mean, do you mean remove water?
    ServiceProvider.Instance.GameWorld.SeaLevel = null;
    .
    (Look through the autofill results to see what else you can do stock with serviceprovider)

    3.5 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions Thank you very much!I did it!
    But I have a question:
    Since you can change the Skybox, then can you eliminate the sea water energy?

    3.5 years ago
  • Profile image

    @Suqingqing your .net api version is 2.0 when it should be 4.x
    Edit > Project settings > Player > API compatibility level
    .
    (don't worry, everyone forgets to change the setting at some point)

    3.5 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions Bug?
    The script 'Examples' could not be instantiated!FileNotFoundException: Could not load file or assembly 'netstandard, Version-2.0.0.0, Culture=neutral, PublickeyToken=cc7b13fcd2dd51' or one of its dependencies.Unable to position aircraf. Location 'Starting Line 2' was not found

    3.5 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions
    The script is perfectly usable
    The sky is constant
    The plane didn't disappear.

    3.7 years ago
  • Profile image

    @1342791782 need the following info to debug for you
    - is examples2 is in a child object of the map component? (it should)
    - is the file or class name changed? (try making sure both match, not sure if it's a must)
    - is the script running partially or not at all? (see below)
    - which debug messages appear? (it will print to the console when TOD gameobjects are destroyed, and when the camera is edited)
    - is the sky unchanged or black?
    - does the ground/ aircraft disappear?

    3.7 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions I've always used custom maps.

    3.7 years ago
  • Profile image

    @1342791782 i've designed it for custom map or map plugin, not persistent object. Is it on a gameobject under a custom map or map plugin?
    Edited post to show usage of each script

    3.7 years ago
  • Profile image
    44.0k 1342791782

    @hpgbproductions I used your Examples2 to put a skybox texture ball in it. When I saved it in the game and opened the game, the sky did not appear.

    3.7 years ago
  • Profile image

    @1342791782 a skybox is only one Material, read the Unity skybox reference
    Can I see your setup?

    3.7 years ago
  • Log in to see more comments