diff --git a/source/src/ECS/Component.ts b/source/src/ECS/Component.ts index da4dbcf1..ce3af7ba 100644 --- a/source/src/ECS/Component.ts +++ b/source/src/ECS/Component.ts @@ -75,7 +75,7 @@ module es { * 当实体的位置改变时调用。这允许组件知道它们由于父实体的移动而移动了。 * @param comp */ - public onEntityTransformChanged(comp: Transform.Component) { + public onEntityTransformChanged(comp: transform.Component) { } /** diff --git a/source/src/ECS/Components/Camera.ts b/source/src/ECS/Components/Camera.ts index d6a1f354..49f0d259 100644 --- a/source/src/ECS/Components/Camera.ts +++ b/source/src/ECS/Components/Camera.ts @@ -1,33 +1,45 @@ module es { + export enum CameraStyle { + lockOn, + cameraWindow, + } + export class Camera extends Component { - private _zoom; - private _origin: Vector2 = Vector2.zero; - - private _minimumZoom = 0.3; - private _maximumZoom = 3; - - private _position: Vector2 = Vector2.zero; /** - * 如果相机模式为cameraWindow 则会进行缓动移动 - * 该值为移动速度 + * 对entity.transform.position的快速访问 */ - public followLerp = 0.1; - public deadzone: Rectangle = new Rectangle(); - /** 锁定偏移量 默认中心 */ - public focusOffset: Vector2 = new Vector2(); - /** 是否地图锁定 如果锁定则需要设置mapSize属性 */ - public mapLockEnabled: boolean = false; - /** 设置地图大小 默认从0 0左上角开始 只需要输入地图宽高 */ - public mapSize: Vector2 = new Vector2(); - /** 跟随的实体 设置后镜头将锁定目标为中心 */ - public targetEntity: Entity; - private _worldSpaceDeadZone: Rectangle = new Rectangle(); - private _desiredPositionDelta: Vector2 = new Vector2(); - private _targetCollider: Collider; - /** 相机模式 */ - public cameraStyle: CameraStyle = CameraStyle.lockOn; + public get position() { + return this.entity.transform.position; + } - public get zoom(){ + /** + * 对entity.transform.position的快速访问 + * @param value + */ + public set position(value: Vector2) { + this.entity.transform.position = value; + } + + /** + * 对entity.transform.rotation的快速访问 + */ + public get rotation(): number { + return this.entity.transform.rotation; + } + + /** + * 对entity.transform.rotation的快速访问 + * @param value + */ + public set rotation(value: number) { + this.entity.transform.rotation = value; + } + + /** + * 缩放值应该在-1和1之间、然后将该值从minimumZoom转换为maximumZoom。 + * 允许你设置适当的最小/最大值,然后使用更直观的-1到1的映射来更改缩放 + */ + public get zoom() { if (this._zoom == 0) return 1; @@ -37,93 +49,143 @@ module es { return MathHelper.map(this._zoom, 1, this._maximumZoom, 0, 1); } - public set zoom(value: number){ + /** + * 缩放值应该在-1和1之间、然后将该值从minimumZoom转换为maximumZoom。 + * 允许你设置适当的最小/最大值,然后使用更直观的-1到1的映射来更改缩放 + * @param value + */ + public set zoom(value: number) { this.setZoom(value); } - public get minimumZoom(){ + /** + * 相机变焦可以达到的最小非缩放值(0-number.max)。默认为0.3 + */ + public get minimumZoom() { return this._minimumZoom; } - public set minimumZoom(value: number){ + /** + * 相机变焦可以达到的最小非缩放值(0-number.max)。默认为0.3 + * @param value + */ + public set minimumZoom(value: number) { this.setMinimumZoom(value); } - public get maximumZoom(){ + /** + * 相机变焦可以达到的最大非缩放值(0-number.max)。默认为3 + */ + public get maximumZoom() { return this._maximumZoom; } - public set maximumZoom(value: number){ + /** + * 相机变焦可以达到的最大非缩放值(0-number.max)。默认为3 + * @param value + */ + public set maximumZoom(value: number) { this.setMaximumZoom(value); } - public get origin(){ + /** + * 相机的边框 + */ + public get bounds(){ + return new Rectangle(0, 0, SceneManager.stage.stageWidth, SceneManager.stage.stageHeight); + } + + public get origin() { return this._origin; } - public set origin(value: Vector2){ - if (this._origin != value){ + public set origin(value: Vector2) { + if (this._origin != value) { this._origin = value; } } - public get position(){ - return this._position; - } + private _zoom; + private _minimumZoom = 0.3; + private _maximumZoom = 3; + private _origin: Vector2 = Vector2.zero; + /** + * 如果相机模式为cameraWindow 则会进行缓动移动 + * 该值为移动速度 + */ + public followLerp = 0.1; + /** + * 在cameraWindow模式下,宽度/高度被用做边界框,允许在不移动相机的情况下移动 + * 在lockOn模式下,只使用deadZone的x/y值 你可以通过直接setCenteredDeadzone重写它来自定义deadZone + */ + public deadzone: Rectangle = new Rectangle(); + /** + * 相机聚焦于屏幕中心的偏移 + */ + public focusOffset: Vector2 = new Vector2(); + /** + * 如果为true 相机位置则不会超出地图矩形(0, 0, mapwidth, mapheight) + */ + public mapLockEnabled: boolean = false; + /** + * 當前地圖映射的寬度和高度 + */ + public mapSize: Vector2 = new Vector2(); - public set position(value: Vector2){ - this._position = value; - } + public _targetEntity: Entity; + public _targetCollider: Collider; + public _desiredPositionDelta: Vector2 = new Vector2(); + public _cameraStyle: CameraStyle; + public _worldSpaceDeadZone: Rectangle = new Rectangle(); - public get x(){ - return this._position.x; - } - public set x(value: number){ - this._position.x = value; - } - public get y(){ - return this._position.y; - } - public set y(value: number){ - this._position.y = value; - } - - constructor() { + constructor(targetEntity: Entity = null, cameraStyle: CameraStyle = CameraStyle.lockOn) { super(); - this.width = SceneManager.stage.stageWidth; - this.height = SceneManager.stage.stageHeight; + this._targetEntity = targetEntity; + this._cameraStyle = cameraStyle; this.setZoom(0); } - public onSceneSizeChanged(newWidth: number, newHeight: number){ + /** + * 当场景渲染目标的大小发生变化时,我们会更新相机的原点并调整它的位置以保持它原来的位置 + * @param newWidth + * @param newHeight + */ + public onSceneSizeChanged(newWidth: number, newHeight: number) { let oldOrigin = this._origin; this.origin = new Vector2(newWidth / 2, newHeight / 2); - this.entity.position = Vector2.add(this.entity.position, Vector2.subtract(this._origin, oldOrigin)); + this.entity.transform.position = Vector2.add(this.entity.transform.position, Vector2.subtract(this._origin, oldOrigin)); } - public setMinimumZoom(minZoom: number): Camera{ - if (this._zoom < minZoom) - this._zoom = this.minimumZoom; - - this._minimumZoom = minZoom; + /** + * 对entity.transform.setPosition快速访问 + * @param position + */ + public setPosition(position: Vector2) { + this.entity.transform.setPosition(position.x, position.y); return this; } - public setMaximumZoom(maxZoom: number): Camera { - if (this._zoom > maxZoom) - this._zoom = maxZoom; - - this._maximumZoom = maxZoom; + /** + * 对entity.transform.setRotation的快速访问 + * @param rotation + */ + public setRotation(rotation: number): Camera { + this.entity.transform.setRotation(rotation); return this; } - public setZoom(zoom: number): Camera{ + /** + * 设置缩放值,缩放值应该在-1到1之间。然后将该值从minimumZoom转换为maximumZoom + * 允许您设置适当的最小/最大值。使用更直观的-1到1的映射来更改缩放 + * @param zoom + */ + public setZoom(zoom: number): Camera { let newZoom = MathHelper.clamp(zoom, -1, 1); - if (newZoom == 0){ + if (newZoom == 0) { this._zoom = 1; - } else if(newZoom < 0){ + } else if (newZoom < 0) { this._zoom = MathHelper.map(newZoom, -1, 0, this._minimumZoom, 1); } else { this._zoom = MathHelper.map(newZoom, 0, 1, 1, this._maximumZoom); @@ -134,103 +196,138 @@ module es { return this; } - public setRotation(rotation: number): Camera { - SceneManager.scene.rotation = rotation; + /** + * 相机变焦可以达到的最小非缩放值(0-number.max) 默认为0.3 + * @param minZoom + */ + public setMinimumZoom(minZoom: number): Camera { + if (this._zoom < minZoom) + this._zoom = this.minimumZoom; + + this._minimumZoom = minZoom; return this; } - public setPosition(position: Vector2){ - this.entity.position = position; - - return this; - } - - public follow(targetEntity: Entity, cameraStyle: CameraStyle = CameraStyle.cameraWindow){ - this.targetEntity = targetEntity; - this.cameraStyle = cameraStyle; - let cameraBounds = new Rectangle(0, 0, SceneManager.stage.stageWidth, SceneManager.stage.stageHeight); - - switch (this.cameraStyle){ - case CameraStyle.cameraWindow: - let w = cameraBounds.width / 6; - let h = cameraBounds.height / 3; - this.deadzone = new Rectangle((cameraBounds.width - w) / 2, (cameraBounds.height - h) / 2, w, h); - break; - case CameraStyle.lockOn: - this.deadzone = new Rectangle(cameraBounds.width / 2, cameraBounds.height / 2, 10, 10); - break; + /** + * 相机变焦可以达到的最大非缩放值(0-number.max) 默认为3 + * @param maxZoom + */ + public setMaximumZoom(maxZoom: number): Camera { + if (maxZoom <= 0) { + console.error("maximumZoom must be greater than zero"); + return; } + + if (this._zoom > maxZoom) + this._zoom = maxZoom; + + this._maximumZoom = maxZoom; + return this; } - public update(){ - let cameraBounds = new Rectangle(0, 0, SceneManager.stage.stageWidth, SceneManager.stage.stageHeight); - let halfScreen = Vector2.multiply(new Vector2(cameraBounds.width, cameraBounds.height), new Vector2(0.5)); - this._worldSpaceDeadZone.x = this.position.x - halfScreen.x * SceneManager.scene.scaleX + this.deadzone.x + this.focusOffset.x; + public zoomIn(deltaZoom: number) { + this.zoom += deltaZoom; + } + + public zoomOut(deltaZoom: number) { + this.zoom -= deltaZoom; + } + + public onAddedToEntity() { + this.follow(this._targetEntity, this._cameraStyle); + } + + public update() { + let halfScreen = Vector2.multiply(new Vector2(this.bounds.width, this.bounds.height), new Vector2(0.5)); + this._worldSpaceDeadZone.x = this.position.x - halfScreen.x * SceneManager.scene.scaleX + this.deadzone.x + this.focusOffset.x; this._worldSpaceDeadZone.y = this.position.y - halfScreen.y * SceneManager.scene.scaleY + this.deadzone.y + this.focusOffset.y; this._worldSpaceDeadZone.width = this.deadzone.width; this._worldSpaceDeadZone.height = this.deadzone.height; - if (this.targetEntity) + if (this._targetEntity) this.updateFollow(); this.position = Vector2.lerp(this.position, Vector2.add(this.position, this._desiredPositionDelta), this.followLerp); - this.entity.roundPosition(); + this.entity.transform.roundPosition(); - if (this.mapLockEnabled){ + if (this.mapLockEnabled) { this.position = this.clampToMapSize(this.position); - this.entity.roundPosition(); + this.entity.transform.roundPosition(); } } - private clampToMapSize(position: Vector2){ - let cameraBounds = new Rectangle(0, 0, SceneManager.stage.stageWidth, SceneManager.stage.stageHeight); - let halfScreen = Vector2.multiply(new Vector2(cameraBounds.width, cameraBounds.height), new Vector2(0.5)); + /** + * 固定相机 永远不会离开地图的可见区域 + * @param position + */ + public clampToMapSize(position: Vector2) { + let halfScreen = Vector2.multiply(new Vector2(this.bounds.width, this.bounds.height), new Vector2(0.5)); let cameraMax = new Vector2(this.mapSize.x - halfScreen.x, this.mapSize.y - halfScreen.y); return Vector2.clamp(position, halfScreen, cameraMax); } - private updateFollow(){ + public updateFollow() { this._desiredPositionDelta.x = this._desiredPositionDelta.y = 0; - if (this.cameraStyle == CameraStyle.lockOn){ - let targetX = this.targetEntity.position.x; - let targetY = this.targetEntity.position.y; + if (this._cameraStyle == CameraStyle.lockOn) { + let targetX = this._targetEntity.transform.position.x; + let targetY = this._targetEntity.transform.position.y; if (this._worldSpaceDeadZone.x > targetX) this._desiredPositionDelta.x = targetX - this._worldSpaceDeadZone.x; - else if(this._worldSpaceDeadZone.x < targetX) + else if (this._worldSpaceDeadZone.x < targetX) this._desiredPositionDelta.x = targetX - this._worldSpaceDeadZone.x; if (this._worldSpaceDeadZone.y < targetY) this._desiredPositionDelta.y = targetY - this._worldSpaceDeadZone.y; - else if(this._worldSpaceDeadZone.y > targetY) + else if (this._worldSpaceDeadZone.y > targetY) this._desiredPositionDelta.y = targetY - this._worldSpaceDeadZone.y; } else { - if (!this._targetCollider){ - this._targetCollider = this.targetEntity.getComponent(Collider); + if (!this._targetCollider) { + this._targetCollider = this._targetEntity.getComponent(Collider); if (!this._targetCollider) return; } - let targetBounds = this.targetEntity.getComponent(Collider).bounds; - if (!this._worldSpaceDeadZone.containsRect(targetBounds)){ + let targetBounds = this._targetEntity.getComponent(Collider).bounds; + if (!this._worldSpaceDeadZone.containsRect(targetBounds)) { if (this._worldSpaceDeadZone.left > targetBounds.left) this._desiredPositionDelta.x = targetBounds.left - this._worldSpaceDeadZone.left; - else if(this._worldSpaceDeadZone.right < targetBounds.right) + else if (this._worldSpaceDeadZone.right < targetBounds.right) this._desiredPositionDelta.x = targetBounds.right - this._worldSpaceDeadZone.right; if (this._worldSpaceDeadZone.bottom < targetBounds.bottom) this._desiredPositionDelta.y = targetBounds.bottom - this._worldSpaceDeadZone.bottom; - else if(this._worldSpaceDeadZone.top > targetBounds.top) + else if (this._worldSpaceDeadZone.top > targetBounds.top) this._desiredPositionDelta.y = targetBounds.top - this._worldSpaceDeadZone.top; } } } - } - export enum CameraStyle { - lockOn, - cameraWindow, + public follow(targetEntity: Entity, cameraStyle: CameraStyle = CameraStyle.cameraWindow) { + this._targetEntity = targetEntity; + this._cameraStyle = cameraStyle; + + switch (this._cameraStyle) { + case CameraStyle.cameraWindow: + let w = this.bounds.width / 6; + let h = this.bounds.height / 3; + this.deadzone = new Rectangle((this.bounds.width - w) / 2, (this.bounds.height - h) / 2, w, h); + break; + case CameraStyle.lockOn: + this.deadzone = new Rectangle(this.bounds.width / 2, this.bounds.height / 2, 10, 10); + break; + } + } + + /** + * 以给定的尺寸设置当前相机边界中心的死区 + * @param width + * @param height + */ + public setCenteredDeadzone(width: number, height: number) { + this.deadzone = new Rectangle((this.bounds.width - width) / 2, (this.bounds.height - height) / 2, width, height); + } } } diff --git a/source/src/ECS/Components/IUpdatableComparer.ts b/source/src/ECS/Components/IUpdatableComparer.ts new file mode 100644 index 00000000..46a4f90a --- /dev/null +++ b/source/src/ECS/Components/IUpdatableComparer.ts @@ -0,0 +1,10 @@ +module es { + /** + * 用于比较组件更新排序 + */ + export class IUpdatableComparer { + public compare(a: Component, b: Component){ + return a.updateOrder - b.updateOrder; + } + } +} \ No newline at end of file diff --git a/source/src/ECS/Components/Physics/Colliders/Collider.ts b/source/src/ECS/Components/Physics/Colliders/Collider.ts index c6fe6d02..5b482e79 100644 --- a/source/src/ECS/Components/Physics/Colliders/Collider.ts +++ b/source/src/ECS/Components/Physics/Colliders/Collider.ts @@ -1,161 +1,210 @@ -abstract class Collider extends Component { - /** 对撞机的基本形状 */ - public shape: Shape; - protected _localOffset: Vector2 = Vector2.zero; - public get localOffset(){ - return this._localOffset; - } - public set localOffset(){ +module es { + export abstract class Collider extends Component { + /** + * 对撞机的基本形状 + */ + public shape: Shape; + /** + * 将localOffset添加到实体。获取碰撞器几何图形的最终位置。 + * 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转 + */ + public get localOffset(): Vector2{ + return this._localOffset; + } - } - public _localOffsetLength: number; - /** 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法。 */ - public physicsLayer = 1 << 0; - /** 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件 */ - public isTrigger: boolean; - /** - * 这个对撞机在物理系统注册时的边界。 - * 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。 - */ - public registeredPhysicsBounds: Rectangle = new Rectangle(); - /** 如果为true,碰撞器将根据附加的变换缩放和旋转 */ - public shouldColliderScaleAndRotateWithTransform = true; - /** 默认为所有层。 */ - public collidesWithLayers = Physics.allLayers; + /** + * 将localOffset添加到实体。获取碰撞器几何图形的最终位置。 + * 允许向一个实体添加多个碰撞器并分别定位,还允许你设置缩放/旋转 + * @param value + */ + public set localOffset(value: Vector2){ + this.setLocalOffset(value); + } - /** 标记来跟踪我们的实体是否被添加到场景中 */ - protected _isParentEntityAddedToScene; - protected _colliderRequiresAutoSizing; - /** 标记来记录我们是否注册了物理系统 */ - protected _isColliderRegistered; + /** + * 镖师碰撞器的绝对位置 + */ + public get absolutePosition(): Vector2{ + return Vector2.add(this.entity.transform.position, this._localOffset); + } - public get bounds(): Rectangle { - let shapeBounds = this.shape.bounds; - let colliderBuonds = new Rectangle(this.entity.x, this.entity.y, shapeBounds.width, shapeBounds.height); - return colliderBuonds; - } + /** + * 封装变换。如果碰撞器没和实体一起旋转 则返回transform.rotation + */ + public get rotation(): number{ + if (this.shouldColliderScaleAndRotateWithTransform && this.entity) + return this.entity.transform.rotation; - /** - * 将localOffset添加到实体。获取碰撞器的最终位置。 - * 这允许您向一个实体添加多个碰撞器并分别定位它们。 - * @param offset - */ - public setLocalOffset(offset: Vector2): Collider{ - if (this._localOffset != offset){ - this.unregisterColliderWithPhysicsSystem(); - this._localOffset = offset; - this._localOffsetLength = this._localOffset.length(); + return 0; + } + + /** + * 如果这个碰撞器是一个触发器,它将不会引起碰撞,但它仍然会触发事件 + */ + public isTrigger: boolean; + /** + * 在处理冲突时,physicsLayer可以用作过滤器。Flags类有帮助位掩码的方法 + */ + public physicsLayer = 1 << 0; + /** + * 碰撞器在使用移动器移动时应该碰撞的层 + * 默认为所有层 + */ + public collidesWithLayers = Physics.allLayers; + + /** + * 如果为true,碰撞器将根据附加的变换缩放和旋转 + */ + public shouldColliderScaleAndRotateWithTransform = true; + + public get bounds(): Rectangle { + this.shape.recalculateBounds(this); + + return this.shape.bounds; + } + + /** + * 这个对撞机在物理系统注册时的边界。 + * 存储这个允许我们始终能够安全地从物理系统中移除对撞机,即使它在试图移除它之前已经被移动了。 + */ + public registeredPhysicsBounds: Rectangle = new Rectangle(); + protected _colliderRequiresAutoSizing; + protected _localOffset: Vector2 = Vector2.zero; + public _localOffsetLength: number; + + /** + * 标记来跟踪我们的实体是否被添加到场景中 + */ + protected _isParentEntityAddedToScene; + /** + * 标记来记录我们是否注册了物理系统 + */ + protected _isColliderRegistered; + + /** + * 将localOffset添加到实体。获取碰撞器的最终位置。 + * 这允许您向一个实体添加多个碰撞器并分别定位它们。 + * @param offset + */ + public setLocalOffset(offset: Vector2): Collider{ + if (this._localOffset != offset){ + this.unregisterColliderWithPhysicsSystem(); + this._localOffset = offset; + this._localOffsetLength = this._localOffset.length(); + this.registerColliderWithPhysicsSystem(); + } + + return this; + } + + /** + * 如果为true,碰撞器将根据附加的变换缩放和旋转 + * @param shouldColliderScaleAndRotationWithTransform + */ + public setShouldColliderScaleAndRotateWithTransform(shouldColliderScaleAndRotationWithTransform: boolean): Collider { + this.shouldColliderScaleAndRotateWithTransform = shouldColliderScaleAndRotationWithTransform; + return this; + } + + public onAddedToEntity() { + if (this._colliderRequiresAutoSizing) { + if (!(this instanceof BoxCollider || this instanceof CircleCollider)) { + console.error("Only box and circle colliders can be created automatically"); + return; + } + + let renderable = this.entity.getComponent(RenderableComponent); + if (renderable) { + let bounds = renderable.bounds; + + // 这里我们需要大小*反尺度,因为当我们自动调整碰撞器的大小时,它需要没有缩放的渲染 + let width = bounds.width / this.entity.scale.x; + let height = bounds.height / this.entity.scale.y; + // 圆碰撞器需要特别注意原点 + if (this instanceof CircleCollider){ + let circleCollider = this as CircleCollider; + circleCollider.radius = Math.max(width, height) * 0.5; + } else { + this.width = width; + this.height = height; + } + + // 获取渲染的中心,将其转移到本地坐标,并使用它作为碰撞器的localOffset + this.localOffset = Vector2.subtract(bounds.center, this.entity.transform.position); + } else { + console.warn("Collider has no shape and no RenderableComponent. Can't figure out how to size it."); + } + } + + this._isParentEntityAddedToScene = true; this.registerColliderWithPhysicsSystem(); } - return this; - } - - /** - * 父实体会在不同的时间调用它(当添加到场景,启用,等等) - */ - public registerColliderWithPhysicsSystem() { - // 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null - if (this._isParentEntityAddedToScene && !this._isColliderRegistered) { - Physics.addCollider(this); - this._isColliderRegistered = true; + public onRemovedFromEntity() { + this.unregisterColliderWithPhysicsSystem(); + this._isParentEntityAddedToScene = false; } - } - /** - * 父实体会在不同的时候调用它(从场景中移除,禁用,等等) - */ - public unregisterColliderWithPhysicsSystem() { - if (this._isParentEntityAddedToScene && this._isColliderRegistered) { - Physics.removeCollider(this); + public onEntityTransformChanged(comp: transform.Component) { + if (this._isColliderRegistered) + Physics.updateCollider(this); } - this._isColliderRegistered = false; - } - /** - * 检查这个形状是否与物理系统中的其他对撞机重叠 - * @param other - */ - public overlaps(other: Collider) { - return this.shape.overlaps(other.shape); - } + public onEnabled() { + this.registerColliderWithPhysicsSystem(); + } - /** - * 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。 - * @param collider - * @param motion - */ - public collidesWith(collider: Collider, motion: Vector2) { - // 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠 - let oldPosition = this.entity.position; - this.entity.position = Vector2.add(this.entity.position, motion); + public onDisabled() { + this.unregisterColliderWithPhysicsSystem(); + } - let result = this.shape.collidesWithShape(collider.shape); - if (result) - result.collider = collider; - - // 将图形位置返回到检查前的位置 - this.entity.position = oldPosition; - - return result; - } - - public onAddedToEntity() { - if (this._colliderRequiresAutoSizing) { - if (!(this instanceof BoxCollider || this instanceof CircleCollider)) { - console.error("Only box and circle colliders can be created automatically"); - } - - let renderable = this.entity.getComponent(RenderableComponent); - if (renderable) { - let bounds = renderable.bounds; - - // 这里我们需要大小*反尺度,因为当我们自动调整碰撞器的大小时,它需要没有缩放的渲染 - let width = bounds.width / this.entity.scale.x; - let height = bounds.height / this.entity.scale.y; - // 圆碰撞器需要特别注意原点 - if (this instanceof CircleCollider){ - let circleCollider = this as CircleCollider; - circleCollider.radius = Math.max(width, height) * 0.5; - } else { - this.width = width; - this.height = height; - } - - // 获取渲染的中心,将其转移到本地坐标,并使用它作为碰撞器的localOffset - this.localOffset = Vector2.subtract(bounds.center, this.entity.position); - } else { - console.warn("Collider has no shape and no RenderableComponent. Can't figure out how to size it."); + /** + * 父实体会在不同的时间调用它(当添加到场景,启用,等等) + */ + public registerColliderWithPhysicsSystem() { + // 如果在将我们添加到实体之前更改了origin等属性,则实体可以为null + if (this._isParentEntityAddedToScene && !this._isColliderRegistered) { + Physics.addCollider(this); + this._isColliderRegistered = true; } } - this._isParentEntityAddedToScene = true; - this.registerColliderWithPhysicsSystem(); - } + /** + * 父实体会在不同的时候调用它(从场景中移除,禁用,等等) + */ + public unregisterColliderWithPhysicsSystem() { + if (this._isParentEntityAddedToScene && this._isColliderRegistered) { + Physics.removeCollider(this); + } + this._isColliderRegistered = false; + } - public onRemovedFromEntity() { - this.unregisterColliderWithPhysicsSystem(); - this._isParentEntityAddedToScene = false; - } + /** + * 检查这个形状是否与物理系统中的其他对撞机重叠 + * @param other + */ + public overlaps(other: Collider) { + return this.shape.overlaps(other.shape); + } - public onEnabled() { - this.registerColliderWithPhysicsSystem(); - } + /** + * 检查这个与运动应用的碰撞器(移动向量)是否与碰撞器碰撞。如果是这样,将返回true,并且结果将填充碰撞数据。 + * @param collider + * @param motion + */ + public collidesWith(collider: Collider, motion: Vector2) { + // 改变形状的位置,使它在移动后的位置,这样我们可以检查重叠 + let oldPosition = this.entity.position; + this.entity.position = Vector2.add(this.entity.position, motion); - public onDisabled() { - this.unregisterColliderWithPhysicsSystem(); - } + let result = this.shape.collidesWithShape(collider.shape); + if (result) + result.collider = collider; - public onEntityTransformChanged(comp: TransformComponent) { - if (this._isColliderRegistered) - Physics.updateCollider(this); - } + // 将图形位置返回到检查前的位置 + this.entity.position = oldPosition; - public update(){ - let renderable = this.entity.getComponent(RenderableComponent); - if (renderable){ - this.$setX(renderable.x); - this.$setY(renderable.y); + return result; } } -} \ No newline at end of file +} diff --git a/source/src/ECS/Components/PooledComponent.ts b/source/src/ECS/Components/PooledComponent.ts index 91fd8e1d..1d289140 100644 --- a/source/src/ECS/Components/PooledComponent.ts +++ b/source/src/ECS/Components/PooledComponent.ts @@ -1,4 +1,6 @@ -/** 回收实例的组件类型。 */ -abstract class PooledComponent extends Component { - public abstract reset(); -} \ No newline at end of file +module es { + /** 回收实例的组件类型。 */ + export abstract class PooledComponent extends Component { + public abstract reset(); + } +} diff --git a/source/src/ECS/Components/RenderableComponent.ts b/source/src/ECS/Components/RenderableComponent.ts index 9a1940a3..8a961eb1 100644 --- a/source/src/ECS/Components/RenderableComponent.ts +++ b/source/src/ECS/Components/RenderableComponent.ts @@ -1,56 +1,172 @@ /// -/** - * 所有可渲染组件的基类 - */ -abstract class RenderableComponent extends PooledComponent implements IRenderable { - private _isVisible: boolean; - protected _areBoundsDirty = true; - protected _bounds: Rectangle = new Rectangle(); - protected _localOffset: Vector2 = Vector2.zero; +module es { + /** + * 所有可渲染组件的基类 + */ + export abstract class RenderableComponent extends Component implements IRenderable { + /** + * renderableComponent的宽度 + * 如果你不重写bounds属性则需要实现这个 + */ + public get width() { + return this.bounds.width; + } - public color: number = 0x000000; + /** + * renderableComponent的高度 + * 如果你不重写bounds属性则需要实现这个 + */ + public get height() { + return this.bounds.height; + } - public get width(){ - return this.getWidth(); - } + /** + * 这个物体的AABB, 用于相机剔除 + */ + public get bounds(): Rectangle { + if (this._areBoundsDirty){ + this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, Vector2.zero, + this.entity.transform.scale, this.entity.transform.rotation, this.width, this.height); + this._areBoundsDirty = false; + } - public get height(){ - return this.getHeight(); - } + return this._bounds; + } - public get isVisible(){ - return this._isVisible; - } + /** + * 较低的渲染层在前面,较高的在后面 + */ + public get renderLayer(): number{ + return this._renderLayer; + } - public set isVisible(value: boolean){ - this._isVisible = value; + public set renderLayer(value: number){ - if (this._isVisible) - this.onBecameVisible(); - else - this.onBecameInvisible(); - } + } - public get bounds(): Rectangle{ - return new Rectangle(this.getBounds().x, this.getBounds().y, this.getBounds().width, this.getBounds().height); - } + /** + * 用于着色器处理精灵 + */ + public color: number = 0x000000; - protected getWidth(){ - return this.bounds.width; - } + /** + * 从父实体的偏移量。用于向需要特定定位的实体 + */ + public get localOffset(): Vector2{ + return this._localOffset; + } - protected getHeight(){ - return this.bounds.height; - } + /** + * 从父实体的偏移量。用于向需要特定定位的实体 + * @param value + */ + public set localOffset(value: Vector2){ + this.setLocalOffset(value); + } - protected onBecameVisible(){} + /** + * 可渲染的可见性。状态的改变会调用onBecameVisible/onBecameInvisible方法 + */ + public get isVisible() { + return this._isVisible; + } - protected onBecameInvisible(){} + /** + * 可渲染的可见性。状态的改变会调用onBecameVisible/onBecameInvisible方法 + * @param value + */ + public set isVisible(value: boolean) { + if (this._isVisible != value){ + this._isVisible = value; - public abstract render(camera: Camera); + if (this._isVisible) + this.onBecameVisible(); + else + this.onBecameInvisible(); + } + } - public isVisibleFromCamera(camera: Camera): boolean{ - this.isVisible = camera.getBounds().intersects(this.getBounds()); - return this.isVisible; + protected _localOffset: Vector2 = Vector2.zero; + protected _renderLayer: number = 0; + protected _bounds: Rectangle = new Rectangle(); + private _isVisible: boolean; + protected _areBoundsDirty = true; + + public onEntityTransformChanged(comp: transform.Component) { + this._areBoundsDirty = true; + } + + /** + * 由渲染器调用。可以使用摄像机进行剔除 + * @param camera + */ + public abstract render(camera: Camera); + + /** + * 当renderableComponent进入相机框架时调用 + * 如果渲染器不适用isVisibleFromCamera进行剔除检查 这些方法不会被调用 + */ + protected onBecameVisible() { + } + + /** + * 当renderableComponent离开相机框架时调用 + * 如果渲染器不适用isVisibleFromCamera进行剔除检查 这些方法不会被调用 + */ + protected onBecameInvisible() { + } + + /** + * 如果renderableComponent的边界与camera.bounds相交 返回true + * 用于处理isVisible标志的状态开关 + * 在渲染方法中使用这个方法来决定是否渲染 + * @param camera + */ + public isVisibleFromCamera(camera: Camera): boolean { + this.isVisible = camera.bounds.intersects(this.bounds); + return this.isVisible; + } + + /** + * 较低的渲染层在前面,较高的在后面 + * @param renderLayer + */ + public setRenderLayer(renderLayer: number): RenderableComponent{ + if (renderLayer != this._renderLayer){ + let oldRenderLayer = this._renderLayer; + this._renderLayer = renderLayer; + + // 如果该组件拥有一个实体,那么是由ComponentList管理,需要通知它改变了渲染层 + if (this.entity && this.entity.scene) + this.entity.scene.renderableComponents.updateRenderableRenderLayer(this, oldRenderLayer, this._renderLayer); + } + + return this; + } + + /** + * 用于着色器处理精灵 + * @param color + */ + public setColor(color: number): RenderableComponent{ + this.color = color; + return this; + } + + /** + * 从父实体的偏移量。用于向需要特定定位的实体 + * @param offset + */ + public setLocalOffset(offset: Vector2): RenderableComponent{ + if (this._localOffset != offset){ + this._localOffset = offset; + } + + return this; + } + + public toString(){ + return `[RenderableComponent] ${this}, renderLayer: ${this.renderLayer}`; + } } } \ No newline at end of file diff --git a/source/src/ECS/Components/Sprite.ts b/source/src/ECS/Components/Sprite.ts index b9c2f7a8..ba8ced5e 100644 --- a/source/src/ECS/Components/Sprite.ts +++ b/source/src/ECS/Components/Sprite.ts @@ -14,7 +14,7 @@ class Sprite { this.origin = origin; let inverseTexW = 1 / texture.textureWidth; - let inverseTexH = 1 / texture.textureHeight + let inverseTexH = 1 / texture.textureHeight; this.uvs.x = sourceRect.x * inverseTexW; this.uvs.y = sourceRect.y * inverseTexH; diff --git a/source/src/ECS/Components/SpriteRenderer.ts b/source/src/ECS/Components/SpriteRenderer.ts index 32df55cf..6db35138 100644 --- a/source/src/ECS/Components/SpriteRenderer.ts +++ b/source/src/ECS/Components/SpriteRenderer.ts @@ -1,66 +1,120 @@ -class SpriteRenderer extends RenderableComponent { - private _sprite: Sprite; - protected bitmap: egret.Bitmap; +module es { + export class SpriteRenderer extends RenderableComponent { + public get bounds(){ + if (this._areBoundsDirty){ + if (this._sprite){ + this._bounds.calculateBounds(this.entity.transform.position, this._localOffset, this._origin, + this.entity.transform.scale, this.entity.transform.rotation, this._sprite.sourceRect.width, + this._sprite.sourceRect.height); + this._areBoundsDirty = false; + } - /** 应该由这个精灵显示的精灵 */ - public get sprite(): Sprite { - return this._sprite; - } - /** 应该由这个精灵显示的精灵 */ - public set sprite(value: Sprite) { - this.setSprite(value); - } - - public setSprite(sprite: Sprite): SpriteRenderer { - this.removeChildren(); - this._sprite = sprite; - if (this._sprite) { - this.anchorOffsetX = this._sprite.origin.x / this._sprite.sourceRect.width; - this.anchorOffsetY = this._sprite.origin.y / this._sprite.sourceRect.height; + return this._bounds; + } } - this.bitmap = new egret.Bitmap(sprite.texture2D); - this.addChild(this.bitmap); - return this; - } - - public setColor(color: number): SpriteRenderer { - let colorMatrix = [ - 1, 0, 0, 0, 0, - 0, 1, 0, 0, 0, - 0, 0, 1, 0, 0, - 0, 0, 0, 1, 0 - ]; - colorMatrix[0] = Math.floor(color / 256 / 256) / 255; - colorMatrix[6] = Math.floor(color / 256 % 256) / 255; - colorMatrix[12] = color % 256 / 255; - let colorFilter = new egret.ColorMatrixFilter(colorMatrix); - this.filters = [colorFilter]; - - return this; - } - - public isVisibleFromCamera(camera: Camera): boolean { - this.isVisible = new Rectangle(0, 0, this.stage.stageWidth, this.stage.stageHeight).intersects(this.bounds); - this.visible = this.isVisible; - return this.isVisible; - } - - /** 渲染处理 在每个模块中处理各自的渲染逻辑 */ - public render(camera: Camera) { - if (this.x != -camera.position.x + camera.origin.x || - this.y != -camera.position.y + camera.origin.y) { - this.x = -camera.position.x + camera.origin.x; - this.y = -camera.position.y + camera.origin.y; - this.entity.onEntityTransformChanged(TransformComponent.position); + /** + * 精灵的原点。这是在设置精灵时自动设置的 + */ + public get origin(): Vector2{ + return this._origin; } - } - public onRemovedFromEntity() { - if (this.parent) - this.parent.removeChild(this); - } + /** + * 精灵的原点。这是在设置精灵时自动设置的 + * @param value + */ + public set origin(value: Vector2){ + this.setOrigin(value); + } - public reset() { + /** + * 用归一化方法设置原点 + * x/y 均为 0-1 + */ + public get originNormalized(): Vector2{ + return new Vector2(this._origin.x / this.width * this.entity.transform.scale.x, + this._origin.y / this.height * this.entity.transform.scale.y); + } + + /** + * 用归一化方法设置原点 + * x/y 均为 0-1 + * @param value + */ + public set originNormalized(value: Vector2){ + this.setOrigin(new Vector2(value.x * this.width / this.entity.transform.scale.x, + value.y * this.height / this.entity.transform.scale.y)); + } + + /** + * 应该由这个精灵显示的精灵 + * 当设置时,精灵的原点也被设置为精灵的origin + */ + public get sprite(): Sprite { + return this._sprite; + } + + /** + * 应该由这个精灵显示的精灵 + * 当设置时,精灵的原点也被设置为精灵的origin + * @param value + */ + public set sprite(value: Sprite) { + this.setSprite(value); + } + + protected _origin: Vector2; + protected _sprite: Sprite; + + constructor(sprite: Sprite | egret.Texture) { + super(); + if (sprite instanceof Sprite) + this.setSprite(sprite); + else if(sprite instanceof egret.Texture) + this.setSprite(new Sprite(sprite)); + } + + /** + * 设置精灵并更新精灵的原点以匹配sprite.origin + * @param sprite + */ + public setSprite(sprite: Sprite): SpriteRenderer { + this._sprite = sprite; + if (this._sprite) { + this._origin = this._sprite.origin; + } + + return this; + } + + /** + * 设置可渲染的原点 + * @param origin + */ + public setOrigin(origin: Vector2): SpriteRenderer{ + if (this._origin != origin){ + this._origin = origin; + this._areBoundsDirty = true; + } + + return this; + } + + /** + * 用归一化方法设置原点 + * x/y 均为 0-1 + * @param value + */ + public setOriginNormalized(value: Vector2): SpriteRenderer{ + this.setOrigin(new Vector2(value.x * this.width / this.entity.transform.scale.x, + value.y * this.height / this.entity.transform.scale.y)); + return this; + } + + public render(camera: Camera) { + // TODO: render + } } } + diff --git a/source/src/ECS/Entity.ts b/source/src/ECS/Entity.ts index 82f9fb0c..0aa137d2 100644 --- a/source/src/ECS/Entity.ts +++ b/source/src/ECS/Entity.ts @@ -133,7 +133,7 @@ module es { this.componentBits = new BitSet(); } - public onTransformChanged(comp: Transform.Component) { + public onTransformChanged(comp: transform.Component) { // 通知我们的子项改变了位置 this.components.onEntityTransformChanged(comp); } diff --git a/source/src/ECS/Transform.ts b/source/src/ECS/Transform.ts index 94a3e2bc..1a69464a 100644 --- a/source/src/ECS/Transform.ts +++ b/source/src/ECS/Transform.ts @@ -1,9 +1,22 @@ +module transform { + export enum Component { + position, + scale, + rotation, + } +} + module es { + export enum DirtyType { + clean, + positionDirty, + scaleDirty, + rotationDirty, + } + export class Transform { /** 与此转换关联的实体 */ public readonly entity: Entity; - - private _parent: Transform; /** * 获取此转换的父转换 */ @@ -38,6 +51,9 @@ module es { * 变换在世界空间的缩放 */ public scale: Vector2; + + public _parent: Transform; + public hierarchyDirty: DirtyType; public _children: Transform[]; constructor(entity: Entity) { @@ -68,6 +84,7 @@ module es { } this._parent = parent; + this.setDirty(DirtyType.positionDirty); return this; } @@ -79,6 +96,7 @@ module es { */ public setPosition(x: number, y: number): Transform { this.position = new Vector2(x, y); + this.setDirty(DirtyType.positionDirty); return this; } @@ -88,6 +106,7 @@ module es { */ public setRotation(degrees: number): Transform { this.rotation = degrees; + this.setDirty(DirtyType.rotationDirty); return this; } @@ -97,6 +116,7 @@ module es { */ public setScale(scale: Vector2): Transform { this.scale = scale; + this.setDirty(DirtyType.scaleDirty); return this; } @@ -117,6 +137,31 @@ module es { this.position = this.position.round(); } + public setDirty(dirtyFlagType: DirtyType){ + if ((this.hierarchyDirty & dirtyFlagType) == 0){ + this.hierarchyDirty |= dirtyFlagType; + + switch (dirtyFlagType) { + case es.DirtyType.positionDirty: + this.entity.onTransformChanged(transform.Component.position); + break; + case es.DirtyType.rotationDirty: + this.entity.onTransformChanged(transform.Component.rotation); + break; + case es.DirtyType.scaleDirty: + this.entity.onTransformChanged(transform.Component.scale); + break; + } + + if (!this._children) + this._children = []; + + // 告诉子项发生了变换 + for (let i = 0; i < this._children.length; i ++) + this._children[i].setDirty(dirtyFlagType); + } + } + /** * 从另一个transform属性进行拷贝 * @param transform @@ -125,14 +170,10 @@ module es { this.position = transform.position; this.rotation = transform.rotation; this.scale = transform.scale; + + this.setDirty(DirtyType.positionDirty); + this.setDirty(DirtyType.rotationDirty); + this.setDirty(DirtyType.scaleDirty); } } -} - -module Transform { - export enum Component { - position, - scale, - rotation, - } } \ No newline at end of file diff --git a/source/src/ECS/Utils/ComponentList.ts b/source/src/ECS/Utils/ComponentList.ts index 0b1ae9f4..38769848 100644 --- a/source/src/ECS/Utils/ComponentList.ts +++ b/source/src/ECS/Utils/ComponentList.ts @@ -1,5 +1,10 @@ +/// module es { export class ComponentList { + /** + * 组件列表的全局updateOrder排序 + */ + public static compareUpdatableOrder: IUpdatableComparer = new IUpdatableComparer(); public _entity: Entity; /** @@ -15,6 +20,10 @@ module es { */ public _componentsToRemove: Component[] = []; public _tempBufferList: Component[] = []; + /** + * 用于确定是否需要对该框架中的组件进行排序的标志 + */ + public _isComponentListUnsorted: boolean; constructor(entity: Entity) { this._entity = entity; @@ -28,13 +37,17 @@ module es { return this._components; } + public markEntityListUnsorted(){ + this._isComponentListUnsorted = true; + } + public add(component: Component) { this._componentsToAdd.push(component); } public remove(component: Component) { if (this._componentsToRemove.contains(component)) - console.warn(`You are trying to remove a Component (${component}) that you already removed`) + console.warn(`You are trying to remove a Component (${component}) that you already removed`); // 这可能不是一个活动的组件,所以我们必须注意它是否还没有被处理,它可能正在同一帧中被删除 if (this._componentsToAdd.contains(component)) { @@ -111,6 +124,7 @@ module es { // 在调用onAddedToEntity之前清除,以防添加更多组件 this._componentsToAdd.length = 0; + this._isComponentListUnsorted = true; // 现在所有的组件都添加到了场景中,我们再次循环并调用onAddedToEntity/onEnabled for (let i = 0; i < this._tempBufferList.length; i++) { @@ -125,6 +139,11 @@ module es { this._tempBufferList.length = 0; } + + if (this._isComponentListUnsorted){ + this._components.sort(ComponentList.compareUpdatableOrder.compare); + this._isComponentListUnsorted = false; + } } public handleRemove(component: Component) { @@ -216,7 +235,7 @@ module es { } } - public onEntityTransformChanged(comp: Transform.Component) { + public onEntityTransformChanged(comp: transform.Component) { for (let i = 0; i < this._components.length; i++) { if (this._components[i].enabled) this._components[i].onEntityTransformChanged(comp); diff --git a/source/src/ECS/Utils/RenderableComponentList.ts b/source/src/ECS/Utils/RenderableComponentList.ts index df443385..74746f76 100644 --- a/source/src/ECS/Utils/RenderableComponentList.ts +++ b/source/src/ECS/Utils/RenderableComponentList.ts @@ -1,22 +1,101 @@ -class RenderableComponentList { - private _components: IRenderable[] = []; - public get count(){ - return this._components.length; - } +/// +module es { + export class RenderableComponentList { + /** + * IRenderable列表的全局updatePrder排序 + */ + public static compareUpdatableOrder = new RenderableComparer(); + /** + * 添加到实体的组件列表 + */ + public _components: IRenderable[] = []; + /** + * 通过渲染层跟踪组件,便于检索 + */ + public _componentsByRenderLayer: Map = new Map(); + public _unsortedRenderLayers: number[] = []; + public _componentsNeedSort: boolean = true; - public get buffer(){ - return this._components; - } + public get count() { + return this._components.length; + } - public add(component: IRenderable){ - this._components.push(component); - } + public get buffer() { + return this._components; + } - public remove(component: IRenderable){ - this._components.remove(component); - } + public add(component: IRenderable) { + this._components.push(component); + this.addToRenderLayerList(component, component.renderLayer); + } - public updateList(){ + public remove(component: IRenderable) { + this._components.remove(component); + this._componentsByRenderLayer.get(component.renderLayer).remove(component); + } + public updateRenderableRenderLayer(component: IRenderable, oldRenderLayer: number, newRenderLayer: number) { + // 需要注意的是,如果渲染层在组件update之前发生了改变 + if (this._componentsByRenderLayer.has(oldRenderLayer) && this._componentsByRenderLayer.get(oldRenderLayer).contains(component)) { + this._componentsByRenderLayer.get(oldRenderLayer).remove(component); + this.addToRenderLayerList(component, newRenderLayer); + } + } + + /** + * 将渲染层排序标志弄脏,让所有组件重新排序 + * @param renderLayer + */ + public setRenderLayerNeedsComponentSort(renderLayer: number) { + if (!this._unsortedRenderLayers.contains(renderLayer)) + this._unsortedRenderLayers.push(renderLayer); + this._componentsNeedSort = true; + } + + public setNeedsComponentSort() { + this._componentsNeedSort = true; + } + + public addToRenderLayerList(component: IRenderable, renderLayer: number) { + let list = this.componentsWithRenderLayer(renderLayer); + if (!list.contains(component)) { + console.warn("Component renderLayer list already contains this component"); + return; + } + + list.push(component); + if (!this._unsortedRenderLayers.contains(renderLayer)) + this._unsortedRenderLayers.push(renderLayer); + this._componentsNeedSort = true; + } + + /** + * 使用给定的渲染层获取所有组件。组件列表是预先排序的 + * @param renderLayer + */ + public componentsWithRenderLayer(renderLayer: number): IRenderable[] { + if (!this._componentsByRenderLayer.get(renderLayer)) { + this._componentsByRenderLayer.set(renderLayer, []); + } + return this._componentsByRenderLayer.get(renderLayer); + } + + public updateList() { + if (this._componentsNeedSort) { + this._components.sort(RenderableComponentList.compareUpdatableOrder.compare); + this._componentsNeedSort = false; + } + + if (this._unsortedRenderLayers.length > 0) { + for (let i = 0, count = this._unsortedRenderLayers.length; i < count; i++) { + let renderLayerComponents = this._componentsByRenderLayer.get(this._unsortedRenderLayers[i]); + if (renderLayerComponents) { + renderLayerComponents.sort(RenderableComponentList.compareUpdatableOrder.compare); + } + } + + this._unsortedRenderLayers.length = 0; + } + } } -} \ No newline at end of file +} diff --git a/source/src/Graphics/Renderers/IRenderable.ts b/source/src/Graphics/Renderers/IRenderable.ts index c19dd59d..cb1a2b9d 100644 --- a/source/src/Graphics/Renderers/IRenderable.ts +++ b/source/src/Graphics/Renderers/IRenderable.ts @@ -1,7 +1,47 @@ -interface IRenderable { - bounds: Rectangle; - enabled: boolean; - isVisible: boolean; - isVisibleFromCamera(camera: Camera); - render(camera: Camera); -} \ No newline at end of file +module es { + /** + * 当该接口应用到组件时,它将注册组件以场景渲染器显示 + * 该接口请谨慎实现 + */ + export interface IRenderable { + /** + * 对象的AABB用于相机剔除 + */ + bounds: Rectangle; + /** + * 这个组件是否应该被渲染 + */ + enabled: boolean; + /** + * 较低的渲染层在前面,较高的在后面 + */ + renderLayer: number; + /** + * 可渲染的可见性。状态的改变会调用onBecameVisible/onBecameInvisible方法 + */ + isVisible: boolean; + + /** + * 如果renderableComponent的边界与camera.bounds相交 返回true + * 用于处理isVisible标志的状态开关 + * 在渲染方法中使用这个方法来决定是否渲染 + * @param camera + */ + isVisibleFromCamera(camera: Camera); + + /** + * 由渲染器调用。可以使用摄像机进行剔除 + * @param camera + */ + render(camera: Camera); + } + + /** + * 用于排序IRenderables的比较器 + */ + export class RenderableComparer { + public compare(self: IRenderable, other: IRenderable){ + return other.renderLayer - self.renderLayer; + } + } +} diff --git a/source/src/Math/Rectangle.ts b/source/src/Math/Rectangle.ts index 7db9bc97..694b553a 100644 --- a/source/src/Math/Rectangle.ts +++ b/source/src/Math/Rectangle.ts @@ -138,6 +138,13 @@ class Rectangle extends egret.Rectangle { return boundsPoint; } + public calculateBounds(parentPosition: Vector2, position: Vector2, origin: Vector2, scale: Vector2, rotation: number, width: number, height: number){ + this.x = parentPosition.x + position.x - origin.x * scale.x; + this.y = parentPosition.y + position.y - origin.y * scale.y; + this.width = width * scale.x; + this.height = height * scale.y; + } + /** * 将egret矩形转化为Rectangle * @param rect diff --git a/source/src/Physics/Shapes/Box.ts b/source/src/Physics/Shapes/Box.ts index 1871a244..e9e23c13 100644 --- a/source/src/Physics/Shapes/Box.ts +++ b/source/src/Physics/Shapes/Box.ts @@ -1,81 +1,88 @@ /// -/** - * 多边形的特殊情况。在进行SAT碰撞检查时,我们只需要检查2个轴而不是8个轴 - */ -class Box extends Polygon { - constructor(width: number, height: number){ - super(Box.buildBox(width, height), true); - this.width = width; - this.height = height; - } - +module es { /** - * 在一个盒子的形状中建立多边形需要的点的帮助方法 - * @param width - * @param height + * 多边形的特殊情况。在进行SAT碰撞检查时,我们只需要检查2个轴而不是8个轴 */ - private static buildBox(width: number, height: number): Vector2[]{ - // 我们在(0,0)的中心周围创建点 - let halfWidth = width / 2; - let halfHeight = height / 2; - let verts = new Array(4); - verts[0] = new Vector2(-halfWidth, -halfHeight); - verts[1] = new Vector2(halfWidth, -halfHeight); - verts[2] = new Vector2(halfWidth, halfHeight); - verts[3] = new Vector2(-halfWidth, halfHeight); + export class Box extends Polygon { + public width: number; + public height: number; - return verts; - } - - public overlaps(other: Shape){ - // 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测 - if (other instanceof Box) - return this.bounds.intersects(other.bounds); - - if (other instanceof Circle) - return Collisions.isRectToCircle(this.bounds, other.position, other.radius); - - return super.overlaps(other); - } - - public collidesWithShape(other: Shape){ - // 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测 - if (other instanceof Box){ - return ShapeCollisions.boxToBox(this, other); + constructor(width: number, height: number){ + super(Box.buildBox(width, height), true); + this.width = width; + this.height = height; } - // TODO: 让 minkowski 运行于 cricleToBox + /** + * 在一个盒子的形状中建立多边形需要的点的帮助方法 + * @param width + * @param height + */ + private static buildBox(width: number, height: number): Vector2[]{ + // 我们在(0,0)的中心周围创建点 + let halfWidth = width / 2; + let halfHeight = height / 2; + let verts = new Array(4); + verts[0] = new Vector2(-halfWidth, -halfHeight); + verts[1] = new Vector2(halfWidth, -halfHeight); + verts[2] = new Vector2(halfWidth, halfHeight); + verts[3] = new Vector2(-halfWidth, halfHeight); - return super.collidesWithShape(other); - } + return verts; + } - /** - * 更新框点,重新计算中心,设置宽度/高度 - * @param width - * @param height - */ - public updateBox(width: number, height: number){ - this.width = width; - this.height = height; + /** + * 更新框点,重新计算中心,设置宽度/高度 + * @param width + * @param height + */ + public updateBox(width: number, height: number){ + this.width = width; + this.height = height; - // 我们在(0,0)的中心周围创建点 - let halfWidth = width / 2; - let halfHeight = height / 2; + // 我们在(0,0)的中心周围创建点 + let halfWidth = width / 2; + let halfHeight = height / 2; - this.points[0] = new Vector2(-halfWidth, -halfHeight); - this.points[1] = new Vector2(halfWidth, -halfHeight); - this.points[2] = new Vector2(halfWidth, halfHeight); - this.points[3] = new Vector2(-halfWidth, halfHeight); + this.points[0] = new Vector2(-halfWidth, -halfHeight); + this.points[1] = new Vector2(halfWidth, -halfHeight); + this.points[2] = new Vector2(halfWidth, halfHeight); + this.points[3] = new Vector2(-halfWidth, halfHeight); - for (let i = 0; i < this.points.length; i ++) - this._originalPoints[i] = this.points[i]; - } + for (let i = 0; i < this.points.length; i ++) + this._originalPoints[i] = this.points[i]; + } - /** - * - * @param point - */ - public containsPoint(point: Vector2){ - return this.bounds.contains(point.x, point.y); + public overlaps(other: Shape){ + // 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测 + if (other instanceof Box) + return this.bounds.intersects(other.bounds); + + if (other instanceof Circle) + return Collisions.isRectToCircle(this.bounds, other.position, other.radius); + + return super.overlaps(other); + } + + public collidesWithShape(other: Shape){ + // 特殊情况,这一个高性能方式实现,其他情况则使用polygon方法检测 + if (other instanceof Box){ + return ShapeCollisions.boxToBox(this, other); + } + + // TODO: 让 minkowski 运行于 cricleToBox + + return super.collidesWithShape(other); + } + + + + /** + * + * @param point + */ + public containsPoint(point: Vector2){ + return this.bounds.contains(point.x, point.y); + } } } \ No newline at end of file diff --git a/source/src/Physics/Shapes/Polygon.ts b/source/src/Physics/Shapes/Polygon.ts index c034d1b9..d6991b19 100644 --- a/source/src/Physics/Shapes/Polygon.ts +++ b/source/src/Physics/Shapes/Polygon.ts @@ -1,215 +1,250 @@ /// -/** - * 多边形 - */ -class Polygon extends Shape { - /** 组成多边形的点。它们应该是CW和凸的。 */ - public points: Vector2[]; - private _polygonCenter: Vector2; - private _areEdgeNormalsDirty = true; - protected _originalPoints: Vector2[]; - public center = new Vector2(); - /** - * 多边形坐标 - * 此为内部字段 可访问 - */ - public position: Vector2 = Vector2.zero; - - public get bounds(){ - return new Rectangle(this.position.x, this.position.y, 0, 0); - } - - public _edgeNormals: Vector2[]; - public get edgeNormals(){ - if (this._areEdgeNormalsDirty) - this.buildEdgeNormals(); - return this._edgeNormals; - } - public isBox: boolean; - - constructor(points: Vector2[], isBox?: boolean){ - super(); - - this.setPoints(points); - this.isBox = isBox; - } - - private buildEdgeNormals(){ - let totalEdges = this.isBox ? 2 : this.points.length; - if (this._edgeNormals == null || this._edgeNormals.length != totalEdges) - this._edgeNormals = new Array(totalEdges); - - let p2: Vector2; - for (let i = 0; i < totalEdges; i ++){ - let p1 = this.points[i]; - if (i + 1 >= this.points.length) - p2 = this.points[0]; - else - p2 = this.points[i + 1]; - - let perp = Vector2Ext.perpendicular(p1, p2); - perp = Vector2.normalize(perp); - this._edgeNormals[i] = perp; - } - } - - public setPoints(points: Vector2[]) { - this.points = points; - this.recalculateCenterAndEdgeNormals(); - - this._originalPoints = []; - for (let i = 0; i < this.points.length; i ++){ - this._originalPoints.push(this.points[i]); - } - } - - public collidesWithShape(other: Shape){ - let result = new CollisionResult(); - if (other instanceof Polygon){ - return ShapeCollisions.polygonToPolygon(this, other); - } - - if (other instanceof Circle){ - result = ShapeCollisions.circleToPolygon(other, this); - if (result){ - result.invertResult(); - return result; - } - - return null; - } - - throw new Error(`overlaps of Polygon to ${other} are not supported`); - } - - public recalculateCenterAndEdgeNormals() { - this._polygonCenter = Polygon.findPolygonCenter(this.points); - this._areEdgeNormalsDirty = true; - } - - public overlaps(other: Shape){ - let result: CollisionResult; - if (other instanceof Polygon) - return ShapeCollisions.polygonToPolygon(this, other); - - if (other instanceof Circle){ - result = ShapeCollisions.circleToPolygon(other, this); - if (result){ - result.invertResult(); - return true; - } - - return false; - } - - throw new Error(`overlaps of Pologon to ${other} are not supported`); - } - +module es { /** - * 找到多边形的中心。注意,这对于正则多边形是准确的。不规则多边形没有中心。 - * @param points + * 多边形 */ - public static findPolygonCenter(points: Vector2[]) { - let x = 0, y = 0; + export class Polygon extends Shape { + /** + * 组成多边形的点 + * 保持顺时针与凸边形 + */ + public points: Vector2[]; - for (let i = 0; i < points.length; i++) { - x += points[i].x; - y += points[i].y; + /** + * 边缘法线用于SAT碰撞检测。缓存它们用于避免squareRoots + * box只有两个边缘 因为其他两边是平行的 + */ + public get edgeNormals(){ + if (this._areEdgeNormalsDirty) + this.buildEdgeNormals(); + return this._edgeNormals; } - return new Vector2(x / points.length, y / points.length); - } + public _areEdgeNormalsDirty = true; + public _edgeNormals: Vector2[]; + /** + * 多边形的原始数据 + */ + public _originalPoints: Vector2[]; + public _polygonCenter: Vector2; + /** + * 用于优化未旋转box碰撞 + */ + public isBox: boolean; + public isUnrotated: boolean = true; - /** - * 重定位多边形的点 - * @param points - */ - public static recenterPolygonVerts(points: Vector2[]){ - let center = this.findPolygonCenter(points); - for (let i = 0; i < points.length; i ++) - points[i] = Vector2.subtract(points[i], center); - } + /** + * 从点构造一个多边形 + * 多边形应该以顺时针方式指定 不能重复第一个/最后一个点,它们以0 0为中心 + * @param points + * @param isBox + */ + constructor(points: Vector2[], isBox?: boolean){ + super(); - /** - * 迭代多边形的所有边,并得到任意边上离点最近的点。 - * 通过最近点的平方距离和它所在的边的法线返回。 - * 点应该在多边形的空间中(点-多边形.位置) - * @param points - * @param point - */ - public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): { closestPoint, distanceSquared, edgeNormal } { - let distanceSquared = Number.MAX_VALUE; - let edgeNormal = new Vector2(0, 0); - let closestPoint = new Vector2(0, 0); + this.setPoints(points); + this.isBox = isBox; + } - let tempDistanceSquared; - for (let i = 0; i < points.length; i++) { - let j = i + 1; - if (j == points.length) - j = 0; + /** + * 重置点并重新计算中心和边缘法线 + * @param points + */ + public setPoints(points: Vector2[]) { + this.points = points; + this.recalculateCenterAndEdgeNormals(); - let closest = ShapeCollisions.closestPointOnLine(points[i], points[j], point); - tempDistanceSquared = Vector2.distanceSquared(point, closest); - - if (tempDistanceSquared < distanceSquared) { - distanceSquared = tempDistanceSquared; - closestPoint = closest; - - // 求直线的法线 - let line = Vector2.subtract(points[j], points[i]); - edgeNormal.x = -line.y; - edgeNormal.y = line.x; + this._originalPoints = []; + for (let i = 0; i < this.points.length; i ++){ + this._originalPoints.push(this.points[i]); } } - edgeNormal = Vector2.normalize(edgeNormal); + /** + * 重新计算多边形中心 + * 如果点数改变必须调用该方法 + */ + public recalculateCenterAndEdgeNormals() { + this._polygonCenter = Polygon.findPolygonCenter(this.points); + this._areEdgeNormalsDirty = true; + } - return { closestPoint: closestPoint, distanceSquared: distanceSquared, edgeNormal: edgeNormal }; - } + /** + * 建立多边形边缘法线 + * 它们仅由edgeNormals getter惰性创建和更新 + */ + public buildEdgeNormals(){ + // 对于box 我们只需要两条边,因为另外两条边是平行的 + let totalEdges = this.isBox ? 2 : this.points.length; + if (this._edgeNormals == null || this._edgeNormals.length != totalEdges) + this._edgeNormals = new Array(totalEdges); - public recalculateBounds(collider: Collider){ - // 如果我们没有旋转或不关心TRS我们使用localOffset作为中心,我们会从那开始 - - } + let p2: Vector2; + for (let i = 0; i < totalEdges; i ++){ + let p1 = this.points[i]; + if (i + 1 >= this.points.length) + p2 = this.points[0]; + else + p2 = this.points[i + 1]; - public pointCollidesWithShape(point: Vector2): CollisionResult { - return ShapeCollisions.pointToPoly(point, this); - } - - /** - * 本质上,这个算法所做的就是从一个点发射一条射线。 - * 如果它与奇数条多边形边相交,我们就知道它在多边形内部。 - * @param point - */ - public containsPoint(point: Vector2) { - // 将点归一化到多边形坐标空间中 - point = Vector2.subtract(point, this.position); - - let isInside = false; - for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) { - if (((this.points[i].y > point.y) != (this.points[j].y > point.y)) && - (point.x < (this.points[j].x - this.points[i].x) * (point.y - this.points[i].y) / (this.points[j].y - this.points[i].y) + - this.points[i].x)) { - isInside = !isInside; + let perp = Vector2Ext.perpendicular(p1, p2); + perp = Vector2.normalize(perp); + this._edgeNormals[i] = perp; } } - return isInside; - } - /** - * 建立一个对称的多边形(六边形,八角形,n角形)并返回点 - * @param vertCount - * @param radius - */ - public static buildSymmertricalPolygon(vertCount: number, radius: number) { - let verts = new Array(vertCount); + /** + * 建立一个对称的多边形(六边形,八角形,n角形)并返回点 + * @param vertCount + * @param radius + */ + public static buildSymmetricalPolygon(vertCount: number, radius: number) { + let verts = new Array(vertCount); - for (let i = 0; i < vertCount; i++) { - let a = 2 * Math.PI * (i / vertCount); - verts[i] = new Vector2(Math.cos(a), Math.sin(a) * radius); + for (let i = 0; i < vertCount; i++) { + let a = 2 * Math.PI * (i / vertCount); + verts[i] = new Vector2(Math.cos(a), Math.sin(a) * radius); + } + + return verts; } - return verts; + /** + * 重定位多边形的点 + * @param points + */ + public static recenterPolygonVerts(points: Vector2[]){ + let center = this.findPolygonCenter(points); + for (let i = 0; i < points.length; i ++) + points[i] = Vector2.subtract(points[i], center); + } + + /** + * 找到多边形的中心。注意,这对于正则多边形是准确的。不规则多边形没有中心。 + * @param points + */ + public static findPolygonCenter(points: Vector2[]) { + let x = 0, y = 0; + + for (let i = 0; i < points.length; i++) { + x += points[i].x; + y += points[i].y; + } + + return new Vector2(x / points.length, y / points.length); + } + + /** + * 迭代多边形的所有边,并得到任意边上离点最近的点。 + * 通过最近点的平方距离和它所在的边的法线返回。 + * 点应该在多边形的空间中(点-多边形.位置) + * @param points + * @param point + */ + public static getClosestPointOnPolygonToPoint(points: Vector2[], point: Vector2): { closestPoint, distanceSquared, edgeNormal } { + let distanceSquared = Number.MAX_VALUE; + let edgeNormal = new Vector2(0, 0); + let closestPoint = new Vector2(0, 0); + + let tempDistanceSquared; + for (let i = 0; i < points.length; i++) { + let j = i + 1; + if (j == points.length) + j = 0; + + let closest = ShapeCollisions.closestPointOnLine(points[i], points[j], point); + tempDistanceSquared = Vector2.distanceSquared(point, closest); + + if (tempDistanceSquared < distanceSquared) { + distanceSquared = tempDistanceSquared; + closestPoint = closest; + + // 求直线的法线 + let line = Vector2.subtract(points[j], points[i]); + edgeNormal.x = -line.y; + edgeNormal.y = line.x; + } + } + + edgeNormal = Vector2.normalize(edgeNormal); + + return { closestPoint: closestPoint, distanceSquared: distanceSquared, edgeNormal: edgeNormal }; + } + + public recalculateBounds(collider: Collider){ + // 如果我们没有旋转或不关心TRS我们使用localOffset作为中心,我们会从那开始 + this.center = collider.localOffset; + + if (collider.shouldColliderScaleAndRotateWithTransform){ + this.isUnrotated = collider.entity.transform.rotation == 0; + } + + this.position = Vector2.add(collider.entity.transform.position, this.center); + this.bounds = Rectangle.rectEncompassingPoints(this.points); + this.bounds.location = Vector2.add(this.bounds.location, this.position); + } + + public overlaps(other: Shape){ + let result: CollisionResult; + if (other instanceof Polygon) + return ShapeCollisions.polygonToPolygon(this, other); + + if (other instanceof Circle){ + result = ShapeCollisions.circleToPolygon(other, this); + if (result){ + result.invertResult(); + return true; + } + + return false; + } + + throw new Error(`overlaps of Pologon to ${other} are not supported`); + } + + public collidesWithShape(other: Shape){ + let result = new CollisionResult(); + if (other instanceof Polygon){ + return ShapeCollisions.polygonToPolygon(this, other); + } + + if (other instanceof Circle){ + result = ShapeCollisions.circleToPolygon(other, this); + if (result){ + result.invertResult(); + return result; + } + + return null; + } + + throw new Error(`overlaps of Polygon to ${other} are not supported`); + } + + /** + * 本质上,这个算法所做的就是从一个点发射一条射线。 + * 如果它与奇数条多边形边相交,我们就知道它在多边形内部。 + * @param point + */ + public containsPoint(point: Vector2) { + // 将点归一化到多边形坐标空间中 + point = Vector2.subtract(point, this.position); + + let isInside = false; + for (let i = 0, j = this.points.length - 1; i < this.points.length; j = i++) { + if (((this.points[i].y > point.y) != (this.points[j].y > point.y)) && + (point.x < (this.points[j].x - this.points[i].x) * (point.y - this.points[i].y) / (this.points[j].y - this.points[i].y) + + this.points[i].x)) { + isInside = !isInside; + } + } + + return isInside; + } + + public pointCollidesWithShape(point: Vector2): CollisionResult { + return ShapeCollisions.pointToPoly(point, this); + } } } \ No newline at end of file diff --git a/source/src/Physics/Shapes/Shape.ts b/source/src/Physics/Shapes/Shape.ts index 6f351c4b..1dcb1951 100644 --- a/source/src/Physics/Shapes/Shape.ts +++ b/source/src/Physics/Shapes/Shape.ts @@ -1,21 +1,23 @@ -abstract class Shape { - /** 缓存的形状边界 内部字段 */ - public bounds: Rectangle; - /** - * 这不是中心。这个值不一定是物体的中心。对撞机更准确。 - * 应用任何转换旋转的localOffset - * 内部字段 - */ - public center: Vector2; - /** - * 有一个单独的位置字段可以让我们改变形状的位置来进行碰撞检查,而不是改变entity.position。 - * 触发碰撞器/边界/散列更新的位置。 - * 内部字段 - */ - public position: Vector2; +module es { + export abstract class Shape { + /** + * 有一个单独的位置字段可以让我们改变形状的位置来进行碰撞检查,而不是改变entity.position。 + * 触发碰撞器/边界/散列更新的位置。 + * 内部字段 + */ + public position: Vector2; + /** + * 这不是中心。这个值不一定是物体的中心。对撞机更准确。 + * 应用任何转换旋转的localOffset + * 内部字段 + */ + public center: Vector2; + /** 缓存的形状边界 内部字段 */ + public bounds: Rectangle; - public abstract recalculateBounds(collider: Collider); - public abstract pointCollidesWithShape(point: Vector2): CollisionResult; - public abstract overlaps(other: Shape); - public abstract collidesWithShape(other: Shape): CollisionResult; -} \ No newline at end of file + public abstract recalculateBounds(collider: Collider); + public abstract pointCollidesWithShape(point: Vector2): CollisionResult; + public abstract overlaps(other: Shape); + public abstract collidesWithShape(other: Shape): CollisionResult; + } +} diff --git a/source/src/Utils/DrawUtils.ts b/source/src/Utils/DrawUtils.ts index 3775c611..54be26fd 100644 --- a/source/src/Utils/DrawUtils.ts +++ b/source/src/Utils/DrawUtils.ts @@ -43,4 +43,17 @@ class DrawUtils { shape.graphics.drawRect(destRect.x, destRect.y, destRect.width, destRect.height); shape.graphics.endFill(); } + + public static getColorMatrix(color: number): egret.ColorMatrixFilter{ + let colorMatrix = [ + 1, 0, 0, 0, 0, + 0, 1, 0, 0, 0, + 0, 0, 1, 0, 0, + 0, 0, 0, 1, 0 + ]; + colorMatrix[0] = Math.floor(color / 256 / 256) / 255; + colorMatrix[6] = Math.floor(color / 256 % 256) / 255; + colorMatrix[12] = color % 256 / 255; + return new egret.ColorMatrixFilter(colorMatrix); + } } \ No newline at end of file