diff --git a/.yamato/ngo-publish.yml b/.yamato/ngo-publish.yml
index 81f9f57b3b..ecd6f2fc70 100644
--- a/.yamato/ngo-publish.yml
+++ b/.yamato/ngo-publish.yml
@@ -3,7 +3,7 @@ ngo_release_preparation:
agent: { type: Unity::VM, flavor: b1.small, image: package-ci/ubuntu-22.04:v4 }
triggers:
recurring:
- - branch: develop-2.0.0 # We make new releases from this branch
+ - branch: develop-3.x.x # We make new releases from this branch
frequency: "10 ? * 1" # Runs every Sunday at 10:00 AM
commands:
- pip install PyGithub
diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml
index 7554274fe8..50ce3587d9 100644
--- a/.yamato/package-pack.yml
+++ b/.yamato/package-pack.yml
@@ -42,6 +42,15 @@ package_pack_-_ngo_{{ platform.name }}:
variables:
XRAY_PROFILE: "gold ./pvpExceptions.json rme"
commands:
+ # Remove ValidationExceptions.json and its .meta file if they exist. For this specific test it's necessary (otherwise we want to keep them since we use those for release).
+{% if platform.name == "win" %}
+ - if exist com.unity.netcode.gameobjects\ValidationExceptions.json del /f /q com.unity.netcode.gameobjects\ValidationExceptions.json
+ - if exist com.unity.netcode.gameobjects\ValidationExceptions.json.meta del /f /q com.unity.netcode.gameobjects\ValidationExceptions.json.meta
+{% else %}
+ - rm -f com.unity.netcode.gameobjects/ValidationExceptions.json
+ - rm -f com.unity.netcode.gameobjects/ValidationExceptions.json.meta
+{% endif %}
+
- upm-pvp pack "com.unity.netcode.gameobjects" --output upm-ci~/packages
- upm-pvp xray --packages "upm-ci~/packages/com.unity.netcode.gameobjects*.tgz" --results pvp-results
- upm-pvp require {% if platform.name == "win" %}"%XRAY_PROFILE%"{% else %}"$XRAY_PROFILE"{% endif %} --results pvp-results --allow-missing
diff --git a/Tools/scripts/ReleaseAutomation/release_config.py b/Tools/scripts/ReleaseAutomation/release_config.py
index fc968fd160..ddd35bb366 100644
--- a/Tools/scripts/ReleaseAutomation/release_config.py
+++ b/Tools/scripts/ReleaseAutomation/release_config.py
@@ -50,9 +50,9 @@ class ReleaseConfig:
def __init__(self):
self.manifest_path = 'com.unity.netcode.gameobjects/package.json'
self.changelog_path = 'com.unity.netcode.gameobjects/CHANGELOG.md'
- self.validation_exceptions_path = './ValidationExceptions.json'
+ self.validation_exceptions_path = 'com.unity.netcode.gameobjects/ValidationExceptions.json'
self.github_repo = 'Unity-Technologies/com.unity.netcode.gameobjects'
- self.default_repo_branch = 'develop-2.0.0' # Changelog and package version change will be pushed to this branch
+ self.default_repo_branch = 'develop-3.x.x' # Changelog and package version change will be pushed to this branch
self.yamato_project_id = '1201'
self.command_to_run_on_release_branch = make_package_release_ready
diff --git a/Tools/scripts/Utils/general_utils.py b/Tools/scripts/Utils/general_utils.py
index 0b822ab8d9..ca45a29953 100644
--- a/Tools/scripts/Utils/general_utils.py
+++ b/Tools/scripts/Utils/general_utils.py
@@ -132,13 +132,20 @@ def update_changelog(changelog_path, new_version, add_unreleased_template=False)
with open(changelog_path, 'r', encoding='UTF-8') as f:
changelog_text = f.read()
- # This pattern finds a line starting with '###', followed by its newline,
- # and then two more lines that contain only whitespace.
+ # This pattern finds a line starting with '###' followed only by blank lines up to the next heading or end of section.
# The re.MULTILINE flag allows '^' to match the start of each line.
- pattern = re.compile(r"^###.*\n\n\n", re.MULTILINE)
-
- # Replace every match with an empty string. The goal is to remove empty CHANGELOG subsections.
- cleaned_content = pattern.sub('', changelog_text)
+ pattern = re.compile(r"^###[^\n]*\n(?:[ \t]*\n)*(?=##|\Z)", re.MULTILINE)
+
+ # Replace every match within the [Unreleased] section with an empty string. The goal is to remove empty CHANGELOG subsections.
+ # Released sections are left untouched since some of them use '###' for their version heading.
+ cleaned_content = changelog_text
+ unreleased_section = re.search(r'(?ms)^## \[Unreleased\].*?(?=^## \[|\Z)', changelog_text)
+ if unreleased_section:
+ cleaned_content = (
+ changelog_text[:unreleased_section.start()] +
+ pattern.sub('', unreleased_section.group(0)) +
+ changelog_text[unreleased_section.end():]
+ )
if version_header_to_find_if_exists in changelog_text:
warnings.warn(f"A changelog entry for version '{new_version}' already exists. The script will just remove Unreleased section and its content.")
diff --git a/Tools/scripts/release.py b/Tools/scripts/release.py
index 4693d90bc9..b55ce22ba5 100644
--- a/Tools/scripts/release.py
+++ b/Tools/scripts/release.py
@@ -88,7 +88,7 @@ def make_package_release_ready(manifest_path, changelog_path, validation_excepti
if __name__ == '__main__':
manifest_path = 'com.unity.netcode.gameobjects/package.json'
changelog_path = 'com.unity.netcode.gameobjects/CHANGELOG.md'
- validation_exceptions_path = './ValidationExceptions.json'
+ validation_exceptions_path = 'com.unity.netcode.gameobjects/ValidationExceptions.json'
package_version = get_package_version_from_manifest(manifest_path)
make_package_release_ready(manifest_path, changelog_path, validation_exceptions_path, package_version)
diff --git a/ValidationExceptions.json b/ValidationExceptions.json
deleted file mode 100644
index c6b271631e..0000000000
--- a/ValidationExceptions.json
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "ErrorExceptions": [],
- "WarningExceptions": []
-}
diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md
index 15f400f9db..9be4e4fba7 100644
--- a/com.unity.netcode.gameobjects/CHANGELOG.md
+++ b/com.unity.netcode.gameobjects/CHANGELOG.md
@@ -12,8 +12,6 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Changed
-- Updated Netcode for Entities dependency to 7.1.0
-
### Deprecated
### Removed
@@ -24,6 +22,12 @@ Additional documentation and release notes are available at [Multiplayer Documen
### Obsolete
+## [3.1.0] - 2026-10-04
+
+### Changed
+
+- Updated Netcode for Entities dependency to 7.1.0
+
## [3.0.0] - 2026-09-14
### Added
diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs
index 4314aa243d..8c326e5107 100644
--- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs
+++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs
@@ -5,20 +5,47 @@
using UnityEngine;
using UnityEngine.SceneManagement;
+#if UNITY_7000_0_OR_NEWER
+using UnityEditor.Build.Content;
+#endif
+
namespace Unity.Netcode.GameObjects.Editor
{
///
- /// A that sets the property to true for all s in the scene.
/// Ensures that InScenePlaced is always true for all objects in the scene.
///
///
- /// This will always run as the game enters the scene,
+ /// This will always run as a scene is loaded in playmode or while processed for a player build.
///
+#if UNITY_7000_0_OR_NEWER
+ internal class SetInScenePlaced : AssetPostprocessor
+ {
+ public override uint GetVersion() => 1;
+
+ public override int GetPostprocessOrder() => 0;
+
+ void OnProcessScene(Scene scene, SceneImportContext sceneImportContext)
+ {
+ InScenePlaceChecks.ReportSceneIssues(scene, sceneImportContext.awakeDidRun);
+ }
+ }
+#else
internal class SetInScenePlaced : IProcessSceneWithReport
{
public int callbackOrder => 0;
+
public void OnProcessScene(Scene scene, BuildReport report)
+ {
+ InScenePlaceChecks.ReportSceneIssues(scene, Application.isPlaying);
+ }
+ }
+#endif
+
+ internal static class InScenePlaceChecks
+ {
+ public static void ReportSceneIssues(Scene scene, bool hasAwakeRun)
{
var log = new ContextualLogger();
log.AddInfo(scene.name, scene.handle);
@@ -42,15 +69,15 @@ public void OnProcessScene(Scene scene, BuildReport report)
continue;
}
- // If already marked, the do nothing.
+ // If already marked, do nothing.
if (networkObject.InScenePlaced)
{
continue;
}
networkObject.InScenePlaced = true;
- // Will not be true when making a build and the values are serialized.
- networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying;
+ // Mark that awake has run so this could have been a dynamically instantiated object.
+ networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = hasAwakeRun;
}
}
}
diff --git a/com.unity.netcode.gameobjects/ValidationExceptions.json b/com.unity.netcode.gameobjects/ValidationExceptions.json
new file mode 100644
index 0000000000..d258246b0c
--- /dev/null
+++ b/com.unity.netcode.gameobjects/ValidationExceptions.json
@@ -0,0 +1,15 @@
+{
+ "ErrorExceptions": [
+ {
+ "ValidationTest": "NDA Keyword Validation",
+ "ExceptionMessage": "#if UNITY_7000_0_OR_NEWER",
+ "PackageVersion": "3.1.1"
+ },
+ {
+ "ValidationTest": "NDA Keyword Validation",
+ "ExceptionMessage": "File 'Editor/InScenePlacedProcessor.cs' contains deny-listed keyword pattern '(?