Input Integration¶
Massive Swarm System ships an optional input layer that feeds movement, aim and fire into the swarm simulation from any player-controlled character. This page covers the IMoveInputSource contract and its aim/fire extensions, the Unity Input System adapters, and the mobile joystick canvas.
Optional package required
The Input System adapter components (InputSystemMoveInputSource, InputSystemTwinStickInputSource and MobileTouchJoystickCanvas) live in the MassiveSwarmSystem.InputSystem assembly. That assembly only compiles when com.unity.inputsystem is installed in your project. The core runtime (MassiveSwarmSystem.Runtime) has no dependency on Input System and compiles cleanly without it.
If com.unity.inputsystem is not in your project, install it from the Package Manager, under Unity Registry → Input System, before using these components. The window opens from Window → Package Manager in Unity 2022.3 and from Window → Package Management → Package Manager in Unity 6.
The switch is automatic. The assembly declares a version define that sets MASSIVE_SWARM_SYSTEM_INPUT_SYSTEM whenever com.unity.inputsystem is present, and constrains itself to that symbol, so installing the package compiles the assembly in and removing it compiles the assembly out. Nothing to configure, and no compile errors either way. You can guard your own code with the same symbol when it has to work in projects that may or may not have Input System installed.
DefaultTargetMoveController: Zero-Setup Option¶
DefaultTargetMoveController moves the bundled default target with WASD or a gamepad left stick. Add it to your target GameObject and press Play. No Input Actions asset, no package dependencies, no wiring required.
It lives in the core runtime (MassiveSwarmSystem.Runtime) and has no dependency on com.unity.inputsystem. When the Input System package is installed, it reads keyboard and gamepad input via reflection so it works without an Input Actions asset. It also reads the legacy Horizontal and Vertical axes whenever the old Input Manager is compiled in, which includes Active Input Handling = Both. Under Both it sums the two reads and clamps the result to magnitude 1, so holding W on the keyboard and pushing a stick does not double the speed. If those axes are missing from Project Settings, the legacy half falls back to zero and logs one warning rather than throwing every frame.
This component is not an IMoveInputSource. It moves the target transform directly in Update and is meant to be replaced by your real player controller once you are past the getting-started stage.
Add it via Component → Massive Swarm System → Input → Default Target Move Controller. Only one per GameObject.
Inspector fields¶
| Field | What it controls |
|---|---|
| Move Speed | World units per second the target moves. The bundled MSS Default Target prefab ships with 10. A component you add by hand starts at 6. |
| Dead Zone | Analog stick dead zone as a magnitude fraction (0 to 0.5). Input below this is ignored. Default 0.15. |
| Camera Relative Movement | When on, WASD and stick input move relative to the main camera's flat forward and right axes. When off, input maps directly to world X/Z. On by default. |
| Face Move Direction | Rotates the target toward the movement direction each frame. On by default. |
| Rotation Speed | Degrees per second used to rotate toward the movement direction when Face Move Direction is on. Default 720. |
Movement is flat, on the world XZ plane, with no grounding at all. That suits the flat getting-started ground and nothing else, which is another reason to replace it with your own controller before you build a real level.
When to use this vs InputSystemMoveInputSource
Use DefaultTargetMoveController to get the swarm moving in minutes with no setup. Switch to InputSystemMoveInputSource (or a custom IMoveInputSource) once you want input rebinding, action maps, or mobile touch support.
Overview¶
Any component that implements IMoveInputSource can feed movement to a controller that reads directional input: a player controller, an AI driver, or any custom system. A source that also reports where the player is aiming, or whether they are firing, implements IAimInputSource or IFireInputSource on top of it, covered in Adding Aim and Fire below.
Two ready-made implementations ship. InputSystemMoveInputSource wraps a Unity InputActionReference such as a Gameplay/Move action from your Input Actions asset, and only reports movement. InputSystemTwinStickInputSource reports movement, aim and firing from a set of actions, for a twin-stick setup that also works on mouse and keyboard. See InputSystemTwinStickInputSource below.
MobileTouchJoystickCanvas builds a complete on-screen joystick at runtime, no prefab required, but it is not an IMoveInputSource and cannot be assigned where one is expected. It works a layer lower down: it simulates a virtual gamepad left-stick, so any Move action bound to <Gamepad>/leftStick picks it up automatically. The chain is joystick, then your Move action, then InputSystemMoveInputSource, then your controller.
The IMoveInputSource Contract¶
IMoveInputSource is the base contract every input source implements. A source that also reports aim or firing does not replace it: it derives from it, through IAimInputSource and IFireInputSource, so a consumer that only needs movement can keep typing its field as IMoveInputSource and stay unaware of the rest.
ReadMove() returns the current movement vector. The XY axes map to horizontal and vertical movement in whatever coordinate space the consuming system expects (typically world-relative XZ). Return Vector2.zero when there is no input.
Implement this interface on any MonoBehaviour to provide input from a source not covered by the shipped adapters: keyboard polling, a custom gamepad mapper, a nav-mesh-driven AI controller, or a replay system.
using MassiveSwarmSystem.Runtime.Input;
using UnityEngine;
public sealed class KeyboardMoveInputSource : MonoBehaviour, IMoveInputSource
{
public Vector2 ReadMove()
{
#if ENABLE_LEGACY_INPUT_MANAGER
return new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"));
#else
// Input System only: read Keyboard.current / Gamepad.current here instead.
return Vector2.zero;
#endif
}
}
Do not call UnityEngine.Input unguarded
Input.GetAxis compiles under any setting, but it throws an InvalidOperationException at runtime once Active Input Handling is set to Input System Package (New), which is what the note at the top of this page asks you to install. The ENABLE_LEGACY_INPUT_MANAGER guard above is the same one DefaultTargetMoveController uses, and it wraps the call in a try/catch for the same reason.
Have your own player controller hold a reference to this component as an IMoveInputSource and call ReadMove() each frame. Nothing in the core package calls it for you. The Full Demo's DemoPlayerController shows the pattern, reading its source once per Update.
Adding Aim and Fire¶
A twin-stick character needs more than a movement vector: it needs to know where the player is pointing and whether they are pulling the trigger. Two derived interfaces cover that, and a set of extension methods is what lets a field typed as plain IMoveInputSource reach them without every caller casting by hand.
IAimInputSource¶
public interface IAimInputSource : IMoveInputSource
{
bool TryReadAimDirection(out Vector2 direction);
bool TryReadAimScreenPoint(out Vector2 screenPoint);
}
Aim arrives in one of two shapes, and a source reports whichever its device produced. TryReadAimDirection is a stick direction, already meaningful on its own: a right stick pushed forward means "aim forward", full stop. TryReadAimScreenPoint is a pointer position in screen pixels, which means nothing until something crosses it with a camera ray, and that resolution belongs to the consumer rather than the input source, because only the consumer knows which camera and which ground plane apply. Both methods return false when the device in use cannot supply that shape, or when a stick is inside its dead zone.
IFireInputSource¶
ReadFireHeld() reports whether the fire control is held right now, not a single shot. A weapon owns its own rate of fire, so it polls this every frame and decides for itself when the cooldown allows the next shot. Kept separate from IAimInputSource because aiming without a weapon is a real case (a cursor-follow demo, for example), and a source that only points should not have to answer a question about shooting.
MoveInputSourceExtensions¶
A consumer that holds an IMoveInputSource field can still ask for aim or fire, because these extension methods dispatch to the derived interface when the assigned source implements it and answer a safe default when it does not:
| Method | Returns when the source implements the interface | Returns when it does not |
|---|---|---|
TryReadAimDirection(out Vector2) |
Whatever the source reports | false, direction Vector2.zero |
TryReadAimScreenPoint(out Vector2) |
Whatever the source reports | false, point Vector2.zero |
ReadFireHeld() |
Whatever the source reports | false |
SupportsAim() |
true |
false |
SupportsFire() |
true |
false |
A null source also answers false rather than throwing, so a consumer does not need a separate null guard before every call. SupportsAim and SupportsFire are for an inspector warning or a one-time capability check; the hot path can call the TryRead... methods directly and read the returned bool.
using MassiveSwarmSystem.Runtime.Input;
// m_inputSource is typed as IMoveInputSource, but the assigned component may implement more.
if (m_inputSource.TryReadAimDirection(out Vector2 aimStick))
{
// Turn towards aimStick.
}
else if (m_inputSource.TryReadAimScreenPoint(out Vector2 screenPoint))
{
// Cross screenPoint with a ground plane using your own camera.
}
bool firing = m_inputSource.ReadFireHeld();
RadialDeadZone¶
Every shipped source runs its stick input through this before returning it, and a hand-rolled IMoveInputSource should too.
public static class RadialDeadZone
{
public const float MaxDeadZone = 0.5f;
public static Vector2 Apply(Vector2 raw, float deadZone);
public static bool TryApply(Vector2 raw, float deadZone, out Vector2 value);
}
Apply clamps the dead zone to MaxDeadZone, returns zero for input inside it, and rescales everything above it back to the full 0..1 range. The rescale is the part worth keeping: without it, a stick jumps from standing still to whatever fraction the dead zone left on the table the instant it crosses the threshold, which reads as a twitch rather than a start. TryApply does the same and also returns whether the result is non-zero, saving a caller a second magnitude check.
The hand-rolled KeyboardMoveInputSource example above has no dead zone
That example reads raw axis values directly. Keyboard input does not need one (a key is either pressed or not), but a stick-reading source without a dead zone will drift from a controller that never quite recenters. Run stick input through RadialDeadZone.Apply before returning it.
DemoPlayerController: a Reference Consumer¶
The Full Demo's DemoPlayerController consumes aim and fire through the extension methods above without adding a hard dependency on IAimInputSource or IFireInputSource. Its input field stays typed as IMoveInputSource, so a move-only source still works and simply never triggers the aim/fire paths.
| Field / Member | What it does |
|---|---|
| Face Aim Direction | When on, and the assigned source reports an aim direction, the character turns to face aim instead of its movement direction each frame. A source that only moves ignores this, so a scene wired the old way behaves exactly as before. |
FireHeld (public property) |
True while the assigned source reports the fire control held. Always false for a move-only source. |
TryGetAimDirection(out Vector3 direction) (public method) |
Resolves aim to a planar world-space direction. Tries a stick direction first; if the source only reports a screen point, it raycasts that point against the ground plane at the player's height and returns the direction from the player to where the ray lands. Returns false when the source reports no aim, or when the resolved point sits on the player. |
| Use Swarm Crowd Blocking, Crowd Blocking Radius, Crowd Blocking Strength | Hold the player back from the swarm's crowd readout. They read TryGetCrowdBlocking, so they do nothing while Enable Target Crowd Blocking is off on SwarmSettings, which is how the samples ship. |
| Crowd Push Speed | How hard that readout shoves the player away from the crowd, in units per second. Same precondition. |
| Agent Separation Mask, Agent Separation Capacity | The layers of agent bodies the player is pushed out of, horizontally only, and how many bodies one frame can process. An empty mask turns the separation off. |
| Push Rigidbodies, Push Speed Fraction, Push Force, Max Push Mass | Shove non-kinematic Rigidbodies the player walks into, since a CharacterController does not push them on its own. Speed fraction is how close to the player's speed the object is brought, force is the strongest shove in newtons, and mass is the heaviest body that moves. |
| Sprint Action | A Button action that is held to run. Leave empty for a player that only moves at Move Speed. The controller enables the action itself if nothing else has. |
| Sprint Speed | Speed in units per second while the sprint control is held. Zero turns sprinting off, and the field starts at zero, so assign a value to enable it. |
| Stamina | A DemoStamina component that running spends. Sprinting stops when the bar is exhausted. Leave empty to run for free. |
| Fire Blocks Sprint | When on, holding fire drops the player back to Move Speed, so running and shooting are a choice. |
IsSprinting (public property) |
True on a frame the player moved at Sprint Speed. That needs sprint held, a non-zero Sprint Speed, the move input pushed, no blocking fire, and stamina available when one is assigned. |
| Ground Mask, Slide Off Speed | Which contacts under the capsule count as real footing, and how fast the player slides off a body that does not. Leave the agent layer out of the mask. |
The crowd, separation, pushing and ground fields are explained in Physics & Colliders: The player.
TwinStickWeapon, covered in Combat Integration, reads FireHeld and TryGetAimDirection from this component to decide when and where to fire.
InputSystemMoveInputSource¶
Add via Component → Massive Swarm System → Input → Input System Move Input Source.
Reads a Vector2 action from the Unity Input System and exposes it through IMoveInputSource.
Requires com.unity.inputsystem
This component only compiles when the Input System package is installed. See the warning at the top of this page.
Inspector fields¶
| Field | What it controls |
|---|---|
| Move Action | An InputActionReference pointing to a Vector2 Move action. Assign the Gameplay/Move action (or equivalent) from your Input Actions asset. |
| Dead Zone | Radial dead zone applied to the action's Vector2 (0 to 0.5, default 0.15). Input below this magnitude reads as zero; input above it is rescaled so the usable range still reaches full magnitude. |
How it works¶
On OnEnable, the component enables the assigned action if it is not already enabled, and disables it again on OnDisable, but only if this component was the one that enabled it. If the action was already running (managed by a PlayerInput component or an InputActionAsset enabled elsewhere), the component leaves it alone and does not disable it on teardown.
If no InputActionReference is assigned, ReadMove() returns Vector2.zero and a one-time warning is logged to the console.
Setup steps¶
- Create or open your Input Actions asset (Assets → Create → Input Actions).
- Add a Vector2 action named
Moveunder an action map (e.g.Gameplay). Bind it to the controls you need (keyboard WASD, gamepad left stick, etc.). - Add
InputSystemMoveInputSourceto the same GameObject as your player controller. - Drag the
Moveaction from the Input Actions asset into the Move Action field. - Assign the
InputSystemMoveInputSourcecomponent reference to your player controller's input source field.
InputSystemTwinStickInputSource¶
Add via Component → Massive Swarm System → Input → Input System Twin Stick Input Source.
Reads move, aim and fire from a set of Input System actions, and implements IAimInputSource and IFireInputSource on top of IMoveInputSource. Aim can come from a stick or a pointer, and the component decides which one is live: whichever moved last claims it, so pushing a stick claims aim and nudging the mouse claims it back. Without that arbitration, a pad stick returning to center would snap the aim to wherever the mouse happens to be resting, which is the classic twin-stick annoyance.
Requires com.unity.inputsystem
This component only compiles when the Input System package is installed. See the warning at the top of this page.
Inspector fields¶
| Field | What it controls |
|---|---|
| Move Action | A Vector2 action for movement. Assign Gameplay/Move. |
| Aim Action | A Vector2 action for stick aiming, normally the right stick. Assign Gameplay/Aim. Leave empty and the player can still aim with a pointer. |
| Aim Point Action | A Vector2 action carrying a pointer position in screen pixels, normally Mouse/position. Assign Gameplay/AimPoint. Leave empty and the player can still aim with a stick. |
| Fire Action | A button action for firing. Assign Gameplay/Fire. Reported as held, because the weapon owns its own rate of fire. |
| Move Dead Zone | Radial dead zone for movement (0 to 0.5, default 0.15). Movement below this magnitude reads as standing still, so analog drift never nudges the player. |
| Aim Dead Zone | Radial dead zone for stick aiming (0 to 0.5, default 0.25). Higher than Move Dead Zone on purpose: a resting right stick that steals aim from the mouse is worse than one that responds slightly late. |
| Pointer Reclaim Pixels | Pixels the pointer has to travel in one frame to take aim back from the stick (default 2). At 0 any pointer movement takes it back, and a pointer that has not moved never does. Too low and a shaky desk cancels pad aiming; too high and the mouse feels dead after using a pad. |
How it works¶
TryReadAimDirection and TryReadAimScreenPoint both run the same arbitration step first, once per frame. If the aim stick is outside Aim Dead Zone, the stick claims aim. Then, whether or not the stick claimed aim, the pointer test runs. If the pointer has moved Pointer Reclaim Pixels or more since the previous frame, the pointer takes aim back, so a pointer that moves enough wins even while the stick is pushed. With no Aim Point Action assigned, the stick is the only way to aim and is never handed away.
ReadMove() runs the move axis through Move Dead Zone. ReadFireHeld() reports whether Fire Action is currently pressed.
An empty slot that leaves the player without an input logs one console warning the first time that input is read: an empty Move Action, an empty Fire Action, or Aim Action and Aim Point Action both empty. One empty aim slot on its own is a supported setup and stays silent.
Setup steps¶
- Create or open your Input Actions asset, with a
Gameplaymap carryingMove,Aim,AimPointandFireactions. - Add
InputSystemTwinStickInputSourceto your player GameObject. - Assign
Move, and whichever ofAim/AimPointyour input scheme uses. A gamepad needsAim; mouse and keyboard needsAimPoint; supporting both needs both. - Assign
Fire. - Point your player controller's
IMoveInputSourcefield at this component. Anything that also checksIAimInputSourceorIFireInputSource, directly or through theMoveInputSourceExtensionsmethods, now gets aim and fire too.
MobileTouchJoystickCanvas: Mobile On-Screen Joystick¶
Add via Component → Massive Swarm System → Input → Mobile Touch Joystick Canvas.
Drop this component on any GameObject in your scene. When Play mode starts, it builds a self-contained Canvas with a draggable joystick. No prefab, no manual UI hierarchy needed: everything is created at runtime.
The joystick simulates a virtual gamepad left-stick by default (<Gamepad>/leftStick). Any Move action bound to that path receives the input without extra wiring.
Requires com.unity.inputsystem
This component only compiles when the Input System package is installed.
Quickstart¶
- Add an empty GameObject to your scene. Name it
Mobile Joystick. - Add Mobile Touch Joystick Canvas via the Component menu.
- Leave the defaults. Press Play. A joystick appears in the bottom-left corner (Editor counts as a touch platform under the default Activation Mode).
- If your
Moveaction already binds<Gamepad>/leftStick, input flows automatically. If not, add that binding or change Control Path to match your action.
Parameter Reference¶
Activation group¶
| Field | What it controls |
|---|---|
| Activation Mode | Auto Touch And Editor (default) always shows the joystick in the Editor and in iOS/Android builds, and on any other platform it shows it when Unity reports a touchscreen. Always shows it everywhere with no check. Touch Only shows it whenever a touchscreen device is present, which includes the Editor on a touch laptop or with the Device Simulator open. |
| Joystick Mode | Fixed keeps the joystick in a screen corner permanently. Follow Finger hides it until touched; it appears under the finger and follows it past the outer ring. |
Fixed Layout group¶
Applies only when Joystick Mode is Fixed.
| Field | What it controls |
|---|---|
| Anchor | Which screen corner the joystick base sits in: Bottom Left, Bottom Right, or Bottom Center. |
| Anchor Offset | Pixel offset from the chosen corner to the joystick center, at the configured reference resolution. |
Follow Finger Layout group¶
Applies only when Joystick Mode is Follow Finger.
| Field | What it controls |
|---|---|
| Press Zone | Which portion of the screen reacts to touches: Full Screen, Left Half, or Right Half. |
| Base Follows Finger | When on, the base drags with the finger once the finger crosses the outer ring. When off, the knob saturates at the rim. |
| Show Visuals | When on, the base and knob are visible while the finger is pressed. Turn off for an invisible stick. |
| Inner Dead Zone | Dead zone as a fraction of the outer radius. Movements smaller than this give zero output. |
| Outer Saturation Padding | Once the finger is within this fraction of the rim, output locks to full magnitude. Improves the "full-speed" feel near the edge. |
| Fade Duration | Seconds for the base and knob to fade out after the finger lifts. They snap to fully visible on press, so this governs the release only. 0 is instant. |
| Secondary Tap Control | Input System control path for a secondary button. A second finger landing while the joystick is held sends a press to this control, useful for a jump button on the same touch zone. Default <Gamepad>/buttonSouth. Leave empty to disable. |
The inspector draws the ring for you
Below these fields the custom inspector paints a small preview: an outer circle for the full stick, a filled inner disc for Inner Dead Zone, and a second ring where Outer Saturation Padding starts. Drag either slider and watch the rings move rather than guessing at the numbers.
Visuals group¶
| Field | What it controls |
|---|---|
| Base Diameter | Diameter of the joystick base in pixels at the reference resolution. |
| Handle Diameter | Diameter of the draggable knob in pixels. |
| Outer Radius (px) | How far the knob travels from center (in pixels at the reference resolution) before the output magnitude saturates at 1. |
| Opacity | Overall opacity of the base and knob. |
| Base Color | Fill color of the outer base ring. |
| Handle Color | Fill color of the inner knob. |
Input System group¶
| Field | What it controls |
|---|---|
| Control Path | Input System control path the joystick simulates. Default <Gamepad>/leftStick. |
| Use Isolated Input Actions | Fixed mode only. Prevents the stick snapping back when control schemes auto-switch. Leave on unless your input setup has a specific reason to disable it. |
| Stick Behaviour | Fixed mode only. Passed to Unity's OnScreenStick.Behaviour. |
Canvas group¶
| Field | What it controls |
|---|---|
| Canvas Sort Order | Sort order of the runtime canvas. Raise this if your own UI renders on top of the joystick. |
| Reference Resolution | Reference resolution for the CanvasScaler (Scale With Screen Size mode). Match this to your game's design resolution. |
How the canvas is built¶
MobileTouchJoystickCanvas creates the entire UI hierarchy in code when the component first enables. It adds a Screen Space Overlay Canvas with a CanvasScaler and GraphicRaycaster, plus the joystick base, hit area, and knob Image components sized from your visual settings. If no EventSystem exists in the scene, it creates one with an InputSystemUIInputModule. If one exists without an InputSystemUIInputModule, the component replaces the existing input module so drag events reach the stick correctly.
The Canvas and its children are parented under the same GameObject as MobileTouchJoystickCanvas. Disabling the component hides the canvas; enabling it shows it again (or builds it for the first time).
Follow Finger mode for mobile games
For most mobile games, Follow Finger mode with Press Zone = Left Half is the best starting point. The joystick only occupies screen space while the player is touching it, and the base always appears where the finger lands, so there is no learning curve.
FloatingOnScreenStick: Advanced Use¶
FloatingOnScreenStick is the lower-level component that MobileTouchJoystickCanvas uses internally in Follow Finger mode. You can add it directly to a UI Image when you want to build your own joystick visual hierarchy but still need the floating stick behavior.
Add via Component → Massive Swarm System → Input → Floating On Screen Stick. The component requires a RectTransform.
Inspector fields¶
| Field | What it controls |
|---|---|
| Control Path | Input System control path the stick drives. Default <Gamepad>/leftStick. |
| Base Rect | Optional RectTransform for the visible joystick base. Repositioned to the touch point on press; hidden when not pressed. |
| Knob Rect | Optional RectTransform for the draggable knob. Follows the finger within the outer radius. |
| Outer Radius | Distance from the base center in canvas units at which output magnitude reaches 1. |
| Follow Finger | When on, the base repositions when the finger passes the outer radius so the knob stays within range. |
| Inner Dead Zone | Dead zone as a fraction of Outer Radius. |
| Outer Saturation Padding | Saturation zone as a fraction of Outer Radius. Output locks to 1 before the finger fully reaches the rim. |
| Fade Duration | Seconds for the base and knob to fade out on release. Press is always instant. 0 is instant both ways. |
| Secondary Tap Control Path | A second finger landing while the joystick is held sends a press to this control. Default <Gamepad>/buttonSouth. Leave empty to disable. |
When to use FloatingOnScreenStick directly¶
Use FloatingOnScreenStick when you have your own joystick visual hierarchy and just need the floating-stick input behavior, or when the joystick must sit inside an existing Canvas you control.
Use MobileTouchJoystickCanvas when you want everything built automatically with no manual UI setup.
Attach to a raycast-target Image
FloatingOnScreenStick receives input through Unity's UI event system. Attach it to a RectTransform that has an Image with Raycast Target enabled, because the stick will not receive pointer events without one. MobileTouchJoystickCanvas handles this automatically; it is only a concern when placing FloatingOnScreenStick manually.
Quickstart: wire input in this order
- Install
com.unity.inputsystemif you haven't already (Package Manager, Unity Registry → Input System). - Move only: add
InputSystemMoveInputSource, assign aMoveaction. - Move, aim and fire: add
InputSystemTwinStickInputSourceinstead, assignMoveand whichever ofAim/AimPoint/Fireyou need. - For mobile touch input: add an empty GameObject to the scene, add
MobileTouchJoystickCanvas, leave Joystick Mode = Fixed and press Play to confirm the joystick appears. It feeds a virtual gamepad stick, not anIMoveInputSourcefield. - Switch to Joystick Mode = Follow Finger and Press Zone = Left Half once the basic flow works.
- Assign whichever source you added to your player controller's input source field.
- For a custom source, implement
IMoveInputSource, andIAimInputSource/IFireInputSourceif it needs to report aim or firing, then assign that instead.