feat: add Component move/copy

As usual, experimental API - subject to change.
This commit is contained in:
Oliver Booth 2023-04-07 01:21:56 +01:00
parent 75ac9e2d8f
commit 420ec2433a
No known key found for this signature in database
GPG Key ID: 20BEB9DC87961025
8 changed files with 852 additions and 2 deletions

View File

@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan<char>[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(uint[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan<char>[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(long[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan<char>[, IFormatProvider]])`. - X10D: Added `TextWriter.WriteLineNoAlloc(ulong[, ReadOnlySpan<char>[, IFormatProvider]])`.
- X10D.Unity: Added `Component.CopyTo(GameObject)` and `Component.MoveTo(GameObject)`.
- X10D.Unity: Added `GameObject.CopyComponent<T>(GameObject)` and `GameObject.MoveComponent<T>(GameObject)`.
### Changed ### Changed
- X10D: `DateTime.Age(DateTime)` and `DateTimeOffset.Age(DateTimeOffset)` parameter renamed from `asOf` to `referenceDate`. - X10D: `DateTime.Age(DateTime)` and `DateTimeOffset.Age(DateTimeOffset)` parameter renamed from `asOf` to `referenceDate`.

View File

@ -1,14 +1,77 @@
#nullable enable #nullable enable
using System;
using System.Collections; using System.Collections;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
using UnityEngine.TestTools; using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace X10D.Unity.Tests namespace X10D.Unity.Tests
{ {
public class ComponentTests public class ComponentTests
{ {
[UnityTest]
public IEnumerator CopyTo_ShouldCopyComponent_GivenComponent()
{
var source = new GameObject();
var sourceComponent = source.AddComponent<Rigidbody>();
sourceComponent.mass = 10.0f;
sourceComponent.useGravity = false;
var target = new GameObject();
sourceComponent.CopyTo(target);
Assert.That(target.TryGetComponent(out Rigidbody targetComponent));
Assert.That(targetComponent.mass, Is.EqualTo(10.0f));
Assert.That(targetComponent.useGravity, Is.False);
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyTo_ShouldThrowArgumentNullException_GivenNullComponent()
{
var target = new GameObject();
Rigidbody rigidbody = null!;
Assert.Throws<ArgumentNullException>(() => rigidbody.CopyTo(target));
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyTo_ShouldThrowArgumentNullException_GivenNullTarget()
{
var source = new GameObject();
var rigidbody = source.AddComponent<Rigidbody>();
GameObject target = null!;
Assert.Throws<ArgumentNullException>(() => rigidbody.CopyTo(target));
Object.Destroy(source);
yield break;
}
[UnityTest]
public IEnumerator CopyTo_ShouldThrowInvalidOperationException_GivenDuplicate()
{
var source = new GameObject();
var rigidbody = source.AddComponent<Rigidbody>();
var target = new GameObject();
target.AddComponent<Rigidbody>();
Assert.Throws<InvalidOperationException>(() => rigidbody.CopyTo(target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest] [UnityTest]
public IEnumerator GetComponentsInChildrenOnly_ShouldIgnoreParent() public IEnumerator GetComponentsInChildrenOnly_ShouldIgnoreParent()
{ {
@ -16,13 +79,80 @@ namespace X10D.Unity.Tests
var rigidbody = parent.AddComponent<Rigidbody>(); var rigidbody = parent.AddComponent<Rigidbody>();
var child = new GameObject(); var child = new GameObject();
child.transform.SetParent(parent.transform);
child.AddComponent<Rigidbody>(); child.AddComponent<Rigidbody>();
yield return null;
Rigidbody[] components = rigidbody.GetComponentsInChildrenOnly<Rigidbody>(); Rigidbody[] components = rigidbody.GetComponentsInChildrenOnly<Rigidbody>();
Assert.That(components.Length, Is.EqualTo(1)); Assert.That(components.Length, Is.EqualTo(1));
Assert.That(child, Is.EqualTo(components[0].gameObject)); Assert.That(child, Is.EqualTo(components[0].gameObject));
Object.Destroy(parent);
Object.Destroy(child);
}
[UnityTest]
public IEnumerator MoveTo_ShouldCopyComponent_GivenComponent()
{
var source = new GameObject();
var sourceComponent = source.AddComponent<Rigidbody>();
sourceComponent.mass = 10f;
sourceComponent.useGravity = false;
var target = new GameObject();
sourceComponent.MoveTo(target);
// effects of Destroy only take place at end of frame
yield return null;
Assert.That(sourceComponent == null);
Assert.That(source.TryGetComponent(out Rigidbody _), Is.False);
Assert.That(target.TryGetComponent(out Rigidbody targetComponent));
Assert.That(targetComponent.mass, Is.EqualTo(10.0f));
Assert.That(targetComponent.useGravity, Is.False);
Object.Destroy(source);
Object.Destroy(target);
}
[UnityTest]
public IEnumerator MoveTo_ShouldThrowArgumentNullException_GivenNullComponent()
{
var target = new GameObject();
Rigidbody rigidbody = null!;
Assert.Throws<ArgumentNullException>(() => rigidbody.MoveTo(target));
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator MoveTo_ShouldThrowArgumentNullException_GivenNullTarget()
{
var source = new GameObject();
var rigidbody = source.AddComponent<Rigidbody>();
GameObject target = null!;
Assert.Throws<ArgumentNullException>(() => rigidbody.MoveTo(target));
Object.Destroy(source);
yield break;
}
[UnityTest]
public IEnumerator MoveTo_ShouldThrowInvalidOperationException_GivenDuplicate()
{
var source = new GameObject();
var rigidbody = source.AddComponent<Rigidbody>();
var target = new GameObject();
target.AddComponent<Rigidbody>();
Assert.Throws<InvalidOperationException>(() => rigidbody.MoveTo(target));
Object.Destroy(source);
Object.Destroy(target);
yield break; yield break;
} }
} }

View File

@ -1,14 +1,107 @@
#nullable enable #nullable enable
using System;
using System.Collections; using System.Collections;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
using UnityEngine.TestTools; using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace X10D.Unity.Tests namespace X10D.Unity.Tests
{ {
public class GameObjectTests public class GameObjectTests
{ {
[UnityTest]
public IEnumerator CopyComponent_ShouldCopyComponent_GivenComponent()
{
var source = new GameObject();
var sourceComponent = source.AddComponent<Rigidbody>();
sourceComponent.mass = 10.0f;
sourceComponent.useGravity = false;
var target = new GameObject();
source.CopyComponent<Rigidbody>(target);
Assert.That(target.TryGetComponent(out Rigidbody targetComponent));
Assert.That(targetComponent.mass, Is.EqualTo(10.0f));
Assert.That(targetComponent.useGravity, Is.False);
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyComponent_ShouldThrowArgumentNullException_GivenNullComponentType()
{
var source = new GameObject();
var target = new GameObject();
Type componentType = null!;
Assert.Throws<ArgumentNullException>(() => source.CopyComponent(componentType, target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyComponent_ShouldThrowArgumentNullException_GivenNullGameObject()
{
var target = new GameObject();
GameObject source = null!;
Assert.Throws<ArgumentNullException>(() => source.CopyComponent<Rigidbody>(target));
Assert.Throws<ArgumentNullException>(() => source.CopyComponent(typeof(Rigidbody), target));
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyComponent_ShouldThrowArgumentNullException_GivenNullTarget()
{
var source = new GameObject();
GameObject target = null!;
Assert.Throws<ArgumentNullException>(() => source.CopyComponent<Rigidbody>(target));
Assert.Throws<ArgumentNullException>(() => source.CopyComponent(typeof(Rigidbody), target));
Object.Destroy(source);
yield break;
}
[UnityTest]
public IEnumerator CopyComponent_ShouldThrowInvalidOperationException_GivenInvalidComponent()
{
var source = new GameObject();
var target = new GameObject();
Assert.Throws<InvalidOperationException>(() => source.CopyComponent<Rigidbody>(target));
Assert.Throws<InvalidOperationException>(() => source.CopyComponent(typeof(Rigidbody), target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator CopyComponent_ShouldThrowInvalidOperationException_GivenDuplicate()
{
var source = new GameObject();
source.AddComponent<Rigidbody>();
var target = new GameObject();
target.AddComponent<Rigidbody>();
Assert.Throws<InvalidOperationException>(() => source.CopyComponent<Rigidbody>(target));
Assert.Throws<InvalidOperationException>(() => source.CopyComponent(typeof(Rigidbody), target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest] [UnityTest]
public IEnumerator GetComponentsInChildrenOnly_ShouldIgnoreParent() public IEnumerator GetComponentsInChildrenOnly_ShouldIgnoreParent()
{ {
@ -23,6 +116,8 @@ namespace X10D.Unity.Tests
Assert.That(components.Length, Is.EqualTo(1)); Assert.That(components.Length, Is.EqualTo(1));
Assert.That(child, Is.EqualTo(components[0].gameObject)); Assert.That(child, Is.EqualTo(components[0].gameObject));
Object.Destroy(parent);
Object.Destroy(child);
yield break; yield break;
} }
@ -58,6 +153,103 @@ namespace X10D.Unity.Tests
first.LookAt(Vector3.right); first.LookAt(Vector3.right);
Assert.That(firstTransform.rotation, Is.EqualTo(expected)); Assert.That(firstTransform.rotation, Is.EqualTo(expected));
Object.Destroy(first);
Object.Destroy(second);
yield break;
}
[UnityTest]
public IEnumerator MoveComponent_ShouldCopyComponent_GivenComponent()
{
var source = new GameObject();
var sourceComponent = source.AddComponent<Rigidbody>();
sourceComponent.mass = 10.0f;
sourceComponent.useGravity = false;
var target = new GameObject();
source.MoveComponent<Rigidbody>(target);
// effects of Destroy only take place at end of frame
yield return null;
Assert.That(sourceComponent == null);
Assert.That(source.TryGetComponent(out Rigidbody _), Is.False);
Assert.That(target.TryGetComponent(out Rigidbody targetComponent));
Assert.That(targetComponent.mass, Is.EqualTo(10.0f));
Assert.That(targetComponent.useGravity, Is.False);
Object.Destroy(source);
Object.Destroy(target);
}
[UnityTest]
public IEnumerator MoveComponent_ShouldThrowArgumentNullException_GivenNullComponentType()
{
var source = new GameObject();
var target = new GameObject();
Type componentType = null!;
Assert.Throws<ArgumentNullException>(() => source.MoveComponent(componentType, target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator MoveComponent_ShouldThrowArgumentNullException_GivenNullGameObject()
{
var target = new GameObject();
GameObject source = null!;
Assert.Throws<ArgumentNullException>(() => source.MoveComponent<Rigidbody>(target));
Assert.Throws<ArgumentNullException>(() => source.MoveComponent(typeof(Rigidbody), target));
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator MoveComponent_ShouldThrowArgumentNullException_GivenNullTarget()
{
var source = new GameObject();
GameObject target = null!;
Assert.Throws<ArgumentNullException>(() => source.MoveComponent<Rigidbody>(target));
Assert.Throws<ArgumentNullException>(() => source.MoveComponent(typeof(Rigidbody), target));
Object.Destroy(source);
yield break;
}
[UnityTest]
public IEnumerator MoveComponent_ShouldThrowInvalidOperationException_GivenInvalidComponent()
{
var source = new GameObject();
var target = new GameObject();
Assert.Throws<InvalidOperationException>(() => source.MoveComponent<Rigidbody>(target));
Assert.Throws<InvalidOperationException>(() => source.MoveComponent(typeof(Rigidbody), target));
Object.Destroy(source);
Object.Destroy(target);
yield break;
}
[UnityTest]
public IEnumerator MoveComponent_ShouldThrowInvalidOperationException_GivenDuplicate()
{
var source = new GameObject();
source.AddComponent<Rigidbody>();
var target = new GameObject();
target.AddComponent<Rigidbody>();
Assert.Throws<InvalidOperationException>(() => source.MoveComponent<Rigidbody>(target));
Assert.Throws<InvalidOperationException>(() => source.MoveComponent(typeof(Rigidbody), target));
Object.Destroy(source);
Object.Destroy(target);
yield break; yield break;
} }
@ -82,6 +274,9 @@ namespace X10D.Unity.Tests
Assert.That(child.layer, Is.EqualTo(layer)); Assert.That(child.layer, Is.EqualTo(layer));
Assert.That(grandChild.layer, Is.EqualTo(layer)); Assert.That(grandChild.layer, Is.EqualTo(layer));
Object.Destroy(parent);
Object.Destroy(child);
Object.Destroy(grandChild);
yield break; yield break;
} }
@ -103,6 +298,8 @@ namespace X10D.Unity.Tests
second.SetParent(first); second.SetParent(first);
Assert.That(second.transform.parent, Is.EqualTo(first.transform)); Assert.That(second.transform.parent, Is.EqualTo(first.transform));
Object.Destroy(first);
Object.Destroy(second);
yield break; yield break;
} }
} }

View File

@ -70,4 +70,19 @@
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Update="src\ExceptionMessages.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ExceptionMessages.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Compile Update="src\ExceptionMessages.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ExceptionMessages.resx</DependentUpon>
</Compile>
</ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,7 @@
using UnityEngine; using System.Globalization;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;
namespace X10D.Unity; namespace X10D.Unity;
@ -7,6 +10,95 @@ namespace X10D.Unity;
/// </summary> /// </summary>
public static class ComponentExtensions public static class ComponentExtensions
{ {
/// <summary>
/// Copies the component to another game object.
/// </summary>
/// <param name="component">The component to copy.</param>
/// <param name="target">The game object to which the component will be copied.</param>
/// <typeparam name="T">The type of the component to copy.</typeparam>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="component" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <paramref name="target" /> already has a component of type <typeparamref name="T" />.
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void CopyTo<T>(this T component, GameObject target)
where T : Component
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (target.TryGetComponent(out T targetComponent))
{
string message = ExceptionMessages.ComponentAlreadyExists;
message = string.Format(CultureInfo.CurrentCulture, message, target.name, typeof(T).Name);
throw new InvalidOperationException(message);
}
targetComponent = target.AddComponent<T>();
var typeInfo = typeof(T).GetTypeInfo();
CopyFields(typeInfo, component, targetComponent);
CopyProperties(typeInfo, component, targetComponent);
}
/// <summary>
/// Copies the component to another game object.
/// </summary>
/// <param name="component">The component to copy.</param>
/// <param name="target">The game object to which the component will be copied.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="component" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <paramref name="target" /> already has a component of the same type.
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void CopyTo(this Component component, GameObject target)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
var componentType = component.GetType();
if (target.TryGetComponent(componentType, out Component targetComponent))
{
string message = ExceptionMessages.ComponentAlreadyExists;
message = string.Format(CultureInfo.CurrentCulture, message, target.name, componentType.Name);
throw new InvalidOperationException(message);
}
targetComponent = target.AddComponent(componentType);
var typeInfo = componentType.GetTypeInfo();
CopyFields(typeInfo, component, targetComponent);
CopyProperties(typeInfo, component, targetComponent);
}
/// <summary> /// <summary>
/// Returns an array of components of the specified type, excluding components that live on the object to which this /// Returns an array of components of the specified type, excluding components that live on the object to which this
/// component is attached. /// component is attached.
@ -18,4 +110,124 @@ public static class ComponentExtensions
{ {
return component.gameObject.GetComponentsInChildrenOnly<T>(); return component.gameObject.GetComponentsInChildrenOnly<T>();
} }
/// <summary>
/// Moves the component to another game object.
/// </summary>
/// <param name="component">The component to move.</param>
/// <param name="target">The game object to which the component will be moved.</param>
/// <typeparam name="T">The type of the component to move.</typeparam>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="component" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <paramref name="target" /> already has a component of type <typeparamref name="T" />.
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void MoveTo<T>(this T component, GameObject target)
where T : Component
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
component.CopyTo(target);
Object.Destroy(component);
}
/// <summary>
/// Moves the component to another game object.
/// </summary>
/// <param name="component">The component to move.</param>
/// <param name="target">The game object to which the component will be moved.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="component" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <paramref name="target" /> already has a component of the same type.
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void MoveTo(this Component component, GameObject target)
{
if (component == null)
{
throw new ArgumentNullException(nameof(component));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
component.CopyTo(target);
Object.Destroy(component);
}
private static void CopyFields<T>(TypeInfo typeInfo, T component, T targetComponent)
where T : Component
{
foreach (FieldInfo field in typeInfo.DeclaredFields)
{
if (field.IsStatic)
{
continue;
}
object fieldValue = GetNewReferences(component, targetComponent, field.GetValue(component));
field.SetValue(targetComponent, fieldValue);
}
}
private static void CopyProperties<T>(TypeInfo typeInfo, T component, T targetComponent)
where T : Component
{
foreach (PropertyInfo property in typeInfo.DeclaredProperties)
{
if (!property.CanRead || !property.CanWrite)
{
continue;
}
MethodInfo getMethod = property.GetMethod;
MethodInfo setMethod = property.SetMethod;
if (getMethod.IsStatic || setMethod.IsStatic)
{
continue;
}
object propertyValue = GetNewReferences(component, targetComponent, property.GetValue(component));
property.SetValue(targetComponent, propertyValue);
}
}
private static object GetNewReferences<T>(T component, T targetComponent, object value)
where T : Component
{
if (ReferenceEquals(value, component))
{
value = targetComponent;
}
else if (ReferenceEquals(value, component.gameObject))
{
value = targetComponent.gameObject;
}
return value;
}
} }

View File

@ -0,0 +1,80 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace X10D.Unity {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class ExceptionMessages {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ExceptionMessages() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("X10D.Unity.src.ExceptionMessages", typeof(ExceptionMessages).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to The game object {0} already has a component of type {1}..
/// </summary>
internal static string ComponentAlreadyExists {
get {
return ResourceManager.GetString("ComponentAlreadyExists", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The game object {0} does not have a component of type {1}..
/// </summary>
internal static string ComponentDoesNotExist {
get {
return ResourceManager.GetString("ComponentDoesNotExist", resourceCulture);
}
}
}
}

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
</value>
</resheader>
<data name="ComponentDoesNotExist" xml:space="preserve">
<value>The game object {0} does not have a component of type {1}.</value>
</data>
<data name="ComponentAlreadyExists" xml:space="preserve">
<value>The game object {0} already has a component of type {1}.</value>
</data>
</root>

View File

@ -1,4 +1,6 @@
using System.Globalization;
using UnityEngine; using UnityEngine;
using Object = UnityEngine.Object;
namespace X10D.Unity; namespace X10D.Unity;
@ -7,6 +9,90 @@ namespace X10D.Unity;
/// </summary> /// </summary>
public static class GameObjectExtensions public static class GameObjectExtensions
{ {
/// <summary>
/// Copies the component of the specified type from one game object to another.
/// </summary>
/// <param name="gameObject">The game object from which to copy the component.</param>
/// <param name="target">The game object to which the component will be copied.</param>
/// <typeparam name="T">The type of the component to copy.</typeparam>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="gameObject" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <para><paramref name="gameObject" /> does not have a component of type <typeparamref name="T" />.</para>
/// -or-
/// <para><paramref name="target" /> already has a component of type <typeparamref name="T" />.</para>
/// </exception>
public static void CopyComponent<T>(this GameObject gameObject, GameObject target)
where T : Component
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (!gameObject.TryGetComponent(out T sourceComponent))
{
string message = ExceptionMessages.ComponentDoesNotExist;
message = string.Format(CultureInfo.CurrentCulture, message, gameObject.name, typeof(T).Name);
throw new InvalidOperationException(message);
}
sourceComponent.CopyTo(target);
}
/// <summary>
/// Copies the component of the specified type from one game object to another.
/// </summary>
/// <param name="gameObject">The game object from which to copy the component.</param>
/// <param name="componentType">The type of the component to copy.</param>
/// <param name="target">The game object to which the component will be copied.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="gameObject" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <para><paramref name="gameObject" /> does not have a component of type <paramref name="componentType" />.</para>
/// -or-
/// <para><paramref name="target" /> already has a component of type <paramref name="componentType" />.</para>
/// </exception>
public static void CopyComponent(this GameObject gameObject, Type componentType, GameObject target)
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
}
if (componentType is null)
{
throw new ArgumentNullException(nameof(componentType));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (!gameObject.TryGetComponent(componentType, out Component sourceComponent))
{
string message = ExceptionMessages.ComponentDoesNotExist;
message = string.Format(CultureInfo.CurrentCulture, message, gameObject.name, componentType.Name);
throw new InvalidOperationException(message);
}
sourceComponent.CopyTo(target);
}
/// <summary> /// <summary>
/// Returns an array of components of the specified type, excluding components that live on this game object. /// Returns an array of components of the specified type, excluding components that live on this game object.
/// </summary> /// </summary>
@ -171,6 +257,100 @@ public static class GameObjectExtensions
gameObject.transform.LookAt(target, worldUp); gameObject.transform.LookAt(target, worldUp);
} }
/// <summary>
/// Moves the component of the specified type from one game object to another.
/// </summary>
/// <param name="gameObject">The game object from which to move the component.</param>
/// <param name="target">The game object to which the component will be moved.</param>
/// <typeparam name="T">The type of the component to copy.</typeparam>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="gameObject" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <para><paramref name="gameObject" /> does not have a component of type <typeparamref name="T" />.</para>
/// -or-
/// <para><paramref name="target" /> already has a component of type <typeparamref name="T" />.</para>
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void MoveComponent<T>(this GameObject gameObject, GameObject target)
where T : Component
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (!gameObject.TryGetComponent(out T sourceComponent))
{
string message = ExceptionMessages.ComponentDoesNotExist;
message = string.Format(CultureInfo.CurrentCulture, message, gameObject.name, typeof(T).Name);
throw new InvalidOperationException(message);
}
sourceComponent.MoveTo(target);
Object.Destroy(sourceComponent);
}
/// <summary>
/// Moves the component of the specified type from one game object to another.
/// </summary>
/// <param name="gameObject">The game object from which to move the component.</param>
/// <param name="componentType">The type of the component to copy.</param>
/// <param name="target">The game object to which the component will be moved.</param>
/// <exception cref="ArgumentNullException">
/// <para><paramref name="gameObject" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="componentType" /> is <see langword="null" />.</para>
/// -or-
/// <para><paramref name="target" /> is <see langword="null" />.</para>
/// </exception>
/// <exception cref="InvalidOperationException">
/// <para><paramref name="gameObject" /> does not have a component of type <paramref name="componentType" />.</para>
/// -or-
/// <para><paramref name="target" /> already has a component of type <paramref name="componentType" />.</para>
/// </exception>
/// <remarks>
/// This method will destroy the component on the source game object, creating a new instance on the target. Use with
/// caution.
/// </remarks>
public static void MoveComponent(this GameObject gameObject, Type componentType, GameObject target)
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
}
if (componentType is null)
{
throw new ArgumentNullException(nameof(componentType));
}
if (target == null)
{
throw new ArgumentNullException(nameof(target));
}
if (!gameObject.TryGetComponent(componentType, out Component sourceComponent))
{
string message = ExceptionMessages.ComponentDoesNotExist;
message = string.Format(CultureInfo.CurrentCulture, message, gameObject.name, componentType.Name);
throw new InvalidOperationException(message);
}
sourceComponent.MoveTo(target);
Object.Destroy(sourceComponent);
}
/// <summary> /// <summary>
/// Sets the new layer of this game object and its children, recursively. /// Sets the new layer of this game object and its children, recursively.
/// </summary> /// </summary>