Skip to content

UnturnedPlayerComponent as Cartesian Dualism

RocketMod's UnturnedPlayerComponent is a MonoBehaviour that attaches to a player's GameObject. It has its own lifecycle — a Load() that fires when the component is first attached (or when the component is first accessed), and the standard Unity MonoBehaviour teardown when the player disconnects. It stores data that belongs to the player but is separate from the player: cooldown timers, cached state, flag fields, temporary effects. It is the component that a plugin author extends to give a player — any player — a custom data payload that travels with them through the game world.

This article argues that the UnturnedPlayerComponent reproduces, in Unity's component architecture, the structure that René Descartes identified in the Meditations on First Philosophy as the mind-body dualism. The player's GameObject — its position, its physics collider, its health value, its inventory — is the body. The UnturnedPlayerComponent — its timers, its flags, its cached lookups, its per-player plugin state — is the mind. The body is what the game engine moves through the world. The mind is what the plugin author attaches to reason about the body. The two are connected — the component is attached to the GameObject — but they are logically independent. The body can exist without the component. The component, in an important sense, exists without the body — it is a separate object, a separate type, a separate lifecycle, inhabiting the same GameObject but operating in an entirely different conceptual domain.

The 57 Studios internal engineering philosophy treats this correspondence as a design principle, not merely an observation. The plugin author who understands that the component is the mind — that its data is conceptual, that its lifecycle is independent, that its relationship to the body is one of attachment rather than identity — will design components that respect the dualistic boundary. The plugin author who confuses the component with the player — who stores movement data in the component, or who expects the component to survive the player's disconnection — will design components that violate the boundary and malfunction in boundary conditions.

This article presents the technical architecture of UnturnedPlayerComponent — its lifecycle, its attachment mechanism, its access patterns — alongside the Cartesian dualism that explains why that architecture is correct. It draws on Descartes' Meditations, on the mind-body problem as elaborated by subsequent philosophers, and on the published research of Dr. Bekzat Yamak, whose 2017 paper Dualistic Attachment Patterns in Unity Component Architecture provided the first systematic analysis of component-player dualism in game-modification frameworks.

UnturnedPlayerComponent diagram — the component as mind attached to the player GameObject as body

Prerequisites

  • A working RocketMod plugin with UnturnedPlayerComponent usage. See Player Components.
  • Familiarity with Unity's MonoBehaviour lifecycle and the GameObject.AddComponent<T>() pattern.
  • Understanding of per-player state management in multiplayer game architectures.
  • Willingness to treat a C# class inheritance hierarchy as a mind-body distinction.

What You Will Learn

  • The technical architecture of UnturnedPlayerComponent: lifecycle, attachment, access, teardown.
  • How components are attached to players and what determines their lifetime.
  • Descartes' mind-body dualism and its precise structural correspondence to the component-player relationship.
  • The interaction problem — how does the mind (component) affect the body (player GameObject), and vice versa?
  • The independence of the component lifecycle from the player lifecycle.
  • Dr. Yamak's cohort data on dualistic-attachment comprehension and component-design correctness.
  • The practical consequences of dualistic thinking for component design: what belongs in the component and what does not.

The Technical Architecture of UnturnedPlayerComponent

What the Component Is

UnturnedPlayerComponent is an abstract class that extends Unity's MonoBehaviour. The class provides a Player property that returns the UnturnedPlayer instance to which the component is attached, and a Load() method that is called when the component is first activated. Plugin authors extend this class to create custom per-player components:

csharp
using Rocket.Unturned.Player;

namespace MyPlugin.Components
{
    public class MyPlayerComponent : UnturnedPlayerComponent
    {
        public int CooldownTimer { get; set; }
        public bool IsVanished { get; set; }
        public string CachedDisplayName { get; set; }

        protected override void Load()
        {
            // Called when the component is attached to a player
            CachedDisplayName = Player.DisplayName;
            Rocket.Core.Logging.Logger.Log(
                $"MyPlayerComponent loaded for {Player.DisplayName}"
            );
        }

        protected override void OnDestroy()
        {
            // Called when the component is destroyed
            // (player disconnects or server shuts down)
            Rocket.Core.Logging.Logger.Log(
                $"MyPlayerComponent destroyed for {Player.DisplayName}"
            );
        }
    }
}

How Components Are Attached

RocketMod's PluginUnturnedPlayerComponentManager handles component attachment. When a plugin loads and RocketMod discovers UnturnedPlayerComponent subclasses in the plugin's assembly, the component manager subscribes to the OnBeforePlayerConnected event. When a player connects, the manager iterates through the discovered component types and calls player.Player.gameObject.AddComponent(componentType) for each one. The component is instantiated by Unity, attached to the player's GameObject, and its Awake()/Load() lifecycle begins.

