@adatechnology/game-characters-three 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/index.d.ts +9 -2
- package/dist/index.global.js +19 -15
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +13 -9
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -29,7 +29,7 @@ time.value += dt
|
|
|
29
29
|
- `createFluidsBridge({ sink, film })` and `bloodLookOfAge(age)`: a `body` event throws drops of blood into the film, in the fresh and dry colours of the infection age (`ages.json`), `old_blood` for the thin ones.
|
|
30
30
|
|
|
31
31
|
**Player avatar.** `createPlayerAvatar(THREE, options?)` is the team game's player body: the rich human of the Bestiário/Elenco built from `presets/player-avatar.json`
|
|
32
|
-
(`{ type: 'Lento', human: true, seed: 4242, skin, hs: 'short', hair }`, shirt `0x3d5a73`), overridable with `{ spec, shirtColor, clips, blend, fallSeconds }
|
|
32
|
+
(`{ type: 'Lento', human: true, seed: 4242, skin, hs: 'short', hair }`, shirt `0x3d5a73`), overridable with `{ spec, shirtColor, clips, blend, fallSeconds, cascade, clipCascade }` (`clipCascade`, default 5: how many times faster the joints follow while a clip that does not loop plays, so a strike reaches its pose by its impact).
|
|
33
33
|
|
|
34
34
|
```ts
|
|
35
35
|
const player = createPlayerAvatar(THREE)
|
|
@@ -41,16 +41,16 @@ player.play('grabbed_bitten_human', { onEvent: (event) => event.type === 'impact
|
|
|
41
41
|
|
|
42
42
|
Crouch, down and the aimed arms are derived literally from the game's GPU pose code (`BODY_CHUNK`, lines quoted in `src/player/avatar.ts`): crouch is thigh `-0.95`, knee `1.9`, trunk `+0.25`, hip `-0.35`;
|
|
43
43
|
down turns the body about the hip (`±1.5` rad, face up or down by the phase) with the limbs of pose 2; `armed` follows `(pitch + 1) / 3`. `play` overlays a `PoseClip` of `@adatechnology/game-characters`
|
|
44
|
-
(`channels: 'upper'` leaves the legs to the gait) with a 0.12 s blend, and fires its events (`impact`, `phase`) once through `onEvent` and `on('event')`. `hand('R')` is the weapon hand (the puppet's right, at -x).
|
|
44
|
+
(`channels: 'upper'` leaves the legs to the gait) with a 0.12 s blend, and fires its events (`impact`, `phase`) once through `onEvent` and `on('event')`. `hand('R')` is the weapon hand (the puppet's right, at -x). A pose clip's `armL`/`legL` channels move that side (`arms[1]`/`legs[0]`), so `punch_right` and `front_kick` of `ATTACK_CLIPS` drive `armL` and `legL`, the same arm and leg the first-person view shows.
|
|
45
45
|
|
|
46
46
|
**In the test room.** `room.setAvatar(toRoomAvatar(player))` shows the body at the player's feet (`toRoomAvatar` only flips the pitch: the room counts a look up as positive, the team game a look down) and V switches between the first and the third person of
|
|
47
47
|
`@adatechnology/game-test-room`. `createAvatarTestModule({ avatar, camera, state })` is the module of the area `avatar_area` (`AVATAR_AREA_ID`): bench buttons for the view (1st/3rd person), crouch, stand, fall, get up, armed on/off,
|
|
48
48
|
one button per registered clip (`Tocar: grabbed_bitten_human`, `grab_bite_zombie`, and with `clips: [...POSE_CLIPS, ...ATTACK_CLIPS]` the punch, shove, kick and the recoils; `clipLocales` gives the phase texts) and stop; metrics for the camera, the clip playing, the last clip event and the joints. Every action is a JSON line in the room log (`{"type":"avatar","action":"play_clip","clip":...}`), clip events included, and an `impact` event also gets its own `{"action":"impact","clip":...,"t":...}` line.
|
|
49
49
|
The module takes the body, the view (`camera: { mode, set }`) and the state (`state: { set }`) by option and imports only `@adatechnology/game-contracts`.
|
|
50
50
|
|
|
51
|
-
`toRoomAvatar` also gives the room the first-person arms (`firstPerson: false` for none) and `strike(clipId)`, which plays the attack clip on the body and the first-person clip of the same id on the arms and answers the seconds to the impact (the same in both views).
|
|
51
|
+
`toRoomAvatar` also gives the room the first-person arms (`firstPerson: false` for none) and `strike(clipId)`, which plays the attack clip on the body and the first-person clip of the same id on the arms and answers the seconds to the impact (the same in both views). `strike(clip, { leg: 'left' })` kicks with the left leg and `strike(clip, { hand: 'left' })` punches with the left hand: the mirror of the clip on the body (`mirrorPoseClip`, id `<clip>_left`) and on the arms.
|
|
52
52
|
|
|
53
|
-
**First-person arms.** `createFirstPersonArms(THREE, { colors?, rig?, stances?, clips?, blend?, bob? })` builds sleeves (upper arm, forearm, elbow, cuff) and hands of skin (palm, four fingers of three segments, a thumb that close on a grip) drawn in the camera's space, plus the right leg that only shows during a kick. `arms.update(dt, { camera, targets, stance, speed, running, crouch, contact? })`: the `relaxed` and `fists` stances are rest poses (`FIRST_PERSON_STANCES`, from `presets/first-person-arms.json`), `hold` follows the `HandTargets` of `game-contracts` (world space) with a two-bone IK from fixed shoulders, the elbow bending down and out; a target out of reach is clamped (the shoulder moves `reachShift` toward it first). `arms.play('punch_right' | 'shove_two_hands' | 'front_kick')` overlays a `FirstPersonClip` (`FIRST_PERSON_CLIPS` of `game-characters-people`, same durations and impact seconds as the attack clips); `arms.on('event', fn)` gives its `impact`; `arms.handPose(side)` reads where a hand is drawn. `contact(origin, direction, maxDistance)` (optional; distance to the first surface or null) makes the hands and the kick foot stop a few millimetres before a surface along the camera forward (the elbow bends); `arms.play('front_kick', { leg: 'left' })` kicks with the left leg. Hand frame: x wrist to knuckles, y back of the hand, z little-finger side; a left hand is the right one mirrored.
|
|
53
|
+
**First-person arms.** `createFirstPersonArms(THREE, { colors?, rig?, stances?, clips?, blend?, bob? })` builds sleeves (upper arm, forearm, elbow, cuff) and hands of skin (palm, four fingers of three segments, a thumb that close on a grip) drawn in the camera's space, plus the right leg that only shows during a kick. `arms.update(dt, { camera, targets, stance, speed, running, crouch, contact? })`: the `relaxed` and `fists` stances are rest poses (`FIRST_PERSON_STANCES`, from `presets/first-person-arms.json`), `hold` follows the `HandTargets` of `game-contracts` (world space) with a two-bone IK from fixed shoulders, the elbow bending down and out; a target out of reach is clamped (the shoulder moves `reachShift` toward it first). `arms.play('punch_right' | 'shove_two_hands' | 'front_kick')` overlays a `FirstPersonClip` (`FIRST_PERSON_CLIPS` of `game-characters-people`, same durations and impact seconds as the attack clips); `arms.on('event', fn)` gives its `impact`; `arms.handPose(side)` reads where a hand is drawn. `contact(origin, direction, maxDistance)` (optional; distance to the first surface or null) makes the hands and the kick foot stop a few millimetres before a surface along the camera forward (the elbow bends); `arms.play('front_kick', { leg: 'left' })` kicks with the left leg and `arms.play('punch_right', { hand: 'left' })` punches with the left hand (the hands of the clip in a mirror). Hand frame: x wrist to knuckles, y back of the hand, z little-finger side; a left hand is the right one mirrored.
|
|
54
54
|
|
|
55
55
|
## Geometry
|
|
56
56
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1040,6 +1040,11 @@ type PlayerAvatarOptions = {
|
|
|
1040
1040
|
readonly fallSeconds?: number;
|
|
1041
1041
|
/** Smooth the joints with the Bestiário cascade (default true). */
|
|
1042
1042
|
readonly cascade?: boolean;
|
|
1043
|
+
/**
|
|
1044
|
+
* How many times faster the cascade follows while a clip that does not loop plays: a strike is over in a third of a second, and at the walk's pace the joints would
|
|
1045
|
+
* still be on their way when it ends (a punch that never reaches the shoulder). 1 keeps the walk's pace. Default 5.
|
|
1046
|
+
*/
|
|
1047
|
+
readonly clipCascade?: number;
|
|
1043
1048
|
};
|
|
1044
1049
|
type AvatarEvent = PoseClipEvent & {
|
|
1045
1050
|
clip: string;
|
|
@@ -1206,9 +1211,10 @@ type FirstPersonArms = {
|
|
|
1206
1211
|
/** Add it to the scene: it follows the camera by itself. */
|
|
1207
1212
|
root: THREE_NS.Group;
|
|
1208
1213
|
/** Starts a clip; false when no registered clip has that id. The previous clip is dropped. */
|
|
1209
|
-
/** `leg: 'left'` plays the foot of the clip (the right foot) as the left one, in a mirror. */
|
|
1214
|
+
/** `leg: 'left'` plays the foot of the clip (the right foot) as the left one, in a mirror; `hand: 'left'` plays the hands in a mirror (the right hand's punch thrown by the left). */
|
|
1210
1215
|
play(clip: string, options?: {
|
|
1211
1216
|
leg?: 'left' | 'right';
|
|
1217
|
+
hand?: 'left' | 'right';
|
|
1212
1218
|
}): boolean;
|
|
1213
1219
|
stop(): void;
|
|
1214
1220
|
/** The clip being played, or null. */
|
|
@@ -1263,6 +1269,7 @@ type RoomAvatarLike = {
|
|
|
1263
1269
|
/** Plays a strike on the body and the arms and answers the seconds to its impact (`RoomAvatar.strike`). */
|
|
1264
1270
|
strike(clipId: string, options?: {
|
|
1265
1271
|
leg?: 'left' | 'right';
|
|
1272
|
+
hand?: 'left' | 'right';
|
|
1266
1273
|
}): number | null;
|
|
1267
1274
|
/** The avatar it wraps. */
|
|
1268
1275
|
readonly avatar: PlayerAvatar;
|
|
@@ -1281,7 +1288,7 @@ type ToRoomAvatarOptions = {
|
|
|
1281
1288
|
* Fits the player's body to the test room's `RoomAvatar` (`room.setAvatar(toRoomAvatar(createPlayerAvatar(THREE)))`). It changes the sign of the pitch: the room
|
|
1282
1289
|
* counts a look up as positive, the team game's `view.pitch` (what `AvatarUpdate.pitch` reads) counts a look down as positive. It also gives the room the first-person arms, dressed in the
|
|
1283
1290
|
* colours of the body (`firstPerson: false` for none), and `strike(clipId)`: the strike plays on the body (the attack clips) and on the arms (the first-person clips), and answers the seconds to its
|
|
1284
|
-
* impact, which are the same in both views.
|
|
1291
|
+
* impact, which are the same in both views. A kick with the left leg (`leg: 'left'`) or a punch with the left hand (`hand: 'left'`) plays the mirror of the clip written for the right.
|
|
1285
1292
|
*/
|
|
1286
1293
|
declare const toRoomAvatar: (avatar: PlayerAvatar, options?: ToRoomAvatarOptions) => RoomAvatarLike;
|
|
1287
1294
|
|
package/dist/index.global.js
CHANGED
|
@@ -8640,6 +8640,7 @@ var GameCharactersThree = (() => {
|
|
|
8640
8640
|
var TWO_PI2 = Math.PI * 2;
|
|
8641
8641
|
var MAX_GAIT = 1.4;
|
|
8642
8642
|
var CLIP_BLEND = 0.12;
|
|
8643
|
+
var CLIP_CASCADE = 5;
|
|
8643
8644
|
var FALL_SECONDS2 = 0.3;
|
|
8644
8645
|
var HIP_HEIGHT = 0.95;
|
|
8645
8646
|
var WALK_ARM = 0.28;
|
|
@@ -8679,6 +8680,7 @@ var GameCharactersThree = (() => {
|
|
|
8679
8680
|
const blendDefault = options.blend ?? CLIP_BLEND;
|
|
8680
8681
|
const fallSeconds = options.fallSeconds ?? FALL_SECONDS2;
|
|
8681
8682
|
const smooth = options.cascade ?? true;
|
|
8683
|
+
const clipCascade = options.clipCascade ?? CLIP_CASCADE;
|
|
8682
8684
|
const root = new THREE.Group();
|
|
8683
8685
|
const tilt = new THREE.Group();
|
|
8684
8686
|
tilt.add(body);
|
|
@@ -8969,14 +8971,14 @@ var GameCharactersThree = (() => {
|
|
|
8969
8971
|
}
|
|
8970
8972
|
placeBody(fall);
|
|
8971
8973
|
stepClip(dt, down);
|
|
8972
|
-
if (smooth) animator.cascade(rig, dt);
|
|
8974
|
+
if (smooth) animator.cascade(rig, run && !run.clip.loop ? dt * clipCascade : dt);
|
|
8973
8975
|
const clipId = run ? run.clip.id : null;
|
|
8974
8976
|
const ph = cycles * TWO_PI2;
|
|
8975
8977
|
hands.forEach((hand) => {
|
|
8976
8978
|
const isRight = hand.side < 0;
|
|
8977
8979
|
const holdRight = isRight && (!!state.holding || !!state.itemUse);
|
|
8978
8980
|
const holdLeft = !isRight && (state.itemUse?.id === "open" || state.itemUse?.id === "close");
|
|
8979
|
-
const grip = clipId
|
|
8981
|
+
const grip = clipId?.startsWith("punch_") ? 1.4 : clipId === "shove_two_hands" ? 0.15 : state.armed || holdRight || holdLeft ? 1.22 : running ? 0.85 : 0.12;
|
|
8980
8982
|
const wristSwing = holdRight || state.armed || running ? 0 : -Math.min(1, gait) * Math.sin(ph - 0.6) * 0.12 * hand.side;
|
|
8981
8983
|
setHandGrip(hand, grip, wristSwing);
|
|
8982
8984
|
});
|
|
@@ -9022,9 +9024,9 @@ var GameCharactersThree = (() => {
|
|
|
9022
9024
|
channels: "upper",
|
|
9023
9025
|
keys: [
|
|
9024
9026
|
{ t: 0, pose: { lean: 0, twist: 0, side: 0, headX: 0, headY: 0, headZ: 0, armR: 0.05, armL: -0.1, elbR: -0.2, elbL: -0.2, armRz: 0, armLz: 0 } },
|
|
9025
|
-
{ t: 0.1, pose: { lean: 0.04, twist:
|
|
9026
|
-
{ t: 0.16, pose: { lean: 0.
|
|
9027
|
-
{ t: 0.22, pose: { lean: 0.
|
|
9027
|
+
{ t: 0.1, pose: { lean: 0.04, twist: 0.3, side: 0, headX: 0, headY: -0.24, headZ: 0, armR: -0.9, armL: -0.35, elbR: -1.9, elbL: -2.1, armRz: -0.2, armLz: 0.15 } },
|
|
9028
|
+
{ t: 0.16, pose: { lean: 0.16, twist: -0.35, side: 0, headX: 0, headY: 0.3, headZ: 0, armR: -0.95, armL: -1.55, elbR: -1.95, elbL: -0.05, armRz: -0.25, armLz: 0.3 } },
|
|
9029
|
+
{ t: 0.22, pose: { lean: 0.13, twist: -0.3, side: 0, headX: 0, headY: 0.25, headZ: 0, armR: -0.9, armL: -1.45, elbR: -1.9, elbL: -0.2, armRz: -0.22, armLz: 0.26 } },
|
|
9028
9030
|
{ t: 0.35, pose: { lean: 0, twist: 0, side: 0, headX: 0, headY: 0, headZ: 0, armR: 0.05, armL: -0.1, elbR: -0.2, elbL: -0.2, armRz: 0, armLz: 0 } }
|
|
9029
9031
|
],
|
|
9030
9032
|
events: [
|
|
@@ -9062,9 +9064,9 @@ var GameCharactersThree = (() => {
|
|
|
9062
9064
|
channels: "full",
|
|
9063
9065
|
keys: [
|
|
9064
9066
|
{ t: 0, pose: { lean: 0, twist: 0, side: 0, drop: 0, headX: 0, headY: 0, headZ: 0, armR: 0.05, armL: -0.1, elbR: -0.2, elbL: -0.2, armRz: 0, armLz: 0, legL: -0.05, legR: 0.05, kneeL: 0.05, kneeR: 0.05 } },
|
|
9065
|
-
{ t: 0.18, pose: { lean: -0.1, twist: 0, side: 0.04, drop: -0.05, headX: 0.05, headY: 0, headZ: 0, armR:
|
|
9066
|
-
{ t: 0.32, pose: { lean: -0.22, twist: 0, side: 0.04, drop: -0.06, headX: 0.1, headY: 0, headZ: 0, armR:
|
|
9067
|
-
{ t: 0.42, pose: { lean: -0.18, twist: 0, side: 0.03, drop: -0.05, headX: 0.08, headY: 0, headZ: 0, armR:
|
|
9067
|
+
{ t: 0.18, pose: { lean: -0.1, twist: 0, side: -0.04, drop: -0.05, headX: 0.05, headY: 0, headZ: 0, armR: 0.4, armL: -0.5, elbR: -0.4, elbL: -0.5, armRz: -0.1, armLz: 0.1, legL: -1.1, legR: 0.1, kneeL: 1.8, kneeR: 0.35 } },
|
|
9068
|
+
{ t: 0.32, pose: { lean: -0.22, twist: 0, side: -0.04, drop: -0.06, headX: 0.1, headY: 0, headZ: 0, armR: 0.7, armL: -0.7, elbR: -0.3, elbL: -0.6, armRz: -0.15, armLz: 0.15, legL: -1.45, legR: 0.12, kneeL: 0.1, kneeR: 0.2 } },
|
|
9069
|
+
{ t: 0.42, pose: { lean: -0.18, twist: 0, side: -0.03, drop: -0.05, headX: 0.08, headY: 0, headZ: 0, armR: 0.5, armL: -0.5, elbR: -0.3, elbL: -0.5, armRz: -0.1, armLz: 0.1, legL: -1.2, legR: 0.1, kneeL: 1.2, kneeR: 0.3 } },
|
|
9068
9070
|
{ t: 0.6, pose: { lean: 0, twist: 0, side: 0, drop: 0, headX: 0, headY: 0, headZ: 0, armR: 0.05, armL: -0.1, elbR: -0.2, elbL: -0.2, armRz: 0, armLz: 0, legL: -0.05, legR: 0.05, kneeL: 0.05, kneeR: 0.05 } }
|
|
9069
9071
|
],
|
|
9070
9072
|
events: [
|
|
@@ -9670,10 +9672,11 @@ var GameCharactersThree = (() => {
|
|
|
9670
9672
|
const palmAt = new THREE.Vector3();
|
|
9671
9673
|
const fingers = new THREE.Vector3();
|
|
9672
9674
|
const towards = new THREE.Vector3();
|
|
9673
|
-
const fromChannel = (channel, side, base, out) => {
|
|
9675
|
+
const fromChannel = (channel, side, base, out, from = side) => {
|
|
9674
9676
|
out.p.set(...channel.p);
|
|
9675
9677
|
out.q.copy(quaternionOf(channel.r));
|
|
9676
|
-
|
|
9678
|
+
const rightNumbers = from === "R" || channel.mirror;
|
|
9679
|
+
if (rightNumbers !== (side === "R")) {
|
|
9677
9680
|
out.p.x = -out.p.x;
|
|
9678
9681
|
out.q.copy(mirrorQuaternion(THREE, out.q));
|
|
9679
9682
|
}
|
|
@@ -9738,7 +9741,7 @@ var GameCharactersThree = (() => {
|
|
|
9738
9741
|
play(id, options2) {
|
|
9739
9742
|
const clip = clips.get(id);
|
|
9740
9743
|
if (!clip) return false;
|
|
9741
|
-
run = { clip, time: 0, left: options2?.leg === "left" };
|
|
9744
|
+
run = { clip, time: 0, left: options2?.leg === "left", swap: options2?.hand === "left" };
|
|
9742
9745
|
fire(clip, -1e-9, 0);
|
|
9743
9746
|
return true;
|
|
9744
9747
|
},
|
|
@@ -9786,10 +9789,11 @@ var GameCharactersThree = (() => {
|
|
|
9786
9789
|
arm.blend = blendSeconds <= 0 ? 1 : Math.min(1, arm.blend + dt / blendSeconds);
|
|
9787
9790
|
if (arm.blend >= 1) copyPose(arm.shown, desired);
|
|
9788
9791
|
else mixPose(THREE, arm.shown, arm.from, desired, ease2(arm.blend));
|
|
9789
|
-
const
|
|
9792
|
+
const from = run?.swap ? side === "R" ? "L" : "R" : side;
|
|
9793
|
+
const channel = sample?.[from];
|
|
9790
9794
|
let pose;
|
|
9791
9795
|
if (channel && channel.w > 0) {
|
|
9792
|
-
fromChannel(channel, side, arm.shown, channelPose);
|
|
9796
|
+
fromChannel(channel, side, arm.shown, channelPose, from);
|
|
9793
9797
|
mixPose(THREE, mixed, arm.shown, channelPose, channel.w);
|
|
9794
9798
|
pose = mode === "target" ? mixed : keepOutside(mixed, handReach(mixed));
|
|
9795
9799
|
} else pose = mode === "target" ? arm.shown : keepOutside(arm.shown, handReach(arm.shown));
|
|
@@ -9878,9 +9882,9 @@ var GameCharactersThree = (() => {
|
|
|
9878
9882
|
strike: (clipId, strikeOptions) => {
|
|
9879
9883
|
const clip = strikes.get(clipId);
|
|
9880
9884
|
if (!clip) return null;
|
|
9881
|
-
const left = strikeOptions?.leg === "left";
|
|
9885
|
+
const left = strikeOptions?.leg === "left" || strikeOptions?.hand === "left";
|
|
9882
9886
|
avatar.play(left ? leftOf(clip) : clip);
|
|
9883
|
-
arms?.play(clipId, left ? { leg: "left" } : void 0);
|
|
9887
|
+
arms?.play(clipId, strikeOptions?.leg === "left" ? { leg: "left" } : strikeOptions?.hand === "left" ? { hand: "left" } : void 0);
|
|
9884
9888
|
return impactOf(clip);
|
|
9885
9889
|
}
|
|
9886
9890
|
};
|