How it works
Two scene components run the system. The PortraitAvatars manager creates and destroys portraits and remembers your color and light settings. The PortraitsPanel sits under a Canvas and adds a UI Portrait cell to its grid for each new portrait.
A 3D portrait starts as a prefab: a Portrait3D component on the root, an Avatar Camera child with a Camera and an AvatarCamera component, optional lights, and your character. The package includes a template prefab you can duplicate. When you call PortraitAvatars.instance.Create3DPortrait(), the manager spawns that prefab as its own child, then:
- puts the whole rig on a free portrait layer,
- sets the rig’s lights to light only that layer,
- adds that layer to the camera’s culling mask,
- renders the camera into a new
RenderTexture, shown in the cell’s RawImage.
Every 3D rig spawns at the same local position, and the layers keep them apart: each camera sees only its own character. So you create one Unity layer per 3D portrait you want on screen at once (for example Portrait0, Portrait1 and Portrait2) and select them in the manager’s Available Layers. Three layers means up to three live 3D portraits.
A 2D portrait skips the camera, texture and layer. Create2DPortrait() adds a cell that shows a sprite in a UI Image, and SetSprite() on the returned handle sets the art.
2D or 3D portraits?
Each 3D portrait runs its own camera, and too many cameras slow your game down. Depending on the project and platform, 4, 6 or even 8 at once may be fine, so check your frame rate as you add them. Use 3D for characters that should move and react to light, like the player’s party or a talking NPC, and 2D for everyone else.
Version 1.2.1 added two ways to lower that cost. Portrait cameras can refresh at a set frames per second instead of every frame. And when all portraits share the same background, such as the game world behind the player, one background camera renders it at its own rate, so the portraits themselves can update more slowly.
In a first-person game you usually put the manager under your player. 3D portraits then spawn where the player stands and pick up the world’s lighting as they move. The docs explain the tradeoffs, like light bleeding through walls, and fixes such as scaling the avatar down and tightening its camera’s clipping planes.
Scripting and extending
The runtime code lives in the MagicPigGames.Portraits namespace, in its own assembly definitions. Each create call returns an InGamePortrait handle. The color and light methods (SetUIColor, SetImageColor, SetBackgroundColor, SetLightColor, SetLightIntensity) change every portrait or one by index. To remove portraits, call DestroyPortrait() or DestroyAllPortraits(): they release the RenderTexture and free the layer for the next one.
For your own logic, subclass PortraitAvatars, Portrait3D, AvatarCamera or PortraitUI, or add a loader component that dresses each avatar after it spawns, like the demo’s DemoAvatarLoader. The scripting documentation covers the full API.