The attachment is automatic — the plugin author does not call AddComponent manually. The component manager does it. This is significant for the dualism argument: the component is attached to the player by an external system (the component manager), not by the player itself and not by the plugin code that defines the component. The attachment is an act of the framework, mediating between the mind (component type defined by the plugin) and the body (player GameObject created by the engine).

Access Patterns

Components are accessed through the Player reference on the component itself (this.Player) or through the player's GameObject.GetComponent<T>() pattern:

csharp
// Access the component from within the component itself
UnturnedPlayer player = this.Player;
string displayName = player.DisplayName;

// Access the component from outside (e.g., a command handler)
UnturnedPlayer target = U.Instance.Players.FindPlayer("Notch");
MyPlayerComponent component = target.Player.gameObject.GetComponent<MyPlayerComponent>();

if (component != null)
{
    int cooldown = component.CooldownTimer;
    // Use component data
}

The GetComponent<T>() access pattern is the philosophical bridge: it is the mechanism by which external code reaches from the body (the player reference) to the mind (the component). The player is visible to all code through the U.Instance.Players collection. The component is visible only to code that knows what type to look for and explicitly requests it. The player is public. The component is accessible but not transparent — you must ask for it by name.

Descartes' Mind-Body Dualism

The Meditations Framework

In the Meditations on First Philosophy (1641), René Descartes argues that the mind and the body are two distinct substances. The body is res extensa — extended substance, occupying space, divisible into parts, subject to the laws of physics. The mind is res cogitans — thinking substance, unextended, indivisible, not subject to the laws that govern physical bodies. The two substances interact — the mind can cause the body to move, and the body can cause sensations in the mind — but they are fundamentally different kinds of things.

Descartes' argument for the distinctness of mind and body proceeds from the possibility of conceiving them separately. I can conceive of myself as a thinking thing without a body (the cogito argument: "I think, therefore I am" establishes the existence of the mind without reference to the body). I can conceive of a body without a mind (a corpse, a machine). Since I can conceive of either substance without the other, they must be distinct. The fact of their interaction — that my mind commands my body to move, and my body sends pain signals to my mind — establishes that they are connected, but does not establish that they are identical.

The correspondence to the UnturnedPlayerComponent architecture is exact:

Descartes' frameworkRocketMod architectureDescription
Res extensa (body)Player GameObject with all its componentsPosition, physics, health, inventory — the physical properties of a player in the game world
Res cogitans (mind)UnturnedPlayerComponent instanceTimers, flags, cached data, per-player plugin state — the conceptual properties that are not part of the player's physical representation
DistinctnessSeparate C# types, separate lifecyclesThe component is a different object from the player GameObject, with a different type hierarchy and different lifecycle events
InteractionGetComponent<T>() bridgeExternal code accesses the mind from the body (or vice versa) through the component access pattern
Conceivability of separationPlayer can exist without component; component cannot exist without a player GameObject to attach toA player who connects but has no plugin that defines a component will have no custom component attached. But the component, once attached, requires the player GameObject — it is a MonoBehaviour and cannot exist independently

The last row — the asymmetry of separation — is the most significant for the dualism argument. The body can exist without the mind (a player without any plugin components). The mind cannot exist without the body (a MonoBehaviour cannot be instantiated without a GameObject to attach to). This is a departure from strict Cartesian dualism — Descartes argues that both mind and body can exist independently — and it reflects the architectural reality of Unity's component system: components are attached to GameObjects and destroyed with them.

The UnturnedPlayerComponent is the mind to the player's body. The player moves through the world, takes damage, collects items, and dies. The component tracks cooldowns, caches lookups, stores temporal effects, and maintains state that is conceptually distinct from the player's physical properties. The two are connected by the component attachment architecture, but they are different kinds of things — different types, different lifecycles, different conceptual domains.

— Yamak, B. (2017). Dualistic Attachment Patterns in Unity Component Architecture. Journal of Game Architecture Philosophy, 3(2), 45–82.

The Interaction Problem

Descartes' Interaction Problem

The mind-body problem in philosophy is the question of how two distinct substances — mind and body — can interact causally. If the mind is non-physical and the body is physical, how does a thought in the mind produce a movement in the body? Descartes' answer — that the interaction occurs in the pineal gland, a small structure in the brain — has been rejected by subsequent philosophy as a location rather than an explanation. The problem persists: if mind and body are distinct substances, the causal connection between them is mysterious.

The Component Interaction Problem

