Unity-Technologies/ml-agents · error · UnityGymException

The gym wrapper does not provide explicit support for both…

Error message

The gym wrapper does not provide explicit support for both discrete and continuous actions.

What it means

UnityGymException thrown by UnityGymEnv.__init__ when the wrapped behavior's action_spec has both nonzero continuous_size and nonzero discrete branches. The gym wrapper only supports one action modality at a time, so it refuses hybrid (discrete+continuous) action spaces.

Solutions

  1. In Unity Behavior Parameters, set one modality to zero: either Continuous Actions = 0 or remove all discrete branches, then rebuild.
  2. Set flatten_branches/use_discrete handling consistent with the spec — only discrete-only or continuous-only environments can be wrapped.
  3. Use the raw UnityEnvironment API (behavior_specs action_spec gives continuous and discrete parts) for hybrid control.
  4. Report/extend the wrapper if hybrid gym support is required; upstream gym wrapper doesn't support it.

Example fix

// before
# Unity Behavior Parameters: Continuous Actions = 3, Discrete branches = [2,2]
env = UnityGymEx(UnityEnvironment(file_name='hybrid_app'))
// after
# Unity Behavior Parameters: Continuous Actions = 0, Discrete branches = [2,2]
env = UnityGymEnv(UnityEnvironment(file_name='discrete_app'))
Defensive patterns

Strategy: validation

Validate before calling

spec = unity_env.behavior_specs[behavior_name]
aspec = spec.action_spec
if aspec.continuous_size > 0 and len(aspec.discrete_branches) > 0:
    raise ValueError("Hybrid actions not supported by the gym wrapper")

Type guard

def is_single_modality(action_spec) -> bool:
    return (action_spec.continuous_size > 0) != (len(action_spec.discrete_branches) > 0)

Try / catch

try:
    env = UnityGymEnv(unity_env)
except UnityGymException:
    logger.error("Hybrid action space; set exactly one of continuous/discrete in Behavior Parameters")

Prevention

When it happens

Trigger: Wrapping an environment whose Behavior Parameters in Unity enable both a continuous action vector and discrete action branches (Continuous Actions > 0 AND Discrete Actions branches defined).

Common situations: ML-Agents project configured with hybrid actions for research; recently edited Behavior Parameters adding discrete branches to a previously continuous controller; trying to wrap ML-Agents 1.0+ hybrid-action environments in gym.

Related errors


AI-assisted analysis of Unity-Technologies/ml-agents@3ecb446f75 (2026-09-02). Data as JSON: /api/errors/883d5c160bbec83f. Report an issue: GitHub.

Appendix: source

Thrown at ml-agents-envs/mlagents_envs/envs/unity_gym_env.py:129

            else:
                if flatten_branched:
                    self._flattener = ActionFlattener(branches)
                    self._action_space = self._flattener.action_space
                else:
                    self._action_space = spaces.MultiDiscrete(branches)

        elif self.group_spec.action_spec.is_continuous():
            if flatten_branched:
                logger.warning(
                    "The environment has a non-discrete action space. It will "
                    "not be flattened."
                )

            self.action_size = self.group_spec.action_spec.continuous_size
            high = np.array([1] * self.group_spec.action_spec.continuous_size)
            self._action_space = spaces.Box(-high, high, dtype=np.float32)
        else:
            raise UnityGymException(
                "The gym wrapper does not provide explicit support for both discrete "
                "and continuous actions."
            )

        if action_space_seed is not None:
            self._action_space.seed(action_space_seed)

        # Set observations space
        list_spaces: List[gym.Space] = []
        shapes = self._get_vis_obs_shape()
        for shape in shapes:
            if uint8_visual:
                list_spaces.append(spaces.Box(0, 255, dtype=np.uint8, shape=shape))
            else:
                list_spaces.append(spaces.Box(0, 1, dtype=np.float32, shape=shape))
        if self._get_vec_obs_size() > 0:
            # vector observation is last
            high = np.array([np.inf] * self._get_vec_obs_size())

View on GitHub (pinned to 3ecb446f75)