新增角色控制器
This commit is contained in:
228
source/bin/framework.d.ts
vendored
228
source/bin/framework.d.ts
vendored
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
2
source/bin/framework.min.js
vendored
2
source/bin/framework.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -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,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);
|
||||
}
|
||||
|
||||
@@ -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