Skip to content

Commit 4bd8d4b

Browse files
authored
Add crouch and hit animation (#177)
1 parent a355a04 commit 4bd8d4b

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

examples/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,40 @@ <h1>Animation</h1>
5555
<label><input type="radio" id="animation_run" name="animation" value="run" /> Run</label>
5656
<label><input type="radio" id="animation_fly" name="animation" value="fly" /> Fly</label>
5757
<label><input type="radio" id="animation_wave" name="animation" value="wave" /> Wave</label>
58+
<label><input type="radio" id="animation_crouch" name="animation" value="crouch" /> Crouch</label>
59+
<label><input type="radio" id="animation_hit" name="animation" value="hit" /> Hit</label>
5860
</div>
5961
<label class="control">Speed: <input id="animation_speed" type="number" value="1" step="0.1" size="3" /></label>
6062
<button id="animation_pause_resume" type="button" class="control">Pause / Resume</button>
63+
64+
<br />
65+
66+
<label class="control" id="crouch_setting" style="display: none"
67+
>Crouch settings:
68+
<label><input type="checkbox" id="run_once" name="crouch_setting_item" value="run_once" />Run once</label>
69+
<label
70+
><input type="checkbox" id="show_progress" name="crouch_setting_item" value="show_progress" />Show
71+
progress</label
72+
>
73+
<label
74+
><input
75+
type="checkbox"
76+
id="add_hitting_animation"
77+
name="crouch_setting_item"
78+
value="add_hit_animation"
79+
/>Add hitting animation</label
80+
>
81+
<br /><label class="control" id="hit_speed_label" style="display: none"
82+
>Hit speed :<input
83+
id="hit_speed"
84+
type="number"
85+
value=""
86+
step="0.1"
87+
size="3"
88+
placeholder="default following crouch speed"
89+
style="width:190px"
90+
/></label>
91+
</label>
6192
</div>
6293

6394
<div class="control-section">

examples/main.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const availableAnimations = {
99
run: new skinview3d.RunningAnimation(),
1010
fly: new skinview3d.FlyingAnimation(),
1111
wave: new skinview3d.WaveAnimation(),
12+
crouch: new skinview3d.CrouchAnimation(),
13+
hit: new skinview3d.HitAnimation(),
1214
};
1315

1416
let skinViewer: skinview3d.SkinViewer;
@@ -156,6 +158,9 @@ function initializeControls() {
156158
.addEventListener("change", e => (skinViewer.autoRotateSpeed = e.target.value));
157159
for (const el of document.querySelectorAll('input[type="radio"][name="animation"]')) {
158160
el.addEventListener("change", e => {
161+
document.getElementById("crouch_setting").style.display = document.getElementById("animation_crouch").checked
162+
? "block"
163+
: "none";
159164
if (e.target.value === "") {
160165
skinViewer.animation = null;
161166
} else {
@@ -164,10 +169,60 @@ function initializeControls() {
164169
}
165170
});
166171
}
172+
document.getElementById("animation_crouch").addEventListener("change", () => {
173+
for (const el of document.querySelectorAll('input[type="checkbox"][name="crouch_setting_item"]')) {
174+
el.checked = false;
175+
document.getElementById("hit_speed").value = "";
176+
document.getElementById("hit_speed_label").style.display = "none";
177+
}
178+
});
179+
const crouchSettings = {
180+
run_once: value => {
181+
skinViewer.animation.runOnce = value;
182+
},
183+
show_progress: value => {
184+
skinViewer.animation.showProgress = value;
185+
},
186+
add_hit_animation: value => {
187+
document.getElementById("hit_speed_label").style.display = value ? "block" : "none";
188+
const hitSpeed = document.getElementById("hit_speed").value;
189+
if (value) {
190+
if (hitSpeed === "") {
191+
skinViewer.animation.addHitAnimation();
192+
} else {
193+
skinViewer.animation.addHitAnimation(hitSpeed);
194+
}
195+
}
196+
},
197+
};
198+
199+
const updateCrouchAnimation = () => {
200+
skinViewer.animation = new skinview3d.CrouchAnimation();
201+
skinViewer.animation.speed = document.getElementById("animation_speed").value;
202+
for (const el of document.querySelectorAll('input[type="checkbox"][name="crouch_setting_item"]')) {
203+
crouchSettings[el.value](el.checked);
204+
}
205+
};
206+
for (const el of document.querySelectorAll('input[type="checkbox"][name="crouch_setting_item"]')) {
207+
el.addEventListener("change", e => {
208+
updateCrouchAnimation();
209+
});
210+
}
211+
document.getElementById("hit_speed").addEventListener("change", e => {
212+
updateCrouchAnimation();
213+
});
214+
167215
document.getElementById("animation_speed").addEventListener("change", e => {
168216
if (skinViewer.animation !== null) {
169217
skinViewer.animation.speed = e.target.value;
170218
}
219+
if (
220+
document.getElementById("animation_crouch").checked &&
221+
document.getElementById("add_hitting_animation").checked &&
222+
document.getElementById("hit_speed").value == ""
223+
) {
224+
updateCrouchAnimation();
225+
}
171226
});
172227
document
173228
.getElementById("control_rotate")

src/animation.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,3 +208,113 @@ export class WaveAnimation extends PlayerAnimation {
208208
targetArm.rotation.z = Math.sin(t) * 0.5;
209209
}
210210
}
211+
export class CrouchAnimation extends PlayerAnimation {
212+
/**
213+
* Whether to show the progress of animation.
214+
* Because there is no progress in the crouch animation in Minecraft, the default value here is false.
215+
* @defaultValue `false`
216+
*/
217+
showProgress: boolean = false;
218+
/**
219+
* Whether to run this animation once.
220+
*
221+
* @defaultValue `false`
222+
*/
223+
runOnce: boolean = false;
224+
225+
private isRunningHitAnimation: boolean = false;
226+
private hitAnimationSpeed: number = 1;
227+
/**
228+
* Add the hit animation.
229+
*
230+
* @param speed - The speed of hit animation and the default is follow the speed of CrouchAnimation.But if the speed of CrouchAnimation is 0,this animation will not run.
231+
*/
232+
addHitAnimation(speed: number = this.speed): void {
233+
this.isRunningHitAnimation = true;
234+
this.hitAnimationSpeed = speed;
235+
}
236+
private erp: number = 0; //elytra rotate progress
237+
private isCrouched: any;
238+
protected animate(player: PlayerObject): void {
239+
let pr = this.progress * 8;
240+
if (pr === 0) {
241+
this.isCrouched = undefined;
242+
}
243+
if (this.runOnce) {
244+
pr = clamp(pr, -1, 1);
245+
}
246+
if (!this.showProgress) {
247+
pr = Math.floor(pr);
248+
}
249+
player.skin.body.rotation.x = 0.4537860552 * Math.abs(Math.sin((pr * Math.PI) / 2));
250+
player.skin.body.position.z =
251+
1.3256181 * Math.abs(Math.sin((pr * Math.PI) / 2)) - 3.4500310377 * Math.abs(Math.sin((pr * Math.PI) / 2));
252+
player.skin.body.position.y = -6 - 2.103677462 * Math.abs(Math.sin((pr * Math.PI) / 2));
253+
player.cape.position.y = 8 - 1.851236166577372 * Math.abs(Math.sin((pr * Math.PI) / 2));
254+
player.cape.rotation.x = (10.8 * Math.PI) / 180 + 0.294220265771 * Math.abs(Math.sin((pr * Math.PI) / 2));
255+
player.cape.position.z =
256+
-2 + 3.786619432 * Math.abs(Math.sin((pr * Math.PI) / 2)) - 3.4500310377 * Math.abs(Math.sin((pr * Math.PI) / 2));
257+
player.elytra.position.x = player.cape.position.x;
258+
player.elytra.position.y = player.cape.position.y;
259+
player.elytra.position.z = player.cape.position.z;
260+
player.elytra.rotation.x = player.cape.rotation.x - (10.8 * Math.PI) / 180;
261+
const pr1 = this.progress / this.speed;
262+
if (Math.abs(Math.sin((pr * Math.PI) / 2)) === 1) {
263+
this.erp = !this.isCrouched ? pr1 : this.erp;
264+
this.isCrouched = true;
265+
player.elytra.leftWing.rotation.z =
266+
0.26179944 + 0.4582006 * Math.abs(Math.sin((Math.min(pr1 - this.erp, 1) * Math.PI) / 2));
267+
player.elytra.updateRightWing();
268+
} else if (this.isCrouched !== undefined) {
269+
this.erp = this.isCrouched ? pr1 : this.erp;
270+
player.elytra.leftWing.rotation.z =
271+
0.72 - 0.4582006 * Math.abs(Math.sin((Math.min(pr1 - this.erp, 1) * Math.PI) / 2));
272+
player.elytra.updateRightWing();
273+
this.isCrouched = false;
274+
}
275+
player.skin.head.position.y = -3.618325234674 * Math.abs(Math.sin((pr * Math.PI) / 2));
276+
player.skin.leftArm.position.z =
277+
3.618325234674 * Math.abs(Math.sin((pr * Math.PI) / 2)) - 3.4500310377 * Math.abs(Math.sin((pr * Math.PI) / 2));
278+
player.skin.rightArm.position.z = player.skin.leftArm.position.z;
279+
player.skin.leftArm.rotation.x = 0.410367746202 * Math.abs(Math.sin((pr * Math.PI) / 2));
280+
player.skin.rightArm.rotation.x = player.skin.leftArm.rotation.x;
281+
player.skin.leftArm.rotation.z = 0.1;
282+
player.skin.rightArm.rotation.z = -player.skin.leftArm.rotation.z;
283+
player.skin.leftArm.position.y = -2 - 2.53943318 * Math.abs(Math.sin((pr * Math.PI) / 2));
284+
player.skin.rightArm.position.y = player.skin.leftArm.position.y;
285+
player.skin.rightLeg.position.z = -3.4500310377 * Math.abs(Math.sin((pr * Math.PI) / 2));
286+
player.skin.leftLeg.position.z = player.skin.rightLeg.position.z;
287+
if (this.isRunningHitAnimation) {
288+
const pr2 = this.progress;
289+
let t = (this.progress * 18 * this.hitAnimationSpeed) / this.speed;
290+
if (this.speed == 0) {
291+
t = 0;
292+
}
293+
const isCrouching = Math.abs(Math.sin((pr2 * Math.PI) / 2)) === 1;
294+
player.skin.rightArm.rotation.x =
295+
-0.4537860552 + 2 * Math.sin(t + Math.PI) * 0.3 - (isCrouching ? 0.4537860552 : 0);
296+
const basicArmRotationZ = 0.01 * Math.PI + 0.06;
297+
player.skin.rightArm.rotation.z = -Math.cos(t) * 0.403 + basicArmRotationZ;
298+
player.skin.body.rotation.y = -Math.cos(t) * 0.06;
299+
player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.077 + (isCrouching ? 0.47 : 0);
300+
player.skin.leftArm.rotation.z = -Math.cos(t) * 0.015 + 0.13 - (!isCrouching ? 0.05 : 0);
301+
if (!isCrouching) {
302+
player.skin.leftArm.position.z = Math.cos(t) * 0.3;
303+
player.skin.leftArm.position.x = 5 - Math.cos(t) * 0.05;
304+
}
305+
}
306+
}
307+
}
308+
export class HitAnimation extends PlayerAnimation {
309+
protected animate(player: PlayerObject): void {
310+
const t = this.progress * 18;
311+
player.skin.rightArm.rotation.x = -0.4537860552 * 2 + 2 * Math.sin(t + Math.PI) * 0.3;
312+
const basicArmRotationZ = 0.01 * Math.PI + 0.06;
313+
player.skin.rightArm.rotation.z = -Math.cos(t) * 0.403 + basicArmRotationZ;
314+
player.skin.body.rotation.y = -Math.cos(t) * 0.06;
315+
player.skin.leftArm.rotation.x = Math.sin(t + Math.PI) * 0.077;
316+
player.skin.leftArm.rotation.z = -Math.cos(t) * 0.015 + 0.13 - 0.05;
317+
player.skin.leftArm.position.z = Math.cos(t) * 0.3;
318+
player.skin.leftArm.position.x = 5 - Math.cos(t) * 0.05;
319+
}
320+
}

src/model.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,22 @@ export class SkinObject extends Group {
309309
this.rightArm.rotation.set(0, 0, 0);
310310
this.leftLeg.rotation.set(0, 0, 0);
311311
this.rightLeg.rotation.set(0, 0, 0);
312+
this.body.rotation.set(0, 0, 0);
313+
this.head.position.y = 0;
314+
this.body.position.y = -6;
315+
this.body.position.z = 0;
316+
this.rightArm.position.x = -5;
317+
this.rightArm.position.y = -2;
318+
this.rightArm.position.z = 0;
319+
this.leftArm.position.x = 5;
320+
this.leftArm.position.y = -2;
321+
this.leftArm.position.z = 0;
322+
this.rightLeg.position.x = -1.9;
323+
this.rightLeg.position.y = -12;
324+
this.rightLeg.position.z = -0.1;
325+
this.leftLeg.position.x = 1.9;
326+
this.leftLeg.position.y = -12;
327+
this.leftLeg.position.z = -0.1;
312328
}
313329
}
314330

@@ -510,6 +526,11 @@ export class PlayerObject extends Group {
510526
resetJoints(): void {
511527
this.skin.resetJoints();
512528
this.cape.rotation.x = CapeDefaultAngle;
529+
this.cape.position.y = 8;
530+
this.cape.position.z = -2;
531+
this.elytra.position.y = 8;
532+
this.elytra.position.z = -2;
533+
this.elytra.rotation.x = 0;
513534
this.elytra.resetJoints();
514535
}
515536
}

0 commit comments

Comments
 (0)