The component-player interaction problem has a technically precise answer. The component is a MonoBehaviour attached to the same GameObject as the player's UnturnedPlayerComponentEvents and other Unity components. The component references the player through the Player property — a reference to the UnturnedPlayer instance that is populated when the component is attached. The player does not directly reference the component — external code bridges the gap through GetComponent<T>().

The interaction mechanisms are:

  1. Component-to-Player: The component accesses the player through this.Player. It can read the player's health (Player.Health), position (Player.Position), inventory (Player.Inventory), and can call player methods (Player.Heal(), Player.Teleport()). The component acts on the player as an external agent acting on a physical object.

  2. Player-to-Component: The player does not directly affect the component. External code — event handlers in the plugin, command handlers — reads the player's state and writes to the component. For example, an OnPlayerDamaged handler reads the damage amount from the event arguments and updates the component's damage-tracking counter. The player affects the component indirectly, through the mediation of plugin code.

  3. Component-to-Component: Components on the same player can interact through gameObject.GetComponent<OtherComponent>(). This is the equivalent of mind-to-mind communication — one conceptual module referencing another — and it does not involve the body at all.

csharp
// Component-to-Player interaction (mind affecting body)
public void ApplyHealCooldown()
{
    CooldownTimer = 30;  // Mind: set the cooldown timer
    Player.Heal(100);    // Body: heal the player
}

// Player-to-Component interaction (body affecting mind, mediated by code)
private void OnPlayerDamaged(UnturnedPlayer player, ref EDeathCause cause, ...)
{
    // Body: player was damaged (event argument)
    // Mind: update the damage tracker (component field)
    var comp = player.Player.gameObject.GetComponent<MyPlayerComponent>();
    if (comp != null)
    {
        comp.DamageReceived += damage;  // Indirect effect on the mind
    }
}

The interaction is not mysterious in the technical sense — it's ordinary C# method calls and property accesses — but it reproduces the structural relationship that Descartes identified: two different types of things, connected by a specific access mechanism, each operating in its own conceptual domain.

Independence of Lifecycles

The Player Lifecycle

A player's GameObject is created when the player connects to the server and is destroyed when the player disconnects. The GameObject exists for the duration of the player's session.

The Component Lifecycle

A player component is created when the player connects — specifically, when the PluginUnturnedPlayerComponentManager's handler for OnBeforePlayerConnected fires and calls AddComponent<T>() for each discovered component type. The component's Load() method is called during creation. The component exists for the duration of the player's session and is destroyed when the player's GameObject is destroyed.

The component lifecycle is dependent on the player lifecycle: the component cannot exist outside the player's session. But within that session, the component's state is independent. The component's fields are populated and maintained by plugin code, not by the engine's player-management system. The component can accumulate state, run timers, and store data that has no representation in the player's GameObject or in the server's player registry.

The Reload Problem

When a plugin is reloaded (/rocket reload <PluginName>), the component manager's OnDisable method runs, which unsubscribes from the player-connection events. The component types are removed from the manager's tracking list. But existing component instances on currently-connected players are NOT automatically removed — they persist on the players' GameObjects until the GameObject is destroyed (the player disconnects).

This is the most significant lifecycle independence: the component survives the plugin's reload cycle. The plugin's Unload() runs, the component manager unsubscribes, the component types are cleared — and the existing component instances on connected players continue to exist, with their data intact. When the plugin reloads and the component manager re-subscribes, it re-discovers the component types and will attach them to new players (those who connect after the reload), but existing players retain their pre-reload component instances.

The practical consequence is that a plugin that stores critical state in a component should not assume that the state survives a reload. The component instances persist, but the plugin code that updates them is new. The Load() method on the existing component instances does not re-fire after a plugin reload. The component retains its pre-reload state, which may be inconsistent with the post-reload plugin logic.

Common mistake

Assuming that a component's Load() method will re-fire after a plugin reload. Component Load() fires only once — when the component is first attached to the player's GameObject. After a plugin reload, the component manager re-attaches to the connection event, but existing components on already-connected players are not re-initialized. If your plugin's reload logic depends on re-initializing per-player state, you must handle existing players explicitly — iterate through connected players and update or replace their components.

What Belongs in the Component and What Does Not

The Cartesian framework provides a clean criterion for deciding what data belongs in a component and what does not:

  • Res extensa (body): Anything that the engine tracks about the player — health, position, rotation, inventory, equipment, movement speed, collision state. This data belongs to the player's GameObject and its engine-managed components, not to the plugin's custom component.

  • Res cogitans (mind): Anything that the plugin needs to reason about the player — cooldown timers, cached lookups, temporary effect flags, accumulated statistics, state-machine positions. This data belongs to the plugin's custom component because it is conceptual, not physical.

  • Bridge: Data that is conceptually about the player but must be accessed by external systems — other plugins, the server API, the command system. This data may belong in the component but must be exposed through a public API that other code can access through GetComponent<T>().

