#28 新增tiledMapRenderer用于渲染tiledmap

This commit is contained in:
yhh
2020-08-12 18:08:12 +08:00
parent 167ef03df6
commit e5805960e0
14 changed files with 1402 additions and 2 deletions

View File

@@ -28,6 +28,20 @@ module es {
this.setHeight(value);
}
/**
* 创建一个BoxCollider并使用x/y组件作为localOffset
* @param x
* @param y
* @param width
* @param height
*/
public createBoxRect(x: number, y: number, width: number, height: number): BoxCollider{
this._localOffset = new Vector2(x + width / 2, y + width / 2);
this.shape = new Box(width, height);
this._colliderRequiresAutoSizing = false;
return this;
}
/**
* 设置BoxCollider的大小
* @param width

View File

@@ -0,0 +1,135 @@
module es {
export class TiledMapRenderer extends RenderableComponent{
public tiledMap: TmxMap;
public physicsLayer: number = 1 << 0;
/**
* 如果空,所有层将被渲染
*/
public layerIndicesToRender: number[];
public get width(){
return this.tiledMap.width * this.tiledMap.tileWidth;
}
public get height(){
return this.tiledMap.height * this.tiledMap.tileHeight;
}
public collisionLayer: TmxLayer;
public _shouldCreateColliders: boolean;
public _colliders: Collider[];
constructor(tiledMap: TmxMap, collisionLayerName: string = null, shouldCreateColliders: boolean = true){
super();
this.tiledMap = tiledMap;
this._shouldCreateColliders = shouldCreateColliders;
if (collisionLayerName){
this.collisionLayer = tiledMap.tileLayers.get(collisionLayerName);
}
}
/**
* 将此组件设置为只渲染单层
* @param layerName
*/
public setLayerToRender(layerName: string){
this.layerIndicesToRender = [];
this.layerIndicesToRender[0] = this.getLayerIndex(layerName);
}
/**
* 设置该组件应该按名称呈现哪些层。如果你知道索引你可以直接设置layerIndicesToRender
* @param layerNames
*/
public setLayersToRender(...layerNames: string[]){
this.layerIndicesToRender = [];
for (let i = 0; i < layerNames.length; i ++)
this.layerIndicesToRender[i] = this.getLayerIndex(layerNames[i]);
}
private getLayerIndex(layerName: string){
let index = 0;
let layerType = this.tiledMap.getLayer(layerName);
for (let layer in this.tiledMap.layers){
if (this.tiledMap.layers.hasOwnProperty(layer) &&
this.tiledMap.layers.get(layer) == layerType){
return index;
}
}
return -1;
}
public getRowAtWorldPosition(yPos: number): number {
yPos -= this.entity.transform.position.y + this._localOffset.y;
return this.tiledMap.worldToTilePositionY(yPos);
}
public getColumnAtWorldPosition(xPos: number): number{
xPos -= this.entity.transform.position.x + this._localOffset.x;
return this.tiledMap.worldToTilePositionX(xPos);
}
public onEntityTransformChanged(comp: transform.Component) {
// 这里我们只处理位置变化。平铺地图不能缩放
if (this._shouldCreateColliders && comp == transform.Component.position){
this.removeColliders();
this.addColliders();
}
}
public onAddedToEntity() {
this.addColliders();
}
public onRemovedFromEntity() {
this.removeColliders();
}
public update() {
this.tiledMap.update();
}
public render(camera: es.Camera) {
if (!this.layerIndicesToRender){
TiledRendering.renderMap(this.tiledMap, Vector2.add(this.entity.transform.position, this._localOffset),
this.transform.scale, this.renderLayer);
}else{
for (let i = 0; i < this.tiledMap.layers.size; i ++){
if (this.tiledMap.layers.get(i.toString()).visible && this.layerIndicesToRender.contains(i))
TiledRendering.renderLayer(this.tiledMap.layers.get(i.toString()), Vector2.add(this.entity.transform.position, this._localOffset),
this.transform.scale, this.renderLayer);
}
}
}
public addColliders(){
if (!this.collisionLayer || !this._shouldCreateColliders)
return;
// 获取冲突层及其冲突的矩形
let collisionRects = this.collisionLayer.getCollisionRectangles();
// 为我们收到的矩形创建碰撞器
this._colliders = [];
for (let i = 0; i < collisionRects.length; i ++){
let collider = new BoxCollider().createBoxRect(collisionRects[i].x + this._localOffset.x,
collisionRects[i].y + this._localOffset.y, collisionRects[i].width, collisionRects[i].height);
collider.physicsLayer = this.physicsLayer;
collider.entity = this.entity;
this._colliders[i] = collider;
Physics.addCollider(collider);
}
}
public removeColliders(){
if (this._colliders == null)
return;
for (let collider of this._colliders){
Physics.removeCollider(collider);
}
this._colliders = null;
}
}
}

