新增角色控制器
This commit is contained in:
@@ -37,7 +37,7 @@ module es {
|
||||
public _coroutineManager: CoroutineManager = new CoroutineManager();
|
||||
public _timerManager: TimerManager = new TimerManager();
|
||||
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true, remoteUrl: string = "") {
|
||||
private constructor(debug: boolean = true, enableEntitySystems: boolean = true) {
|
||||
Core._instance = this;
|
||||
Core.emitter = new Emitter<CoreEvents>();
|
||||
Core.emitter.addObserver(CoreEvents.frameUpdated, this.update, this);
|
||||
|
||||
588
source/src/ECS/Components/Physics/CharacterController.ts
Normal file
588
source/src/ECS/Components/Physics/CharacterController.ts
Normal file
@@ -0,0 +1,588 @@
|
||||
module es {
|
||||
class CharacterRaycastOrigins {
|
||||
public topLeft: Vector2;
|
||||
public bottomRight: Vector2;
|
||||
public bottomLeft: Vector2;
|
||||
|
||||
public constructor() {
|
||||
this.topLeft = Vector2.zero;
|
||||
this.bottomRight = Vector2.zero;
|
||||
this.bottomLeft = Vector2.zero;
|
||||
}
|
||||
}
|
||||
|
||||
class CharacterCollisionState2D {
|
||||
public right: boolean = false;
|
||||
public left: boolean = false;
|
||||
public above: boolean = false;
|
||||
public below: boolean = false;
|
||||
public becameGroundedThisFrame: boolean = false;
|
||||
public wasGroundedLastFrame: boolean = false;
|
||||
public movingDownSlope: boolean = false;
|
||||
public slopeAngle: number = 0;
|
||||
|
||||
public hasCollision(): boolean {
|
||||
return this.below || this.right || this.left || this.above;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
this.right = this.left = false;
|
||||
this.above = this.below = false;
|
||||
this.becameGroundedThisFrame = this.movingDownSlope = false;
|
||||
this.slopeAngle = 0;
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return `[CharacterCollisionState2D] r: ${this.right}, l: ${this.left}, a: ${this.above}, b: ${this.below}, movingDownSlope: ${this.movingDownSlope}, angle: ${this.slopeAngle}, wasGroundedLastFrame: ${this.wasGroundedLastFrame}, becameGroundedThisFrame: ${this.becameGroundedThisFrame}`;
|
||||
}
|
||||
}
|
||||
|
||||
export class CharacterController implements ITriggerListener {
|
||||
public onControllerCollidedEvent: ObservableT<RaycastHit>;
|
||||
public onTriggerEnterEvent: ObservableT<Collider>;
|
||||
public onTriggerExitEvent: ObservableT<Collider>;
|
||||
|
||||
|
||||
/**
|
||||
* 如果为 true,则在垂直移动单帧时将忽略平台的一种方式
|
||||
*/
|
||||
public ignoreOneWayPlatformsTime: number;
|
||||
public supportSlopedOneWayPlatforms: boolean;
|
||||
|
||||
public ignoredColliders: Set<Collider> = new Set();
|
||||
|
||||
|
||||
/**
|
||||
* 定义距离碰撞射线的边缘有多远。
|
||||
* 如果使用 0 范围进行投射,则通常会导致不需要的光线击中(例如,直接从表面水平投射的足部碰撞器可能会导致击中)
|
||||
*/
|
||||
public get skinWidth() {
|
||||
return this._skinWidth;
|
||||
}
|
||||
|
||||
public set skinWidth(value: number) {
|
||||
this._skinWidth = value;
|
||||
this.recalculateDistanceBetweenRays();
|
||||
}
|
||||
|
||||
/**
|
||||
* CC2D 可以爬升的最大坡度角
|
||||
*/
|
||||
public slopeLimit: number = 30;
|
||||
|
||||
/**
|
||||
* 构成跳跃的帧之间垂直运动变化的阈值
|
||||
*/
|
||||
public jumpingThreshold: number = -7;
|
||||
|
||||
/**
|
||||
* 基于斜率乘以速度的曲线(负 = 下坡和正 = 上坡)
|
||||
*/
|
||||
public slopeSpeedMultiplier: AnimCurve;
|
||||
|
||||
public totalHorizontalRays: number = 5;
|
||||
public totalVerticalRays: number = 3;
|
||||
|
||||
public collisionState: CharacterCollisionState2D = new CharacterCollisionState2D();
|
||||
public velocity: Vector2 = new Vector2(0, 0);
|
||||
|
||||
public get isGrounded(): boolean {
|
||||
return this.collisionState.below;
|
||||
}
|
||||
|
||||
public get raycastHitsThisFrame(): RaycastHit[] {
|
||||
return this._raycastHitsThisFrame;
|
||||
}
|
||||
|
||||
public constructor(
|
||||
player: Entity,
|
||||
skinWidth?: number,
|
||||
platformMask: number = -1,
|
||||
onewayPlatformMask: number = -1,
|
||||
triggerMask: number = -1
|
||||
) {
|
||||
this.onTriggerEnterEvent = new ObservableT();
|
||||
this.onTriggerExitEvent = new ObservableT();
|
||||
this.onControllerCollidedEvent = new ObservableT();
|
||||
|
||||
this.platformMask = platformMask;
|
||||
this.oneWayPlatformMask = onewayPlatformMask;
|
||||
this.triggerMask = triggerMask;
|
||||
|
||||
// 将我们的单向平台添加到我们的普通平台掩码中,以便我们可以从上方降落
|
||||
this.platformMask |= this.oneWayPlatformMask;
|
||||
|
||||
this._player = player;
|
||||
let collider = null;
|
||||
for (let i = 0; i < this._player.components.buffer.length; i++) {
|
||||
let component = this._player.components.buffer[i];
|
||||
if (component instanceof Collider) {
|
||||
collider = component;
|
||||
break;
|
||||
}
|
||||
}
|
||||
collider.isTrigger = false;
|
||||
if (collider instanceof BoxCollider) {
|
||||
this._collider = collider as BoxCollider;
|
||||
} else {
|
||||
throw new Error('player collider must be box');
|
||||
}
|
||||
|
||||
// 在这里,我们触发了具有主体的 setter 的属性
|
||||
this.skinWidth = skinWidth || collider.width * 0.05;
|
||||
|
||||
this._slopeLimitTangent = Math.tan(75 * MathHelper.Deg2Rad);
|
||||
this._triggerHelper = new ColliderTriggerHelper(this._player);
|
||||
|
||||
// 我们想设置我们的 CC2D 忽略所有碰撞层,除了我们的 triggerMask
|
||||
for (let i = 0; i < 32; i++) {
|
||||
// 查看我们的 triggerMask 是否包含此层,如果不包含则忽略它
|
||||
if ((this.triggerMask & (1 << i)) === 0) {
|
||||
Flags.unsetFlag(this._collider.collidesWithLayers, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public onTriggerEnter(other: Collider, local: Collider): void {
|
||||
this.onTriggerEnterEvent.notify(other);
|
||||
}
|
||||
|
||||
public onTriggerExit(other: Collider, local: Collider): void {
|
||||
this.onTriggerExitEvent.notify(other);
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试将角色移动到位置 + deltaMovement。 任何挡路的碰撞器都会在遇到时导致运动停止
|
||||
* @param deltaMovement
|
||||
* @param deltaTime
|
||||
*/
|
||||
public move(deltaMovement: Vector2, deltaTime: number): void {
|
||||
this.collisionState.wasGroundedLastFrame = this.collisionState.below;
|
||||
|
||||
this.collisionState.reset();
|
||||
this._raycastHitsThisFrame = [];
|
||||
this._isGoingUpSlope = false;
|
||||
|
||||
this.primeRaycastOrigins();
|
||||
|
||||
if (deltaMovement.y > 0 && this.collisionState.wasGroundedLastFrame) {
|
||||
deltaMovement = this.handleVerticalSlope(deltaMovement);
|
||||
}
|
||||
|
||||
if (deltaMovement.x !== 0) {
|
||||
deltaMovement = this.moveHorizontally(deltaMovement);
|
||||
}
|
||||
|
||||
if (deltaMovement.y !== 0) {
|
||||
deltaMovement = this.moveVertically(deltaMovement);
|
||||
}
|
||||
|
||||
this._player.setPosition(
|
||||
this._player.position.x + deltaMovement.x,
|
||||
this._player.position.y + deltaMovement.y
|
||||
);
|
||||
|
||||
if (deltaTime > 0) {
|
||||
this.velocity.x = deltaMovement.x / deltaTime;
|
||||
this.velocity.y = deltaMovement.y / deltaTime;
|
||||
}
|
||||
|
||||
if (
|
||||
!this.collisionState.wasGroundedLastFrame &&
|
||||
this.collisionState.below
|
||||
) {
|
||||
this.collisionState.becameGroundedThisFrame = true;
|
||||
}
|
||||
|
||||
if (this._isGoingUpSlope) {
|
||||
this.velocity.y = 0;
|
||||
}
|
||||
|
||||
if (!this._isWarpingToGround) {
|
||||
this._triggerHelper.update();
|
||||
}
|
||||
for (let i = 0; i < this._raycastHitsThisFrame.length; i++) {
|
||||
this.onControllerCollidedEvent.notify(this._raycastHitsThisFrame[i]);
|
||||
}
|
||||
|
||||
if (this.ignoreOneWayPlatformsTime > 0) {
|
||||
this.ignoreOneWayPlatformsTime -= deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 直接向下移动直到接地
|
||||
* @param maxDistance
|
||||
*/
|
||||
public warpToGrounded(maxDistance: number = 1000): void {
|
||||
this.ignoreOneWayPlatformsTime = 0;
|
||||
this._isWarpingToGround = true;
|
||||
let delta = 0;
|
||||
do {
|
||||
delta += 1;
|
||||
this.move(new Vector2(0, 1), 0.02);
|
||||
if (delta > maxDistance) {
|
||||
break;
|
||||
}
|
||||
} while (!this.isGrounded);
|
||||
this._isWarpingToGround = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这应该在您必须在运行时修改 BoxCollider2D 的任何时候调用。
|
||||
* 它将重新计算用于碰撞检测的光线之间的距离。
|
||||
* 它也用于 skinWidth setter,以防在运行时更改。
|
||||
*/
|
||||
public recalculateDistanceBetweenRays(): void {
|
||||
const colliderUsableHeight =
|
||||
this._collider.height * Math.abs(this._player.scale.y) -
|
||||
2 * this._skinWidth;
|
||||
this._verticalDistanceBetweenRays =
|
||||
colliderUsableHeight / (this.totalHorizontalRays - 1);
|
||||
|
||||
const colliderUsableWidth =
|
||||
this._collider.width * Math.abs(this._player.scale.x) -
|
||||
2 * this._skinWidth;
|
||||
this._horizontalDistanceBetweenRays =
|
||||
colliderUsableWidth / (this.totalVerticalRays - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 raycastOrigins 重置为由 skinWidth 插入的框碰撞器的当前范围。
|
||||
* 插入它是为了避免从直接接触另一个碰撞器的位置投射光线,从而导致不稳定的法线数据。
|
||||
*/
|
||||
private primeRaycastOrigins(): void {
|
||||
const rect = this._collider.bounds;
|
||||
this._raycastOrigins.topLeft = new Vector2(
|
||||
rect.x + this._skinWidth,
|
||||
rect.y + this._skinWidth
|
||||
);
|
||||
this._raycastOrigins.bottomRight = new Vector2(
|
||||
rect.right - this._skinWidth,
|
||||
rect.bottom - this._skinWidth
|
||||
);
|
||||
this._raycastOrigins.bottomLeft = new Vector2(
|
||||
rect.x + this._skinWidth,
|
||||
rect.bottom - this._skinWidth
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我们必须在这方面使用一些技巧。
|
||||
* 光线必须从我们的碰撞器(skinWidth)内部的一小段距离投射,以避免零距离光线会得到错误的法线。
|
||||
* 由于这个小偏移,我们必须增加光线距离 skinWidth 然后记住在实际移动玩家之前从 deltaMovement 中删除 skinWidth
|
||||
* @param deltaMovement
|
||||
* @returns
|
||||
*/
|
||||
private moveHorizontally(deltaMovement: Vector2): Vector2 {
|
||||
const isGoingRight = deltaMovement.x > 0;
|
||||
let rayDistance =
|
||||
Math.abs(deltaMovement.x) +
|
||||
this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
const rayDirection: Vector2 = isGoingRight ? Vector2.right : Vector2.left;
|
||||
const initialRayOriginY = this._raycastOrigins.bottomLeft.y;
|
||||
const initialRayOriginX = isGoingRight
|
||||
? this._raycastOrigins.bottomRight.x -
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
|
||||
: this._raycastOrigins.bottomLeft.x +
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
|
||||
|
||||
for (let i = 0; i < this.totalHorizontalRays; i++) {
|
||||
const ray = new Vector2(
|
||||
initialRayOriginX,
|
||||
initialRayOriginY - i * this._verticalDistanceBetweenRays
|
||||
);
|
||||
|
||||
// if we are grounded we will include oneWayPlatforms
|
||||
// only on the first ray (the bottom one). this will allow us to
|
||||
// walk up sloped oneWayPlatforms
|
||||
if (
|
||||
i === 0 &&
|
||||
this.supportSlopedOneWayPlatforms &&
|
||||
this.collisionState.wasGroundedLastFrame
|
||||
) {
|
||||
this._raycastHit = Physics.linecastIgnoreCollider(
|
||||
ray,
|
||||
ray.add(rayDirection.multiplyScaler(rayDistance)),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
} else {
|
||||
this._raycastHit = Physics.linecastIgnoreCollider(
|
||||
ray,
|
||||
ray.add(rayDirection.multiplyScaler(rayDistance)),
|
||||
this.platformMask & ~this.oneWayPlatformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
if (this._raycastHit.collider) {
|
||||
if (
|
||||
i === 0 &&
|
||||
this.handleHorizontalSlope(
|
||||
deltaMovement,
|
||||
Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up)
|
||||
)
|
||||
) {
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
break;
|
||||
}
|
||||
|
||||
deltaMovement.x = this._raycastHit.point.x - ray.x;
|
||||
rayDistance = Math.abs(deltaMovement.x);
|
||||
|
||||
if (isGoingRight) {
|
||||
deltaMovement.x -= this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.right = true;
|
||||
} else {
|
||||
deltaMovement.x += this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.left = true;
|
||||
}
|
||||
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
|
||||
if (
|
||||
rayDistance <
|
||||
this._skinWidth * this.rayOriginSkinMutiplier +
|
||||
this.kSkinWidthFloatFudgeFactor
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
private moveVertically(deltaMovement: Vector2): Vector2 {
|
||||
const isGoingUp = deltaMovement.y < 0;
|
||||
let rayDistance =
|
||||
Math.abs(deltaMovement.y) +
|
||||
this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
const rayDirection = isGoingUp ? Vector2.up : Vector2.down;
|
||||
|
||||
let initialRayOriginX = this._raycastOrigins.topLeft.x;
|
||||
const initialRayOriginY = isGoingUp
|
||||
? this._raycastOrigins.topLeft.y +
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
|
||||
: this._raycastOrigins.bottomLeft.y -
|
||||
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
|
||||
|
||||
initialRayOriginX += deltaMovement.x;
|
||||
|
||||
let mask = this.platformMask;
|
||||
if (isGoingUp || this.ignoreOneWayPlatformsTime > 0) {
|
||||
mask &= ~this.oneWayPlatformMask;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.totalVerticalRays; i++) {
|
||||
const rayStart = new Vector2(
|
||||
initialRayOriginX + i * this._horizontalDistanceBetweenRays,
|
||||
initialRayOriginY
|
||||
);
|
||||
this._raycastHit = Physics.linecastIgnoreCollider(
|
||||
rayStart,
|
||||
rayStart.add(rayDirection.multiplyScaler(rayDistance)),
|
||||
mask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
if (this._raycastHit.collider) {
|
||||
deltaMovement.y = this._raycastHit.point.y - rayStart.y;
|
||||
rayDistance = Math.abs(deltaMovement.y);
|
||||
|
||||
if (isGoingUp) {
|
||||
deltaMovement.y += this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.above = true;
|
||||
} else {
|
||||
deltaMovement.y -= this._skinWidth * this.rayOriginSkinMutiplier;
|
||||
this.collisionState.below = true;
|
||||
}
|
||||
|
||||
this._raycastHitsThisFrame.push(this._raycastHit);
|
||||
|
||||
if (!isGoingUp && deltaMovement.y < -0.00001) {
|
||||
this._isGoingUpSlope = true;
|
||||
}
|
||||
|
||||
if (
|
||||
rayDistance <
|
||||
this._skinWidth * this.rayOriginSkinMutiplier +
|
||||
this.kSkinWidthFloatFudgeFactor
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 BoxCollider2D 下的中心点是否存在坡度。
|
||||
* 如果找到一个,则调整 deltaMovement 以便玩家保持接地,并考虑slopeSpeedModifier 以加快移动速度。
|
||||
* @param deltaMovement
|
||||
* @returns
|
||||
*/
|
||||
private handleVerticalSlope(deltaMovement: Vector2): Vector2 {
|
||||
const centerOfCollider =
|
||||
(this._raycastOrigins.bottomLeft.x +
|
||||
this._raycastOrigins.bottomRight.x) *
|
||||
0.5;
|
||||
const rayDirection = Vector2.down;
|
||||
|
||||
const slopeCheckRayDistance =
|
||||
this._slopeLimitTangent *
|
||||
(this._raycastOrigins.bottomRight.x - centerOfCollider);
|
||||
|
||||
const slopeRay = new Vector2(
|
||||
centerOfCollider,
|
||||
this._raycastOrigins.bottomLeft.y
|
||||
);
|
||||
|
||||
this._raycastHit = Physics.linecastIgnoreCollider(
|
||||
slopeRay,
|
||||
slopeRay.add(rayDirection.multiplyScaler(slopeCheckRayDistance)),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
if (this._raycastHit.collider) {
|
||||
const angle = Vector2.unsignedAngle(this._raycastHit.normal, Vector2.up);
|
||||
if (angle === 0) {
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
const isMovingDownSlope =
|
||||
Math.sign(this._raycastHit.normal.x) === Math.sign(deltaMovement.x);
|
||||
if (isMovingDownSlope) {
|
||||
const slopeModifier = this.slopeSpeedMultiplier
|
||||
? this.slopeSpeedMultiplier.lerp(-angle)
|
||||
: 1;
|
||||
deltaMovement.y +=
|
||||
this._raycastHit.point.y - slopeRay.y - this.skinWidth;
|
||||
deltaMovement.x *= slopeModifier;
|
||||
this.collisionState.movingDownSlope = true;
|
||||
this.collisionState.slopeAngle = angle;
|
||||
}
|
||||
}
|
||||
|
||||
return deltaMovement;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果我们要上坡,则处理调整 deltaMovement
|
||||
* @param deltaMovement
|
||||
* @param angle
|
||||
* @returns
|
||||
*/
|
||||
private handleHorizontalSlope(
|
||||
deltaMovement: Vector2,
|
||||
angle: number
|
||||
): boolean {
|
||||
if (Math.round(angle) === 90) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (angle < this.slopeLimit) {
|
||||
if (deltaMovement.y > this.jumpingThreshold) {
|
||||
const slopeModifier = this.slopeSpeedMultiplier
|
||||
? this.slopeSpeedMultiplier.lerp(angle)
|
||||
: 1;
|
||||
deltaMovement.x *= slopeModifier;
|
||||
|
||||
deltaMovement.y = Math.abs(
|
||||
Math.tan(angle * MathHelper.Deg2Rad) * deltaMovement.x
|
||||
);
|
||||
const isGoingRight = deltaMovement.x > 0;
|
||||
|
||||
const ray = isGoingRight
|
||||
? this._raycastOrigins.bottomRight
|
||||
: this._raycastOrigins.bottomLeft;
|
||||
let raycastHit = null;
|
||||
if (
|
||||
this.supportSlopedOneWayPlatforms &&
|
||||
this.collisionState.wasGroundedLastFrame
|
||||
) {
|
||||
raycastHit = Physics.linecastIgnoreCollider(
|
||||
ray,
|
||||
ray.add(deltaMovement),
|
||||
this.platformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
} else {
|
||||
raycastHit = Physics.linecastIgnoreCollider(
|
||||
ray,
|
||||
ray.add(deltaMovement),
|
||||
this.platformMask & ~this.oneWayPlatformMask,
|
||||
this.ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
if (raycastHit.collider) {
|
||||
deltaMovement.x = raycastHit.point.x - ray.x;
|
||||
deltaMovement.y = raycastHit.point.y - ray.y;
|
||||
if (isGoingRight) {
|
||||
deltaMovement.x -= this._skinWidth;
|
||||
} else {
|
||||
deltaMovement.x += this._skinWidth;
|
||||
}
|
||||
}
|
||||
|
||||
this._isGoingUpSlope = true;
|
||||
this.collisionState.below = true;
|
||||
}
|
||||
} else {
|
||||
deltaMovement.x = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private _player: Entity;
|
||||
private _collider: BoxCollider;
|
||||
private _skinWidth: number = 0.02;
|
||||
private _triggerHelper: ColliderTriggerHelper;
|
||||
|
||||
/**
|
||||
* 这用于计算为检查坡度而投射的向下光线。
|
||||
* 我们使用有点随意的值 75 度来计算检查斜率的射线的长度。
|
||||
*/
|
||||
private _slopeLimitTangent: number;
|
||||
|
||||
private readonly kSkinWidthFloatFudgeFactor: number = 0.001;
|
||||
|
||||
/**
|
||||
* 我们的光线投射原点角的支架(TR、TL、BR、BL)
|
||||
*/
|
||||
private _raycastOrigins: CharacterRaycastOrigins = new CharacterRaycastOrigins();
|
||||
|
||||
/**
|
||||
* 存储我们在移动过程中命中的光线投射
|
||||
*/
|
||||
private _raycastHit: RaycastHit = new RaycastHit();
|
||||
|
||||
/**
|
||||
* 存储此帧发生的任何光线投射命中。
|
||||
* 我们必须存储它们,以防我们遇到水平和垂直移动的碰撞,以便我们可以在设置所有碰撞状态后发送事件
|
||||
*/
|
||||
private _raycastHitsThisFrame: RaycastHit[];
|
||||
|
||||
// 水平/垂直移动数据
|
||||
private _verticalDistanceBetweenRays: number;
|
||||
private _horizontalDistanceBetweenRays: number;
|
||||
|
||||
/**
|
||||
* 我们使用这个标志来标记我们正在爬坡的情况,我们修改了 delta.y 以允许爬升。
|
||||
* 原因是,如果我们到达斜坡的尽头,我们可以进行调整以保持接地
|
||||
*/
|
||||
private _isGoingUpSlope: boolean = false;
|
||||
|
||||
private _isWarpingToGround: boolean = true;
|
||||
|
||||
private platformMask: number = -1;
|
||||
private triggerMask: number = -1;
|
||||
private oneWayPlatformMask: number = -1;
|
||||
|
||||
private readonly rayOriginSkinMutiplier = 4;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
module es {
|
||||
export abstract class Collider extends Component {
|
||||
protected _isEnabled: boolean = true;
|
||||
/**
|
||||
* 对撞机的基本形状
|
||||
*/
|
||||
|
||||
@@ -129,6 +129,10 @@ module es {
|
||||
return from + (to - from) * this.clamp01(t);
|
||||
}
|
||||
|
||||
public static betterLerp(a: number, b: number, t: number, epsilon: number): number {
|
||||
return Math.abs(a - b) < epsilon ? b : MathHelper.lerp(a, b, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使度数的角度在a和b之间
|
||||
* 用于处理360度环绕
|
||||
@@ -604,5 +608,68 @@ module es {
|
||||
|
||||
return !Number.isFinite(x);
|
||||
}
|
||||
|
||||
public static smoothDamp(current: number, target: number, currentVelocity: number, smoothTime: number, maxSpeed: number, deltaTime: number): { value: number; currentVelocity: number } {
|
||||
smoothTime = Math.max(0.0001, smoothTime);
|
||||
const num: number = 2 / smoothTime;
|
||||
const num2: number = num * deltaTime;
|
||||
const num3: number =
|
||||
1 /
|
||||
(1 + (num2 + (0.48 * (num2 * num2) + 0.235 * (num2 * (num2 * num2)))));
|
||||
let num4: number = current - target;
|
||||
const num5: number = target;
|
||||
const num6: number = maxSpeed * smoothTime;
|
||||
num4 = this.clamp(num4, num6 * -1, num6);
|
||||
target = current - num4;
|
||||
const num7: number = (currentVelocity + num * num4) * deltaTime;
|
||||
currentVelocity = (currentVelocity - num * num7) * num3;
|
||||
let num8: number = target + (num4 + num7) * num3;
|
||||
if (num5 - current > 0 === num8 > num5) {
|
||||
num8 = num5;
|
||||
currentVelocity = (num8 - num5) / deltaTime;
|
||||
}
|
||||
return { value: num8, currentVelocity };
|
||||
}
|
||||
|
||||
public static smoothDampVector(current: Vector2, target: Vector2, currentVelocity: Vector2, smoothTime: number, maxSpeed: number, deltaTime: number): Vector2 {
|
||||
const v = Vector2.zero;
|
||||
|
||||
const resX = this.smoothDamp(
|
||||
current.x,
|
||||
target.x,
|
||||
currentVelocity.x,
|
||||
smoothTime,
|
||||
maxSpeed,
|
||||
deltaTime
|
||||
);
|
||||
v.x = resX.value;
|
||||
currentVelocity.x = resX.currentVelocity;
|
||||
|
||||
const resY = this.smoothDamp(
|
||||
current.y,
|
||||
target.y,
|
||||
currentVelocity.y,
|
||||
smoothTime,
|
||||
maxSpeed,
|
||||
deltaTime
|
||||
);
|
||||
v.y = resY.value;
|
||||
currentVelocity.y = resY.currentVelocity;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将值(在 leftMin - leftMax 范围内)映射到 rightMin - rightMax 范围内的值
|
||||
* @param value
|
||||
* @param leftMin
|
||||
* @param leftMax
|
||||
* @param rightMin
|
||||
* @param rightMax
|
||||
* @returns
|
||||
*/
|
||||
public static mapMinMax(value: number, leftMin: number, leftMax: number, rightMin: number, rightMax): number {
|
||||
return rightMin + ((MathHelper.clamp(value, leftMin, leftMax) - leftMin) * (rightMax - rightMin)) / (leftMax - leftMin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,22 @@ module es {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
|
||||
public static get up() {
|
||||
return new Vector2(0, -1);
|
||||
}
|
||||
|
||||
public static get down() {
|
||||
return new Vector2(0, 1);
|
||||
}
|
||||
|
||||
public static get left() {
|
||||
return new Vector2(-1, 0);
|
||||
}
|
||||
|
||||
public static get right() {
|
||||
return new Vector2(1, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param value1
|
||||
@@ -200,7 +216,7 @@ module es {
|
||||
* @param from
|
||||
* @param to
|
||||
*/
|
||||
public static angle(from: Vector2, to: Vector2): number{
|
||||
public static angle(from: Vector2, to: Vector2): number {
|
||||
from = Vector2.normalize(from);
|
||||
to = Vector2.normalize(to);
|
||||
return Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
@@ -348,10 +364,10 @@ module es {
|
||||
* @returns 如果实例相同true 否则false
|
||||
*/
|
||||
public equals(other: Vector2 | object): boolean {
|
||||
if (other instanceof Vector2){
|
||||
if (other instanceof Vector2) {
|
||||
return other.x == this.x && other.y == this.y;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -390,11 +406,20 @@ module es {
|
||||
* @param amount
|
||||
* @returns
|
||||
*/
|
||||
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number){
|
||||
public static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number) {
|
||||
return new Vector2(MathHelper.hermite(value1.x, tangent1.x, value2.x, tangent2.x, amount),
|
||||
MathHelper.hermite(value1.y, tangent1.y, value2.y, tangent2.y, amount));
|
||||
}
|
||||
|
||||
public static unsignedAngle(from: Vector2, to: Vector2, round: boolean = true) {
|
||||
from.normalize();
|
||||
to.normalize();
|
||||
const angle =
|
||||
Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
|
||||
return round ? Math.round(angle) : angle;
|
||||
}
|
||||
|
||||
|
||||
public clone(): Vector2 {
|
||||
return new Vector2(this.x, this.y);
|
||||
}
|
||||
|
||||
@@ -156,6 +156,18 @@ module es {
|
||||
return this._hitArray[0];
|
||||
}
|
||||
|
||||
public static linecastIgnoreCollider(start: Vector2,end: Vector2,layerMask: number = this.allLayers,ignoredColliders: Set<Collider> = null): RaycastHit {
|
||||
this._hitArray[0].reset();
|
||||
Physics.linecastAllIgnoreCollider(
|
||||
start,
|
||||
end,
|
||||
this._hitArray,
|
||||
layerMask,
|
||||
ignoredColliders
|
||||
);
|
||||
return this._hitArray[0].clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过空间散列强制执行一行,并用该行命中的任何碰撞器填充hits数组
|
||||
* @param start
|
||||
@@ -172,6 +184,16 @@ module es {
|
||||
return this._spatialHash.linecast(start, end, hits, layerMask);
|
||||
}
|
||||
|
||||
public static linecastAllIgnoreCollider(start: Vector2,end: Vector2,hits: RaycastHit[],layerMask: number = this.allLayers,ignoredColliders: Set<Collider> = null): number {
|
||||
return this._spatialHash.linecastIgnoreCollider(
|
||||
start,
|
||||
end,
|
||||
hits,
|
||||
layerMask,
|
||||
ignoredColliders
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否有对撞机落在一个矩形区域中
|
||||
* @param rect
|
||||
|
||||
@@ -30,7 +30,7 @@ module es {
|
||||
*/
|
||||
public centroid: Vector2;
|
||||
|
||||
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2){
|
||||
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2) {
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
@@ -38,26 +38,46 @@ module es {
|
||||
this.centroid = Vector2.zero;
|
||||
}
|
||||
|
||||
public setValues(collider: Collider, fraction: number, distance: number, point: Vector2){
|
||||
public setValues(collider: Collider, fraction: number, distance: number, point: Vector2) {
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
}
|
||||
|
||||
public setValuesNonCollider(fraction: number, distance: number, point: Vector2, normal: Vector2){
|
||||
public setValuesNonCollider(fraction: number, distance: number, point: Vector2, normal: Vector2) {
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
public reset(){
|
||||
public setAllValues(collider: Collider, fraction: number, distance: number, point: Vector2, normal: Vector2) {
|
||||
this.collider = collider;
|
||||
this.fraction = fraction;
|
||||
this.distance = distance;
|
||||
this.point = point;
|
||||
this.normal = normal;
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this.collider = null;
|
||||
this.fraction = this.distance = 0;
|
||||
}
|
||||
|
||||
public toString(){
|
||||
public clone(): RaycastHit {
|
||||
const hit = new RaycastHit();
|
||||
hit.setAllValues(
|
||||
this.collider,
|
||||
this.fraction,
|
||||
this.distance,
|
||||
this.point,
|
||||
this.normal
|
||||
);
|
||||
return hit;
|
||||
}
|
||||
|
||||
public toString() {
|
||||
return `[RaycastHit] fraction: ${this.fraction}, distance: ${this.distance}, normal: ${this.normal}, centroid: ${this.centroid}, point: ${this.point}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,8 +95,8 @@ module es {
|
||||
}
|
||||
|
||||
public debugDraw(secondsToDisplay: number) {
|
||||
for (let x = this.gridBounds.x; x <= this.gridBounds.right; x ++) {
|
||||
for (let y = this.gridBounds.y; y <= this.gridBounds.bottom; y ++) {
|
||||
for (let x = this.gridBounds.x; x <= this.gridBounds.right; x++) {
|
||||
for (let y = this.gridBounds.y; y <= this.gridBounds.bottom; y++) {
|
||||
let cell = this.cellAtPosition(x, y);
|
||||
if (cell != null && cell.length > 0)
|
||||
this.debugDrawCellDetails(x, y, secondsToDisplay);
|
||||
@@ -154,7 +154,7 @@ module es {
|
||||
* @param hits
|
||||
* @param layerMask
|
||||
*/
|
||||
public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number){
|
||||
public linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number) {
|
||||
let ray = new Ray2D(start, end);
|
||||
this._raycastParser.start(ray, hits, layerMask);
|
||||
|
||||
@@ -189,24 +189,24 @@ module es {
|
||||
// 开始遍历并返回交叉单元格。
|
||||
let cell = this.cellAtPosition(currentCell.x, currentCell.y);
|
||||
|
||||
if (cell != null && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)){
|
||||
if (cell != null && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
|
||||
this._raycastParser.reset();
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
|
||||
while (currentCell.x != lastCell.x || currentCell.y != lastCell.y){
|
||||
if (tMaxX < tMaxY){
|
||||
while (currentCell.x != lastCell.x || currentCell.y != lastCell.y) {
|
||||
if (tMaxX < tMaxY) {
|
||||
currentCell.x = MathHelper.approach(currentCell.x, lastCell.x, Math.abs(stepX));
|
||||
|
||||
tMaxX += tDeltaX;
|
||||
}else{
|
||||
} else {
|
||||
currentCell.y = MathHelper.approach(currentCell.y, lastCell.y, Math.abs(stepY));
|
||||
|
||||
tMaxY += tDeltaY;
|
||||
}
|
||||
|
||||
cell = this.cellAtPosition(currentCell.x, currentCell.y);
|
||||
if (cell && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)){
|
||||
if (cell && this._raycastParser.checkRayIntersection(currentCell.x, currentCell.y, cell)) {
|
||||
this._raycastParser.reset();
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
@@ -217,6 +217,68 @@ module es {
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
|
||||
public linecastIgnoreCollider(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>): number {
|
||||
start = start.clone();
|
||||
const ray = new Ray2D(start, end);
|
||||
this._raycastParser.startIgnoreCollider(ray, hits, layerMask, ignoredColliders);
|
||||
start.x = start.x * this._inverseCellSize;
|
||||
start.y = start.y * this._inverseCellSize;
|
||||
const endCell = this.cellCoords(end.x, end.y);
|
||||
let intX = Math.floor(start.x);
|
||||
let intY = Math.floor(start.y);
|
||||
let stepX = Math.sign(ray.direction.x);
|
||||
let stepY = Math.sign(ray.direction.y);
|
||||
if (intX === endCell.x) {
|
||||
stepX = 0;
|
||||
}
|
||||
|
||||
if (intY === endCell.y) {
|
||||
stepY = 0;
|
||||
}
|
||||
|
||||
const boundaryX = intX + (stepX > 0 ? 1 : 0);
|
||||
const boundaryY = intY + (stepY > 0 ? 1 : 0);
|
||||
|
||||
let tMaxX = (boundaryX - start.x) / ray.direction.x;
|
||||
let tMaxY = (boundaryY - start.y) / ray.direction.y;
|
||||
if (ray.direction.x === 0 || stepX === 0) {
|
||||
tMaxX = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
if (ray.direction.y === 0 || stepY === 0) {
|
||||
tMaxY = Number.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
const tDeltaX = stepX / ray.direction.x;
|
||||
const tDeltaY = stepY / ray.direction.y;
|
||||
let cell = this.cellAtPosition(intX, intY);
|
||||
if (cell && this._raycastParser.checkRayIntersection(intX, intY, cell)) {
|
||||
this._raycastParser.reset();
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
|
||||
let n = 0;
|
||||
while ((intX !== endCell.x || intY !== endCell.y) && n < 100) {
|
||||
if (tMaxX < tMaxY) {
|
||||
intX = intX + stepX;
|
||||
tMaxX = tMaxX + tDeltaX;
|
||||
} else {
|
||||
intY = intY + stepY;
|
||||
tMaxY = tMaxY + tDeltaY;
|
||||
}
|
||||
|
||||
cell = this.cellAtPosition(intX, intY);
|
||||
if (cell && this._raycastParser.checkRayIntersection(intX, intY, cell)) {
|
||||
this._raycastParser.reset();
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
this._raycastParser.reset();
|
||||
return this._raycastParser.hitCounter;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取所有在指定矩形范围内的碰撞器
|
||||
* @param rect
|
||||
@@ -232,16 +294,16 @@ module es {
|
||||
for (let collider of potentials) {
|
||||
if (collider instanceof BoxCollider) {
|
||||
results[resultCounter] = collider;
|
||||
resultCounter ++;
|
||||
} else if(collider instanceof CircleCollider) {
|
||||
resultCounter++;
|
||||
} else if (collider instanceof CircleCollider) {
|
||||
if (Collisions.rectToCircle(rect, collider.bounds.center, collider.bounds.width * 0.5)) {
|
||||
results[resultCounter] = collider;
|
||||
resultCounter ++;
|
||||
resultCounter++;
|
||||
}
|
||||
} else if(collider instanceof PolygonCollider) {
|
||||
} else if (collider instanceof PolygonCollider) {
|
||||
if (collider.shape.overlaps(this._overlapTestBox)) {
|
||||
results[resultCounter] = collider;
|
||||
resultCounter ++;
|
||||
resultCounter++;
|
||||
}
|
||||
} else {
|
||||
throw new Error("overlapRectangle对这个类型没有实现!");
|
||||
@@ -348,7 +410,7 @@ module es {
|
||||
return this._store.get(this.getKey(x, y));
|
||||
}
|
||||
|
||||
public getKey(x: number, y: number){
|
||||
public getKey(x: number, y: number) {
|
||||
return `${x}_${y}`;
|
||||
}
|
||||
|
||||
@@ -372,6 +434,7 @@ module es {
|
||||
public _cellHits: RaycastHit[] = [];
|
||||
public _ray: Ray2D;
|
||||
public _layerMask: number;
|
||||
private _ignoredColliders: Set<Collider>;
|
||||
|
||||
public start(ray: Ray2D, hits: RaycastHit[], layerMask: number) {
|
||||
this._ray = ray;
|
||||
@@ -380,6 +443,14 @@ module es {
|
||||
this.hitCounter = 0;
|
||||
}
|
||||
|
||||
public startIgnoreCollider(ray: Ray2D, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>) {
|
||||
this._ray = ray;
|
||||
this._hits = hits;
|
||||
this._layerMask = layerMask;
|
||||
this._ignoredColliders = ignoredColliders;
|
||||
this.hitCounter = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果hits数组被填充,返回true。单元格不能为空!
|
||||
* @param cellX
|
||||
@@ -408,7 +479,7 @@ module es {
|
||||
// TODO: 如果边界检查返回更多数据,我们就不需要为BoxCollider检查做任何事情
|
||||
// 在做形状测试之前先做一个边界检查
|
||||
let colliderBounds = potential.bounds.clone();
|
||||
if (colliderBounds.rayIntersects(this._ray, fraction) && fraction.value <= 1){
|
||||
if (colliderBounds.rayIntersects(this._ray, fraction) && fraction.value <= 1) {
|
||||
if (potential.shape.collidesWithLine(this._ray.start, this._ray.end, this._tempHit)) {
|
||||
// 检查一下,我们应该排除这些射线,射线cast是否在碰撞器中开始
|
||||
if (!Physics.raycastsStartInColliders && potential.shape.containsPoint(this._ray.start))
|
||||
@@ -427,11 +498,11 @@ module es {
|
||||
|
||||
// 所有处理单元完成。对结果进行排序并将命中结果打包到结果数组中
|
||||
this._cellHits.sort(RaycastResultParser.compareRaycastHits);
|
||||
for (let i = 0; i < this._cellHits.length; i ++){
|
||||
for (let i = 0; i < this._cellHits.length; i++) {
|
||||
this._hits[this.hitCounter] = this._cellHits[i];
|
||||
|
||||
// 增加命中计数器,如果它已经达到数组大小的限制,我们就完成了
|
||||
this.hitCounter ++;
|
||||
this.hitCounter++;
|
||||
if (this.hitCounter == this._hits.length)
|
||||
return true;
|
||||
}
|
||||
@@ -439,7 +510,7 @@ module es {
|
||||
return false;
|
||||
}
|
||||
|
||||
public reset(){
|
||||
public reset() {
|
||||
this._hits = null;
|
||||
this._checkedColliders.length = 0;
|
||||
this._cellHits.length = 0;
|
||||
|
||||
48
source/src/Utils/AnimCurve.ts
Normal file
48
source/src/Utils/AnimCurve.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
module es {
|
||||
export interface IAnimFrame {
|
||||
t: number;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export class AnimCurve {
|
||||
public get points(): IAnimFrame[] {
|
||||
return this._points;
|
||||
}
|
||||
|
||||
public constructor(points: IAnimFrame[]) {
|
||||
if (points.length < 2) {
|
||||
throw new Error('curve length must be >= 2');
|
||||
}
|
||||
points.sort((a: IAnimFrame, b: IAnimFrame) => {
|
||||
return a.t - b.t;
|
||||
});
|
||||
if (points[0].t !== 0) {
|
||||
throw new Error('curve must start with 0');
|
||||
}
|
||||
if (points[points.length - 1].t !== 1) {
|
||||
throw new Error('curve must end with 1');
|
||||
}
|
||||
this._points = points;
|
||||
}
|
||||
|
||||
public lerp(t: number): number {
|
||||
for (let i = 1; i < this._points.length; i++) {
|
||||
if (t <= this._points[i].t) {
|
||||
const m = MathHelper.map01(
|
||||
t,
|
||||
this._points[i - 1].t,
|
||||
this._points[i].t
|
||||
);
|
||||
return MathHelper.lerp(
|
||||
this._points[i - 1].value,
|
||||
this._points[i].value,
|
||||
m
|
||||
);
|
||||
}
|
||||
}
|
||||
throw new Error('should never be here');
|
||||
}
|
||||
|
||||
public _points: IAnimFrame[];
|
||||
}
|
||||
}
|
||||
184
source/src/Utils/Observable.ts
Normal file
184
source/src/Utils/Observable.ts
Normal file
@@ -0,0 +1,184 @@
|
||||
module es {
|
||||
export interface IListener {
|
||||
caller: object;
|
||||
callback: Function;
|
||||
}
|
||||
|
||||
export interface IObservable {
|
||||
addListener(caller: object, callback: Function);
|
||||
removeListener(caller: object, callback: Function);
|
||||
clearListener();
|
||||
clearListenerWithCaller(caller: object);
|
||||
}
|
||||
|
||||
export class Observable implements IObservable {
|
||||
public constructor() {
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
if (
|
||||
this._listeners.findIndex(
|
||||
listener =>
|
||||
listener.callback === callback && listener.caller === caller
|
||||
) === -1
|
||||
) {
|
||||
this._listeners.push({ caller, callback });
|
||||
}
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
const index = this._listeners.findIndex(
|
||||
listener => listener.callback === callback && listener.caller === caller
|
||||
);
|
||||
if (index >= 0) {
|
||||
this._listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._listeners = [];
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
for (let i = this._listeners.length - 1; i >= 0; i--) {
|
||||
const listener = this._listeners[i];
|
||||
if (listener.caller === caller) {
|
||||
this._listeners.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public notify(...args) {
|
||||
for (let i = this._listeners.length - 1; i >= 0; i--) {
|
||||
const listener = this._listeners[i];
|
||||
if (listener.caller) {
|
||||
listener.callback.call(listener.caller, ...args);
|
||||
} else {
|
||||
listener.callback(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _listeners: IListener[];
|
||||
}
|
||||
|
||||
export class ObservableT<T> extends Observable {
|
||||
public addListener(caller: object, callback: (arg: T) => void) {
|
||||
super.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: (arg: T) => void) {
|
||||
super.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public notify(arg: T) {
|
||||
super.notify(arg);
|
||||
}
|
||||
}
|
||||
|
||||
export class ObservableTT<T, R> extends Observable {
|
||||
public addListener(caller: object, callback: (arg1: T, arg2: R) => void) {
|
||||
super.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: (arg: T, arg2: R) => void) {
|
||||
super.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public notify(arg1: T, arg2: R) {
|
||||
super.notify(arg1, arg2);
|
||||
}
|
||||
}
|
||||
|
||||
export class Command implements IObservable {
|
||||
public constructor(caller: object, action: Function) {
|
||||
this.bindAction(caller, action);
|
||||
this._onExec = new Observable();
|
||||
}
|
||||
|
||||
public bindAction(caller: object, action: Function) {
|
||||
this._caller = caller;
|
||||
this._action = action;
|
||||
}
|
||||
|
||||
public dispatch(...args: any[]) {
|
||||
if (this._action) {
|
||||
if (this._caller) {
|
||||
this._action.call(this._caller, ...args);
|
||||
} else {
|
||||
this._action(...args);
|
||||
}
|
||||
this._onExec.notify();
|
||||
} else {
|
||||
console.warn('command not bind with an action');
|
||||
}
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
this._onExec.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
this._onExec.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._onExec.clearListener();
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
this._onExec.clearListenerWithCaller(caller);
|
||||
}
|
||||
|
||||
private _onExec: Observable;
|
||||
private _caller: object;
|
||||
private _action: Function;
|
||||
}
|
||||
|
||||
export class ValueChangeCommand<T> implements IObservable {
|
||||
public constructor(value: T) {
|
||||
this._onValueChange = new Observable();
|
||||
this._value = value;
|
||||
}
|
||||
|
||||
public get onValueChange() {
|
||||
return this._onValueChange;
|
||||
}
|
||||
|
||||
public get value() {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
public set value(newValue: T) {
|
||||
this._value = newValue;
|
||||
}
|
||||
|
||||
public dispatch(value: T) {
|
||||
if (value !== this._value) {
|
||||
const oldValue = this._value;
|
||||
this._value = value;
|
||||
this._onValueChange.notify(this._value, oldValue);
|
||||
}
|
||||
}
|
||||
|
||||
public addListener(caller: object, callback: Function) {
|
||||
this._onValueChange.addListener(caller, callback);
|
||||
}
|
||||
|
||||
public removeListener(caller: object, callback: Function) {
|
||||
this._onValueChange.removeListener(caller, callback);
|
||||
}
|
||||
|
||||
public clearListener() {
|
||||
this._onValueChange.clearListener();
|
||||
}
|
||||
|
||||
public clearListenerWithCaller(caller: object) {
|
||||
this._onValueChange.clearListenerWithCaller(caller);
|
||||
}
|
||||
|
||||
private _onValueChange: Observable;
|
||||
private _value: T;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user