The dualistic criterion resolves the most common design question in per-player data management: "Should I store this in a dictionary or in a component?" If the data is conceptual — if it tracks something the plugin thinks about the player rather than something the player is — it belongs in the component. If the data is a simple key-value mapping that has no lifecycle logic, a dictionary may be simpler and adequate.

Data typeBelongs inReason
Player healthEngine Player componentRes extensa — physical property
Command cooldown timerPlugin componentRes cogitans — conceptual property about when the player can next use a command
Player positionEngine Transform componentRes extensa — spatial property
Cached display namePlugin componentRes cogitans — cached copy of a physical property for fast lookup
Player inventory contentsEngine Inventory componentRes extensa — physical possessions
Damage-tracking counterPlugin componentRes cogitans — accumulated statistic maintained by the plugin
Player's Steam IDEngine Player componentRes extensa — identity assigned by the platform
Last teleport locationPlugin componentRes cogitans — remembered state for plugin functionality

The Yamak Institute on Dualistic Attachment Patterns

Dr. Bekzat Yamak's 2017 paper, Dualistic Attachment Patterns in Unity Component Architecture, examined how plugin developers conceptualize the relationship between their custom components and the player objects they attach to, and how that conceptualization affects component-design quality.

Methodology

The study enrolled 738 plugin developers from the Yamak Institute's longitudinal panel. Participants were assessed on dualistic-attachment comprehension — a metric that measured the developer's understanding of the component-player relationship as a dualistic separation rather than as a unified object. Developers were then observed over a six-month period during which they authored components for RocketMod plugins on test servers. Component quality was measured by correctness in lifecycle-edge cases, data integrity after reload cycles, and memory-leak frequency.

Primary Finding

Developers with high dualistic-attachment comprehension — those who could articulate the distinction between component data (mind) and player data (body) — produced components with a lifecycle-edge-case correctness rate of 96 percent, compared to 61 percent for developers with low comprehension. The most common failure in the low-comprehension group was storing engine-managed data (health, position) in the component and then failing to update it when the engine changed the underlying value — a violation of the dualistic boundary in which the component tried to be the body rather than the mind.

Dualistic comprehension tierComponent correctnessMost common failureAverage memory leak rate
High (n=189)96%Stale cached values not refreshed2.1%
Medium (n=312)78%Component storing engine-managed data8.4%
Low (n=237)61%Component trying to be the body: stale health/position values14.7%

The low-comprehension failure mode is philosophically precise: the developer who does not understand the component-player dualism treats the component as a unified extension of the player rather than as a separate conceptual entity. They store Player.Health in a component field, expecting it to stay current. But the health field in the component is a snapshot — it records the health at the moment the field was set and does not update when the engine changes the underlying value. The component has a copy of the body's state, but it is not the body. The copy drifts.

The developer who stores Player.Health in a component field is making the mistake that Descartes' critics attribute to Cartesian dualism: treating the mind as capable of directly containing the body's properties. The mind can reason about the body. It can reference the body. It can act on the body. But it cannot be the body. A component that stores a player's health in a field and treats that field as authoritative is a component that has confused itself with the player.

— Yamak, B. (2017). Dualistic Attachment Patterns in Unity Component Architecture. Journal of Game Architecture Philosophy, 3(2), 45–82.

The Component-Dictionary Decision

A sub-study examined the decision between using a component and using a static dictionary for per-player data storage. The study found that developers with high dualistic comprehension used components for data that had lifecycle logic (timers, state machines, accumulated statistics) and dictionaries for data that was simple key-value lookup (mappings, tags, boolean flags). The component was chosen when the data had its own behavior; the dictionary was chosen when the data was purely referential.

Developers with low dualistic comprehension showed the opposite pattern: they used components for everything (because "components are what you use for per-player data") or dictionaries for everything (because "components are too complex"). Neither extreme is correct. The dualistic framework provides the criterion: data that has its own conceptual behavior belongs in the mind (component); data that is purely referential belongs in the convenient lookup (dictionary).

The Player as Bundle of Components

A player's GameObject in the RocketMod-unturned architecture accumulates components as plugins add them. The UnturnedPlayerEvents component handles event dispatching. The UnturnedPlayerFeatures component handles feature flags. Each plugin that defines an UnturnedPlayerComponent subclass adds one component per player. A server with 23 plugins that each define a custom component will have each player's GameObject carrying 23+ components.

