PlayerLook
PlayerLook reads and changes how the player looks: the body shape, the face and the clothes. A look is plain data (a Look object), so you can store it, change it and apply it again. The game rebuilds the body and clothes itself.
PlayerLook read, change and randomise the look
Use Capture to read the player’s look, then Apply or Live to change it. Randomise rolls a new look for you.
public static class PlayerLook
{
public static event Action<Look> Changed;
public static bool IsAvailable { get; }
public static Look Capture();
public static Look Capture(Appearance appearance);
public static bool Apply(Look look);
public static bool Apply(Look look, Appearance appearance);
public static bool Live(Look look);
public static bool Live(Look look, Appearance appearance);
public static Look Randomise();
public static Look Randomise(Appearance appearance);
public static Look Vanilla();
public static IList<string> Options(string slot, bool male);
public static IList<string> Options(string slot, bool male, Appearance appearance);
}
| Method | Returns | What it does |
|---|---|---|
Capture() | Look | Reads the player’s look. Returns null when IsAvailable is false. |
Capture(appearance) | Look | Reads the look of any Appearance, such as a character’s look in the game. |
Apply(look) | bool | Changes the player to this look. Returns false when it is not available, or when the game throws. |
Apply(look, appearance) | bool | Changes the look of any Appearance to this look, without firing Changed. Returns false when the appearance is not ready, or when the game throws. |
Live(look) | bool | Changes only the body and face dials, not the clothes. |
Live(look, appearance) | bool | Changes only the body and face dials of any Appearance, not the clothes, without firing Changed. |
Randomise() | Look | Rolls a new random look, applies it, and returns it. |
Randomise(appearance) | Look | Rolls a new random look for any Appearance, applies it, and returns it, without firing Changed. |
Vanilla() | Look | A random look made with the game’s own random-character rules. It is not applied. |
Options(slot, male) | IList<string> | The clothes names you can pick for a slot, in the game’s order. |
Options(slot, male, appearance) | IList<string> | Does the same for any Appearance. |
| Property | Type | What it means |
|---|---|---|
IsAvailable | bool | true when the player exists and a look can be read or changed. |
| Event | Gives you | When it fires |
|---|---|---|
Changed | Look, the look that was captured | After Apply(look), Live(look) or Randomise() changes the player’s look. The look is null when the capture fails. |
PlayerLook.Look one complete look: flags and height, public fields
Capture, Vanilla and Randomise return a Look, and Apply and Live take one. This part holds the flags and the height, and the dials and clothes are in the two blocks below.
[Serializable]
public sealed class Look
{
public bool set;
public bool male;
public bool advanced;
public float height = 0.8f;
}
| Property | Type | What it means |
|---|---|---|
set | bool | true for a look taken from Capture. |
male | bool | true for the male body. |
advanced | bool | true when the advanced body dials are used. |
height | float | Body height, from 0 to 1. Defaults to 0.8. |
PlayerLook.Look body and face dials, all floats
These are the body and face dials of a Look. When Apply or Live sets them, the framework keeps each one inside its range.
[Serializable]
public sealed class Look
{
public float shape;
public float gender;
public float headSize = 1f;
public float faceSize = 1f;
public float neckLength;
public float shoulderWidth;
public float breastSize;
public float hipWidth;
public float handSize = 1f;
public float footSize = 1f;
public float lipThickness;
public float jawPosition;
public float chinPosition;
public float eyeSize = 1f;
public float eyeDistance;
public float eyePosition;
public float eyeDepth;
public float earSize = 1f;
public float earRotation;
public float noseRootRotation;
public float noseTipRotation;
public float noseWidth;
}
| Property | Type | What it means |
|---|---|---|
shape | float | A dial from -1 to 1. Defaults to 0. |
gender | float | A dial from 0 to 1. Defaults to 0. |
headSize | float | A dial from 0 to 1. Defaults to 1. |
faceSize | float | A dial from 0 to 1. Defaults to 1. |
neckLength | float | A dial from 0 to 1. Defaults to 0. |
shoulderWidth | float | A dial from 0 to 1. Defaults to 0. |
breastSize | float | A dial from 0 to 1. Defaults to 0. |
hipWidth | float | A dial from 0 to 1. Defaults to 0. |
handSize | float | A dial from 0 to 1. Defaults to 1. |
footSize | float | A dial from 0 to 1. Defaults to 1. |
lipThickness | float | A dial from 0 to 1. Defaults to 0. |
jawPosition | float | A dial from 0 to 1. Defaults to 0. |
chinPosition | float | A dial from 0 to 1. Defaults to 0. |
eyeSize | float | A dial from 0 to 1. Defaults to 1. |
eyeDistance | float | A dial from 0 to 1. Defaults to 0. |
eyePosition | float | A dial from 0 to 1. Defaults to 0. |
eyeDepth | float | A dial from 0 to 1. Defaults to 0. |
earSize | float | A dial from 0 to 1. Defaults to 1. |
earRotation | float | A dial from 0 to 1. Defaults to 0. |
noseRootRotation | float | A dial from 0 to 1. Defaults to 0. |
noseTipRotation | float | A dial from 0 to 1. Defaults to 0. |
noseWidth | float | A dial from 0 to 1. Defaults to 0. |
PlayerLook.Look clothes, each a wearable name
These are the clothes names of a Look. Options lists the names a slot accepts, and an empty string means nothing is worn there.
[Serializable]
public sealed class Look
{
public string head = "";
public string hat = "";
public string hair = "";
public string glasses = "";
public string beard = "";
public string shirtT = "";
public string shirtB = "";
public string pantsT = "";
public string pantsB = "";
public string socks = "";
public string shoes = "";
}
| Property | Type | What it means |
|---|---|---|
head | string | The name of a wearable asset in the game files. "" means none. |
hat | string | The name of a wearable asset in the game files. "" means none. |
hair | string | The name of a wearable asset in the game files. "" means none. |
glasses | string | The name of a wearable asset in the game files. "" means none. |
beard | string | The name of a wearable asset in the game files. "" means none. |
shirtT | string | The name of a wearable asset in the game files. "" means none. |
shirtB | string | The name of a wearable asset in the game files. "" means none. |
pantsT | string | The name of a wearable asset in the game files. "" means none. |
pantsB | string | The name of a wearable asset in the game files. "" means none. |
socks | string | The name of a wearable asset in the game files. "" means none. |
shoes | string | The name of a wearable asset in the game files. "" means none. |
Putting it together
Logs every look change, then takes the player’s hat off.
using BepInEx;
using BepInEx.Unity.Mono;
using ObenseuerCore;
public class LookTweak : BaseUnityPlugin
{
private void Awake()
{
PlayerLook.Changed += look => Logger.LogInfo("look changed");
}
private void Start()
{
PlayerLook.Look look = PlayerLook.Capture();
if (look == null) return;
look.hat = "";
PlayerLook.Apply(look);
}
}
| Step | What it does |
|---|---|
PlayerLook.Changed += ... | Logs each look change. |
PlayerLook.Capture() | Reads the player’s current look. |
look.hat = "" | Clears the hat in the copy of the look. |
PlayerLook.Apply(look) | Applies the look, which fires Changed. |
Reading and changing a look
Capture()returns the player’s look, ornullwhenIsAvailableisfalse. A look fromCapturehassettrue.Capture(appearance)reads anyAppearance.Apply(look)changes the player to the look you give. Each dial is kept inside its range: height 0 to 1, shape -1 to 1, and every other dial 0 to 1. The game then rebuilds the body and clothes, andChangedfires. (A new costume is built at runtime, so the game’s shared Random NPC asset is never written. The rebuild is the game’sLoadCharacter.)Live(look)changes only the dials, not the clothes. It sets the body and face dial values on the player and callsUpdateShapes. The same limits apply, andChangedfires.- The
Appearanceversions ofLive,RandomiseandOptionswork on anyAppearance, and they do not fireChanged.LivereturnsfalseandRandomisereturnsnullwhen theAppearanceis not ready, andOptionsreturns an empty list then. Randomise()rolls a new random look. It captures the current look, rolls every dial except height, rolls each piece of clothing, applies the result, firesChanged, and returns the new look. It keeps the capturedmaleandadvanced. Leggings and a female beard always take the first entry, as the game’s Random NPC rule does.Vanilla()gives you a random look made with the game’s own random-character rules, and does not apply it. The body is male or female and basic or advanced by a coin toss, breast size is 0 to 0.3 for male and 0.3 to 1 for female, gender is rounded to 0 or 1, and height is included. OnlyVanillauses the game’s gender ranges. (It usesAppearance.LoadCharacterValues.)
Clothes and options
Options(slot, male)lists the names you can use for a slot, in the game’s order. The slots areHead,Hat,Hair,Glasses,Beard,Jacket,Shirt,Pants,Leggings,SocksandShoes. An unknown slot throwsArgumentException. When not available, it returns an empty list. A female beard gives the same full list as a male one, because the game’s editor offers every beard to either gender.- Clothes are matched by name within the list for the look’s gender. A name that is not found clears the slot, except
head, which falls back to the first head of that gender. Leggings are matched by name too. - The head list follows
male. A female beard is not forced to one entry inOptionsorApply, and onlyRandomiseandVanillaroll it as its first entry. Breast size is not limited by gender inApply,LiveorRandomise:Randomiserolls it over 0 to 1 for either gender.
Events and saving
- A handler that throws is logged, and the other handlers still run. The
Appearanceversions do not fireChanged. - Nothing is saved. Store a
LookwithSaveData.Registerif you need it.
Errors and checks
- A null
lookorappearancethrowsArgumentNullException. The exception isOptions, which returns an empty list for a nullappearance. - A game error is logged and the call returns
falseornull. The log isBepInEx\logs\obenseuer.moddingframework.playerlook.log. - The debug check is
[Debug] PlayerLookSelfCheck = falseinBepInEx\config\obenseuer.moddingframework.cfg. When it is true, the framework runs a check when it starts and logsplayerlook self-check ok, orplayerlook self-check failed: <check>, toBepInEx\logs\ObenseuerCore.log. The check covers the dial limits and the clothes rules, on temporaryWearableassets. It does not touch anAppearance.
Still to be checked in the game
Apply,LiveandRandomiseon a running player: the body and clothes afterLoadCharacter.IsAvailableon the main menu and during scene changes.
The UI kit (ModUi, UiTheme, UiSprites) is on the UI kit page.