Files
esengine/source/src/ECS/Components/Camera.ts
2020-07-27 16:10:36 +08:00

481 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
module es {
export enum CameraStyle {
lockOn,
cameraWindow,
}
export class CameraInset {
public left: number = 0;
public right: number = 0;
public top: number = 0;
public bottom: number = 0;
}
export class Camera extends Component {
/**
* 对entity.transform.position的快速访问
*/
public get position() {
return this.entity.transform.position;
}
/**
* 对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;
if (this._zoom < 1)
return MathHelper.map(this._zoom, this._minimumZoom, 1, -1, 0);
return MathHelper.map(this._zoom, 1, this._maximumZoom, 0, 1);
}
/**
* 缩放值应该在-1和1之间、然后将该值从minimumZoom转换为maximumZoom。
* 允许你设置适当的最小/最大值,然后使用更直观的-1到1的映射来更改缩放
* @param value
*/
public set zoom(value: number) {
this.setZoom(value);
}
/**
* 相机变焦可以达到的最小非缩放值0-number.max。默认为0.3
*/
public get minimumZoom() {
return this._minimumZoom;
}
/**
* 相机变焦可以达到的最小非缩放值0-number.max。默认为0.3
* @param value
*/
public set minimumZoom(value: number) {
this.setMinimumZoom(value);
}
/**
* 相机变焦可以达到的最大非缩放值0-number.max。默认为3
*/
public get maximumZoom() {
return this._maximumZoom;
}
/**
* 相机变焦可以达到的最大非缩放值0-number.max。默认为3
* @param value
*/
public set maximumZoom(value: number) {
this.setMaximumZoom(value);
}
/**
* 相机的世界-空间边界
*/
public get bounds(){
if (this._areMatrixedDirty)
this.updateMatrixes();
if (this._areBoundsDirty){
// 旋转或非旋转的边界都需要左上角和右下角
let topLeft = this.screenToWorldPoint(new Vector2(this._inset.left, this._inset.top));
let bottomRight = this.screenToWorldPoint(new Vector2(Core.graphicsDevice.viewport.width - this._inset.right,
Core.graphicsDevice.viewport.height - this._inset.bottom));
if (this.entity.transform.rotation != 0){
// 特别注意旋转的边界。我们需要找到绝对的最小/最大值并从中创建边界
let topRight = this.screenToWorldPoint(new Vector2(Core.graphicsDevice.viewport.width - this._inset.right,
this._inset.top));
let bottomLeft = this.screenToWorldPoint(new Vector2(this._inset.left,
Core.graphicsDevice.viewport.height - this._inset.bottom));
let minX = Math.min(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let maxX = Math.max(topLeft.x, bottomRight.x, topRight.x, bottomLeft.x);
let minY = Math.min(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
let maxY = Math.max(topLeft.y, bottomRight.y, topRight.y, bottomLeft.y);
this._bounds.location = new Vector2(minX, minY);
this._bounds.width = maxX - minX;
this._bounds.height = maxY - minY;
} else {
this._bounds.location = topLeft;
this._bounds.width = bottomRight.x - topLeft.x;
this._bounds.height = bottomRight.y - topLeft.y;
}
this._areBoundsDirty = false;
}
return this._bounds;
}
/**
* 用于从世界坐标转换到屏幕
*/
public get transformMatrix(): Matrix2D {
if (this._areMatrixedDirty)
this.updateMatrixes();
return this._transformMatrix;
}
/**
* 用于从屏幕坐标到世界的转换
*/
public get inverseTransformMatrix(): Matrix2D {
if (this._areMatrixedDirty)
this.updateMatrixes();
return this._inverseTransformMatrix;
}
public get origin() {
return this._origin;
}
public set origin(value: Vector2) {
if (this._origin != value) {
this._origin = value;
this._areMatrixedDirty = true;
}
}
public _zoom;
public _minimumZoom = 0.3;
public _maximumZoom = 3;
public _bounds: Rectangle = new Rectangle();
public _inset: CameraInset = new CameraInset();
public _transformMatrix: Matrix2D = new Matrix2D().identity();
public _inverseTransformMatrix: Matrix2D = new Matrix2D().identity();
public _origin: Vector2 = Vector2.zero;
public _areMatrixedDirty: boolean = true;
public _areBoundsDirty: boolean = true;
public _isProjectionMatrixDirty = true;
/**
* 如果相机模式为cameraWindow 则会进行缓动移动
* 该值为移动速度
*/
public followLerp = 0.1;
/**
* 在cameraWindow模式下宽度/高度被用做边界框,允许在不移动相机的情况下移动
* 在lockOn模式下只使用deadZone的x/y值 你可以通过直接setCenteredDeadzone重写它来自定义deadZone
*/
public deadzone: Rectangle = new Rectangle();
/**
* 相机聚焦于屏幕中心的偏移
*/
public focusOffset: Vector2 = Vector2.zero;
/**
* 如果为true 相机位置则不会超出地图矩形0, 0, mapwidth, mapheight
*/
public mapLockEnabled: boolean = false;
/**
* 當前地圖映射的寬度和高度
*/
public mapSize: Vector2 = Vector2.zero;
public _targetEntity: Entity;
public _targetCollider: Collider;
public _desiredPositionDelta: Vector2 = new Vector2();
public _cameraStyle: CameraStyle;
public _worldSpaceDeadZone: Rectangle = new Rectangle();
constructor(targetEntity: Entity = null, cameraStyle: CameraStyle = CameraStyle.lockOn) {
super();
this._targetEntity = targetEntity;
this._cameraStyle = cameraStyle;
this.setZoom(0);
}
/**
* 当场景渲染目标的大小发生变化时,我们会更新相机的原点并调整它的位置以保持它原来的位置
* @param newWidth
* @param newHeight
*/
public onSceneSizeChanged(newWidth: number, newHeight: number) {
let oldOrigin = this._origin;
this.origin = new Vector2(newWidth / 2, newHeight / 2);
this.entity.transform.position = Vector2.add(this.entity.transform.position, Vector2.subtract(this._origin, oldOrigin));
}
protected updateMatrixes(){
if (!this._areMatrixedDirty)
return;
let tempMat: Matrix2D;
this._transformMatrix = Matrix2D.create().translate(-this.entity.transform.position.x, -this.entity.transform.position.y);
if (this._zoom != 1){
tempMat = Matrix2D.create().scale(this._zoom, this._zoom);
this._transformMatrix = this._transformMatrix.multiply(tempMat);
}
if (this.entity.transform.rotation != 0){
tempMat = Matrix2D.create().rotate(this.entity.transform.rotation);
this._transformMatrix = this._transformMatrix.multiply(tempMat);
}
tempMat = Matrix2D.create().translate(this._origin.x, this._origin.y);
this._transformMatrix =this._transformMatrix.multiply(tempMat);
this._inverseTransformMatrix = this._transformMatrix.invert();
// 无论何时矩阵改变边界都是无效的
this._areBoundsDirty = true;
this._areMatrixedDirty = false;
}
/**
* 设置用于从视口边缘插入摄像机边界的量
* @param left
* @param right
* @param top
* @param bottom
*/
public setInset(left: number, right: number, top: number, bottom: number): Camera {
this._inset = new CameraInset();
this._inset.left = left;
this._inset.right = right;
this._inset.top = top;
this._inset.bottom = bottom;
this._areBoundsDirty = true;
return this;
}
/**
* 对entity.transform.setPosition快速访问
* @param position
*/
public setPosition(position: Vector2) {
this.entity.transform.setPosition(position.x, position.y);
return this;
}
/**
* 对entity.transform.setRotation的快速访问
* @param rotation
*/
public setRotation(rotation: number): Camera {
this.entity.transform.setRotation(rotation);
return this;
}
/**
* 设置缩放值,缩放值应该在-1到1之间。然后将该值从minimumZoom转换为maximumZoom
* 允许您设置适当的最小/最大值。使用更直观的-1到1的映射来更改缩放
* @param zoom
*/
public setZoom(zoom: number): Camera {
let newZoom = MathHelper.clamp(zoom, -1, 1);
if (newZoom == 0) {
this._zoom = 1;
} 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);
}
this._areMatrixedDirty = true;
return this;
}
/**
* 相机变焦可以达到的最小非缩放值0-number.max 默认为0.3
* @param minZoom
*/
public setMinimumZoom(minZoom: number): Camera {
if (minZoom <= 0) {
console.error("minimumZoom must be greater than zero");
return;
}
if (this._zoom < minZoom)
this._zoom = this.minimumZoom;
this._minimumZoom = minZoom;
return this;
}
/**
* 相机变焦可以达到的最大非缩放值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 onEntityTransformChanged(comp: transform.Component) {
this._areMatrixedDirty = true;
}
public zoomIn(deltaZoom: number) {
this.zoom += deltaZoom;
}
public zoomOut(deltaZoom: number) {
this.zoom -= deltaZoom;
}
/**
* 将一个点从世界坐标转换到屏幕
* @param worldPosition
*/
public worldToScreenPoint(worldPosition: Vector2): Vector2{
this.updateMatrixes();
worldPosition = Vector2.transform(worldPosition, this._transformMatrix);
return worldPosition;
}
/**
* 将点从屏幕坐标转换为世界坐标
* @param screenPosition
*/
public screenToWorldPoint(screenPosition: Vector2): Vector2{
this.updateMatrixes();
screenPosition = Vector2.transform(screenPosition, this._inverseTransformMatrix);
return screenPosition;
}
/**
* 返回鼠标在世界空间中的位置
*/
public mouseToWorldPoint(): Vector2{
return this.screenToWorldPoint(Input.touchPosition);
}
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 * Core.scene.scaleX + this.deadzone.x + this.focusOffset.x;
this._worldSpaceDeadZone.y = this.position.y - halfScreen.y * Core.scene.scaleY + this.deadzone.y + this.focusOffset.y;
this._worldSpaceDeadZone.width = this.deadzone.width;
this._worldSpaceDeadZone.height = this.deadzone.height;
if (this._targetEntity)
this.updateFollow();
this.position = Vector2.lerp(this.position, Vector2.add(this.position, this._desiredPositionDelta), this.followLerp);
this.entity.transform.roundPosition();
if (this.mapLockEnabled) {
this.position = this.clampToMapSize(this.position);
this.entity.transform.roundPosition();
}
}
/**
* 固定相机 永远不会离开地图的可见区域
* @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);
}
public updateFollow() {
this._desiredPositionDelta.x = this._desiredPositionDelta.y = 0;
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)
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)
this._desiredPositionDelta.y = targetY - this._worldSpaceDeadZone.y;
} else {
if (!this._targetCollider) {
this._targetCollider = this._targetEntity.getComponent<Collider>(Collider);
if (!this._targetCollider)
return;
}
let targetBounds = this._targetEntity.getComponent<Collider>(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)
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)
this._desiredPositionDelta.y = targetBounds.top - this._worldSpaceDeadZone.top;
}
}
}
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);
}
}
}