新增角色控制器

This commit is contained in:
yhh
2021-06-22 14:41:21 +08:00
parent fe308d35f2
commit e5dfb20aa2
13 changed files with 2074 additions and 31 deletions

View File

@@ -444,6 +444,10 @@ declare module es {
static readonly one: Vector2;
static readonly unitX: Vector2;
static readonly unitY: Vector2;
static readonly up: Vector2;
static readonly down: Vector2;
static readonly left: Vector2;
static readonly right: Vector2;
/**
*
* @param value1
@@ -643,6 +647,7 @@ declare module es {
* @returns
*/
static hermite(value1: Vector2, tangent1: Vector2, value2: Vector2, tangent2: Vector2, amount: number): Vector2;
static unsignedAngle(from: Vector2, to: Vector2, round?: boolean): number;
clone(): Vector2;
}
}
@@ -1114,6 +1119,138 @@ declare module es {
calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity?: Vector2): void;
}
}
declare module es {
class CharacterCollisionState2D {
right: boolean;
left: boolean;
above: boolean;
below: boolean;
becameGroundedThisFrame: boolean;
wasGroundedLastFrame: boolean;
movingDownSlope: boolean;
slopeAngle: number;
hasCollision(): boolean;
reset(): void;
toString(): string;
}
class CharacterController implements ITriggerListener {
onControllerCollidedEvent: ObservableT<RaycastHit>;
onTriggerEnterEvent: ObservableT<Collider>;
onTriggerExitEvent: ObservableT<Collider>;
/**
* 如果为 true则在垂直移动单帧时将忽略平台的一种方式
*/
ignoreOneWayPlatformsTime: number;
supportSlopedOneWayPlatforms: boolean;
ignoredColliders: Set<Collider>;
/**
* 定义距离碰撞射线的边缘有多远。
* 如果使用 0 范围进行投射,则通常会导致不需要的光线击中(例如,直接从表面水平投射的足部碰撞器可能会导致击中)
*/
skinWidth: number;
/**
* CC2D 可以爬升的最大坡度角
*/
slopeLimit: number;
/**
* 构成跳跃的帧之间垂直运动变化的阈值
*/
jumpingThreshold: number;
/**
* 基于斜率乘以速度的曲线(负 = 下坡和正 = 上坡)
*/
slopeSpeedMultiplier: AnimCurve;
totalHorizontalRays: number;
totalVerticalRays: number;
collisionState: CharacterCollisionState2D;
velocity: Vector2;
readonly isGrounded: boolean;
readonly raycastHitsThisFrame: RaycastHit[];
constructor(player: Entity, skinWidth?: number, platformMask?: number, onewayPlatformMask?: number, triggerMask?: number);
onTriggerEnter(other: Collider, local: Collider): void;
onTriggerExit(other: Collider, local: Collider): void;
/**
* 尝试将角色移动到位置 + deltaMovement。 任何挡路的碰撞器都会在遇到时导致运动停止
* @param deltaMovement
* @param deltaTime
*/
move(deltaMovement: Vector2, deltaTime: number): void;
/**
* 直接向下移动直到接地
* @param maxDistance
*/
warpToGrounded(maxDistance?: number): void;
/**
* 这应该在您必须在运行时修改 BoxCollider2D 的任何时候调用。
* 它将重新计算用于碰撞检测的光线之间的距离。
* 它也用于 skinWidth setter以防在运行时更改。
*/
recalculateDistanceBetweenRays(): void;
/**
* 将 raycastOrigins 重置为由 skinWidth 插入的框碰撞器的当前范围。
* 插入它是为了避免从直接接触另一个碰撞器的位置投射光线,从而导致不稳定的法线数据。
*/
private primeRaycastOrigins;
/**
* 我们必须在这方面使用一些技巧。
* 光线必须从我们的碰撞器skinWidth内部的一小段距离投射以避免零距离光线会得到错误的法线。
* 由于这个小偏移,我们必须增加光线距离 skinWidth 然后记住在实际移动玩家之前从 deltaMovement 中删除 skinWidth
* @param deltaMovement
* @returns
*/
private moveHorizontally;
private moveVertically;
/**
* 检查 BoxCollider2D 下的中心点是否存在坡度。
* 如果找到一个,则调整 deltaMovement 以便玩家保持接地并考虑slopeSpeedModifier 以加快移动速度。
* @param deltaMovement
* @returns
*/
private handleVerticalSlope;
/**
* 如果我们要上坡,则处理调整 deltaMovement
* @param deltaMovement
* @param angle
* @returns
*/
private handleHorizontalSlope;
private _player;
private _collider;
private _skinWidth;
private _triggerHelper;
/**
* 这用于计算为检查坡度而投射的向下光线。
* 我们使用有点随意的值 75 度来计算检查斜率的射线的长度。
*/
private _slopeLimitTangent;
private readonly kSkinWidthFloatFudgeFactor;
/**
* 我们的光线投射原点角的支架TR、TL、BR、BL
*/
private _raycastOrigins;
/**
* 存储我们在移动过程中命中的光线投射
*/
private _raycastHit;
/**
* 存储此帧发生的任何光线投射命中。
* 我们必须存储它们,以防我们遇到水平和垂直移动的碰撞,以便我们可以在设置所有碰撞状态后发送事件
*/
private _raycastHitsThisFrame;
private _verticalDistanceBetweenRays;
private _horizontalDistanceBetweenRays;
/**
* 我们使用这个标志来标记我们正在爬坡的情况,我们修改了 delta.y 以允许爬升。
* 原因是,如果我们到达斜坡的尽头,我们可以进行调整以保持接地
*/
private _isGoingUpSlope;
private _isWarpingToGround;
private platformMask;
private triggerMask;
private oneWayPlatformMask;
private readonly rayOriginSkinMutiplier;
}
}
declare module es {
/**
* 当添加到组件时,每当实体上的冲突器与另一个组件重叠/退出时,将调用这些方法。
@@ -1189,6 +1326,7 @@ declare module es {
}
declare module es {
abstract class Collider extends Component {
protected _isEnabled: boolean;
/**
* 对撞机的基本形状
*/
@@ -2672,6 +2810,7 @@ declare module es {
*/
static isPowerOfTwo(value: number): boolean;
static lerp(from: number, to: number, t: number): number;
static betterLerp(a: number, b: number, t: number, epsilon: number): number;
/**
* 使度数的角度在a和b之间
* 用于处理360度环绕
@@ -2914,6 +3053,21 @@ declare module es {
* @returns
*/
static isValid(x: number): boolean;
static smoothDamp(current: number, target: number, currentVelocity: number, smoothTime: number, maxSpeed: number, deltaTime: number): {
value: number;
currentVelocity: number;
};
static smoothDampVector(current: Vector2, target: Vector2, currentVelocity: Vector2, smoothTime: number, maxSpeed: number, deltaTime: number): Vector2;
/**
* 将值(在 leftMin - leftMax 范围内)映射到 rightMin - rightMax 范围内的值
* @param value
* @param leftMin
* @param leftMax
* @param rightMin
* @param rightMax
* @returns
*/
static mapMinMax(value: number, leftMin: number, leftMax: number, rightMin: number, rightMax: any): number;
}
}
declare module es {
@@ -3396,7 +3550,9 @@ declare module es {
constructor(collider?: Collider, fraction?: number, distance?: number, point?: Vector2, normal?: Vector2);
setValues(collider: Collider, fraction: number, distance: number, point: Vector2): void;
setValuesNonCollider(fraction: number, distance: number, point: Vector2, normal: Vector2): void;
setAllValues(collider: Collider, fraction: number, distance: number, point: Vector2, normal: Vector2): void;
reset(): void;
clone(): RaycastHit;
toString(): string;
}
}
@@ -3497,6 +3653,7 @@ declare module es {
* @param layerMask
*/
static linecast(start: Vector2, end: Vector2, layerMask?: number): RaycastHit;
static linecastIgnoreCollider(start: Vector2, end: Vector2, layerMask?: number, ignoredColliders?: Set<Collider>): RaycastHit;
/**
* 通过空间散列强制执行一行并用该行命中的任何碰撞器填充hits数组
* @param start
@@ -3505,6 +3662,7 @@ declare module es {
* @param layerMask
*/
static linecastAll(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask?: number): number;
static linecastAllIgnoreCollider(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask?: number, ignoredColliders?: Set<Collider>): number;
/**
* 检查是否有对撞机落在一个矩形区域中
* @param rect
@@ -3595,6 +3753,7 @@ declare module es {
* @param layerMask
*/
linecast(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number): number;
linecastIgnoreCollider(start: Vector2, end: Vector2, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>): number;
/**
* 获取所有在指定矩形范围内的碰撞器
* @param rect
@@ -3649,7 +3808,9 @@ declare module es {
_cellHits: RaycastHit[];
_ray: Ray2D;
_layerMask: number;
private _ignoredColliders;
start(ray: Ray2D, hits: RaycastHit[], layerMask: number): void;
startIgnoreCollider(ray: Ray2D, hits: RaycastHit[], layerMask: number, ignoredColliders: Set<Collider>): void;
/**
* 如果hits数组被填充返回true。单元格不能为空!
* @param cellX
@@ -3942,6 +4103,18 @@ declare module es {
static intervalDistance(minA: number, maxA: number, minB: number, maxB: any): number;
}
}
declare module es {
interface IAnimFrame {
t: number;
value: number;
}
class AnimCurve {
readonly points: IAnimFrame[];
constructor(points: IAnimFrame[]);
lerp(t: number): number;
_points: IAnimFrame[];
}
}
declare module es {
/**
* 用于包装事件的一个小类
@@ -4087,6 +4260,61 @@ declare module es {
equals(other: T): boolean;
}
}
declare module es {
interface IListener {
caller: object;
callback: Function;
}
interface IObservable {
addListener(caller: object, callback: Function): any;
removeListener(caller: object, callback: Function): any;
clearListener(): any;
clearListenerWithCaller(caller: object): any;
}
class Observable implements IObservable {
constructor();
addListener(caller: object, callback: Function): void;
removeListener(caller: object, callback: Function): void;
clearListener(): void;
clearListenerWithCaller(caller: object): void;
notify(...args: any[]): void;
private _listeners;
}
class ObservableT<T> extends Observable {
addListener(caller: object, callback: (arg: T) => void): void;
removeListener(caller: object, callback: (arg: T) => void): void;
notify(arg: T): void;
}
class ObservableTT<T, R> extends Observable {
addListener(caller: object, callback: (arg1: T, arg2: R) => void): void;
removeListener(caller: object, callback: (arg: T, arg2: R) => void): void;
notify(arg1: T, arg2: R): void;
}
class Command implements IObservable {
constructor(caller: object, action: Function);
bindAction(caller: object, action: Function): void;
dispatch(...args: any[]): void;
addListener(caller: object, callback: Function): void;
removeListener(caller: object, callback: Function): void;
clearListener(): void;
clearListenerWithCaller(caller: object): void;
private _onExec;
private _caller;
private _action;
}
class ValueChangeCommand<T> implements IObservable {
constructor(value: T);
readonly onValueChange: Observable;
value: T;
dispatch(value: T): void;
addListener(caller: object, callback: Function): void;
removeListener(caller: object, callback: Function): void;
clearListener(): void;
clearListenerWithCaller(caller: object): void;
private _onValueChange;
private _value;
}
}
declare module es {
class Ref<T> {
value: T;

View File

@@ -80,10 +80,9 @@ var es;
* 全局核心类
*/
var Core = /** @class */ (function () {
function Core(debug, enableEntitySystems, remoteUrl) {
function Core(debug, enableEntitySystems) {
if (debug === void 0) { debug = true; }
if (enableEntitySystems === void 0) { enableEntitySystems = true; }
if (remoteUrl === void 0) { remoteUrl = ""; }
/**
* 全局访问系统
*/
@@ -1119,6 +1118,34 @@ var es;
enumerable: true,
configurable: true
});
Object.defineProperty(Vector2, "up", {
get: function () {
return new Vector2(0, -1);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector2, "down", {
get: function () {
return new Vector2(0, 1);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector2, "left", {
get: function () {
return new Vector2(-1, 0);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Vector2, "right", {
get: function () {
return new Vector2(1, 0);
},
enumerable: true,
configurable: true
});
/**
*
* @param value1
@@ -1439,6 +1466,13 @@ var es;
Vector2.hermite = function (value1, tangent1, value2, tangent2, amount) {
return new Vector2(es.MathHelper.hermite(value1.x, tangent1.x, value2.x, tangent2.x, amount), es.MathHelper.hermite(value1.y, tangent1.y, value2.y, tangent2.y, amount));
};
Vector2.unsignedAngle = function (from, to, round) {
if (round === void 0) { round = true; }
from.normalize();
to.normalize();
var angle = Math.acos(es.MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * es.MathHelper.Rad2Deg;
return round ? Math.round(angle) : angle;
};
Vector2.prototype.clone = function () {
return new Vector2(this.x, this.y);
};
@@ -2522,6 +2556,421 @@ var es;
es.ArcadeRigidbody = ArcadeRigidbody;
})(es || (es = {}));
var es;
(function (es) {
var CharacterRaycastOrigins = /** @class */ (function () {
function CharacterRaycastOrigins() {
this.topLeft = es.Vector2.zero;
this.bottomRight = es.Vector2.zero;
this.bottomLeft = es.Vector2.zero;
}
return CharacterRaycastOrigins;
}());
var CharacterCollisionState2D = /** @class */ (function () {
function CharacterCollisionState2D() {
this.right = false;
this.left = false;
this.above = false;
this.below = false;
this.becameGroundedThisFrame = false;
this.wasGroundedLastFrame = false;
this.movingDownSlope = false;
this.slopeAngle = 0;
}
CharacterCollisionState2D.prototype.hasCollision = function () {
return this.below || this.right || this.left || this.above;
};
CharacterCollisionState2D.prototype.reset = function () {
this.right = this.left = false;
this.above = this.below = false;
this.becameGroundedThisFrame = this.movingDownSlope = false;
this.slopeAngle = 0;
};
CharacterCollisionState2D.prototype.toString = function () {
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;
};
return CharacterCollisionState2D;
}());
var CharacterController = /** @class */ (function () {
function CharacterController(player, skinWidth, platformMask, onewayPlatformMask, triggerMask) {
if (platformMask === void 0) { platformMask = -1; }
if (onewayPlatformMask === void 0) { onewayPlatformMask = -1; }
if (triggerMask === void 0) { triggerMask = -1; }
this.ignoredColliders = new Set();
/**
* CC2D 可以爬升的最大坡度角
*/
this.slopeLimit = 30;
/**
* 构成跳跃的帧之间垂直运动变化的阈值
*/
this.jumpingThreshold = -7;
this.totalHorizontalRays = 5;
this.totalVerticalRays = 3;
this.collisionState = new CharacterCollisionState2D();
this.velocity = new es.Vector2(0, 0);
this._skinWidth = 0.02;
this.kSkinWidthFloatFudgeFactor = 0.001;
/**
* 我们的光线投射原点角的支架TR、TL、BR、BL
*/
this._raycastOrigins = new CharacterRaycastOrigins();
/**
* 存储我们在移动过程中命中的光线投射
*/
this._raycastHit = new es.RaycastHit();
/**
* 我们使用这个标志来标记我们正在爬坡的情况,我们修改了 delta.y 以允许爬升。
* 原因是,如果我们到达斜坡的尽头,我们可以进行调整以保持接地
*/
this._isGoingUpSlope = false;
this._isWarpingToGround = true;
this.platformMask = -1;
this.triggerMask = -1;
this.oneWayPlatformMask = -1;
this.rayOriginSkinMutiplier = 4;
this.onTriggerEnterEvent = new es.ObservableT();
this.onTriggerExitEvent = new es.ObservableT();
this.onControllerCollidedEvent = new es.ObservableT();
this.platformMask = platformMask;
this.oneWayPlatformMask = onewayPlatformMask;
this.triggerMask = triggerMask;
// 将我们的单向平台添加到我们的普通平台掩码中,以便我们可以从上方降落
this.platformMask |= this.oneWayPlatformMask;
this._player = player;
var collider = null;
for (var i = 0; i < this._player.components.buffer.length; i++) {
var component = this._player.components.buffer[i];
if (component instanceof es.Collider) {
collider = component;
break;
}
}
collider.isTrigger = false;
if (collider instanceof es.BoxCollider) {
this._collider = collider;
}
else {
throw new Error('player collider must be box');
}
// 在这里,我们触发了具有主体的 setter 的属性
this.skinWidth = skinWidth || collider.width * 0.05;
this._slopeLimitTangent = Math.tan(75 * es.MathHelper.Deg2Rad);
this._triggerHelper = new es.ColliderTriggerHelper(this._player);
// 我们想设置我们的 CC2D 忽略所有碰撞层,除了我们的 triggerMask
for (var i = 0; i < 32; i++) {
// 查看我们的 triggerMask 是否包含此层,如果不包含则忽略它
if ((this.triggerMask & (1 << i)) === 0) {
es.Flags.unsetFlag(this._collider.collidesWithLayers, i);
}
}
}
Object.defineProperty(CharacterController.prototype, "skinWidth", {
/**
* 定义距离碰撞射线的边缘有多远。
* 如果使用 0 范围进行投射,则通常会导致不需要的光线击中(例如,直接从表面水平投射的足部碰撞器可能会导致击中)
*/
get: function () {
return this._skinWidth;
},
set: function (value) {
this._skinWidth = value;
this.recalculateDistanceBetweenRays();
},
enumerable: true,
configurable: true
});
Object.defineProperty(CharacterController.prototype, "isGrounded", {
get: function () {
return this.collisionState.below;
},
enumerable: true,
configurable: true
});
Object.defineProperty(CharacterController.prototype, "raycastHitsThisFrame", {
get: function () {
return this._raycastHitsThisFrame;
},
enumerable: true,
configurable: true
});
CharacterController.prototype.onTriggerEnter = function (other, local) {
this.onTriggerEnterEvent.notify(other);
};
CharacterController.prototype.onTriggerExit = function (other, local) {
this.onTriggerExitEvent.notify(other);
};
/**
* 尝试将角色移动到位置 + deltaMovement。 任何挡路的碰撞器都会在遇到时导致运动停止
* @param deltaMovement
* @param deltaTime
*/
CharacterController.prototype.move = function (deltaMovement, deltaTime) {
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 (var i = 0; i < this._raycastHitsThisFrame.length; i++) {
this.onControllerCollidedEvent.notify(this._raycastHitsThisFrame[i]);
}
if (this.ignoreOneWayPlatformsTime > 0) {
this.ignoreOneWayPlatformsTime -= deltaTime;
}
};
/**
* 直接向下移动直到接地
* @param maxDistance
*/
CharacterController.prototype.warpToGrounded = function (maxDistance) {
if (maxDistance === void 0) { maxDistance = 1000; }
this.ignoreOneWayPlatformsTime = 0;
this._isWarpingToGround = true;
var delta = 0;
do {
delta += 1;
this.move(new es.Vector2(0, 1), 0.02);
if (delta > maxDistance) {
break;
}
} while (!this.isGrounded);
this._isWarpingToGround = false;
};
/**
* 这应该在您必须在运行时修改 BoxCollider2D 的任何时候调用。
* 它将重新计算用于碰撞检测的光线之间的距离。
* 它也用于 skinWidth setter以防在运行时更改。
*/
CharacterController.prototype.recalculateDistanceBetweenRays = function () {
var colliderUsableHeight = this._collider.height * Math.abs(this._player.scale.y) -
2 * this._skinWidth;
this._verticalDistanceBetweenRays =
colliderUsableHeight / (this.totalHorizontalRays - 1);
var colliderUsableWidth = this._collider.width * Math.abs(this._player.scale.x) -
2 * this._skinWidth;
this._horizontalDistanceBetweenRays =
colliderUsableWidth / (this.totalVerticalRays - 1);
};
/**
* 将 raycastOrigins 重置为由 skinWidth 插入的框碰撞器的当前范围。
* 插入它是为了避免从直接接触另一个碰撞器的位置投射光线,从而导致不稳定的法线数据。
*/
CharacterController.prototype.primeRaycastOrigins = function () {
var rect = this._collider.bounds;
this._raycastOrigins.topLeft = new es.Vector2(rect.x + this._skinWidth, rect.y + this._skinWidth);
this._raycastOrigins.bottomRight = new es.Vector2(rect.right - this._skinWidth, rect.bottom - this._skinWidth);
this._raycastOrigins.bottomLeft = new es.Vector2(rect.x + this._skinWidth, rect.bottom - this._skinWidth);
};
/**
* 我们必须在这方面使用一些技巧。
* 光线必须从我们的碰撞器skinWidth内部的一小段距离投射以避免零距离光线会得到错误的法线。
* 由于这个小偏移,我们必须增加光线距离 skinWidth 然后记住在实际移动玩家之前从 deltaMovement 中删除 skinWidth
* @param deltaMovement
* @returns
*/
CharacterController.prototype.moveHorizontally = function (deltaMovement) {
var isGoingRight = deltaMovement.x > 0;
var rayDistance = Math.abs(deltaMovement.x) +
this._skinWidth * this.rayOriginSkinMutiplier;
var rayDirection = isGoingRight ? es.Vector2.right : es.Vector2.left;
var initialRayOriginY = this._raycastOrigins.bottomLeft.y;
var initialRayOriginX = isGoingRight
? this._raycastOrigins.bottomRight.x -
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.x +
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
for (var i = 0; i < this.totalHorizontalRays; i++) {
var ray = new es.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 = es.Physics.linecastIgnoreCollider(ray, ray.add(rayDirection.multiplyScaler(rayDistance)), this.platformMask, this.ignoredColliders);
}
else {
this._raycastHit = es.Physics.linecastIgnoreCollider(ray, ray.add(rayDirection.multiplyScaler(rayDistance)), this.platformMask & ~this.oneWayPlatformMask, this.ignoredColliders);
}
if (this._raycastHit.collider) {
if (i === 0 &&
this.handleHorizontalSlope(deltaMovement, es.Vector2.unsignedAngle(this._raycastHit.normal, es.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;
};
CharacterController.prototype.moveVertically = function (deltaMovement) {
var isGoingUp = deltaMovement.y < 0;
var rayDistance = Math.abs(deltaMovement.y) +
this._skinWidth * this.rayOriginSkinMutiplier;
var rayDirection = isGoingUp ? es.Vector2.up : es.Vector2.down;
var initialRayOriginX = this._raycastOrigins.topLeft.x;
var initialRayOriginY = isGoingUp
? this._raycastOrigins.topLeft.y +
this._skinWidth * (this.rayOriginSkinMutiplier - 1)
: this._raycastOrigins.bottomLeft.y -
this._skinWidth * (this.rayOriginSkinMutiplier - 1);
initialRayOriginX += deltaMovement.x;
var mask = this.platformMask;
if (isGoingUp || this.ignoreOneWayPlatformsTime > 0) {
mask &= ~this.oneWayPlatformMask;
}
for (var i = 0; i < this.totalVerticalRays; i++) {
var rayStart = new es.Vector2(initialRayOriginX + i * this._horizontalDistanceBetweenRays, initialRayOriginY);
this._raycastHit = es.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
*/
CharacterController.prototype.handleVerticalSlope = function (deltaMovement) {
var centerOfCollider = (this._raycastOrigins.bottomLeft.x +
this._raycastOrigins.bottomRight.x) *
0.5;
var rayDirection = es.Vector2.down;
var slopeCheckRayDistance = this._slopeLimitTangent *
(this._raycastOrigins.bottomRight.x - centerOfCollider);
var slopeRay = new es.Vector2(centerOfCollider, this._raycastOrigins.bottomLeft.y);
this._raycastHit = es.Physics.linecastIgnoreCollider(slopeRay, slopeRay.add(rayDirection.multiplyScaler(slopeCheckRayDistance)), this.platformMask, this.ignoredColliders);
if (this._raycastHit.collider) {
var angle = es.Vector2.unsignedAngle(this._raycastHit.normal, es.Vector2.up);
if (angle === 0) {
return deltaMovement;
}
var isMovingDownSlope = Math.sign(this._raycastHit.normal.x) === Math.sign(deltaMovement.x);
if (isMovingDownSlope) {
var 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
*/
CharacterController.prototype.handleHorizontalSlope = function (deltaMovement, angle) {
if (Math.round(angle) === 90) {
return false;
}
if (angle < this.slopeLimit) {
if (deltaMovement.y > this.jumpingThreshold) {
var slopeModifier = this.slopeSpeedMultiplier
? this.slopeSpeedMultiplier.lerp(angle)
: 1;
deltaMovement.x *= slopeModifier;
deltaMovement.y = Math.abs(Math.tan(angle * es.MathHelper.Deg2Rad) * deltaMovement.x);
var isGoingRight = deltaMovement.x > 0;
var ray = isGoingRight
? this._raycastOrigins.bottomRight
: this._raycastOrigins.bottomLeft;
var raycastHit = null;
if (this.supportSlopedOneWayPlatforms &&
this.collisionState.wasGroundedLastFrame) {
raycastHit = es.Physics.linecastIgnoreCollider(ray, ray.add(deltaMovement), this.platformMask, this.ignoredColliders);
}
else {
raycastHit = es.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;
};
return CharacterController;
}());
es.CharacterController = CharacterController;
})(es || (es = {}));
var es;
(function (es) {
var TriggerListenerHelper = /** @class */ (function () {
function TriggerListenerHelper() {
@@ -2745,6 +3194,7 @@ var es;
__extends(Collider, _super);
function Collider() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this._isEnabled = true;
/**
* 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件
*/
@@ -6378,6 +6828,9 @@ var es;
MathHelper.lerp = function (from, to, t) {
return from + (to - from) * this.clamp01(t);
};
MathHelper.betterLerp = function (a, b, t, epsilon) {
return Math.abs(a - b) < epsilon ? b : MathHelper.lerp(a, b, t);
};
/**
* 使度数的角度在a和b之间
* 用于处理360度环绕
@@ -6803,6 +7256,48 @@ var es;
}
return !Number.isFinite(x);
};
MathHelper.smoothDamp = function (current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {
smoothTime = Math.max(0.0001, smoothTime);
var num = 2 / smoothTime;
var num2 = num * deltaTime;
var num3 = 1 /
(1 + (num2 + (0.48 * (num2 * num2) + 0.235 * (num2 * (num2 * num2)))));
var num4 = current - target;
var num5 = target;
var num6 = maxSpeed * smoothTime;
num4 = this.clamp(num4, num6 * -1, num6);
target = current - num4;
var num7 = (currentVelocity + num * num4) * deltaTime;
currentVelocity = (currentVelocity - num * num7) * num3;
var num8 = target + (num4 + num7) * num3;
if (num5 - current > 0 === num8 > num5) {
num8 = num5;
currentVelocity = (num8 - num5) / deltaTime;
}
return { value: num8, currentVelocity: currentVelocity };
};
MathHelper.smoothDampVector = function (current, target, currentVelocity, smoothTime, maxSpeed, deltaTime) {
var v = es.Vector2.zero;
var resX = this.smoothDamp(current.x, target.x, currentVelocity.x, smoothTime, maxSpeed, deltaTime);
v.x = resX.value;
currentVelocity.x = resX.currentVelocity;
var 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
*/
MathHelper.mapMinMax = function (value, leftMin, leftMax, rightMin, rightMax) {
return rightMin + ((MathHelper.clamp(value, leftMin, leftMax) - leftMin) * (rightMax - rightMin)) / (leftMax - leftMin);
};
MathHelper.Epsilon = 0.00001;
MathHelper.Rad2Deg = 57.29578;
MathHelper.Deg2Rad = 0.0174532924;
@@ -8237,10 +8732,22 @@ var es;
this.point = point;
this.normal = normal;
};
RaycastHit.prototype.setAllValues = function (collider, fraction, distance, point, normal) {
this.collider = collider;
this.fraction = fraction;
this.distance = distance;
this.point = point;
this.normal = normal;
};
RaycastHit.prototype.reset = function () {
this.collider = null;
this.fraction = this.distance = 0;
};
RaycastHit.prototype.clone = function () {
var hit = new RaycastHit();
hit.setAllValues(this.collider, this.fraction, this.distance, this.point, this.normal);
return hit;
};
RaycastHit.prototype.toString = function () {
return "[RaycastHit] fraction: " + this.fraction + ", distance: " + this.distance + ", normal: " + this.normal + ", centroid: " + this.centroid + ", point: " + this.point;
};
@@ -8374,6 +8881,13 @@ var es;
this.linecastAll(start, end, this._hitArray, layerMask);
return this._hitArray[0];
};
Physics.linecastIgnoreCollider = function (start, end, layerMask, ignoredColliders) {
if (layerMask === void 0) { layerMask = this.allLayers; }
if (ignoredColliders === void 0) { ignoredColliders = null; }
this._hitArray[0].reset();
Physics.linecastAllIgnoreCollider(start, end, this._hitArray, layerMask, ignoredColliders);
return this._hitArray[0].clone();
};
/**
* 通过空间散列强制执行一行并用该行命中的任何碰撞器填充hits数组
* @param start
@@ -8389,6 +8903,11 @@ var es;
}
return this._spatialHash.linecast(start, end, hits, layerMask);
};
Physics.linecastAllIgnoreCollider = function (start, end, hits, layerMask, ignoredColliders) {
if (layerMask === void 0) { layerMask = this.allLayers; }
if (ignoredColliders === void 0) { ignoredColliders = null; }
return this._spatialHash.linecastIgnoreCollider(start, end, hits, layerMask, ignoredColliders);
};
/**
* 检查是否有对撞机落在一个矩形区域中
* @param rect
@@ -8642,6 +9161,60 @@ var es;
this._raycastParser.reset();
return this._raycastParser.hitCounter;
};
SpatialHash.prototype.linecastIgnoreCollider = function (start, end, hits, layerMask, ignoredColliders) {
start = start.clone();
var ray = new es.Ray2D(start, end);
this._raycastParser.startIgnoreCollider(ray, hits, layerMask, ignoredColliders);
start.x = start.x * this._inverseCellSize;
start.y = start.y * this._inverseCellSize;
var endCell = this.cellCoords(end.x, end.y);
var intX = Math.floor(start.x);
var intY = Math.floor(start.y);
var stepX = Math.sign(ray.direction.x);
var stepY = Math.sign(ray.direction.y);
if (intX === endCell.x) {
stepX = 0;
}
if (intY === endCell.y) {
stepY = 0;
}
var boundaryX = intX + (stepX > 0 ? 1 : 0);
var boundaryY = intY + (stepY > 0 ? 1 : 0);
var tMaxX = (boundaryX - start.x) / ray.direction.x;
var 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;
}
var tDeltaX = stepX / ray.direction.x;
var tDeltaY = stepY / ray.direction.y;
var cell = this.cellAtPosition(intX, intY);
if (cell && this._raycastParser.checkRayIntersection(intX, intY, cell)) {
this._raycastParser.reset();
return this._raycastParser.hitCounter;
}
var 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
@@ -8815,6 +9388,13 @@ var es;
this._layerMask = layerMask;
this.hitCounter = 0;
};
RaycastResultParser.prototype.startIgnoreCollider = function (ray, hits, layerMask, ignoredColliders) {
this._ray = ray;
this._hits = hits;
this._layerMask = layerMask;
this._ignoredColliders = ignoredColliders;
this.hitCounter = 0;
};
/**
* 如果hits数组被填充返回true。单元格不能为空!
* @param cellX
@@ -9806,6 +10386,44 @@ var es;
es.ShapeCollisionsPolygon = ShapeCollisionsPolygon;
})(es || (es = {}));
var es;
(function (es) {
var AnimCurve = /** @class */ (function () {
function AnimCurve(points) {
if (points.length < 2) {
throw new Error('curve length must be >= 2');
}
points.sort(function (a, b) {
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;
}
Object.defineProperty(AnimCurve.prototype, "points", {
get: function () {
return this._points;
},
enumerable: true,
configurable: true
});
AnimCurve.prototype.lerp = function (t) {
for (var i = 1; i < this._points.length; i++) {
if (t <= this._points[i].t) {
var m = es.MathHelper.map01(t, this._points[i - 1].t, this._points[i].t);
return es.MathHelper.lerp(this._points[i - 1].value, this._points[i].value, m);
}
}
throw new Error('should never be here');
};
return AnimCurve;
}());
es.AnimCurve = AnimCurve;
})(es || (es = {}));
var es;
(function (es) {
/**
* 用于包装事件的一个小类
@@ -10028,6 +10646,177 @@ var es;
es.Hash = Hash;
})(es || (es = {}));
var es;
(function (es) {
var Observable = /** @class */ (function () {
function Observable() {
this._listeners = [];
}
Observable.prototype.addListener = function (caller, callback) {
if (this._listeners.findIndex(function (listener) {
return listener.callback === callback && listener.caller === caller;
}) === -1) {
this._listeners.push({ caller: caller, callback: callback });
}
};
Observable.prototype.removeListener = function (caller, callback) {
var index = this._listeners.findIndex(function (listener) { return listener.callback === callback && listener.caller === caller; });
if (index >= 0) {
this._listeners.splice(index, 1);
}
};
Observable.prototype.clearListener = function () {
this._listeners = [];
};
Observable.prototype.clearListenerWithCaller = function (caller) {
for (var i = this._listeners.length - 1; i >= 0; i--) {
var listener = this._listeners[i];
if (listener.caller === caller) {
this._listeners.splice(i, 1);
}
}
};
Observable.prototype.notify = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a;
for (var i = this._listeners.length - 1; i >= 0; i--) {
var listener = this._listeners[i];
if (listener.caller) {
(_a = listener.callback).call.apply(_a, __spread([listener.caller], args));
}
else {
listener.callback.apply(listener, __spread(args));
}
}
};
return Observable;
}());
es.Observable = Observable;
var ObservableT = /** @class */ (function (_super) {
__extends(ObservableT, _super);
function ObservableT() {
return _super !== null && _super.apply(this, arguments) || this;
}
ObservableT.prototype.addListener = function (caller, callback) {
_super.prototype.addListener.call(this, caller, callback);
};
ObservableT.prototype.removeListener = function (caller, callback) {
_super.prototype.removeListener.call(this, caller, callback);
};
ObservableT.prototype.notify = function (arg) {
_super.prototype.notify.call(this, arg);
};
return ObservableT;
}(Observable));
es.ObservableT = ObservableT;
var ObservableTT = /** @class */ (function (_super) {
__extends(ObservableTT, _super);
function ObservableTT() {
return _super !== null && _super.apply(this, arguments) || this;
}
ObservableTT.prototype.addListener = function (caller, callback) {
_super.prototype.addListener.call(this, caller, callback);
};
ObservableTT.prototype.removeListener = function (caller, callback) {
_super.prototype.removeListener.call(this, caller, callback);
};
ObservableTT.prototype.notify = function (arg1, arg2) {
_super.prototype.notify.call(this, arg1, arg2);
};
return ObservableTT;
}(Observable));
es.ObservableTT = ObservableTT;
var Command = /** @class */ (function () {
function Command(caller, action) {
this.bindAction(caller, action);
this._onExec = new Observable();
}
Command.prototype.bindAction = function (caller, action) {
this._caller = caller;
this._action = action;
};
Command.prototype.dispatch = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var _a;
if (this._action) {
if (this._caller) {
(_a = this._action).call.apply(_a, __spread([this._caller], args));
}
else {
this._action.apply(this, __spread(args));
}
this._onExec.notify();
}
else {
console.warn('command not bind with an action');
}
};
Command.prototype.addListener = function (caller, callback) {
this._onExec.addListener(caller, callback);
};
Command.prototype.removeListener = function (caller, callback) {
this._onExec.removeListener(caller, callback);
};
Command.prototype.clearListener = function () {
this._onExec.clearListener();
};
Command.prototype.clearListenerWithCaller = function (caller) {
this._onExec.clearListenerWithCaller(caller);
};
return Command;
}());
es.Command = Command;
var ValueChangeCommand = /** @class */ (function () {
function ValueChangeCommand(value) {
this._onValueChange = new Observable();
this._value = value;
}
Object.defineProperty(ValueChangeCommand.prototype, "onValueChange", {
get: function () {
return this._onValueChange;
},
enumerable: true,
configurable: true
});
Object.defineProperty(ValueChangeCommand.prototype, "value", {
get: function () {
return this._value;
},
set: function (newValue) {
this._value = newValue;
},
enumerable: true,
configurable: true
});
ValueChangeCommand.prototype.dispatch = function (value) {
if (value !== this._value) {
var oldValue = this._value;
this._value = value;
this._onValueChange.notify(this._value, oldValue);
}
};
ValueChangeCommand.prototype.addListener = function (caller, callback) {
this._onValueChange.addListener(caller, callback);
};
ValueChangeCommand.prototype.removeListener = function (caller, callback) {
this._onValueChange.removeListener(caller, callback);
};
ValueChangeCommand.prototype.clearListener = function () {
this._onValueChange.clearListener();
};
ValueChangeCommand.prototype.clearListenerWithCaller = function (caller) {
this._onValueChange.clearListenerWithCaller(caller);
};
return ValueChangeCommand;
}());
es.ValueChangeCommand = ValueChangeCommand;
})(es || (es = {}));
var es;
(function (es) {
var Ref = /** @class */ (function () {
function Ref(value) {

File diff suppressed because one or more lines are too long

View File

@@ -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);

View 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;
}
}

View File

@@ -1,5 +1,6 @@
module es {
export abstract class Collider extends Component {
protected _isEnabled: boolean = true;
/**
* 对撞机的基本形状
*/

View File

@@ -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);
}
}
}

View File

@@ -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,7 +364,7 @@ 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;
}
@@ -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);
}

View File

@@ -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

View File

@@ -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}`;
}
}

View File

@@ -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;

View 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[];
}
}

View 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;
}
}