View File

@@ -3,6 +3,10 @@ module es {
public static readonly Epsilon: number = 0.00001;
public static readonly Rad2Deg = 57.29578;
public static readonly Deg2Rad = 0.0174532924;
/**
* 表示pi除以2的值(1.57079637)
*/
public static readonly PiOver2 = Math.PI / 2;
/**
* 将弧度转换成角度。

View File

@@ -33,6 +33,81 @@ module es {
return null;
}
/**
* 获取x/y坐标处的TmxLayerTile。注意这些是平铺坐标而不是世界坐标!
* @param x
* @param y
*/
public getTile(x: number, y: number): TmxLayerTile {
return this.tiles[x + y * this.width];
}
/**
* 返回平铺空间中的矩形列表,其中任何非空平铺组合为边界区域
*/
public getCollisionRectangles(): Rectangle[] {
let checkedIndexes = [];
let rectangles = [];
let startCol = -1;
let index = -1;
for (let y = 0; y < this.map.height; y ++){
for (let x = 0; x< this.map.width; x ++){
index = y * this.map.width + x;
let tile = this.getTile(x, y);
if (tile && !checkedIndexes[index]){
if (startCol < 0)
startCol = x;
checkedIndexes[index] = true;
}else if(tile || checkedIndexes[index]){
if (startCol >= 0){
rectangles.push(this.findBoundsRect(startCol, x, y, checkedIndexes));
startCol = -1;
}
}
}
if (startCol >= 0){
rectangles.push(this.findBoundsRect(startCol, this.map.width, y, checkedIndexes));
startCol = -1;
}
}
return rectangles;
}
/**
* 在startX和endX之间的tile周围找到最大的边界矩形从startY开始尽可能向下
* @param startX
* @param endX
* @param startY
* @param checkedIndexes
*/
public findBoundsRect(startX: number, endX: number, startY: number, checkedIndexes?: boolean[]){
let index = -1;
for (let y = startY + 1; y < this.map.height; y ++){
for (let x = startX; x < endX; x ++){
index = y * this.map.width + x;
let tile = this.getTile(x, y);
if (tile || checkedIndexes[index]){
// 再次将我们到目前为止在这一行中访问过的所有内容设置为false因为它不会包含在矩形中应该再次进行检查
for (let _x = startX; _x < x; _x++){
index = y * this.map.width + _x;
checkedIndexes[index] = false;
}
return new Rectangle(startX * this.map.tileWidth, startY * this.map.tileHeight,
(endX - startX) * this.map.tileWidth, (y - startY) * this.map.tileHeight);
}
checkedIndexes[index] = true;
}
}
return new Rectangle(startX * this.map.tileWidth, startY * this.map.tileHeight,
(endX - startX) * this.map.tileWidth, (this.map.height - startY) * this.map.tileHeight);
}
}
export class TmxLayerTile {

View File

@@ -67,6 +67,38 @@ module es {
console.error(`tile gid${gid}未在任何tileset中找到`);
}
/**
* 转换从世界平铺位置获取tilemap边界
* @param x
* @param clampToTilemapBounds
*/
public worldToTilePositionX(x: number, clampToTilemapBounds = true){
let tileX = Math.floor(x / this.tileWidth);
if (!clampToTilemapBounds)
return tileX;
return MathHelper.clamp(tileX, 0, this.width - 1);
}
/**
* 转换从世界平铺位置获取tilemap边界
* @param y
* @param clampToTilemapBounds
*/
public worldToTilePositionY(y: number, clampToTilemapBounds = true){
let tileY = Math.floor(y / this.tileHeight);
if (!clampToTilemapBounds)
return tileY;
return MathHelper.clamp(tileY, 0, this.height - 1);
}
/**
* 按名称获取平铺层
* @param name
*/
public getLayer(name: string): ITmxLayer {
return this.layers.get(name);
}
/**
* 更新他们的动画tile
*/

View File

@@ -25,6 +25,8 @@ module es {
public tile: TmxLayerTile;
public visible: boolean;
public text: TmxText;
public points: Vector2[];
public properties: Map<string, string>;
}
export class TmxText {

View File

@@ -0,0 +1,162 @@
module es {
export class TiledRendering {
public static renderMap(map: TmxMap, position: Vector2, scale: Vector2, layerDepth: number) {
map.layers.forEach(layer => {
if (layer instanceof TmxLayer && layer.visible) {
this.renderLayer(layer, position, scale, layerDepth);
} else if (layer instanceof TmxImageLayer && layer.visible) {
this.renderImageLayer(layer, position, scale, layerDepth);
} else if (layer instanceof TmxGroup && layer.visible) {
this.renderGroup(layer, position, scale, layerDepth);
} else if (layer instanceof TmxObjectGroup && layer.visible) {
this.renderObjectGroup(layer, position, scale, layerDepth);
}
});
}
public static renderLayer(layer: TmxLayer, position: Vector2, scale: Vector2, layerDepth: number) {
if (!layer.visible)
return;
let tileWidth = layer.map.tileWidth * scale.x;
let tileHeight = layer.map.tileHeight * scale.y;
let color = new Color(0, 0, 0, layer.opacity * 255);
for (let i = 0; i < layer.tiles.length; i ++){
let tile = layer.tiles[i];
if (!tile)
continue;
this.renderTile(tile, position, scale, tileWidth, tileHeight, color, layerDepth);
}
}
public static renderImageLayer(layer: TmxImageLayer, position: Vector2, scale: Vector2, layerDepth: number) {
if (!layer.visible)
return;
let color = new Color(0, 0, 0, layer.opacity * 255);
let pos = Vector2.add(position, new Vector2(layer.offsetX, layer.offsetY).multiply(scale));
// TODO: draw
}
public static renderObjectGroup(objGroup: TmxObjectGroup, position: Vector2, scale: Vector2, layerDepth: number) {
if (!objGroup.visible)
return;
for (let object in objGroup.objects) {
let obj = objGroup.objects.get(object);
if (!obj.visible)
continue;
// TODO: debug draw
let pos = Vector2.add(position, new Vector2(obj.x, obj.y).multiply(scale));
switch (obj.objectType) {
case TmxObjectType.basic:
// TODO: draw
break;
case TmxObjectType.point:
let size = objGroup.map.tileWidth * 0.5;
pos.x -= size * 0.5;
pos.y -= size * 0.5;
// TODO: draw
break;
case TmxObjectType.tile:
let tileset = objGroup.map.getTilesetForTileGid(obj.tile.gid);
let sourceRect = tileset.tileRegions[obj.tile.gid];
pos.y -= obj.tile.tilesetTile.image.height;
// TODO: draw
break;
case TmxObjectType.ellipse:
pos = new Vector2(obj.x + obj.width * 0.5, obj.y + obj.height * 0.5).multiply(scale);
// TODO: draw
break;
case TmxObjectType.polygon:
case TmxObjectType.polyline:
let points = [];
for (let i = 0; i < obj.points.length; i++)
points[i] = Vector2.multiply(obj.points[i], scale);
// TODO: draw
break;
case TmxObjectType.text:
// TODO: draw
break;
default:
// TODO: debug draw
break;
}
}
}
public static renderGroup(group: TmxGroup, position: Vector2, scale: Vector2, layerDepth: number) {
if (!group.visible)
return;
group.layers.forEach(layer => {
if (layer instanceof TmxGroup) {
this.renderGroup(layer, position, scale, layerDepth);
}
if (layer instanceof TmxObjectGroup) {
this.renderObjectGroup(layer, position, scale,layerDepth);
}
if (layer instanceof TmxLayer){
this.renderLayer(layer, position, scale, layerDepth);
}
if (layer instanceof TmxImageLayer){
this.renderImageLayer(layer, position, scale, layerDepth);
}
});
}
public static renderTile(tile: TmxLayerTile, position: Vector2, scale: Vector2, tileWidth: number, tileHeight: number, color: Color, layerDepth: number) {
let gid = tile.gid;
// 动画tiles(以及来自图像tile的tiles)将位于Tileset本身内位于单独的TmxTilesetTile对象中不要与我们在此循环中处理的TmxLayerTiles混淆
let tilesetTile = tile.tilesetTile;
if (tilesetTile && tilesetTile.animationFrames.length > 0)
gid = tilesetTile.currentAnimationFrameGid;
let sourceRect = tile.tileset.tileRegions.get(gid);
// 对于y位置我们需要考虑瓦片是否大于瓦片的高度和移位。
// tiled使用左下角的坐标系统而egret则使用左上角的坐标系统
let tx = tile.x * tileWidth;
let ty = tile.y * tileHeight;
let rotation = 0;
if (tile.diagonalFlip) {
if (tile.horizontalFlip && tile.verticalFlip) {
rotation = MathHelper.PiOver2;
tx += tileHeight + (sourceRect.height * scale.y - tileHeight);
ty -= (sourceRect.width * scale.x - tileWidth);
} else if (tile.horizontalFlip) {
rotation = -MathHelper.PiOver2;
ty += tileHeight;
} else if (tile.verticalFlip) {
rotation = MathHelper.PiOver2;
tx += tileWidth + (sourceRect.height * scale.y - tileHeight);
ty += (tileWidth - sourceRect.width * scale.x);
} else {
rotation = -MathHelper.PiOver2;
ty += tileHeight;
}
}
// 如果我们没有旋转(对角线翻转)移动y轴
if (rotation == 0)
ty += (tileHeight - sourceRect.height * scale.y);
let pos = new Vector2(tx, ty).add(position);
if (tile.tileset.image) {
// TODO: draw
} else {
// TODO: draw
}
}
}
}

68
source/src/Utils/Color.ts Normal file
View File

@@ -0,0 +1,68 @@
module es {
export class Color {
/**
* 存储为RGBA
*/
private _packedValue: number;
/**
* 从代表红、绿、蓝和alpha值的标量构造RGBA颜色。
*/
constructor(r: number, g: number,b: number, alpha: number){
if (((r | g |b | alpha) & 0xFFFFFF00) != 0){
let clampedR = MathHelper.clamp(r, 0, 255);
let clampedG = MathHelper.clamp(g, 0, 255);
let clampedB = MathHelper.clamp(b, 0, 255);
let clampedA = MathHelper.clamp(alpha, 0, 255);
this._packedValue = (clampedA << 24) | (clampedB << 16) | (clampedG << 8) | (clampedR);
}else{
this._packedValue = (alpha << 24 ) | (b << 16) | (g << 8) | r;
}
}
public get b(){
return this._packedValue >> 16;
}
public set b(value: number){
this._packedValue = (this._packedValue & 0xff00ffff) | (value << 16);
}
public get g(){
return this._packedValue >> 8;
}
public set g(value: number){
this._packedValue = (this._packedValue & 0xffff00ff) | (value << 8)
}
public get r(){
return this._packedValue;
}
public set r(value: number){
this._packedValue = (this._packedValue & 0xffffff00) | value;
}
public get a(){
return this._packedValue >> 24;
}
public set a(value: number){
this._packedValue = (this._packedValue & 0x00ffffff) | (value << 24);
}
public get packedValue(){
return this._packedValue;
}
public set packedValue(value: number){
this._packedValue = value;
}
public equals(other: Color):boolean{
return this._packedValue == other._packedValue;
}
}
}