新增arcadeRigidbody组件, 可以用于简单的,街机风格的物理学

This commit is contained in:
yhh
2020-12-07 11:48:42 +08:00
parent 2a0d4ef4dd
commit bddae046a0
12 changed files with 835 additions and 82 deletions

View File

@@ -0,0 +1,241 @@
module es {
/**
* 请注意,这不是一个完整的、多迭代的物理系统!它可以用于简单的、街机风格的物理。
* 这可以用于简单的,街机风格的物理学
*/
export class ArcadeRigidbody extends Component implements IUpdatable {
/** 这个刚体的质量。质量为0则是一个不可移动的物体 */
public get mass(){
return this._mass;
}
public set mass(value: number){
this.setMass(value);
}
/**
* 0-1范围其中0为无反弹1为完全反射。
*/
public get elasticity() {
return this._elasticity;
}
public set elasticiy(value: number){
this.setElasticity(value);
}
/**
* 0 - 1范围。0表示没有摩擦力1表示物体会停止在原地
*/
public get friction(){
return this._friction;
}
public set friction(value: number){
this.setFriction(value);
}
/**
* 0-9的范围。当发生碰撞时沿碰撞面做直线运动时如果其平方幅度小于glue摩擦力则将碰撞设置为上限
*/
public get glue() {
return this._glue;
}
public set glue(value: number){
this.setGlue(value);
}
/**
* 如果为真则每一帧都会考虑到Physics.gravity
*/
public shouldUseGravity: boolean = true;
/**
* 该刚体的速度
*/
public velocity: Vector2;
/**
* 质量为0的刚体被认为是不可移动的。改变速度和碰撞对它们没有影响
*/
public get isImmovable(){
return this._mass < 0.0001;
}
public _mass = 10;
public _elasticity = 0.5;
public _friction = 0.5;
public _glue = 0.01;
public _inverseMass;
public _collider: Collider;
constructor(){
super();
this._inverseMass = 1 / this._mass;
}
/**
* 这个刚体的质量。质量为0则是一个不可移动的物体
* @param mass
*/
public setMass(mass: number): ArcadeRigidbody {
this._mass = MathHelper.clamp(mass, 0, Number.MAX_VALUE);
if (this._mass > 0.0001)
this._inverseMass = 1 / this._mass;
else
this._inverseMass = 0;
return this;
}
/**
* 0-1范围其中0为无反弹1为完全反射。
* @param value
*/
public setElasticity(value: number): ArcadeRigidbody {
this._elasticity = MathHelper.clamp01(value);
return this;
}
/**
* 0 - 1范围。0表示没有摩擦力1表示物体会停止在原地
* @param value
*/
public setFriction(value: number): ArcadeRigidbody {
this._friction = MathHelper.clamp01(value);
return this;
}
/**
* 0-9的范围。当发生碰撞时沿碰撞面做直线运动时如果其平方幅度小于glue摩擦力则将碰撞设置为上限
* @param value
*/
public setGlue(value: number): ArcadeRigidbody {
this._glue = MathHelper.clamp(value, 0, 10);
return this;
}
/**
* 用刚体的质量给刚体加上一个瞬间的力脉冲。力是一个加速度单位是每秒像素每秒。将力乘以100000使数值使用更合理
* @param force
*/
public addImpulse(force: Vector2) {
if (!this.isImmovable) {
this.velocity.add(new Vector2(force.x * 100000, force.y * 100000).multiply(new Vector2(this._inverseMass * Time.deltaTime)));
}
}
public onAddedToEntity(){
this._collider = this.entity.getComponent<es.Collider>(es.Collider);
if (this._collider == null) {
console.warn("ArcadeRigidbody 没有 Collider。ArcadeRigidbody需要一个Collider!");
}
}
public update(){
if (this.isImmovable || this._collider == null) {
this.velocity = Vector2.zero;
return;
}
if (this.shouldUseGravity)
this.velocity.add(Vector2.multiply(Physics.gravity, new Vector2(Time.deltaTime)));
this.entity.transform.position.add(Vector2.multiply(this.velocity, new Vector2(Time.deltaTime)));
let collisionResult = new CollisionResult();
// 捞取我们在新的位置上可能会碰撞到的任何东西
let neighbors = Physics.boxcastBroadphaseExcludingSelfNonRect(this._collider, this._collider.collidesWithLayers.value);
for (let neighbor of neighbors) {
// 如果邻近的对撞机是同一个实体,则忽略它
if (neighbor.entity == this.entity) {
continue;
}
if (this._collider.collidesWithNonMotion(neighbor, collisionResult)) {
// 如果附近有一个ArcadeRigidbody我们就会处理完整的碰撞响应。如果没有我们会根据附近是不可移动的来计算事情
let neighborRigidbody = neighbor.entity.getComponent<ArcadeRigidbody>(ArcadeRigidbody);
if (neighborRigidbody != null) {
this.processOverlap(neighborRigidbody, collisionResult.minimumTranslationVector);
this.processCollision(neighborRigidbody, collisionResult.minimumTranslationVector);
} else {
// 没有ArcadeRigidbody所以我们假设它是不动的只移动我们自己的
this.entity.transform.position.subtract(collisionResult.minimumTranslationVector);
let relativeVelocity = this.velocity.clone();
this.calculateResponseVelocity(relativeVelocity, collisionResult.minimumTranslationVector, relativeVelocity);
this.velocity.add(relativeVelocity);
}
}
}
}
/**
* 将两个重叠的刚体分开。也处理其中一个不可移动的情况
* @param other
* @param minimumTranslationVector
*/
public processOverlap(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
if (this.isImmovable) {
other.entity.transform.position.add(minimumTranslationVector);
}else if(other.isImmovable) {
this.entity.transform.position.subtract(minimumTranslationVector);
} else {
this.entity.transform.position.subtract(Vector2.multiply(minimumTranslationVector, Vector2Ext.halfVector()));
other.entity.transform.position.add(Vector2.multiply(minimumTranslationVector, Vector2Ext.halfVector()));
}
}
/**
* 处理两个非重叠的刚体的碰撞。新的速度将根据情况分配给每个刚体
* @param other
* @param minimumTranslationVector
*/
public processCollision(other: ArcadeRigidbody, minimumTranslationVector: Vector2) {
// 我们计算两个相撞物体的响应。
// 计算的基础是沿碰撞表面法线反射的物体的相对速度。
// 然后,响应的一部分会根据质量加到每个物体上
let relativeVelocity = Vector2.subtract(this.velocity, other.velocity);
this.calculateResponseVelocity(relativeVelocity, minimumTranslationVector, relativeVelocity);
// 现在,我们使用质量来线性缩放两个刚体上的响应
let totalinverseMass = this._inverseMass + other._inverseMass;
let ourResponseFraction = this._inverseMass / totalinverseMass;
let otherResponseFraction = other._inverseMass / totalinverseMass;
this.velocity.add(Vector2.multiply(relativeVelocity, new Vector2(ourResponseFraction)));
other.velocity.subtract(Vector2.multiply(relativeVelocity, new Vector2(otherResponseFraction)));
}
/**
* 给定两个物体和MTV之间的相对速度本方法修改相对速度使其成为碰撞响应
* @param relativeVelocity
* @param minimumTranslationVector
* @param responseVelocity
*/
public calculateResponseVelocity(relativeVelocity: Vector2, minimumTranslationVector: Vector2, responseVelocity: Vector2 = new Vector2()){
// 首先我们得到反方向的归一化MTV表面法线
let inverseMTV = Vector2.multiply(minimumTranslationVector, new Vector2(-1));
let normal = Vector2.normalize(inverseMTV);
// 速度是沿碰撞法线和碰撞平面分解的。
// 弹性将影响沿法线的响应(法线速度分量),摩擦力将影响速度的切向分量(切向速度分量)
let n = Vector2.dot(relativeVelocity, normal);
let normalVelocityComponent = Vector2.multiply(normal, new Vector2(n));
let tangentialVelocityComponent = Vector2.subtract(relativeVelocity, normalVelocityComponent);
if (n > 0)
normalVelocityComponent = Vector2.zero;
// 如果切向分量的平方幅度小于glue那么我们就把摩擦力提升到最大
let coefficientOfFriction = this._friction;
if (tangentialVelocityComponent.lengthSquared() < this._glue)
coefficientOfFriction = 1.01;
// 弹性影响速度的法向分量,摩擦力影响速度的切向分量
responseVelocity = Vector2.multiply(new Vector2(-(1 + this._elasticity)), normalVelocityComponent).subtract(Vector2.multiply(new Vector2(coefficientOfFriction), tangentialVelocityComponent));
}
}
}

