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
- In Unity Behavior Parameters, set one modality to zero: either Continuous Actions = 0 or remove all discrete branches, then rebuild.
- Set flatten_branches/use_discrete handling consistent with the spec — only discrete-only or continuous-only environments can be wrapped.
- Use the raw UnityEnvironment API (behavior_specs action_spec gives continuous and discrete parts) for hybrid control.
- 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
- Keep Behavior Parameters single-modality when targeting gym
- Check action_spec.continuous_size and discrete_branches before wrapping
- Use raw UnityEnvironment for hybrid action research setups
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
- There are no observations provided by the environment.
- There can only be one Agent in the environment but
- There can only be one behavior in a UnityEnvironment if it…
- Action spaces with both continuous and discrete actions are…
- The behavior needs a continuous input of dimension for ( …
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)