├── .editorconfig
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── CHANGELOG.md.meta
├── Editor.meta
├── Editor
├── AssetBundleBuilder.cs
├── AssetBundleBuilder.cs.meta
├── Synapse.AssetBundleBuilder.Editor.asmdef
└── Synapse.AssetBundleBuilder.Editor.asmdef.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── Runtime.meta
├── Runtime
├── AssetBundleDescription.cs
├── AssetBundleDescription.cs.meta
├── Json.meta
├── Json
│ ├── AssetBundleDescriptionConverter.cs
│ ├── AssetBundleDescriptionConverter.cs.meta
│ ├── Synapse.AssetBundleBuilder.Runtime.Json.asmdef
│ └── Synapse.AssetBundleBuilder.Runtime.Json.asmdef.meta
├── Synapse.AssetBundleBuilder.Runtime.asmdef
└── Synapse.AssetBundleBuilder.Runtime.asmdef.meta
├── Tests.meta
├── Tests
├── Editor.meta
└── Editor
│ ├── JsonTests.cs
│ ├── JsonTests.cs.meta
│ ├── Unity.AssetBundleBuilder.Tests.Runtime.asmdef
│ └── Unity.AssetBundleBuilder.Tests.Runtime.asmdef.meta
├── package.json
└── package.json.meta
/.editorconfig:
--------------------------------------------------------------------------------
1 | ###############################
2 | # Core EditorConfig Options #
3 | ###############################
4 |
5 | root = true
6 |
7 | # All files
8 | [*]
9 | indent_style = space
10 |
11 | # Code files
12 | [*.{cs,csx,vb,vbx}]
13 | indent_size = 4
14 | insert_final_newline = true
15 | charset = utf-8
16 |
17 | ###############################
18 | # .NET Coding Conventions #
19 | ###############################
20 |
21 | [*.{cs,vb}]
22 | # Organize usings
23 | dotnet_sort_system_directives_first = true
24 | dotnet_separate_import_directive_groups = false
25 |
26 | # this. preferences
27 | dotnet_style_qualification_for_field = false:silent
28 | dotnet_style_qualification_for_property = false:silent
29 | dotnet_style_qualification_for_method = false:silent
30 | dotnet_style_qualification_for_event = false:silent
31 |
32 | # Language keywords vs BCL types preferences
33 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent
34 | dotnet_style_predefined_type_for_member_access = true:silent
35 |
36 | # Parentheses preferences
37 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
38 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
39 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
40 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
41 |
42 | # Modifier preferences
43 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
44 | dotnet_style_readonly_field = true:suggestion
45 |
46 | # Expression-level preferences
47 | dotnet_style_object_initializer = true:suggestion
48 | dotnet_style_collection_initializer = true:suggestion
49 | dotnet_style_explicit_tuple_names = true:suggestion
50 | dotnet_style_null_propagation = true:suggestion
51 | dotnet_style_coalesce_expression = true:suggestion
52 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
53 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
54 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
55 | dotnet_style_prefer_auto_properties = true:silent
56 | dotnet_style_prefer_conditional_expression_over_assignment = true:silent
57 | dotnet_style_prefer_conditional_expression_over_return = true:silent
58 |
59 | ###############################
60 | # Naming Conventions #
61 | ###############################
62 |
63 | # Style Definitions
64 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case
65 |
66 | # Use PascalCase for constant fields
67 | dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
68 | dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
69 | dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
70 | dotnet_naming_symbols.constant_fields.applicable_kinds = field
71 | dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
72 | dotnet_naming_symbols.constant_fields.required_modifiers = const
73 |
74 | ###############################
75 | # C# Code Style Rules #
76 | ###############################
77 |
78 | [*.cs]
79 | # var preferences
80 | csharp_style_var_for_built_in_types = true:silent
81 | csharp_style_var_when_type_is_apparent = true:silent
82 | csharp_style_var_elsewhere = true:silent
83 |
84 | # Expression-bodied members
85 | csharp_style_expression_bodied_methods = false:silent
86 | csharp_style_expression_bodied_constructors = false:silent
87 | csharp_style_expression_bodied_operators = false:silent
88 | csharp_style_expression_bodied_properties = true:silent
89 | csharp_style_expression_bodied_indexers = true:silent
90 | csharp_style_expression_bodied_accessors = true:silent
91 |
92 | # Pattern-matching preferences
93 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
94 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
95 |
96 | # Null-checking preferences
97 | csharp_style_throw_expression = true:suggestion
98 | csharp_style_conditional_delegate_call = true:suggestion
99 |
100 | # Modifier preferences
101 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
102 |
103 | # Expression-level preferences
104 | csharp_prefer_braces = true:silent
105 | csharp_style_deconstructed_variable_declaration = true:suggestion
106 | csharp_prefer_simple_default_expression = true:suggestion
107 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
108 | csharp_style_inlined_variable_declaration = true:suggestion
109 |
110 | ###############################
111 | # C# Formatting Rules #
112 | ###############################
113 |
114 | # New line preferences
115 | csharp_new_line_before_open_brace = all
116 | csharp_new_line_before_else = true
117 | csharp_new_line_before_catch = true
118 | csharp_new_line_before_finally = true
119 | csharp_new_line_before_members_in_object_initializers = true
120 | csharp_new_line_before_members_in_anonymous_types = true
121 | csharp_new_line_between_query_expression_clauses = true
122 |
123 | # Indentation preferences
124 | csharp_indent_case_contents = true
125 | csharp_indent_switch_labels = true
126 | csharp_indent_labels = flush_left
127 |
128 | # Space preferences
129 | csharp_space_after_cast = false
130 | csharp_space_after_keywords_in_control_flow_statements = true
131 | csharp_space_between_method_call_parameter_list_parentheses = false
132 | csharp_space_between_method_declaration_parameter_list_parentheses = false
133 | csharp_space_between_parentheses = false
134 | csharp_space_before_colon_in_inheritance_clause = true
135 | csharp_space_after_colon_in_inheritance_clause = true
136 | csharp_space_around_binary_operators = before_and_after
137 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
138 | csharp_space_between_method_call_name_and_opening_parenthesis = false
139 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
140 | csharp_space_after_comma = true
141 | csharp_space_after_dot = false
142 |
143 | # Wrapping preferences
144 | csharp_preserve_single_line_statements = true
145 | csharp_preserve_single_line_blocks = true
146 |
147 | ##################################
148 | # Visual Basic Code Style Rules #
149 | ##################################
150 |
151 | [*.vb]
152 | # Modifier preferences
153 | visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion
154 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | *.cs diff=csharp text
5 | *.cginc text
6 | *.shader text
7 |
8 | # Unity-generated text assets always use Unix-style line endings.
9 | *.mat eol=lf
10 | *.anim eol=lf
11 | *.unity eol=lf
12 | *.prefab eol=lf
13 | *.physicsMaterial2D eol=lf
14 | *.physicsMaterial eol=lf
15 | *.asset eol=lf
16 | *.meta eol=lf
17 | *.controller eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Ll]ibrary/
2 | [Tt]emp/
3 | [Oo]bj/
4 | [Bb]uild/
5 | [Bb]uilds/
6 | Assets/AssetStoreTools*
7 |
8 | # Visual Studio cache directory
9 | .vs/
10 |
11 | # Autogenerated VS/MD/Consulo solution and project files
12 | ExportedObj/
13 | .consulo/
14 | *.csproj
15 | *.unityproj
16 | *.sln
17 | *.suo
18 | *.tmp
19 | *.user
20 | *.userprefs
21 | *.pidb
22 | *.booproj
23 | *.svd
24 | *.pdb
25 | *.opendb
26 | *.VC.db
27 |
28 | # Unity3D generated meta files
29 | *.pidb.meta
30 | *.pdb.meta
31 |
32 | # Unity3D Generated File On Crash Reports
33 | sysinfo.txt
34 |
35 | # Builds
36 | *.apk
37 | *.unitypackage
38 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [Unreleased]
9 |
10 | ## [v0.2.3]
11 |
12 | ### Fixed
13 |
14 | * Updated `isHttpError` and Linux `BuildTargets` to recommended uses in 2024
15 |
16 | ### Added
17 |
18 | * `PrepareBundlesForUpload()` can now use a short delay between requests
19 | * Simple retry system for requests in `PrepareBundlesForUpload()`. This allows differentiating between cases where the server could not be reached
20 | and where the bundle is not uploaded, throwing an exception in the former case, which helps resolve that ambiguity. Previously, if the server was unresponsive
21 | the asset bundle would be marked for upload but the actual upload would fail.
22 | * `PrepareBundlesForUpload()` logging
23 |
24 | ## [v0.2.2]
25 |
26 | ### Fixed
27 |
28 | * `NormalizePlatform()` no longer throws an exception for "unsupported" platforms. ([#7])
29 |
30 | [#7]: https://github.com/kongregate/asset-bundle-builder/pull/7
31 |
32 | ## [v0.2.1]
33 |
34 | ### Added
35 |
36 | * `ManifestBundlePath`, `CurrentBuildPlatform`, and `GetManifestBundlePathForTarget()`. ([#6])
37 |
38 | [#6]: https://github.com/kongregate/asset-bundle-builder/pull/6
39 |
40 | ## [v0.2.0]
41 |
42 | ### Added
43 |
44 | * `MergePlatformManifests()` as a more robust way of generating the `AssetBundleDescription` objects after building asset bundles. ([#4])
45 |
46 | ### Fixed
47 |
48 | * Build asset bundles are now placed in folders based on their corresponding `RuntimePlatform`, rather than their `BuildTarget`. ([#4])
49 |
50 | ### Breaking Changes
51 |
52 | * `AssetBundleBuilder.GenerateBundleDescriptions()` has been removed and replaced with `MergePlatformManifests()`. You will need to update your build process to provide the `AssetBundleManifest` for each platform to `MergePlatformManifests()`. This can be done by either directly providing the `AssetBundleManifest` asset for each platform, or providing the path to each platform's manifest bundle. See the "Building Bundle Descriptions" section of the README for more information.
53 |
54 | [#4]: https://github.com/kongregate/asset-bundle-builder/pull/4
55 |
56 | ## [v0.1.0] - 2019-11-08
57 |
58 | Initial release :tada: Provides basic workflow for building, distributing, and loading asset bundles.
59 |
60 | [Unreleased]: https://github.com/kongregate/asset-bundle-builder/compare/v0.2.3...master
61 | [v0.2.3]: https://github.com/kongregate/asset-bundle-builder/compare/v0.2.2...v0.2.3
62 | [v0.2.2]: https://github.com/kongregate/asset-bundle-builder/compare/v0.2.1...v0.2.2
63 | [v0.2.1]: https://github.com/kongregate/asset-bundle-builder/compare/v0.2.0...v0.2.1
64 | [v0.2.0]: https://github.com/kongregate/asset-bundle-builder/compare/v0.1.0...v0.2.0
65 | [v0.1.0]: https://github.com/kongregate/asset-bundle-builder/compare/56f87b9...v0.1.0
66 |
--------------------------------------------------------------------------------
/CHANGELOG.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 14c84ee6e6e38674396e18daafd36168
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 73812214a50263c4badbf654c8d48633
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Editor/AssetBundleBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using Unity.EditorCoroutines.Editor;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using UnityEngine.Networking;
11 |
12 | namespace SynapseGames.AssetBundle
13 | {
14 | ///
15 | /// Functionality for building and preparing asset bundles.
16 | ///
17 | public static class AssetBundleBuilder
18 | {
19 | ///
20 | /// The path to the asset bundle build folder, relative to the root directory
21 | /// of the Unity project.
22 | ///
23 | public static readonly string RootBuildPath = "AssetBundles";
24 |
25 | ///
26 | /// The path to the asset bundle staging directory, relative to the root
27 | /// directory of the Unity project.
28 | ///
29 | public static readonly string StagingArea = Path.Combine(RootBuildPath, "Staging");
30 |
31 | ///
32 | /// The path to the asset bundle upload directory, relative to the root directory
33 | /// of the Unity project.
34 | ///
35 | public static readonly string UploadArea = Path.Combine(RootBuildPath, "Upload");
36 |
37 | ///
38 | /// The path to the directory where embedded asset bundles are stored within the
39 | /// Unity project.
40 | ///
41 | ///
42 | ///
43 | /// This path is for use in the editor. For accessing embedded bundles at runtime,
44 | /// use .
45 | ///
46 | public static readonly string EmbeddedBundlePath =
47 | "Assets/StreamingAssets/EmbeddedAssetBundles";
48 |
49 | ///
50 | /// The runtime platform that asset bundles will build for based on the active
51 | /// build target.
52 | ///
53 | public static RuntimePlatform CurrentBuildPlatform =>
54 | GetPlatformForTarget(EditorUserBuildSettings.activeBuildTarget);
55 |
56 | ///
57 | /// The path to the manifest bundle for the currently-active build target.
58 | ///
59 | ///
60 | ///
61 | /// The manifest bundle is an additional bundle generated by the Unity build
62 | /// process that contains the asset generated
63 | /// as part of the asset bundle build process. The manifest bundle will not
64 | /// exist until after asset bundles have been built.
65 | ///
66 | public static string ManifestBundlePath =>
67 | GetManifestBundlePathForTarget(EditorUserBuildSettings.activeBuildTarget);
68 |
69 | ///
70 | /// Builds asset bundles for the specified build target.
71 | ///
72 | ///
73 | ///
74 | /// The build target to build asset bundles for. Defaults to the active build
75 | /// target as specified by .
76 | /// Note that this will be normalized according to the rules specified by
77 | /// .
78 | ///
79 | ///
80 | ///
81 | /// Build options for the asset bundles. Defaults to .
82 | ///
83 | ///
84 | /// The asset bundle manifest for the built bundles.
85 | ///
86 | ///
87 | ///
88 | /// The resulting bundles will be output to a directory under
89 | /// named after . Due to build target normalization,
90 | /// this may not be the exact build target specified.
91 | ///
92 | ///
93 | ///
94 | /// After bundles are built, the set of bundles for the target platform are
95 | /// copied to the staging area and renamed according to the file naming
96 | /// convention. Note that the staging area is cleared at the beginning of the
97 | /// build, so after the build finishes it will only contain bundles for the
98 | /// specified platform.
99 | ///
100 | ///
101 | public static AssetBundleManifest BuildAssetBundles(
102 | BuildTarget? buildTarget = null,
103 | BuildAssetBundleOptions options = BuildAssetBundleOptions.None)
104 | {
105 | var target = buildTarget ?? EditorUserBuildSettings.activeBuildTarget;
106 | target = NormalizeBuildTarget(target);
107 |
108 | // Generate asset bundles in a directory named based on the build target.
109 | var buildDirectory = GetBuildPathForBuildTarget(target);
110 | Directory.CreateDirectory(buildDirectory);
111 |
112 | // Invoke Unity's asset bundle build process.
113 | var manifest = BuildPipeline.BuildAssetBundles(buildDirectory, options, target);
114 |
115 | // TODO: Delete any old bundles that have since been deleted from the project.
116 |
117 | // Clear out the staging area, if it already exists. This prevents a buildup
118 | // of old bundles over time.
119 | ResetDirectory(StagingArea);
120 |
121 | // Copy bundles to the staging area, renaming them based on the target
122 | // platform and asset hash.
123 | CopyBundlesToStagingArea(target, manifest);
124 |
125 | return manifest;
126 | }
127 |
128 | ///
129 | /// Builds asset bundles for all specified build targets.
130 | ///
131 | ///
132 | /// The set of build targets to build bundles for.
133 | ///
134 | ///
135 | /// Build options for the asset bundles. Defaults to .
136 | ///
137 | ///
138 | ///
139 | /// The set of manifests generated for each platform. Note that the keys of the
140 | /// dictionary are the normalized build targets, and so may not exactly match
141 | /// the list of targets in .
142 | ///
143 | ///
144 | ///
145 | /// Behavior is the same as ,
146 | /// but will generate bundles for all specified platforms. The staging folder
147 | /// will be reset before the build, and will contain bundles for all specified
148 | /// platforms after the build finishes.
149 | ///
150 | public static Dictionary BuildAssetBundles(
151 | BuildTarget[] buildTargets,
152 | BuildAssetBundleOptions options = BuildAssetBundleOptions.None)
153 | {
154 | // Normalize the list of build targets by normalizing the individual targets
155 | // specified and then removing any duplicates. For example, if the user
156 | // specifies both StandaloneWindows and StandaloneWindows64, we only want to
157 | // generate output for StandaloneWindows once.
158 | buildTargets = buildTargets
159 | .Select(NormalizeBuildTarget)
160 | .Distinct()
161 | .ToArray();
162 |
163 | // Clear out the staging area, if it already exists. This prevents a buildup
164 | // of old bundles over time.
165 | ResetDirectory(StagingArea);
166 |
167 | var manifests = new Dictionary();
168 | foreach (var buildTarget in buildTargets)
169 | {
170 | var target = NormalizeBuildTarget(buildTarget);
171 |
172 | // Generate asset bundles in a directory named based on the build target.
173 | var buildDirectory = GetBuildPathForBuildTarget(target);
174 | Directory.CreateDirectory(buildDirectory);
175 |
176 | // Invoke Unity's asset bundle build process.
177 | var manifest = BuildPipeline.BuildAssetBundles(buildDirectory, options, target);
178 |
179 | // TODO: Delete any old bundles that have since been deleted from the project.
180 |
181 | // Copy bundles to the staging area, renaming them based on the target
182 | // platform and asset hash.
183 | CopyBundlesToStagingArea(buildTarget, manifest);
184 |
185 | manifests.Add(target, manifest);
186 | }
187 |
188 | return manifests;
189 | }
190 |
191 | ///
192 | /// Copies built bundles to StreamingAssets to be embedded in the built player.
193 | ///
194 | ///
195 | ///
196 | /// A list of bundle names to copy. These should be the names of the asset
197 | /// bundles as defined in the Unity project. Will generate a warning if any of
198 | /// the specified names are invalid.
199 | ///
200 | ///
201 | ///
202 | /// The build target to build asset bundles for. Defaults to the active build
203 | /// target as specified by .
204 | /// Note that this will be normalized according to the rules specified by
205 | /// .
206 | ///
207 | ///
208 | ///
209 | /// is reset each time this function is called.
210 | /// This is to ensure that asset bundles from another platform are not
211 | /// accidentally included when switching build targets. Only one platform's
212 | /// bundles may be embedded at a time since only one set of platform bundles
213 | /// will be valid for a built player.
214 | ///
215 | public static void CopyEmbeddedBundles(
216 | string[] embeddedBundles,
217 | BuildTarget? buildTarget = null)
218 | {
219 | var target = buildTarget ?? EditorUserBuildSettings.activeBuildTarget;
220 | target = NormalizeBuildTarget(target);
221 |
222 | var buildPath = GetBuildPathForBuildTarget(target);
223 |
224 | // Fully reset the embedded bundle path in order to clear out removed
225 | // bundles or bundles from other platforms.
226 | ResetDirectory(EmbeddedBundlePath);
227 |
228 | // Gather the set of valid bundle names in order to validate the names
229 | // passed by the user.
230 | var validBundleNames = new HashSet(AssetDatabase.GetAllAssetBundleNames());
231 |
232 | foreach (var bundleName in embeddedBundles)
233 | {
234 | if (!validBundleNames.Contains(bundleName))
235 | {
236 | Debug.LogWarning($"Unable to copy unknown embedded bundle {bundleName}");
237 | continue;
238 | }
239 |
240 | var srcPath = Path.Combine(buildPath, bundleName);
241 | if (!File.Exists(srcPath))
242 | {
243 | Debug.LogWarning(
244 | $"Asset bundle {bundleName} has not been built for target {target}, " +
245 | $"make sure to build bundles before calling CopyEmbeddedBundles()");
246 | continue;
247 | }
248 |
249 | var destPath = Path.Combine(EmbeddedBundlePath, bundleName);
250 | File.Copy(srcPath, destPath, true);
251 | }
252 | }
253 |
254 | public static bool CheckForMissingBundles(BuildTarget[] targets, out string error)
255 | {
256 | StringBuilder log = new();
257 | var filesInStaging = Directory.GetFiles(StagingArea);
258 | var assetBundles = AssetDatabase.GetAllAssetBundleNames();
259 |
260 | foreach (var bundle in assetBundles)
261 | {
262 | foreach (var target in targets)
263 | {
264 | var platform = GetPlatformForTarget(target);
265 | var prefix = $"{bundle}_{platform}_";
266 | if (!filesInStaging.Any(f => Path.GetFileName(f).StartsWith(prefix))) {
267 | log.AppendLine($"Missing {platform} for {bundle}");
268 | }
269 | }
270 | }
271 |
272 | error = log.ToString();
273 | return string.IsNullOrEmpty(error);
274 | }
275 |
276 | private const int PREPARE_FOR_UPLOAD_MAX_RETRIES = 5;
277 | private const float PREPARE_FOR_UPLOAD_DELAY = 0.1f;
278 |
279 | ///
280 | /// Checks to see which asset bundles are already hosted, and copies any
281 | /// new/updated bundles to a separate folder for easy upload.
282 | ///
283 | ///
284 | ///
285 | /// The address of the public server to check for the bundles. Server must be
286 | /// public (i.e. not require any authentication) and must accept HEAD requests.
287 | ///
288 | ///
289 | ///
290 | /// Add a short delay between HEAD requests in case the CDN can't handle
291 | /// so many successive requests.
292 | ///
293 | ///
294 | ///
295 | /// Retry the HEAD requests up to 5 times to confirm the bundle is not uploaded
296 | /// rather than the CDN could not be reached. If the retry count exceeds 5 for
297 | /// any bundle, will throw an
298 | ///
299 | ///
300 | ///
301 | /// An that will finish once all bundles have
302 | /// been copied.
303 | ///
304 | ///
305 | ///
306 | /// In order to detect which bundles have already been uploaded we make a HEAD
307 | /// request to where the file would be stored relative to .
308 | /// If the response code is 200 (or otherwise indicates success) then the bundle
309 | /// is considered to already be uploaded. If the response is 404 (or otherwise
310 | /// an error) then the bundle is considered not yet uploaded.
311 | ///
312 | public static void PrepareBundlesForUpload(string baseUri, bool addDelay = false, bool useRetries = true)
313 | {
314 | // Clear out the upload area, if it already exists. This prevents a buildup
315 | // of bundles over time, and ensures that after a build the upload area will
316 | // only contain the latest bundles that need to be uploaded.
317 | ResetDirectory(UploadArea);
318 |
319 | // Start all of the web requests at the same time and wait for them to complete.
320 | var bundleRoutines = Directory
321 | .GetFiles(StagingArea)
322 | .Select(PrepareBundle)
323 | .ToArray();
324 |
325 | if (Application.isBatchMode)
326 | {
327 | // HACK: We have to block while waiting for the web requests to complete
328 | // otherwise this function doesn't work when running Unity from the command
329 | // line. When running with the -quit argument (which is necessary when doing
330 | // build automation on a remote server) Unity won't wait for coroutines to
331 | // complete before exiting, so the upload operations never complete.
332 | //
333 | // NOTE: We have to manually drive the coroutines to completion by calling
334 | // the MoveNext() method on each of them until they all are done. The
335 | // WaitForCoroutines() helper method handles this for us. At runtime Unity
336 | // would handle this for us, but it doesn't support blocking on coroutines
337 | // in the editor.
338 | while (WaitForCoroutines(bundleRoutines)) { }
339 | }
340 | else
341 | {
342 | // When running in the editor normally, we run the coroutines in the
343 | // background to avoid blocking the main thread.
344 | foreach (var coroutine in bundleRoutines)
345 | {
346 | EditorCoroutineUtility.StartCoroutineOwnerless(coroutine);
347 | }
348 | }
349 |
350 | IEnumerator PrepareBundle(string bundlePath)
351 | {
352 | var fileName = Path.GetFileName(bundlePath);
353 | var uri = $"{baseUri}/{fileName}";
354 | var retries = 0;
355 |
356 | do {
357 | // Short wait between requests to avoid overloading the CDN
358 | if (addDelay) yield return new WaitForSeconds(PREPARE_FOR_UPLOAD_DELAY);
359 |
360 | // TODO: Execute network requests in parallel. This may be easier to
361 | // do with async/await than with coroutines.
362 | Debug.Log($"Checking if bundle is already uploaded: {uri}");
363 | var request = UnityWebRequest.Head(uri);
364 | request.SendWebRequest();
365 |
366 | // HACK: Manually poll the web request until it finishes. This is
367 | // necessary because yielding on the request doesn't work in the editor,
368 | // and so the coroutine will never resume after yielding. The web
369 | // request is still being processed in the background, though, so we can
370 | // manually poll the request to find out when it has finished.
371 | while (!request.isDone)
372 | {
373 | yield return null;
374 | }
375 |
376 | // Could not connect to server, try again
377 | if (request.result == UnityWebRequest.Result.ConnectionError)
378 | {
379 | retries++;
380 | continue;
381 | }
382 |
383 | // Not found, which means it's not uploaded yet
384 | if (request.result == UnityWebRequest.Result.ProtocolError || request.result == UnityWebRequest.Result.DataProcessingError)
385 | {
386 | var destPath = Path.Combine(UploadArea, fileName);
387 | File.Copy(bundlePath, destPath, true);
388 | }
389 | break;
390 | } while (useRetries && retries < PREPARE_FOR_UPLOAD_MAX_RETRIES);
391 |
392 | if (useRetries) {
393 | // Don't allow the ambiguous case where a bundle wasn't marked for upload because the CDN could not be reached
394 | if (retries == PREPARE_FOR_UPLOAD_MAX_RETRIES) {
395 | throw new Exception($"Could not connect to server while checking {uri}");
396 | } else if (retries > 0) {
397 | Debug.LogWarning($"Had issues connecting to {uri}, succeeded after {retries} attempts");
398 | }
399 | }
400 | }
401 |
402 | bool WaitForCoroutines(IEnumerator[] routines)
403 | {
404 | var workRemaining = false;
405 | foreach (var routine in routines)
406 | {
407 | workRemaining |= routine.MoveNext();
408 | }
409 |
410 | return workRemaining;
411 | }
412 | }
413 |
414 | ///
415 | /// Generates the list of asset bundle descriptions from the set of
416 | /// platform-specific manifests.
417 | ///
418 | ///
419 | ///
420 | /// The paths to each of the platforms-specific manifest bundles. This should
421 | /// include a bundle for every platform you build bundles for in order to ensure
422 | /// that the generated objects contain
423 | /// correct information. Paths should be either an absolute path, or a relative
424 | /// path that is relative to the root folder of the Unity project.
425 | ///
426 | ///
427 | ///
428 | /// The bundle description for each of the bundles defined in the project. The
429 | /// bundle descriptions will include asset hashes for the platforms specified in
430 | /// .
431 | ///
432 | ///
433 | ///
434 | /// This function will handle the work of loading the manifest bundle for each
435 | /// specified platform, loading the manifest from the bundle, and then unloading
436 | /// the bundle to avoid loading conflicts. If you already have the bundle
437 | /// manifests loaded, you can instead use
438 | /// directly.
439 | ///
440 | public static AssetBundleDescription[] MergePlatformManifests(
441 | Dictionary paths)
442 | {
443 | var manifests = paths
444 | .Select(pair => (pair.Key, Value: ExtractManifest(pair.Value)))
445 | .ToDictionary(pair => pair.Key, pair => pair.Value);
446 | return MergePlatformManifests(manifests);
447 |
448 | AssetBundleManifest ExtractManifest(string path)
449 | {
450 | // Load the bundle from the file, skipping any files in the directory
451 | // that aren't valid asset bundles.
452 | var bundle = UnityEngine.AssetBundle.LoadFromFile(path);
453 | if (bundle == null)
454 | {
455 | throw new ArgumentException($"No bundle loaded from {path}");
456 | }
457 |
458 | // Load the manifest from the bundle.
459 | var manifest = bundle.LoadAsset("assetbundlemanifest");
460 | if (manifest == null)
461 | {
462 | throw new ArgumentException($"Failed to load AssetBundleManifest from bundle at path {path}");
463 | }
464 |
465 | // Unload the bundle immediately without unloading the manifest that was
466 | // loaded from it. This prevents issues with trying to load the same
467 | // bundle multiple times, which can mostly come up when testing the
468 | // bundle build process locally.
469 | bundle.Unload(false);
470 |
471 | return manifest;
472 | }
473 | }
474 |
475 | ///
476 | /// Generates the list of asset bundle descriptions from the set of
477 | /// platform-specific manifests.
478 | ///
479 | ///
480 | ///
481 | /// The set of objects generated when
482 | /// building bundles. This should include a manifest for every platform you
483 | /// build bundles for in order to ensure that the generated
484 | /// objects contain correct information.
485 | ///
486 | ///
487 | ///
488 | /// The bundle description for each of the bundles defined in the project. The
489 | /// bundle descriptions will include asset hashes for the platforms specified in
490 | /// .
491 | ///
492 | public static AssetBundleDescription[] MergePlatformManifests(
493 | Dictionary manifests)
494 | {
495 | var descriptions = new Dictionary hashes, HashSet dependencies)>();
496 | foreach (var keyValue in manifests)
497 | {
498 | var platform = keyValue.Key;
499 | var manifest = keyValue.Value;
500 |
501 | foreach (var bundleName in manifest.GetAllAssetBundles())
502 | {
503 | // Get the existing description object for the current bundle, or
504 | // create a new one and add it to the description dictionary.
505 | if (!descriptions.TryGetValue(bundleName, out var description))
506 | {
507 | // The first time we create the list of description objects,
508 | // populate the set of dependencies.
509 | var dependencies = new HashSet(
510 | manifest.GetDirectDependencies(bundleName));
511 |
512 | description = (new Dictionary(), dependencies);
513 | descriptions.Add(bundleName, description);
514 | }
515 |
516 | // Set the hash for the current build target.
517 | description.hashes[platform] = manifest.GetAssetBundleHash(bundleName);
518 | }
519 | }
520 |
521 | return descriptions
522 | .Select(pair => new AssetBundleDescription(pair.Key, pair.Value.hashes, pair.Value.dependencies))
523 | .ToArray();
524 | }
525 |
526 | ///
527 | /// Gets the corresponding for the specified .
528 | ///
529 | public static RuntimePlatform GetPlatformForTarget(BuildTarget target)
530 | {
531 | switch (target)
532 | {
533 | case BuildTarget.StandaloneWindows:
534 | case BuildTarget.StandaloneWindows64:
535 | return RuntimePlatform.WindowsPlayer;
536 |
537 | case BuildTarget.StandaloneOSX:
538 | return RuntimePlatform.OSXPlayer;
539 |
540 | case BuildTarget.StandaloneLinux64:
541 | return RuntimePlatform.LinuxPlayer;
542 |
543 | case BuildTarget.Android:
544 | return RuntimePlatform.Android;
545 |
546 | case BuildTarget.iOS:
547 | return RuntimePlatform.IPhonePlayer;
548 |
549 | case BuildTarget.WebGL:
550 | return RuntimePlatform.WebGLPlayer;
551 |
552 | // TODO: Support for other platforms.
553 |
554 | default:
555 | throw new ArgumentException($"Cannot determine asset bundle target for unsupported build target {target}");
556 | }
557 | }
558 |
559 | ///
560 | /// Normalizes the target and appends it to .
561 | ///
562 | private static string GetBuildPathForBuildTarget(BuildTarget target)
563 | {
564 | var platform = GetPlatformForTarget(NormalizeBuildTarget(target));
565 | return Path.Combine(RootBuildPath, platform.ToString());
566 | }
567 |
568 | ///
569 | /// Returns the normalized equivalent of the specified build target.
570 | ///
571 | ///
572 | ///
573 | /// There are a handful of cases where two or more variants of
574 | /// use the same asset bundles at runtime, e.g.
575 | /// and . In these cases, we define
576 | /// a single variant to be the "normalized" variant that is used when building
577 | /// bundles for all equivalent platforms. See the package documentation for the
578 | /// full set of conversions performed.
579 | ///
580 | public static BuildTarget NormalizeBuildTarget(BuildTarget target)
581 | {
582 | switch (target)
583 | {
584 | case BuildTarget.StandaloneWindows64:
585 | return BuildTarget.StandaloneWindows;
586 |
587 | case BuildTarget.StandaloneLinux64:
588 | return BuildTarget.StandaloneLinux64;
589 |
590 | default:
591 | return target;
592 | }
593 | }
594 |
595 | ///
596 | /// The path to the manifest bundle for the specified build target.
597 | ///
598 | ///
599 | ///
600 | /// The manifest bundle is an additional bundle generated by the Unity build
601 | /// process that contains the asset generated
602 | /// as part of the asset bundle build process. The manifest bundle will not
603 | /// exist until after asset bundles have been built for the specified platform.
604 | ///
605 | public static string GetManifestBundlePathForTarget(BuildTarget target)
606 | {
607 | target = NormalizeBuildTarget(target);
608 | var targetBuildPath = GetBuildPathForBuildTarget(target);
609 | var platformName = GetPlatformForTarget(target);
610 | return Path.Combine(targetBuildPath, platformName.ToString());
611 | }
612 |
613 | ///
614 | /// Ensures that a directory exists and is empty.
615 | ///
616 | private static void ResetDirectory(string path)
617 | {
618 | if (Directory.Exists(path))
619 | {
620 | Directory.Delete(path, true);
621 | }
622 |
623 | Directory.CreateDirectory(path);
624 | }
625 |
626 | ///
627 | /// Copies all bundles for the specified target to the staging area.
628 | ///
629 | ///
630 | ///
631 | /// The build target to use. Assumed to already be normalized.
632 | ///
633 | ///
634 | ///
635 | /// The asset bundle manifest resulting from building asset bundles for the
636 | /// specified target.
637 | ///
638 | private static void CopyBundlesToStagingArea(
639 | BuildTarget buildTarget,
640 | AssetBundleManifest manifest)
641 | {
642 | var buildDirectory = GetBuildPathForBuildTarget(buildTarget);
643 | var target = GetPlatformForTarget(buildTarget);
644 |
645 | foreach (var bundle in manifest.GetAllAssetBundles())
646 | {
647 | var sourceFile = Path.Combine(buildDirectory, bundle);
648 | var fileName = AssetBundleDescription.BuildFileName(
649 | bundle,
650 | target,
651 | manifest.GetAssetBundleHash(bundle));
652 |
653 | File.Copy(
654 | sourceFile,
655 | Path.Combine(StagingArea, fileName),
656 | true);
657 | }
658 | }
659 | }
660 | }
--------------------------------------------------------------------------------
/Editor/AssetBundleBuilder.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 051bce8569c72e74192339d5eb806df1
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Editor/Synapse.AssetBundleBuilder.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Synapse.AssetBundleBuilder.Editor",
3 | "references": [
4 | "Unity.EditorCoroutines.Editor",
5 | "Synapse.AssetBundleBuilder.Runtime"
6 | ],
7 | "optionalUnityReferences": [],
8 | "includePlatforms": [
9 | "Editor"
10 | ],
11 | "excludePlatforms": [],
12 | "allowUnsafeCode": false,
13 | "overrideReferences": false,
14 | "precompiledReferences": [],
15 | "autoReferenced": true,
16 | "defineConstraints": []
17 | }
--------------------------------------------------------------------------------
/Editor/Synapse.AssetBundleBuilder.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6b7d3b67e29ae364e9610ecc683c66d7
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Kongregate
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2421c1dc21342de45a3291740791c748
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Asset Bundle Builder
2 |
3 | This tool provides a workflow for managing and automating the process of building, uploading, and deploying asset bundles for Unity 3D games. It extends the built-in Unity tools be defining a convention for how bundles are named, where they are stored, how they are tracked, and how they are downloaded, such that it's easy to continually release updates to asset bundles for a live game.
4 |
5 | ## Motivation and Use Cases
6 |
7 | The goal of this system is to make it easy to continuously update and deploy asset bundles for a live game. The default naming convention and build process provided by Unity is generic and doesn't impose many restrictions on how you manage asset bundles for your game, but that also means that common usage patterns have to be re-invented for each new project. This package attempts to provide a streamlined process that covers some common use cases:
8 |
9 | - You want to iterate on and release updates to asset bundles without having to release an update to your game.
10 | - Your game has a server component which can provide your game client with an updated list of asset bundle definitions.
11 | - You would like to be able to automate the build and upload process for asset bundles as part of your CI process.
12 | - Storage for built bundles isn't an issue (i.e. you're using Amazon S3 or some other cloud storage service), but you would like to limit when new bundles are deployed to minimize how often players need to download updated bundles.
13 |
14 | > NOTE: If you are starting a new Unity project and are considering using asset-bundle-builder, consider using Unity's newer [Addressable Asset System](https://blogs.unity3d.com/2019/07/15/addressable-asset-system/) instead. This system is mainly intended for projects that are already using asset bundles and are heavily invested in the asset bundle workflow.
15 |
16 | ## Setup and Usage
17 |
18 | To include asset-bundle-builder as a Unity package, you'll need to be on Unity 2018.3 or later. Open `Packages/manifest.json` in your project and add "com.synapse-games.asset-bundle-builder" to the `dependencies` object:
19 |
20 | ```json
21 | {
22 | "dependencies": {
23 | "com.synapse-games.asset-bundle-builder": "https://github.com/kongregate/asset-bundle-builder.git#v0.2.3"
24 | }
25 | }
26 | ```
27 |
28 | > NOTE: You'll need to have Git installed on your development machine for Unity to be able to download the dependency. See https://git-scm.com/ for more information. Alternatively, you can clone the project directly into your `Packages` folder in order to vendor the package with your project.
29 |
30 | > NOTE: If you're using an older version of Unity, you can still use bundle-builder by copying the contents of `Plugins` into your project's `Plugins` folder.
31 |
32 | ### Building and Uploading Bundles
33 |
34 | Asset bundles are defined as normal in Unity, i.e. by tagging assets as part of a bundle. To build bundles, invoke `AssetBundleBuilder.BuildAssetBundles()`. This will build asset bundles to a folder `AssetBundles/` in your project directory, then copy and rename the bundles into `AssetBundles/Staging`. The `Staging` directory will contain the current set of asset bundles for any built platforms.
35 |
36 | Once you have built all of your asset bundles, call `AssetBundleBuilder.PrepareBundlesForUpload()`, passing it the URL of the web server where you store your built bundles. This will check for the presence of each bundle, and copy any bundles that haven't already been uploaded to `AssetBundles/Upload`. You should then upload any bundles in `AssetBundles/Upload` to your storage server or CDN as appropriate.
37 |
38 | > NOTE: If you do not have your asset bundles in a publicly-accessible server, or are otherwise not able to check for the presence of bundles at build time, you can skip using `PrepareBundlesForUpload()`. In this case, you can upload bundles from `AssetBundles/Staging` directly, or perform any necessary custom logic to determine which bundles need to be uploaded.
39 |
40 | Diagram of the `AssetBundles` directory:
41 |
42 | ```txt
43 | AssetBundles/
44 | ├── /
45 | │ └──
46 | ├── Staging/
47 | │ └──
48 | └── Upload/
49 | └──
50 | ```
51 |
52 | ### Generating the Bundle Descriptions
53 |
54 | To generate the list of asset bundle descriptions used on the platform server, call `AssetBundleBuilder.GenerateBundleDescriptions()`. This will return a list of `AssetBundleDescription` objects which contain the information needed to load each bundle at runtime. This information needs to then be exported in some format that can be used by your game's server to tell your game's client which asset bundles to download.
55 |
56 | While this package doesn't enforce any specific format for this data, it does provide support for using Json.NET to serialize the bundle descriptions to a JSON file. To set this up, you'll need to do the following:
57 |
58 | * Add the [jillejr.newtonsoft.json-for-unity](https://www.npmjs.com/package/jillejr.newtonsoft.json-for-unity) package to your project.
59 | * Add a `JSON_NET` definition to your project's [custom scripts defines](https://docs.unity3d.com/Manual/PlatformDependentCompilation.html) in order to enable the Json.NET compatibility features in asset-bundle-builder.
60 | * Register the `AssetBundleDescriptionConverter` class when performing serialization/deserialization:
61 |
62 | ```c#
63 | var descriptions = AssetBundleBuilder
64 | .GenerateBundleDescriptions(new BuildTarget[] { ... });
65 |
66 | var bundleJson = JsonConvert.SerializeObject(
67 | descriptions,
68 | Formatting.Indented,
69 | new AssetBundleDescriptionConverter());
70 |
71 | File.WriteAllText(
72 | Path.Combine(AssetBundleBuilder.BundleOutputPath, "assetbundles.json"),
73 | bundleJson);
74 | ```
75 |
76 | If not using JSON encoding (or not using Json.NET for the encoding), you will need to implement serialization for `AssetBundleDescription` using whatever system is appropriate for your game.
77 |
78 | ### Preparing Embedded Bundles
79 |
80 | If you would like to distribute any asset bundles with your client directly, you can use `AssetBundleBuilder.CopyEmbeddedBundles()` to copy a set of bundles to `StreamingAssets/EmbeddedAssetBundles`. Only asset bundles for the specified target platform (defaulting to the currently-selected platform) will be copied over, to avoid bloating the built player with unused files.
81 |
82 | Notes that you will have to manually rebuild asset bundles before calling `CopyEmbeddedBundles()`. Additionally, you'll need to make sure to build asset bundles and then call `CopyEmbeddedBundles()` before building your player in order to ensure that you are including the right version of any embedded bundles in your player.
83 |
84 | ### Downloading Bundles
85 |
86 | To determine which asset bundles need to be downloaded at runtime, your game client should load the generated bundle description list (usually as a JSON document). Once loaded, the bundle description list will define the full set of bundles available to the client, including the information needed to identify the current version of a bundle for a given platform.
87 |
88 | In general, the easiest way to determine the name of the file to download is the `FileNameForCurrentTarget` property. If a bundle was baked into your client build (i.e. by specifying it when calling `CopyEmbeddedBundles()`), you should instead use the `EmbeddedPath` property to get the full path to the embedded copy of the bundle.
89 |
90 | > NOTE: This package doesn't provided any utilities for determining the full URL of your hosted bundles, nor does it provide any specific utilities for downloading, loading, or managing your asset bundles at runtime.
91 |
92 | ## Additional Documentation
93 |
94 | This section more thoroughly explains the specifics of how asset-bundle-builder manages the asset bundles in your project.
95 |
96 | ### Asset Bundle File Naming
97 |
98 | The generated bundle files are named according to the pattern `{name}_{platform}_{hash}.unity3d`, where:
99 |
100 | * `{name}` is the bundle name, as defined in the Unity project.
101 | * `{platform}` is the target platform string, matching the normalized variant names for [`RuntimePlatform`](https://docs.unity3d.com/ScriptReference/RuntimePlatform.html) (see [Platform Support](#platform-support) below).
102 | * `{hash}` is the asset hash of the built bundle, determined when the bundle is built.
103 |
104 | This naming conventions serves a number of purposes:
105 |
106 | * Including the platform name in the file name allows the different platform-specific versions of a bundle to exist in the same folder. This simplifies the deployment of bundles and reduces complications when uploading/downloading bundles.
107 | * Including the hash in the file name allows the different historical versions of the same bundle to be stored in the same directory in the CDN, which is necessary to support iteration and re-deployment of new versions of an asset bundle over time.
108 | * Adding an explicit file extension makes the bundles play better with applications that expect all files to have extensions, and makes it easier for humans to tell what purpose the files serve.
109 |
110 | ### Platform Support
111 |
112 | Unity uses the `BuildTarget` enum when building asset bundles and the `RuntimePlatform` enum to specify the current platform at runtime. Unfortunately, neither of these accurately reflects the set of platform-specific asset bundles that need to be built. To address this, asset-bundle-builder uses a normalized subset of both `BuildTarget` and `RuntimePlatform` to identify the target platform for built asset bundles.
113 |
114 | When building asset bundles, the specified `BuildTarget` will be normalized to account for cases where Unity differentiates between build targets at build time but not runtime. Specifically, when building for Windows there are the `StandaloneWindows` and `StandaloneWindows64` targets, however both will use the `WindowsPlayer` runtime platform. As such, asset-bundle-builder will only ever use the `StandaloneWindows` target when building asset bundles.
115 |
116 | At runtime, the "Editor" variants for Windows, OSX, and Linux are normalized to the corresponding platform player variants, e.g. `WindowsEditor` will load bundles for `WindowsPlayer`. This is because the editor uses asset bundles for the corresponding platform's player.
117 |
118 | Additionally, obsolete build targets and runtime platforms are not supported.
119 |
120 | > NOTE: Not all platforms are currently supported, and there are some outstanding questions that need to be answered in order to properly handle asset bundles on all platforms. See [this thread on the Unity forums](https://forum.unity.com/threads/do-macos-and-windows-need-different-asset-bundles.670510/) for more details.
121 |
122 | ### Hosting Asset Bundles
123 |
124 | Built bundles are expected to be hosted in a single folder on a remote server or CDN. This includes both the live version of any given bundle as well as any unpublished versions still in development. At any given time, the full set of asset bundles generated from the project can be uploaded to your hosting server without risking existing bundles being overwritten or broken.
125 |
126 | > NOTE: This package doesn't strictly require or enforce this convention for hosting your asset bundles, but it does provide a default workflow to support it. As such, following this convention will ensure a smoother build and deployment process for your game's asset bundles.
127 |
128 | ### Bundle Description List
129 |
130 | This package supports generating a list of "bundle description" objects which specify the necessary information for loading your asset bundles at runtime. It also provides out-of-the-box functionality for converting this list to and from a JSON document for use at runtime.
131 |
132 | For each asset bundle defined in your project, the bundle description will contain:
133 |
134 | * The name of the bundle.
135 | * The set of supported platforms and the corresponding asset hash for each one.
136 | * The list of direct dependencies for each bundle.
137 |
138 | This is the information needed at runtime to determine which bundles your game's client should download and to determine the correct filename to download for a given bundle. The `AssetBundleDescription` class provides utilities for accessing this information.
139 |
140 | #### Building Bundle Descriptions
141 |
142 | When building bundle descriptions, you need to provide the [`AssetBundleManifest`](https://docs.unity3d.com/ScriptReference/AssetBundleManifest.html) for each platform that you built bundles for. There are two ways to get the manifest after building bundles:
143 |
144 | * `AssetBundleBuilder.BuildAssetBundles()` will return the manifest for each bundle built. If you are building all bundles at once using `BuildAssetBundles()`, you can pass the returned bundles directly into `MergePlatformManifests()`.
145 | * Load the manifest from the manifest bundle generated by Unity. When building asset bundles for a platform, Unity produces an additional asset bundle that only contains the `AssetBundleManifest` for the build. You can either load the manifest from the bundle directly, or you can pass `MergePlatformManifests()` the paths to the bundles and it will load the manifests for you.
146 |
147 | If you are building all of your asset bundles at the same time with a single call to `BuildAssetBundles()`, the first approach is recommended.
148 |
149 | When using a build automation server, though, it is common to have multiple platforms build in parallel with separate copies of the project. In this case, no one workspace will have access to all generated bundle manifests. To generated the asset bundle descriptions, you'll have to setup your build system to copy the manifest bundles to a single workspace after all bundles finish building, and have a single workspace generate the bundle descriptions in a single build step.
150 |
151 | #### JSON Conversion
152 |
153 | Written out to JSON using the provided Json.NET conversion it would looks like this:
154 |
155 | ```json
156 | [
157 | {
158 | "name": "bundle-1",
159 | "hashes": {
160 | "Android": "84dd10474d639ecc804d8fa3088887a2",
161 | "iOS": "4c92bf45a5c9e0281254cc7ad07690e9"
162 | },
163 | "dependencies": []
164 | },
165 | {
166 | "name": "cool-stuff",
167 | "hashes": {
168 | "Android": "301c62868060808f10b236e1de1dfaa8",
169 | "iOS": "90c94041de4ee425eb9ab23b9aa96021"
170 | },
171 | "dependencies": ["bundle-1"]
172 | }
173 | ]
174 | ```
175 |
176 | #### Differences From `AssetBundleManifest`
177 |
178 | Unity already provides the `AssetBundleManifest` asset to determine the hash and dependencies for each bundle, so why provide a separate system for providing that data at runtime? The primary reason for this is that `AssetBundleManifest` always represents the full set of asset bundles as contained in your Unity project when you build your bundles. This means that you can't choose when individual bundles are deployed: If you push the latest bundle manifest, all of your asset bundles are now live. Using a less opaque data format like JSON allows you to maintain a separate list of live bundles and manually choose when to move each bundle into production. Keeping information for all platforms in one file, rather than having a separate file per platform, further eases this process.
179 |
180 | ### Compatibility with Addressables
181 |
182 | In July of 2019 Unity moved their [Addressable Asset System](https://blogs.unity3d.com/2019/07/15/addressable-asset-system/) out of preview status. This system is designed to replace asset bundles, and seeks to address many of the same use cases as asset-bundle-builder. Unfortunately, this package is not currently compatible with addressables, and it is not clear if compatibility is possible. If you believe you have a solution for migrating projects using asset-bundle-builder to the addressable asset system, please [open an issue](https://github.com/kongregate/asset-bundle-builder/issues/new) with your suggestion so that we can discuss integrating that functionality in the package!
183 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4ed7c0d077e5ddc43bb2c53206b8dab2
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c245ecf7470356b49b97b38dfe189221
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/AssetBundleDescription.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using UnityEngine;
6 |
7 | namespace SynapseGames.AssetBundle
8 | {
9 | ///
10 | /// Data needed to load an asset bundle at runtime.
11 | ///
12 | ///
13 | ///
14 | /// This type is intended to be loaded from JSON (or another intermediate data
15 | /// format) at runtime and used to determine what asset bundles need to be loaded.
16 | /// It provides enough information to determine the file name for a given asset
17 | /// bundle for any supported platform. The values are generated at build time using
18 | ///
19 | ///
20 | public struct AssetBundleDescription : IEquatable
21 | {
22 | private Dictionary _hashes;
23 | private HashSet _dependencies;
24 |
25 | ///
26 | /// The name of the asset bundle, as specified in the Unity editor.
27 | ///
28 | public string Name { get; }
29 |
30 | ///
31 | /// The set of supported platforms and their corresponding asset hashes.
32 | ///
33 | public IReadOnlyDictionary Hashes => _hashes;
34 |
35 | public IReadOnlyCollection Dependencies => _dependencies;
36 |
37 | ///
38 | /// The file name for the current platform, if any. Will be null if there is no
39 | /// hash for the current platform.
40 | ///
41 | public string FileName => GetFileNameForPlatform(NormalizedPlatform);
42 |
43 | ///
44 | /// The asset hash for the current platform, if any. Will be null if there is
45 | /// no hash for the current platform.
46 | ///
47 | public Hash128? Hash => GetHashForPlatform(NormalizedPlatform);
48 |
49 | ///
50 | /// The path to the asset bundle when it is distributed as an embedded bundle.
51 | ///
52 | ///
53 | ///
54 | /// This path will be inside the streaming assets path, which may require
55 | /// platform-specific considerations when loading. This also does not guarantee
56 | /// that the bundle is actually embedded in the player, it only provides the
57 | /// path where the file would be stored if it were embedded.
58 | ///
59 | public string EmbeddedPath => Path.Combine(
60 | Application.streamingAssetsPath,
61 | "EmbeddedAssetBundles",
62 | Name);
63 |
64 | ///
65 | /// Initializes a new .
66 | ///
67 | ///
68 | ///
69 | /// The name of the asset bundle, as defined in the Unity project.
70 | ///
71 | ///
72 | ///
73 | /// The set of asset hashes for each supported platform. Only the specified
74 | /// platforms will be considered supported, such that the bundle cannot be
75 | /// loaded on any platforms not specified.
76 | ///
77 | public AssetBundleDescription(
78 | string name,
79 | Dictionary hashes,
80 | HashSet dependencies)
81 | {
82 | Name = name;
83 | _dependencies = dependencies;
84 | _hashes = hashes;
85 | }
86 |
87 | ///
88 | /// Initializes a new , copying the name
89 | /// and hashes from .
90 | ///
91 | public AssetBundleDescription(AssetBundleDescription other)
92 | : this(other.Name, other._hashes, other._dependencies)
93 | {
94 | }
95 |
96 | ///
97 | /// Gets the filename for the bundle on the specified target platform, if supported.
98 | ///
99 | ///
100 | ///
101 | /// The file name for the specified target platform, or null if no hash is
102 | /// present for that platform.
103 | ///
104 | ///
105 | ///
106 | /// Automatically normalizes with .
107 | ///
108 | public string GetFileNameForPlatform(RuntimePlatform platform)
109 | {
110 | platform = NormalizePlatform(platform);
111 | if (Hashes.TryGetValue(platform, out var hash))
112 | {
113 | return BuildFileName(Name, platform, hash);
114 | }
115 |
116 | return null;
117 | }
118 |
119 | ///
120 | /// Returns the hash for the specified target platform, if any.
121 | ///
122 | ///
123 | ///
124 | /// The asset hash for the specified platform, or null if no hash is present
125 | /// for that platform.
126 | ///
127 | ///
128 | ///
129 | /// Automatically normalizes with .
130 | ///
131 | public Hash128? GetHashForPlatform(RuntimePlatform platform)
132 | {
133 | platform = NormalizePlatform(platform);
134 | if (Hashes.TryGetValue(platform, out var hash))
135 | {
136 | return hash;
137 | }
138 |
139 | return null;
140 | }
141 |
142 | public override string ToString()
143 | {
144 | // TODO: Include more information in the string representation of the bundle.
145 | return $"{{ Name = {Name} }}";
146 | }
147 |
148 | public override bool Equals(object obj)
149 | {
150 | if (obj is AssetBundleDescription other)
151 | {
152 | return Equals(other);
153 | }
154 |
155 | return false;
156 | }
157 |
158 | public override int GetHashCode()
159 | {
160 | int hash = Name.GetHashCode();
161 |
162 | // NOTE: We iterate over the variants of RuntimePlatform, rather than
163 | // directly iterating over the contents of Hashes, in order to hash the
164 | // values in the dictionary in a deterministic order.
165 | var variants = Enum.GetValues(typeof(RuntimePlatform)).Cast();
166 | foreach (var variant in variants)
167 | {
168 | Hash128 platformHash;
169 | if (Hashes.TryGetValue(variant, out platformHash))
170 | {
171 | hash = (hash, variant, platformHash).GetHashCode();
172 | }
173 | }
174 |
175 | return hash;
176 | }
177 |
178 | public bool Equals(AssetBundleDescription other)
179 | {
180 | if (Name != other.Name)
181 | {
182 | return false;
183 | }
184 |
185 | if (Hashes == other.Hashes)
186 | {
187 | return true;
188 | }
189 |
190 | if (Hashes.Count != other.Hashes.Count)
191 | {
192 | return false;
193 | }
194 |
195 | foreach (var pair in Hashes)
196 | {
197 | if (!other.Hashes.TryGetValue(pair.Key, out var otherHash)
198 | || otherHash != pair.Value)
199 | {
200 | return false;
201 | }
202 | }
203 |
204 | return _dependencies.SetEquals(other.Dependencies);
205 | }
206 |
207 | ///
208 | /// Returns the appropriate asset bundle target for the current platform.
209 | ///
210 | ///
211 | ///
212 | /// Uses to determine the current runtime platform.
213 | ///
214 | public static RuntimePlatform NormalizedPlatform => NormalizePlatform(Application.platform);
215 |
216 | ///
217 | /// Returns the appropriate asset bundle target for the specified platform.
218 | ///
219 | ///
220 | ///
221 | /// For editor platforms, this will return corresponding platform's player
222 | /// since the editor does not use different asset bundles from the
223 | /// corresponding platform.
224 | ///
225 | public static RuntimePlatform NormalizePlatform(RuntimePlatform platform)
226 | {
227 | switch (platform)
228 | {
229 | case RuntimePlatform.WindowsEditor:
230 | case RuntimePlatform.WindowsPlayer:
231 | return RuntimePlatform.WindowsPlayer;
232 |
233 | case RuntimePlatform.OSXEditor:
234 | case RuntimePlatform.OSXPlayer:
235 | return RuntimePlatform.OSXPlayer;
236 |
237 | case RuntimePlatform.LinuxEditor:
238 | case RuntimePlatform.LinuxPlayer:
239 | return RuntimePlatform.LinuxPlayer;
240 |
241 | default: return platform;
242 | }
243 | }
244 |
245 | ///
246 | /// Canonical method for determining the final file name of a built asset bundle.
247 | ///
248 | ///
249 | ///
250 | /// The name of the asset bundle, as configured in the editor.
251 | ///
252 | /// The platform the bundle was built for.
253 | /// The hash of the built bundle.
254 | ///
255 | ///
256 | /// The full file name for the asset bundle, including file extension.
257 | ///
258 | public static string BuildFileName(
259 | string bundleName,
260 | RuntimePlatform platform,
261 | Hash128 hash)
262 | {
263 | return $"{bundleName}_{platform}_{hash}.unity3d";
264 | }
265 |
266 | public static bool operator ==(AssetBundleDescription left, AssetBundleDescription right)
267 | {
268 | return left.Equals(right);
269 | }
270 |
271 | public static bool operator !=(AssetBundleDescription left, AssetBundleDescription right)
272 | {
273 | return !left.Equals(right);
274 | }
275 | }
276 | }
277 |
--------------------------------------------------------------------------------
/Runtime/AssetBundleDescription.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0a515f78168bca84ebd389c20b9cb003
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 782788af2f0efb44dac75fbc3bb2742e
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Runtime/Json/AssetBundleDescriptionConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using UnityEngine;
6 |
7 | namespace SynapseGames.AssetBundle.Json
8 | {
9 | public class AssetBundleDescriptionConverter : JsonConverter
10 | {
11 | public override AssetBundleDescription ReadJson(
12 | JsonReader reader,
13 | Type objectType,
14 | AssetBundleDescription existingValue,
15 | bool hasExistingValue,
16 | JsonSerializer serializer)
17 | {
18 | var jsonObject = JObject.Load(reader);
19 |
20 | var name = jsonObject.Value("name");
21 |
22 | var hashes = new Dictionary();
23 | foreach (var property in jsonObject.Value("hashes"))
24 | {
25 | if (!Enum.TryParse(property.Key, out RuntimePlatform platform))
26 | {
27 | throw new JsonSerializationException($"Could not parse unknown asset bundle target {property.Key}");
28 | }
29 |
30 | var hash = Hash128.Parse((string)property.Value);
31 | if (!hash.isValid)
32 | {
33 | throw new JsonSerializationException($"Invalid hash {property.Value} found in bundle {name} for platform {platform}");
34 | }
35 |
36 | hashes.Add(platform, hash);
37 | }
38 |
39 | var dependencies = new HashSet();
40 | serializer.Populate(jsonObject.Value("dependencies").CreateReader(), dependencies);
41 |
42 | return new AssetBundleDescription(name, hashes, dependencies);
43 | }
44 |
45 | public override void WriteJson(
46 | JsonWriter writer,
47 | AssetBundleDescription value,
48 | JsonSerializer serializer)
49 | {
50 | writer.WriteStartObject();
51 |
52 | // Serialize the name of the bundle.
53 | writer.WritePropertyName("name");
54 | writer.WriteValue(value.Name);
55 |
56 | // Serialize the set of platforms and hashes.
57 | writer.WritePropertyName("hashes");
58 | writer.WriteStartObject();
59 | foreach (var pair in value.Hashes)
60 | {
61 | writer.WritePropertyName(pair.Key.ToString());
62 | writer.WriteValue(pair.Value.ToString());
63 | }
64 | writer.WriteEndObject();
65 |
66 | // Serialize the list of dependencies.
67 | writer.WritePropertyName("dependencies");
68 | serializer.Serialize(writer, value.Dependencies);
69 |
70 | writer.WriteEndObject();
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Runtime/Json/AssetBundleDescriptionConverter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 82995a0215c66fc43bc944e3335a97b6
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Runtime/Json/Synapse.AssetBundleBuilder.Runtime.Json.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Synapse.AssetBundleBuilder.Runtime.Json",
3 | "references": [
4 | "Synapse.AssetBundleBuilder.Runtime"
5 | ],
6 | "optionalUnityReferences": [],
7 | "includePlatforms": [],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [
14 | "JSON_NET"
15 | ]
16 | }
--------------------------------------------------------------------------------
/Runtime/Json/Synapse.AssetBundleBuilder.Runtime.Json.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 525006528faa72545b0b595168e2ebd6
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Runtime/Synapse.AssetBundleBuilder.Runtime.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Synapse.AssetBundleBuilder.Runtime",
3 | "references": [],
4 | "optionalUnityReferences": [],
5 | "includePlatforms": [],
6 | "excludePlatforms": [],
7 | "allowUnsafeCode": false,
8 | "overrideReferences": false,
9 | "precompiledReferences": [],
10 | "autoReferenced": true,
11 | "defineConstraints": []
12 | }
--------------------------------------------------------------------------------
/Runtime/Synapse.AssetBundleBuilder.Runtime.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d1dabd9245c7da04bac1d452b8c88b97
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Tests.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b53f4eec91007d5438b2381df1a224c1
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 42f838b7e85aa0549adbbfd7b15e4e0d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Tests/Editor/JsonTests.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using NUnit.Framework;
5 | using SynapseGames.AssetBundle;
6 | using SynapseGames.AssetBundle.Json;
7 | using UnityEngine;
8 |
9 | public class JsonTests
10 | {
11 | public static readonly string SampleJson = @"[
12 | {
13 | ""name"": ""bundle-1"",
14 | ""hashes"": {
15 | ""Android"": ""982415458cdaf4e60c420f75fe6c8e8b"",
16 | ""IPhonePlayer"": ""bd7a32acc8931e77eb1174baff409f21"",
17 | ""WindowsPlayer"": ""5425682f11f1eeb1b0ac7e4580cd2237"",
18 | ""OSXPlayer"": ""4a8250d93de151328fe1651096ec8bc1"",
19 | ""WebGLPlayer"": ""bc341e351d4813630d417d33558a51ed""
20 | },
21 | ""dependencies"": []
22 | },
23 | {
24 | ""name"": ""bundle-10"",
25 | ""hashes"": {
26 | ""Android"": ""7de37f7cd9eca4b2a6c9216c6cda8d0c"",
27 | ""IPhonePlayer"": ""e3fe65f3b2b19e4d1550cd7210ca5e0d"",
28 | ""WindowsPlayer"": ""18a5260b52e33fa6e54ff67e14b6c979"",
29 | ""OSXPlayer"": ""25296e04f8fac4a875b4f774b7bea1ba"",
30 | ""WebGLPlayer"": ""7a9b673950467554cad5f25f0ec70da5""
31 | },
32 | ""dependencies"": [""bundle-1""]
33 | }
34 | ]";
35 |
36 | public static readonly string SampleJsonExtended = @"{
37 | ""metadata"": ""Some cool stuff over here"",
38 | ""bundle"": {
39 | ""name"": ""bundle-1"",
40 | ""hashes"": {
41 | ""Android"": ""982415458cdaf4e60c420f75fe6c8e8b"",
42 | ""IPhonePlayer"": ""bd7a32acc8931e77eb1174baff409f21"",
43 | ""WindowsPlayer"": ""5425682f11f1eeb1b0ac7e4580cd2237"",
44 | ""OSXPlayer"": ""4a8250d93de151328fe1651096ec8bc1"",
45 | ""WebGLPlayer"": ""bc341e351d4813630d417d33558a51ed""
46 | },
47 | ""dependencies"": []
48 | }
49 | }";
50 |
51 | public static readonly AssetBundleDescription[] SampleDescriptions = new AssetBundleDescription[]
52 | {
53 | new AssetBundleDescription(
54 | "bundle-1",
55 | new Dictionary()
56 | {
57 | { RuntimePlatform.Android, Hash128.Parse("982415458cdaf4e60c420f75fe6c8e8b") },
58 | { RuntimePlatform.IPhonePlayer, Hash128.Parse("bd7a32acc8931e77eb1174baff409f21") },
59 | { RuntimePlatform.WindowsPlayer, Hash128.Parse("5425682f11f1eeb1b0ac7e4580cd2237") },
60 | { RuntimePlatform.OSXPlayer, Hash128.Parse("4a8250d93de151328fe1651096ec8bc1") },
61 | { RuntimePlatform.WebGLPlayer, Hash128.Parse("bc341e351d4813630d417d33558a51ed") },
62 | },
63 | new HashSet()),
64 |
65 | new AssetBundleDescription(
66 | "bundle-10",
67 | new Dictionary()
68 | {
69 | { RuntimePlatform.Android, Hash128.Parse("7de37f7cd9eca4b2a6c9216c6cda8d0c") },
70 | { RuntimePlatform.IPhonePlayer, Hash128.Parse("e3fe65f3b2b19e4d1550cd7210ca5e0d") },
71 | { RuntimePlatform.WindowsPlayer, Hash128.Parse("18a5260b52e33fa6e54ff67e14b6c979") },
72 | { RuntimePlatform.OSXPlayer, Hash128.Parse("25296e04f8fac4a875b4f774b7bea1ba") },
73 | { RuntimePlatform.WebGLPlayer, Hash128.Parse("7a9b673950467554cad5f25f0ec70da5") },
74 | },
75 | new HashSet() { "bundle-1" }),
76 | };
77 |
78 | [Test]
79 | public void TestSerialize()
80 | {
81 | var json = JsonConvert.SerializeObject(SampleDescriptions, new AssetBundleDescriptionConverter());
82 |
83 | var data = JsonConvert.DeserializeObject(json);
84 | var bundle1Name = (string)data[0]["name"];
85 | var bundle2AndroidHash = (string)data[1]["hashes"]["Android"];
86 | var bundle2Dependency = (string)data[1]["dependencies"][0];
87 |
88 | Assert.AreEqual(bundle1Name, "bundle-1");
89 | Assert.AreEqual(bundle2AndroidHash, "7de37f7cd9eca4b2a6c9216c6cda8d0c");
90 | Assert.AreEqual(bundle2Dependency, "bundle-1");
91 | }
92 |
93 | [Test]
94 | public void TestDeserialize()
95 | {
96 | var descriptions = JsonConvert.DeserializeObject(SampleJson, new AssetBundleDescriptionConverter());
97 | Assert.AreEqual(descriptions, SampleDescriptions);
98 | }
99 |
100 | [Test]
101 | public void TestRoundTrip()
102 | {
103 | var json = JsonConvert.SerializeObject(SampleDescriptions, new AssetBundleDescriptionConverter());
104 | var descriptions = JsonConvert.DeserializeObject(json, new AssetBundleDescriptionConverter());
105 | Assert.AreEqual(descriptions, SampleDescriptions);
106 | }
107 |
108 | [Test]
109 | public void TestDeserializeExtended()
110 | {
111 | var description = JsonConvert.DeserializeObject(SampleJsonExtended, new AssetBundleDescriptionConverter());
112 |
113 | Assert.AreEqual("bundle-1", description.Bundle.Name);
114 | Assert.AreEqual("Some cool stuff over here", description.CustomData);
115 | Assert.AreEqual(Hash128.Parse("982415458cdaf4e60c420f75fe6c8e8b"), description.Bundle.GetHashForPlatform(RuntimePlatform.Android));
116 | }
117 | }
118 |
119 | public struct ExtendedBundleDescription
120 | {
121 | [JsonProperty("bundle")]
122 | public AssetBundleDescription Bundle;
123 |
124 | [JsonProperty("metadata")]
125 | public string CustomData;
126 | }
127 |
--------------------------------------------------------------------------------
/Tests/Editor/JsonTests.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c7b2adff63f7fac4abe70871cc7710b0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Tests/Editor/Unity.AssetBundleBuilder.Tests.Runtime.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Synapse.AssetBundleBuilder.Tests.Editor",
3 | "references": [
4 | "Synapse.AssetBundleBuilder.Runtime",
5 | "Synapse.AssetBundleBuilder.Runtime.Json"
6 | ],
7 | "optionalUnityReferences": [
8 | "TestAssemblies"
9 | ],
10 | "includePlatforms": [
11 | "Editor"
12 | ],
13 | "excludePlatforms": [],
14 | "allowUnsafeCode": false,
15 | "overrideReferences": false,
16 | "precompiledReferences": [],
17 | "autoReferenced": false,
18 | "defineConstraints": [
19 | "JSON_NET"
20 | ]
21 | }
--------------------------------------------------------------------------------
/Tests/Editor/Unity.AssetBundleBuilder.Tests.Runtime.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d85790538624c1f46ad84d6ad4de7681
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.synapse-games.asset-bundle-builder",
3 | "version": "0.2.3",
4 | "displayName": "Asset Bundle Builder",
5 | "description": "A system for automatically building, managing, and publishing asset bundles.",
6 | "unity": "2018.4",
7 | "author": {
8 | "name": "David LeGare",
9 | "email": "dlegare.1001@gmail.com",
10 | "url": "https://randompoison.github.io"
11 | },
12 | "dependencies": {
13 | "com.unity.editorcoroutines": "0.0.2-preview.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 91d94bf7e13e1f34b9566124d6dc57af
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------