View File

@@ -182,7 +182,7 @@ module es {
* @param motion
* @param result
*/
public collidesWith(collider: Collider, motion: Vector2, result: CollisionResult): boolean {
public collidesWith(collider: Collider, motion: Vector2, result: CollisionResult = new CollisionResult()): boolean {
// 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠
let oldPosition = this.entity.position.clone();
this.entity.position = Vector2.add(this.entity.position, motion);
@@ -196,5 +196,19 @@ module es {
return didCollide;
}
/**
* 检查这个对撞机是否与对撞机发生碰撞。如果碰撞则返回true结果将被填充
* @param collider
* @param result
*/
public collidesWithNonMotion(collider: Collider, result: CollisionResult = new CollisionResult()): boolean {
if (this.shape.collidesWithShape(collider.shape, result)) {
result.collider = collider;
return true;
}
return false;
}
}
}

View File

@@ -246,6 +246,17 @@ module es {
return new Vector2(Math.round(this.x), Math.round(this.y));
}
/**
* 返回以自己为中心点的左右角,单位为度
* @param left
* @param right
*/
public angleBetween(left: Vector2, right: Vector2) {
let one = Vector2.subtract(left, this);
let two = Vector2.subtract(right, this);
return Vector2Ext.angle(one, two);
}
/**
* 比较当前实例是否等于指定的对象
* @param other 要比较的对象

View File

@@ -2,6 +2,8 @@
module es {
export class Physics {
public static _spatialHash: SpatialHash;
/** 用于在全局范围内存储重力值的方便字段 */
public static gravity = new Vector2(0, 300);
/** 调用reset并创建一个新的SpatialHash时使用的单元格大小 */
public static spatialHashCellSize = 100;
/** 接受layerMask的所有方法的默认值 */
@@ -68,6 +70,16 @@ module es {
return this._spatialHash.aabbBroadphase(rect, collider, layerMask);
}
/**
* 返回所有边界与 collider.bounds 相交的碰撞器,但不包括传入的碰撞器(self)
* @param collider
* @param layerMask
*/
public static boxcastBroadphaseExcludingSelfNonRect(collider: Collider, layerMask = this.allLayers) {
let bounds = collider.bounds;
return this._spatialHash.aabbBroadphase(bounds, collider, layerMask);
}
/**
* 将对撞机添加到物理系统中
* @param collider

View File

@@ -1,27 +1,12 @@
module es {
/**
* 用于包装事件的一个小类
*/
export class FuncPack {
/** 函数 */
public func: Function;
/** 上下文 */
public context: any;
constructor(func: Function, context: any) {
this.func = func;
this.context = context;
}
}
/**
* 用于事件管理
*/
export class Emitter<T> {
private _messageTable: Map<T, FuncPack[]>;
private _messageTable: Map<T, Function[]>;
constructor() {
this._messageTable = new Map<T, FuncPack[]>();
this._messageTable = new Map<T, Function[]>();
}
/**
@@ -31,15 +16,17 @@ module es {
* @param context 监听上下文
*/
public addObserver(eventType: T, handler: Function, context: any) {
let list: FuncPack[] = this._messageTable.get(eventType);
handler.bind(context);
let list: Function[] = this._messageTable.get(eventType);
if (!list) {
list = [];
this._messageTable.set(eventType, list);
}
if (list.findIndex(funcPack => funcPack.func == handler) != -1)
if (new linq.List(list).contains(handler))
console.warn("您试图添加相同的观察者两次");
list.push(new FuncPack(handler, context));
list.push(handler);
}
/**
@@ -49,9 +36,7 @@ module es {
*/
public removeObserver(eventType: T, handler: Function) {
let messageData = this._messageTable.get(eventType);
let index = messageData.findIndex(data => data.func == handler);
if (index != -1)
new linq.List(messageData).removeAt(index);
new linq.List(messageData).remove(handler);
}
/**
@@ -60,10 +45,10 @@ module es {
* @param data 事件数据
*/
public emit(eventType: T, data?: any) {
let list: FuncPack[] = this._messageTable.get(eventType);
let list: Function[] = this._messageTable.get(eventType);
if (list) {
for (let i = list.length - 1; i >= 0; i--)
list[i].func.call(list[i].context, data);
list[i](data);
}
}
}

View File

@@ -13,7 +13,7 @@ module es {
reset();
/**
*
* 返回投向T的上下文作为方便
*/
getContext<T>(): T;
}

View File

@@ -1,4 +1,7 @@
module es {
/**
* 私有类隐藏ITimer的实现
*/
export class Timer implements ITimer{
public context: any;
public _timeInSeconds: number = 0;

View File

@@ -1,10 +1,10 @@
module es {
/**
* 三角剖分
* 简单的剪耳三角测量器最终的三角形将出现在triangleIndices列表中。
*/
export class Triangulator {
/**
* 最后一次三角调用中使用的点列表的三角列表项的索引
* 次三角函数调用中使用的点列表的三角列表条目索引
*/
public triangleIndices: number[] = [];
@@ -12,15 +12,19 @@ module es {
private _triNext: number[] = new Array<number>(12);
public static testPointTriangle(point: Vector2, a: Vector2, b: Vector2, c: Vector2): boolean {
// 如果点在AB的右边那么外边的三角形是
if (Vector2Ext.cross(Vector2.subtract(point, a), Vector2.subtract(b, a)) < 0)
return false;
// 如果点在BC的右边则在三角形的外侧
if (Vector2Ext.cross(Vector2.subtract(point, b), Vector2.subtract(c, b)) < 0)
return false;
// 如果点在ca的右边则在三角形的外面
if (Vector2Ext.cross(Vector2.subtract(point, c), Vector2.subtract(a, c)) < 0)
return false;
// 点在三角形上
return true;
}
@@ -44,8 +48,9 @@ module es {
// 继续移除所有的三角形,直到只剩下一个三角形
while (count > 3 && iterations < 500) {
iterations++;
let isEar = true;
let a = points[this._triPrev[index]];
let b = points[index];
let c = points[this._triNext[index]];
@@ -94,11 +99,11 @@ module es {
if (this._triNext.length < count) {
this._triNext.reverse();
this._triNext = new Array<number>(Math.max(this._triNext.length * 2, count));
this._triNext.length = Math.max(this._triNext.length * 2, count);
}
if (this._triPrev.length < count) {
this._triPrev.reverse();
this._triPrev = new Array<number>(Math.max(this._triPrev.length * 2, count));
this._triPrev.length = Math.max(this._triPrev.length * 2, count);
}
for (let i = 0; i < count; i++) {

View File

@@ -24,7 +24,7 @@ module es {
}
/**
* 返回传入向量垂直的向量
* 返回垂直于传入向量的向量
* @param first
* @param second
*/
@@ -32,6 +32,45 @@ module es {
return new Vector2(-1 * (second.y - first.y), second.x - first.x);
}
/**
* 返回两个向量之间的角度,单位为度
* @param from
* @param to
*/
public static angle(from: Vector2, to: Vector2) {
this.normalize(from);
this.normalize(to);
return Math.acos(MathHelper.clamp(Vector2.dot(from, to), -1, 1)) * MathHelper.Rad2Deg;
}
/**
* 给定两条直线(ab和cd),求交点
* @param a
* @param b
* @param c
* @param d
* @param intersection
*/
public static getRayIntersection(a: Vector2, b: Vector2, c: Vector2, d: Vector2, intersection: Vector2 = new Vector2()) {
let dy1 = b.y - a.y;
let dx1 = b.x - a.x;
let dy2 = d.y - c.y;
let dx2 = d.x - c.x;
if (dy1 * dx2 == dy2 * dx1) {
intersection.x = Number.NaN;
intersection.y = Number.NaN;
return false;
}
let x = ((c.y - a.y) * dx1 * dx2 + dy1 * dx2 * a.x - dy2 * dx1 * c.x) / (dy1 * dx2 - dy2 * dx1);
let y = a.y + (dy1 / dx1) * (x - a.x);
intersection.x = x;
intersection.y = y;
return true;
}
/**
* Vector2的临时解决方案
* 标准化把向量弄乱了
@@ -66,7 +105,13 @@ module es {
}
}
public static transformR(position: Vector2, matrix: Matrix2D, result: Vector2) {
/**
* 创建一个新的Vector2该Vector2包含了通过指定的Matrix进行的二维向量变换
* @param position
* @param matrix
* @param result
*/
public static transformR(position: Vector2, matrix: Matrix2D, result: Vector2 = new Vector2()) {
let x = (position.x * matrix.m11) + (position.y * matrix.m21) + matrix.m31;
let y = (position.x * matrix.m12) + (position.y * matrix.m22) + matrix.m32;
result.x = x;