day 4
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { EasyEvent } from "@24tools/playable_template";
|
||||
import { AnimationAction, AnimationClip, AnimationMixer, Color, LoopOnce, LoopRepeat, Mesh, MeshBasicMaterial, Object3D, Vector3 } from "three";
|
||||
import { clone } from "three/examples/jsm/utils/SkeletonUtils";
|
||||
import { ThreeC } from "../../ThreeC";
|
||||
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
|
||||
export class Character {
|
||||
tObj: Object3D;
|
||||
animMixer: AnimationMixer;
|
||||
animationList: AnimationClip[] = [];
|
||||
|
||||
// isWalking: boolean = false;
|
||||
curClipAction: null | AnimationAction = null;
|
||||
animStopTimeout: null | NodeJS.Timeout = null
|
||||
|
||||
onAnimLoop: EasyEvent<{}> = new EasyEvent<{}>();
|
||||
onAnimFinish: EasyEvent<{}> = new EasyEvent<{}>();
|
||||
|
||||
constructor(prefab: GLTF, start_position = new Vector3()) {
|
||||
let tObj = clone(prefab.scene);
|
||||
|
||||
tObj.castShadow = true;
|
||||
let animMixer = new AnimationMixer(tObj);
|
||||
|
||||
animMixer.addEventListener('loop', () => {
|
||||
this.onAnimLoop.Invoke({});
|
||||
});
|
||||
animMixer.addEventListener('finished', () => {
|
||||
this.onAnimFinish.Invoke({});
|
||||
});
|
||||
|
||||
if (start_position) tObj.position.copy(start_position);
|
||||
|
||||
this.tObj = tObj;
|
||||
this.animMixer = animMixer;
|
||||
this.animationList = prefab.animations;
|
||||
|
||||
ThreeC.addToScene(tObj);
|
||||
ThreeC.addAnimMixer(animMixer);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
set AnimationSpeed(timeScale: number) {
|
||||
if (this.curClipAction)
|
||||
this.curClipAction.timeScale = timeScale;
|
||||
}
|
||||
|
||||
set AnimationWeight(weight: number) {
|
||||
if (this.curClipAction)
|
||||
this.curClipAction.weight = weight;
|
||||
}
|
||||
|
||||
playAnimation(anim_id: number, one_time: boolean = false, fade = 0.25, randomStart = false) {
|
||||
let oldClipAction: null | AnimationAction = this.curClipAction;
|
||||
var clipAction = this.animMixer.clipAction(this.animationList[anim_id]);
|
||||
|
||||
if (one_time) {
|
||||
clipAction.clampWhenFinished = true;
|
||||
clipAction.setLoop(LoopOnce, 1);
|
||||
}
|
||||
else {
|
||||
clipAction.clampWhenFinished = false;
|
||||
clipAction.setLoop(LoopRepeat, Infinity);
|
||||
}
|
||||
clipAction.timeScale = 1;
|
||||
clipAction.weight = 1;
|
||||
|
||||
clipAction.reset();
|
||||
if (randomStart)
|
||||
clipAction.time = Math.random() * this.animationList[anim_id].duration;
|
||||
clipAction.play();
|
||||
if (oldClipAction && oldClipAction != clipAction)
|
||||
oldClipAction.crossFadeTo(clipAction, fade, true);
|
||||
this.curClipAction = clipAction;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export enum BaseAnimation {
|
||||
Nan = -1,
|
||||
Idle = 0,
|
||||
Run = 1,
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum MeshType {
|
||||
Character = "character",
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export enum ResourcesType {
|
||||
Mesh = "mesh",
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Vec3 } from "cannon-es";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
/**
|
||||
* Vec3(cannon) To Vector3(three)
|
||||
*/
|
||||
export function Vector3CToT(value: Vec3) {
|
||||
return new Vector3(value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Vector3(three) To Vec3(cannon)
|
||||
*/
|
||||
export function Vector3TToC(value: Vector3) {
|
||||
return new Vec3(value.x, value.y, value.z);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Vector3 } from "three";
|
||||
|
||||
export interface IMoveInput {
|
||||
get CurrentDirection(): Vector3;
|
||||
update(delta);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Delegate, JoystickC, UpdateController } from "@24tools/playable_template";
|
||||
import { Vector3 } from "three";
|
||||
import { IMoveInput } from "./MoveInput";
|
||||
import { FollowCameraC } from "../Movment/CameraMovment/FollowCamera";
|
||||
|
||||
// type JoystickVectorData = {
|
||||
// vector?: {
|
||||
// x: number;
|
||||
// y: number;
|
||||
// };
|
||||
// };
|
||||
|
||||
// type JoystickPayload = {
|
||||
// event?: Event;
|
||||
// data?: JoystickVectorData;
|
||||
// };
|
||||
|
||||
export class PlayerInput implements IMoveInput {
|
||||
|
||||
|
||||
public static InitJoystick() {
|
||||
const screenSize = window.screenSize;
|
||||
const minSize = Math.min(screenSize.width, screenSize.height);
|
||||
const joystickSizeAspect = 0.2;
|
||||
const fadeTime = 200;
|
||||
const options = {
|
||||
zone: document.getElementById("joystick_zone") as HTMLElement,
|
||||
size: minSize * joystickSizeAspect,
|
||||
restJoystick: true,
|
||||
dynamicPage: true,
|
||||
catchDistance: minSize * joystickSizeAspect / 2,
|
||||
fadeTime: fadeTime,
|
||||
};
|
||||
JoystickC.init(options);
|
||||
|
||||
// JoystickC.onJoysticMove.addDelegate(({ event, data }) => {
|
||||
// console.log('onJoysticMove', event, data);
|
||||
// })
|
||||
}
|
||||
|
||||
protected currentDirection: Vector3 = new Vector3();
|
||||
private updateDelegate: Delegate<number>;
|
||||
private StartDelegate: Delegate<any>;
|
||||
private MoveDelegate: Delegate<any>;
|
||||
private StopDelegate: Delegate<any>;
|
||||
private static threshold = 0.25;
|
||||
|
||||
get CurrentDirection() { return this.currentDirection.clone(); };
|
||||
|
||||
inputaActive: boolean = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.updateDelegate = UpdateController.Instance.onUpdate.addDelegate(this.update.bind(this));
|
||||
|
||||
this.MoveDelegate = JoystickC.onJoysticMove.addDelegate(this.onTouchMove.bind(this));
|
||||
// "down" also carries joystick data in this SDK, useful for first non-zero direction.
|
||||
JoystickC.onJoysticDown.addListener(this.MoveDelegate);
|
||||
|
||||
this.StopDelegate = JoystickC.onJoysticEnd.addDelegate(this.onTouchUp.bind(this));
|
||||
|
||||
|
||||
this.StartDelegate = JoystickC.onJoysticStart.addDelegate(this.onTouchDown.bind(this));
|
||||
}
|
||||
|
||||
update(delta: number) {
|
||||
|
||||
}
|
||||
|
||||
onTouchMove(payload: any) {
|
||||
this.currentDirection = this.GetDiraction(payload);
|
||||
if (this.currentDirection.length() <= PlayerInput.threshold)
|
||||
this.currentDirection.multiplyScalar(0);
|
||||
}
|
||||
|
||||
onTouchDown(_event: any) {
|
||||
// console.log(event);
|
||||
|
||||
if (this.inputaActive) return;
|
||||
this.inputaActive = true;
|
||||
}
|
||||
|
||||
onTouchUp() {
|
||||
if (!this.inputaActive) return;
|
||||
this.inputaActive = false;
|
||||
this.currentDirection.multiplyScalar(0);
|
||||
}
|
||||
|
||||
GetDiraction(payload: any) {
|
||||
// Compatible with old/new nipplejs payloads wrapped by JoystickC:
|
||||
// - { event, data } where data.vector exists
|
||||
// - { event, data: undefined } where event.data.vector exists
|
||||
const normalizedData =
|
||||
payload?.data ??
|
||||
payload?.event?.data ??
|
||||
payload?.event;
|
||||
const vector = normalizedData?.vector;
|
||||
if (!vector) return new Vector3();
|
||||
|
||||
const x = vector.x;
|
||||
const y = vector.y;
|
||||
const dir = new Vector3(-x, 0, y);
|
||||
dir.applyEuler(FollowCameraC.RotationCorection);
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { Delegate, Helper, Template, UpdateController } from "@24tools/playable_template";
|
||||
import { Object3D, Vector3 } from "three";
|
||||
import { ThreeC } from "../../../ThreeC";
|
||||
import { CameraC } from "../../../CameraC";
|
||||
|
||||
export class FollowCameraC {
|
||||
private static updateDelegate: Delegate<number>;
|
||||
static target: Object3D;
|
||||
static offset: Vector3;
|
||||
|
||||
static mainContainer: Object3D = new Object3D();
|
||||
static cameraContainer: Object3D = new Object3D();
|
||||
static cameraRotation: Object3D = new Object3D();
|
||||
|
||||
/** Normalized movement direction set each frame by the player */
|
||||
static inputDirection: Vector3 = new Vector3();
|
||||
/** How far (world units) the camera shifts ahead of the player */
|
||||
static lookAheadAmount: number = 2;
|
||||
/** Lerp speed for the look-ahead offset (lower = smoother/slower) */
|
||||
static lookAheadLerpSpeed: number = 0.7;
|
||||
|
||||
private static lookAheadCurrent: Vector3 = new Vector3();
|
||||
|
||||
static Init(target: Object3D) {
|
||||
this.target = target;
|
||||
this.offset = this.Offset;
|
||||
|
||||
console.log(target);
|
||||
|
||||
|
||||
this.updateDelegate = new Delegate<number>(this.Update.bind(this));
|
||||
UpdateController.Instance.onUpdate.addListener(this.updateDelegate);
|
||||
|
||||
ThreeC.addToScene(this.mainContainer);
|
||||
this.mainContainer.add(this.cameraContainer);
|
||||
this.cameraContainer.add(this.cameraRotation);
|
||||
this.cameraRotation.add(CameraC.cameraContainer);
|
||||
|
||||
this.cameraRotation.rotateY(Math.PI);
|
||||
|
||||
this.mainContainer.position.copy(target.position);
|
||||
this.mainContainer.position.x += this.Offset.x;
|
||||
this.mainContainer.position.z += this.Offset.z;
|
||||
|
||||
this.cameraContainer.position.y += this.Offset.y;
|
||||
}
|
||||
|
||||
private static Update(delta: number) {
|
||||
if (!this.target.position) return;
|
||||
|
||||
// Lerp look-ahead toward current movement direction (stays at last direction when stopped)
|
||||
const lookAheadTarget = this.inputDirection.clone().multiplyScalar(this.lookAheadAmount);
|
||||
this.lookAheadCurrent.lerp(lookAheadTarget, delta * this.lookAheadLerpSpeed);
|
||||
|
||||
const offset = this.Offset;
|
||||
|
||||
// Base position without look-ahead
|
||||
const basePos = this.target.position.clone();
|
||||
basePos.x += offset.x;
|
||||
basePos.z += offset.z;
|
||||
|
||||
// Final target = base + look-ahead shift
|
||||
const targetPos = basePos.clone();
|
||||
targetPos.x += this.lookAheadCurrent.x;
|
||||
targetPos.z += this.lookAheadCurrent.z;
|
||||
|
||||
// Capture actual previous position BEFORE any mutation
|
||||
const oldPos = this.mainContainer.position.clone();
|
||||
|
||||
// Compute rotation using base position to avoid tilt from look-ahead offset
|
||||
this.mainContainer.position.copy(basePos);
|
||||
this.mainContainer.lookAt(this.target.position);
|
||||
this.cameraContainer.lookAt(this.target.position);
|
||||
|
||||
// Smooth follow lerp from real previous position toward target with look-ahead
|
||||
const lerpSpeed = 10;
|
||||
this.mainContainer.position.lerpVectors(oldPos, targetPos, delta * lerpSpeed);
|
||||
}
|
||||
|
||||
static get RotationCorection() {
|
||||
const rotation = this.mainContainer.rotation.clone();
|
||||
return rotation;
|
||||
}
|
||||
|
||||
static get Offset() {
|
||||
const portrait = window.screenSize.portrait
|
||||
const values = portrait
|
||||
? Template.getValue<number[]>("global", "camera_position_p")
|
||||
: Template.getValue<number[]>("global", "camera_position_l");
|
||||
const offset = Helper.returnVectorCamera(values);
|
||||
return offset
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Delegate, UpdateController } from "@24tools/playable_template";
|
||||
import { IMoveInput } from "../Input/MoveInput";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
export class MoveC {
|
||||
private input: IMoveInput;
|
||||
private speed: number = 5;
|
||||
private updateDelegate: Delegate<number>;
|
||||
private moveDiraction: Vector3 = new Vector3();
|
||||
get Diraction() { return this.moveDiraction };
|
||||
get Weight() { return this.moveDiraction.length() / this.speed };
|
||||
|
||||
constructor(Input: IMoveInput, speed: number = 5) {
|
||||
this.updateDelegate = new Delegate<number>(this.update.bind(this));
|
||||
UpdateController.Instance.onUpdate.addListener(this.updateDelegate);
|
||||
this.input = Input;
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
private update(delta: number) {
|
||||
// delta *= TimeC.TimeScale;
|
||||
const moveStep = this.input.CurrentDirection.multiplyScalar(this.speed);
|
||||
this.moveDiraction.copy(moveStep);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Delegate, UpdateController } from "@24tools/playable_template";
|
||||
import { IMoveInput } from "../Input/MoveInput";
|
||||
import { Object3D, Quaternion, Vector3 } from "three";
|
||||
|
||||
export class RotationC {
|
||||
private target: Object3D;
|
||||
private input: IMoveInput;
|
||||
private speed: number = 5;
|
||||
private updateDelegate: Delegate<number>;
|
||||
private currentQ: Quaternion = new Quaternion();
|
||||
private targetQ: Quaternion = new Quaternion();
|
||||
|
||||
constructor(target: Object3D, Input: IMoveInput, speed: number = 5) {
|
||||
this.updateDelegate = new Delegate<number>(this.update.bind(this));
|
||||
UpdateController.Instance.onUpdate.addListener(this.updateDelegate);
|
||||
this.input = Input;
|
||||
this.speed = speed;
|
||||
this.target = target;
|
||||
this.currentQ.copy(this.target.quaternion);
|
||||
this.targetQ.copy(this.target.quaternion);
|
||||
}
|
||||
|
||||
private update(delta: number) {
|
||||
const loockAtStep = this.input.CurrentDirection;
|
||||
if (loockAtStep.length() != 0) {
|
||||
const loockAtPoint = this.target.position.clone().add(loockAtStep);
|
||||
this.target.lookAt(loockAtPoint);
|
||||
this.targetQ.copy(this.target.quaternion);
|
||||
this.target.quaternion.copy(this.currentQ);
|
||||
}
|
||||
|
||||
this.target.quaternion.slerp(this.targetQ, delta * this.speed);
|
||||
this.currentQ.copy(this.target.quaternion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
import { Delegate, ResourcesC, UpdateController } from "@24tools/playable_template";
|
||||
import { Character } from "./Character/Character";
|
||||
import { ResourcesType } from "./Enums/ResourcesType";
|
||||
import { MeshType } from "./Enums/MeshType";
|
||||
import { GLTF } from "three/examples/jsm/loaders/GLTFLoader";
|
||||
import { BaseAnimation } from "./Enums/BaseAnimation";
|
||||
import { PhysicsBody, PhysicsLayer } from "../PhysicsC";
|
||||
import { Object3D } from "three";
|
||||
import { ThreeC } from "../ThreeC";
|
||||
import { PlayerInput } from "./Input/PlayerInput";
|
||||
import { MoveC } from "./Movment/MoveC";
|
||||
import { Vec3 } from "cannon-es";
|
||||
import { contain } from "three/src/extras/TextureUtils";
|
||||
import { Vector3CToT, Vector3TToC } from "./Helper";
|
||||
import { RotationC } from "./Movment/RotationC";
|
||||
import { FollowCameraC } from "./Movment/CameraMovment/FollowCamera";
|
||||
import { Vector3 } from "three";
|
||||
|
||||
export class Player {
|
||||
private static inited: boolean = false;
|
||||
private static isRunning: boolean = false;
|
||||
|
||||
private static updateDelegate: Delegate<number>;
|
||||
|
||||
private static container: Object3D = new Object3D;
|
||||
private static input: PlayerInput;
|
||||
private static movement: MoveC;
|
||||
private static rotation: RotationC;
|
||||
private static spawnPosition: Vector3 = new Vector3(0, 0, 0);
|
||||
|
||||
static character: Character;
|
||||
static physics: PhysicsBody;
|
||||
|
||||
static SetSpawnPosition(position: Vector3) {
|
||||
this.spawnPosition.copy(position);
|
||||
}
|
||||
|
||||
static Init() {
|
||||
if (this.inited) return;
|
||||
this.inited = true
|
||||
const asset = ResourcesC.getResource<GLTF>(ResourcesType.Mesh, MeshType.Character)
|
||||
this.character = new Character(asset);
|
||||
|
||||
this.container.position.copy(this.spawnPosition);
|
||||
this.character.playAnimation(BaseAnimation.Idle)
|
||||
this.container.add(this.character.tObj);
|
||||
ThreeC.addToScene(this.container);
|
||||
|
||||
this.InitPhisic();
|
||||
const input = new PlayerInput();
|
||||
const moveSpeed = 3;
|
||||
const rotationSpeed = 8;
|
||||
this.movement = new MoveC(input, moveSpeed);
|
||||
this.rotation = new RotationC(this.container, input, rotationSpeed);
|
||||
|
||||
this.updateDelegate = new Delegate<number>((delta) => this.Update(delta));
|
||||
UpdateController.Instance.onUpdate.addListener(this.updateDelegate);
|
||||
|
||||
FollowCameraC.Init(this.container);
|
||||
}
|
||||
|
||||
private static InitPhisic() {
|
||||
this.physics = new PhysicsBody(
|
||||
this.container,
|
||||
false,
|
||||
1,
|
||||
PhysicsLayer.Player,
|
||||
PhysicsLayer.Wall,
|
||||
);
|
||||
}
|
||||
|
||||
private static StartRunning() {
|
||||
if (this.isRunning) return;
|
||||
this.character.playAnimation(BaseAnimation.Run);
|
||||
this.isRunning = true;
|
||||
}
|
||||
private static StopRunning() {
|
||||
if (!this.isRunning) return;
|
||||
this.character.playAnimation(BaseAnimation.Idle);
|
||||
this.AnimationValue = 1;
|
||||
this.isRunning = false;
|
||||
}
|
||||
|
||||
private static set AnimationValue(value: number) {
|
||||
this.character.AnimationSpeed = value;
|
||||
this.character.AnimationWeight = value * 12.5 + 87.5;
|
||||
}
|
||||
|
||||
private static Update(delta: number) {
|
||||
const diraction = this.movement.Diraction;
|
||||
const weight = this.movement.Weight;
|
||||
|
||||
if (diraction.length() > 0) {
|
||||
this.StartRunning();
|
||||
this.AnimationValue = weight;
|
||||
FollowCameraC.inputDirection.copy(diraction).normalize();
|
||||
} else {
|
||||
this.StopRunning();
|
||||
}
|
||||
|
||||
|
||||
|
||||
const cPos = Vector3TToC(diraction);
|
||||
|
||||
this.physics.getPhysicsBody().velocity.copy(cPos);
|
||||
this.MoveVisual(delta);
|
||||
}
|
||||
|
||||
private static MoveVisual(delta: number) {
|
||||
const lerpSpeed = 10;
|
||||
const targetPos = (Vector3CToT(this.physics.getPhysicsBody().position));
|
||||
|
||||
this.container.position.lerp(targetPos, delta * lerpSpeed)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user