face manager

Unimplemented
manages face

the basics

they're called dynamic faces.
on they're own they don't do anything. there's no default face...YOU have to pretty much carve it out.
a face has "face elements", but from here out i'll just refer to them as elements.

FaceDrawer

lets you draw on the face. while there is the canvas context, you should use this as it lets the DynamicFace more easily mess with it.
it's usually returned by some sort of method.
  • ts
this.draw().color("red").rotate(20).rect(10, 10, 20, 20);

// you can also store it as a variable and use it multiple times etc.

const rotated = this.draw().color("red").rotate(20);

rotated.rect(10, 10, 20, 20);
rotated.rect(30, 30, 20, 20);

colors

.color() doesn't specifically say it's for the background or outline. it actually sets both of them based on a map which you can find in .colors.
  • ts
drawer.colors.red = (drawer, color) => {
	drawer.backgroundColor("black").outlineColor("#ff0000");
};

// you can also give a fallback
drawer.colorsFallback = (drawer, color) => {
	d.backgroundColor("black").outlineColor(color);
};

FaceElement

Note
i originally considered them simply being a function, but this would complicate identifying what type of face element they are.
a FaceElement is a class. you extend it.
first process() is called, then draw() is called. if both are to be called then the order is guaranteed. what isn't guaranteed is if both will be called.
so. in draw() you should only draw, and in process() you should not render anything.
  • ts
class MyFaceElement extends FaceElement {
	override draw() {
		this.d().color("red").rect(10, 10, 10, 10);
	}

	override process() {
		// whatever you want.
	}
}
you can also:
  • ts
faceElement.x++;
faceElement.y++;
faceElement.rotate += 90;
faceElement.scaleX += 0.5;
faceElement.scaleY += 0.5;
faceElement.opacity -= 0.1;
faceElement.update();
and to keep track of your state, just add more properties to the class. people will be expected to call .update() whenever it's changed.

DynamicFace

the one and only
  • ts
class DynamicFace {
	// the colors FaceDrawers will all inherit
	colors;
	fallbackColor;

	// the elements. isn't read only; you can mutate.
	elements;
	// adds an element. use this instead of just pushing into .elements
	add();

	// like actually renders the dynamicFace
	render();

	// yk. set to true by facelements when update is called on them
	dirty;
	// just sets dirty to true and nothing else
	update();

	// returns a facedrawer
	d();
}

creating a face

pretty simple. just extend DynamicFace and mutate the class however you want.
  • ts
class MyFace extends DynamicFace {
	constructor() {
		// maybe it should be different...like maybe .add initializes the class so the class itself doesn't have to take anything in from its constructor
		this.add(new Eye(10, 5));
		this.add(new Eye(20, 5));
	}
}

standardization

so many faces, so much to manage. right? well good thing this lil thing comes with a way to easily manage between them.
in the old system this was the DynamicDabricFaceManager i believe. but here, it's in a single class instead of two that the old dabric-only system had for some reason...
anyway.
whenever you use .add() on a class, dynamicface processes it and tries to predict what it is. this data's stored on the faceelement itself.
for example, with some lad with like multiple eyes (referencing a certain somebody):
diagram showing it, with the two left eyes being mark as such, two right eyes being marked as such, etc.
you find this data in faceElement.type. it could look like:
  • ts
{
	type: FaceElementType.Eye,
	position: FaceElementPosition.Left
}
you can also edit it.

facial expressions

when face look
with the standardization above, you can have facial expressions that work across different character setups.
to set one:
  • ts
dynamicFace.express(MyFaceExpression);
to make one:
  • ts
class MyFaceExpression extends FaceExpression {
	constructor() {}
}

snapshots

lets you take a snapshot of how the face elements are so you can tween between them etc.
it's not as complex as it seems.
  • ts
// literally just dynamicFace's children, but the classes are newly initialized classes with all of its properties carried over
const snapshot = dynamicFace.takeSnapshot();
// to be more direct:
const snapshot = DynamicFace.cloneChildren(dynamicFace.children);

dynamicFace.restoreSnapshot(snapshot);
// to be more direct:
dynamicFace.children = DynamicFace.cloneChildren(snapshot);

tweening

first, make sure what you want to be tweened has the following decorator function on it.
  • ts
class MyFaceElement extends FaceElement {
	@(this.tween())
	squint = 0;

	// accepts tween options :DD
	@(this.tween({ duration: 200 }))
	another = 0;
}
now take a snapshot, and tell dynamicFace to tween to some other snapshot.
  • ts
dynamicFace.tweenToSnapshot(snapshot);

internally?

is this a little inefficient?
the decorator makes a tween just for the property, and dynamicFace does a call like faceElement.tweenProperty("squint", value).
:3

the issue with eyebrows

so eyebrows are great. in the old system, they could do this:
dabric's face normal, and then dabric's face annoyed. the annoyed face has eyebrows that go down, making the top part of the eyes disappear.
this was possible because there were no facelements. it was just one big class managing everything.
but now that we aren't, how do we have them like communicate i guess?
i guess the eyes could just manage the eyebrows instead of eyebrows having their own seperate facelement. buti feel like i would get into some issues at some point.
well with process(), i think it's ok.