-
Notifications
You must be signed in to change notification settings - Fork 0
Unity Integration
Yaroslav Sarchuk edited this page Aug 29, 2025
·
1 revision
This guide covers Unity-specific features and best practices for using the Atomic Plugin with Unity projects.
- Unity Version: 2020.3 LTS or newer
- Atomic Framework: Installed via Package Manager
- Rider Integration: Unity configured to use Rider as external editor
- Open Unity Package Manager (
WindowโPackage Manager) - Click + โ Add package from git URL
- Enter:
https://github.com/StarKRE22/Atomic.git - Click Add
- Download Atomic Framework from GitHub
- Extract to
Assets/Plugins/Atomic/ - Unity will automatically import the framework
- Go to
EditโPreferencesโExternal Tools - Set External Script Editor to JetBrains Rider
- Enable Generate .csproj files for:
- Embedded packages
- Local packages
- Built-in packages
Assets/
โโโ Atomic/ # Atomic Framework (if manual install)
โโโ Scripts/
โ โโโ AtomicDefinitions/ # Your .atomic files
โ โ โโโ Entities/
โ โ โโโ Components/
โ โ โโโ Systems/
โ โโโ Generated/ # Generated extension methods
โ โ โโโ Entities/
โ โ โโโ Components/
โ โ โโโ Systems/
โ โโโ Game/ # Your game logic
โโโ Prefabs/
โ โโโ Entities/ # Entity prefabs
โโโ Resources/
โโโ AtomicConfigs/ # Runtime configurations
Create assembly definitions for better compilation:
Scripts/AtomicDefinitions/AtomicDefinitions.asmdef:
{
"name": "AtomicDefinitions",
"rootNamespace": "Game.Atomic",
"references": [
"Atomic.Framework"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}The plugin recognizes Unity types automatically:
entityType: "IEntity"
namespace: "Game.Components"
className: "UnityComponentExtensions"
imports:
UnityEngine
UnityEngine.UI
TMPro
values:
# Unity Components
Transform: Transform
Rigidbody: Rigidbody
Collider: Collider
Animator: Animator
# UI Components
Canvas: Canvas
Button: Button
TextMeshProUGUI: TextMeshProUGUI
# Unity Types
Position: Vector3
Rotation: Quaternion
Velocity: Vector3
Color: Color
Sprite: Sprite
Material: Material
# Collections
Waypoints: List<Vector3>
Targets: Transform[]
using UnityEngine;
using Atomic.Entities;
using Game.Components;
public class EntityBehaviour : MonoBehaviour
{
private IEntity entity;
public IEntity Entity => entity;
void Awake()
{
// Create or get entity
entity = new Entity();
// Bind Unity components
entity.SetTransform(transform);
entity.SetRigidbody(GetComponent<Rigidbody>());
entity.SetAnimator(GetComponent<Animator>());
}
void Start()
{
// Initialize entity values
entity.SetPosition(transform.position);
entity.SetRotation(transform.rotation);
}
void Update()
{
// Sync Unity transform with entity
if (entity.TryGetPosition(out var pos))
{
transform.position = pos;
}
}
}using UnityEngine;
using System.Collections.Generic;
using Atomic.Entities;
public class MovementSystem : MonoBehaviour
{
private List<IEntity> movableEntities = new List<IEntity>();
public void RegisterEntity(IEntity entity)
{
if (entity.HasPosition() && entity.HasVelocity())
{
movableEntities.Add(entity);
}
}
void FixedUpdate()
{
float deltaTime = Time.fixedDeltaTime;
foreach (var entity in movableEntities)
{
if (entity.TryGetVelocity(out var velocity) &&
entity.TryGetPosition(out var position))
{
// Update position based on velocity
position += velocity * deltaTime;
entity.SetPosition(position);
// Update Unity transform
if (entity.TryGetTransform(out var transform))
{
transform.position = position;
}
}
}
}
}- Create GameObject with
EntityBehaviour - Configure components
- Save as prefab
- Use for spawning:
public class EntitySpawner : MonoBehaviour
{
public GameObject entityPrefab;
public IEntity SpawnEntity(Vector3 position)
{
var go = Instantiate(entityPrefab, position, Quaternion.identity);
var entityBehaviour = go.GetComponent<EntityBehaviour>();
// Configure spawned entity
var entity = entityBehaviour.Entity;
entity.SetHealth(100);
entity.AddEnemyTag();
return entity;
}
}[CreateAssetMenu(fileName = "EntityConfig", menuName = "Atomic/Entity Config")]
public class EntityConfig : ScriptableObject
{
public int maxHealth = 100;
public float moveSpeed = 5f;
public float attackDamage = 10f;
public void ApplyToEntity(IEntity entity)
{
entity.SetMaxHealth(maxHealth);
entity.SetMoveSpeed(moveSpeed);
entity.SetDamage(attackDamage);
}
}using UnityEngine;
using UnityEngine.Events;
using Atomic.Entities;
public class HealthComponent : MonoBehaviour
{
private IEntity entity;
[System.Serializable]
public class HealthEvent : UnityEvent<int, int> { }
public HealthEvent OnHealthChanged;
public UnityEvent OnDeath;
void Start()
{
entity = GetComponent<EntityBehaviour>().Entity;
entity.SetHealth(100);
}
public void TakeDamage(int damage)
{
int currentHealth = entity.GetHealth();
int newHealth = Mathf.Max(0, currentHealth - damage);
entity.SetHealth(newHealth);
OnHealthChanged?.Invoke(newHealth, entity.GetMaxHealth());
if (newHealth <= 0)
{
entity.AddDeadTag();
OnDeath?.Invoke();
}
}
}public class EntityPool : MonoBehaviour
{
private Queue<IEntity> pool = new Queue<IEntity>();
private Queue<GameObject> gameObjectPool = new Queue<GameObject>();
public GameObject entityPrefab;
public int poolSize = 50;
void Start()
{
for (int i = 0; i < poolSize; i++)
{
var go = Instantiate(entityPrefab);
go.SetActive(false);
var entity = go.GetComponent<EntityBehaviour>().Entity;
pool.Enqueue(entity);
gameObjectPool.Enqueue(go);
}
}
public IEntity GetEntity()
{
if (pool.Count > 0)
{
var entity = pool.Dequeue();
var go = gameObjectPool.Dequeue();
go.SetActive(true);
ResetEntity(entity);
return entity;
}
// Create new if pool is empty
return CreateNewEntity();
}
public void ReturnEntity(IEntity entity)
{
if (entity.TryGetTransform(out var transform))
{
transform.gameObject.SetActive(false);
pool.Enqueue(entity);
gameObjectPool.Enqueue(transform.gameObject);
}
}
}When using Unity's Job System:
using Unity.Burst;
using Unity.Jobs;
using Unity.Collections;
using Unity.Mathematics;
[BurstCompile]
public struct MoveEntitiesJob : IJobParallelFor
{
public NativeArray<float3> positions;
public NativeArray<float3> velocities;
public float deltaTime;
public void Execute(int index)
{
positions[index] += velocities[index] * deltaTime;
}
}using UnityEngine.Profiling;
public class EntitySystemProfiler : MonoBehaviour
{
void Update()
{
Profiler.BeginSample("Entity Update");
UpdateEntities();
Profiler.EndSample();
Profiler.BeginSample("Entity Physics");
UpdatePhysics();
Profiler.EndSample();
}
}#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(EntityBehaviour))]
public class EntityBehaviourEditor : Editor
{
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
var behaviour = (EntityBehaviour)target;
if (behaviour.Entity == null) return;
EditorGUILayout.Space();
EditorGUILayout.LabelField("Entity Values", EditorStyles.boldLabel);
if (behaviour.Entity.TryGetHealth(out int health))
{
EditorGUILayout.IntField("Health", health);
}
if (behaviour.Entity.TryGetPosition(out var pos))
{
EditorGUILayout.Vector3Field("Position", pos);
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Entity Tags", EditorStyles.boldLabel);
EditorGUILayout.Toggle("Player", behaviour.Entity.HasPlayerTag());
EditorGUILayout.Toggle("Enemy", behaviour.Entity.HasEnemyTag());
EditorGUILayout.Toggle("Dead", behaviour.Entity.HasDeadTag());
}
}
#endifpublic class EntityDebugger : MonoBehaviour
{
public bool showEntityInfo = true;
public Color healthBarColor = Color.green;
void OnDrawGizmos()
{
if (!showEntityInfo) return;
var entity = GetComponent<EntityBehaviour>()?.Entity;
if (entity == null) return;
// Draw health bar
if (entity.TryGetHealth(out int health) &&
entity.TryGetMaxHealth(out int maxHealth))
{
var healthPercent = (float)health / maxHealth;
Gizmos.color = healthBarColor;
Gizmos.DrawLine(
transform.position + Vector3.up * 2,
transform.position + Vector3.up * 2 + Vector3.right * healthPercent
);
}
// Draw velocity vector
if (entity.TryGetVelocity(out var velocity))
{
Gizmos.color = Color.blue;
Gizmos.DrawRay(transform.position, velocity);
}
}
}# Mobile-optimized configuration
entityType: "IEntity"
namespace: "Game.Mobile"
aggressiveInlining: true # Important for mobile performance
unsafe: false # Some mobile platforms don't support unsafe
imports:
UnityEngine
#if UNITY_IOS
UnityEngine.iOS
#endif
Add to link.xml:
<linker>
<assembly fullname="Game.Generated" preserve="all"/>
<assembly fullname="Atomic.Framework" preserve="all"/>
</linker>using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerInputHandler : MonoBehaviour
{
private IEntity playerEntity;
private PlayerInput playerInput;
void Awake()
{
playerEntity = GetComponent<EntityBehaviour>().Entity;
playerInput = GetComponent<PlayerInput>();
}
public void OnMove(InputValue value)
{
Vector2 input = value.Get<Vector2>();
Vector3 movement = new Vector3(input.x, 0, input.y);
playerEntity.SetVelocity(movement * playerEntity.GetMoveSpeed());
}
public void OnFire()
{
if (!playerEntity.HasAttackingTag())
{
playerEntity.AddAttackingTag();
StartCoroutine(PerformAttack());
}
}
}public class EntityAnimationController : MonoBehaviour
{
private IEntity entity;
private Animator animator;
void Start()
{
entity = GetComponent<EntityBehaviour>().Entity;
animator = GetComponent<Animator>();
entity.SetAnimator(animator);
}
void Update()
{
// Sync animation parameters with entity state
if (entity.TryGetVelocity(out var velocity))
{
animator.SetFloat("Speed", velocity.magnitude);
}
animator.SetBool("IsAttacking", entity.HasAttackingTag());
animator.SetBool("IsDead", entity.HasDeadTag());
}
}Generated code not found in Unity
- Ensure generation directory exists
- Check assembly definition references
- Refresh Unity asset database (
Ctrl+R)
Type not recognized in .atomic file
- Add proper Unity namespace to imports
- Use fully qualified type names
- Check assembly references
Performance issues in Play Mode
- Enable aggressive inlining
- Use object pooling
- Profile with Unity Profiler
Need Help? Check Troubleshooting or report Unity-specific issues on GitHub.