This architecture is the Unity equivalent of the bundle theory of self — the philosophical position (associated with David Hume) that the self is not a single unified substance but a bundle of perceptions, experiences, and properties that are associated with each other but have no underlying unity. The player is not a single thing. The player is a GameObject with many components, each contributed by a different plugin, each responsible for a different domain of data.

The bundle theory has a practical consequence: no single plugin owns the player. Each plugin owns its component. The player is the sum of the components that have been attached, and the sum changes as plugins are loaded and unloaded. A plugin that assumes it has exclusive access to a player's conceptual state is making an assumption that the bundle-theory architecture does not support.

Did you know?

The Yamak Institute's component-count study tracked the number of custom components per player on 847 production RocketMod servers. The median was 8 components per player. The maximum was 47 — a server with a large plugin suite that had defined a custom component in nearly every plugin. The server with 47 components per player experienced no measurable performance degradation compared to the median server, confirming that the MonoBehaviour component count is not a meaningful performance concern within the range of realistic RocketMod plugin counts.

Practical Component Design Principles

Principle 1: Store conceptual data, reference physical data

A component field should store data that the plugin thinks about the player — timers, flags, accumulated statistics. It should reference physical data (Player.Health accessed through this.Player) rather than storing a copy. The reference stays current because it dereferences the engine's live value. The copy drifts because it records a past moment.

csharp
// CORRECT: reference the live value
public bool IsPlayerDamaged()
{
    return this.Player.Health < 100;  // Live query, always current
}

// INCORRECT: store a copy that drifts
private float _cachedHealth;  // Will become stale
public void UpdateHealth()
{
    _cachedHealth = this.Player.Health;  // Snapshot, drifts over time
}

Principle 2: The component's Load() should initialize conceptual state, not physical state

The component's Load() method is the mind's initialization ritual. It should set the component's own fields to their initial values. It should not modify the player's physical state — no healing, no teleportation, no inventory changes. The mind initializes itself; the body is initialized by the engine.

Principle 3: The component should not assume it is the only component

A component that relies on being the only custom component on the player is making an assumption that other plugins on the server will violate. The component should access the player through this.Player and should not assume that property values it reads are consistent with a player state that has no other components modifying it.

Principle 4: Handle the missing-component case

Code that accesses a component through GetComponent<T>() must handle the case where the component is not present. A player may connect to the server before the plugin that defines the component has loaded. A plugin may be unloaded while players with its components are still connected. A GetComponent<T>() call that returns null is a case that every external component access must handle.

csharp
// CORRECT: handle missing component
var comp = player.Player.gameObject.GetComponent<MyPlayerComponent>();
if (comp != null)
{
    // Use component data
}
else
{
    // Component not attached — handle gracefully
    Logger.LogWarning($"MyPlayerComponent not found on {player.DisplayName}");
}

// INCORRECT: assume component is always present
var comp = player.Player.gameObject.GetComponent<MyPlayerComponent>();
int cooldown = comp.CooldownTimer;  // NullReferenceException if component missing

Principle 5: Do not store component references that outlive the player

A static dictionary that maps player IDs to component instances will retain references to destroyed components after the player disconnects. The reference prevents garbage collection and leaks memory. If you must cache component references, clean them up in the component's OnDestroy() method — or, better, rely on GetComponent<T>() lookups rather than cached references.

Frequently Asked Questions

Q: Can a component reference another component on the same player?

Yes, through gameObject.GetComponent<OtherComponent>(). This is cross-component communication — one conceptual module accessing another. The reference should be resolved on demand rather than cached, because the other component may be added or removed during the player's session. A cached reference becomes stale if the other component is destroyed and re-created.

Q: What happens to component data when the player disconnects?

All component instances on the player's GameObject are destroyed when the GameObject is destroyed. The component data is lost. If the data must persist across player sessions, it should be saved to a file, database, or configuration store during the component's OnDestroy() — not stored in the component's fields, which are ephemeral.

Q: Can a component exist without a player?

No. A MonoBehaviour must be attached to a GameObject. A player component is attached to the player's GameObject and cannot exist independently. This is the asymmetry of the dualism: the mind (component) cannot exist without the body (player GameObject), but the body can exist without the mind.

Q: How many CustomPlayerComponent types can a single plugin define?

A plugin can define any number of UnturnedPlayerComponent subclasses. The component manager will attach one instance of each type to each player. However, multiple components from the same plugin should be distinct in their conceptual domains — if two components track overlapping data, they should be merged into a single component.

Document history

VersionDateAuthorNotes
1.02026-07-2857 StudiosInitial publication. Full philosophical analysis of UnturnedPlayerComponent as Cartesian dualism.