LayaAir能做RPG吗?不要问我能不能,因为我已经在做 - 杀意来袭

hi.我是zom,非laya的官方人员,之所以写这篇帖子多少有点自己这长时间对制作demo过程中的磕碰一次总结,也跟玉笛冰箫说了是不是写一篇帖子介绍下自己在使用layaair中遇到的坑能让看到的人有些许启发,也是来感谢这一段时间冰萧,小松,梦佳不厌其烦的来处理我提出的问题和解答,就这样,听着布衣乐队的三峰,泡上一杯红茶,写下了laya的故事。
工具及涉及:layaide+typescript+pomelo+tiledmap+其他工具
项目类型:mmorpg
人员:zom(客户端) + arrow(服务端)
进度:单机体验状态,实现 摇杆,切图,释放技能(由于人员和精力有限还需要处理公司正常工作,网络版进度暂时搁置在)
截图:
aaaa.jpg

云盘链接:杀意来袭 密码:asau

正文【不分功能和吐槽顺序,写随心生】:
1.mouseEnabled
sprite我在原地画了个圆为什么只有右下区域能点击,明明设置了mouseEnabled=true,设置了size了呢?虽然官方的文档说明里有介绍,真心很多不细看的人压根不知道,也包括我(!嘻嘻)
var a: Laya.Sprite = new Laya.Sprite();
a.graphics.drawCircle(0, 0, 50, "#ff0000");
Laya.stage.addChild(a);
a.size(100, 100);
a.pos(200, 200);
a.mouseEnabled = true;
a.mouseThrough = true;
a.on(Laya.Event.MOUSE_DOWN, null, function (e: Laya.Event): void {
    console.log("touch a");
}
因为需要将mouseThrough=true才可以,蛋疼的api描叙你能相信是这个熟悉么?
“mouseThrough : Boolean = false,指定当mouseEnabled=true时,是否可穿透。默认值为false,如果设置为true,则点击空白区域可以穿透过去。”

2.Laya.Handler
我在处理技能的时候用了对象池,其中类有对handler的持有,再次使用时发现数据不对了,查找很久才发现handler调用了recover,被对象池再次生成的对象引用覆盖了如:
        var a:Laya.Handler = Laya.Handler.create(null,testhandler,["a"]);
        console.log("a1:",a);
        a.recover();
        console.log("a2:",a);
        var b:Laya.Handler = Laya.Handler.create(null,testhandler,["b"]);
        console.log("b:",b);
        console.log("a3:",a);
        a.run();
        
        日志:
        a1: Handler {once: true, _id: 6, caller: null, args: Array[1]}
        a2: Handler {once: true, _id: 0, caller: null, method: null, args: null}
        b: Handler {once: true, _id: 7, caller: null, args: Array[1]}
        a3: Handler {once: true, _id: 7, caller: null, args: Array[1]}
        testhandler: b
发现没,a被回收了,生成b后,a再次执行时执行的是b,因为Handler内部是对象池逻辑,回收后会再次使用。
所以在使用Laya.Handler时避免持有引用,引用后也尽量回收后置空处理,如需要使用回调逻辑可以使用方法并bind域:如callback.bind(作用域)来实现。【感谢冰萧的指点,还给我写例子熟悉用法】

3.byte
相信大多数aser都习惯了读取byte直接读,发现外部生成的二进制文件加载到laya里面读取的时候怎么都是错位的
,原因很简单,源于bytes.endian,laya的bytes的字节顺序默认是的低位读取,而大部分语言默认的是高位读取,api描述里面毫无描述。我已经不止一次的吐槽api了,解决方案bytes.endian = Laya.Byte.BIG_ENDIAN;设置为高位即可。

4.借用下any
ts里面类的你会发现classB明明继承了classA或者实现了接口IA,但是你在使用classB做处理使会出现报错,这个时候借用下any吧,如:
classA implements IA{

}
classB extends classA{

}

var items:IA = ;
function(item:classB):void {
 //items.push(item);//报错,提示类型错误
 //方案一
 items.push(<any>item);
 //方案二
 var temp:IA = <IA>item;
 items.push(temp);
}
5.get,set
ts里面父类定义的getset,子类必须要全覆盖下,不能只处理get或set,如:
class A {
 protected _id:number = 1;
 
 public get id():number { return this._id }
 public set id(value:number){
    this._id = vaule;
 }
}
class B extends A {
  public set id(value:number){
    this._id = value + 1;
  }
}
class C extends A {
 public get id():number { return this._id }
 public set id(value:number){
    this._id = vaule + 1;
}

var a:A = new A();
var b:B = new B();
var c:C = new C();
a.id = 1;
b.id = 1;
c.id = 1;
console.log("a:",a.id);//a:1
console.log("b:",b.id);//b:undefind
console.log("c:",c.id);//c:2
}
是不是发现b只处理了set后无法获取id的值。

5.pomelo
说实话,框架真心不错,客户端也简单,具体介绍看github上的介绍吧,https://github.com/NetEase/pomelo,谈到这个就顺便讲讲如何使用第三方库吧,以pomeloClient.js为例
a.可在网上找对应的库及对应的PomeloClient.d.ts文件。
b.将PomeloClient.d.ts文件放在工程目录下的libs下,和LayaAir.d.ts同一级目录
c.将pomeloClient.js文件放在工程目录的bin/libs/下
d.打开bin/index.html,添加script指定路径
<!-- pemolo -->
<script type="text/javascript" src="libs/PomeloClient.js"></script>
这样就可以了,便于使用和习惯,个人封装了pomloClient如:
 /** 基于pomelo网络握手通讯*/
    export class Socket extends Laya.EventDispatcher {

        private pomelo: Pomelo;
        private mDecorate:Decorate;//装饰器
        private mHost: string;
        private mPort: number;
        private mConnected: boolean;

        /** 开启*/
        public static OPEN: string = "open";
        /** 关闭*/
        public static CLOSE: string = "close";
        /** io错误*/
        public static IO_ERROR: string = "io-error";
        /** 数据*/
        public static DATA: string = "data";

        constructor(decorate?:Decorate) {
            super();
            this.mDecorate = decorate || new Decorate(this);
            this.pomelo = new Pomelo();
            this.configuration();
        }

        /** 配置*/
        private configuration(): void {
            var caller: any = this;
            this.pomelo.on("close", pomeloClose);
            this.pomelo.on("io-error", pomeloIoError);

            //关闭
            function pomeloClose(response: any): void {
                console.warn("pomelo-close:", response);
                if(!caller || !caller.pomelo) return;
                caller.mConnected = false;
                caller.event(Socket.CLOSE, response);
            }
            //ioerror
            function pomeloIoError(response: any): void {
                console.error("pomelo-ioError:", response);
                if(!caller || !caller.pomelo) return;
                caller.event(Socket.IO_ERROR, response);
            }
        }

        /** 获取是否已连接*/
        public get connected(): boolean { return this.mConnected }

        /** 获取装饰器*/
        public get decorate():Decorate { return this.mDecorate }

        /** 获取pomelo*/
        public get _$pomelo():Pomelo { return this.pomelo }


        /** 
         * 连接远程
         * @params host主机
         * @params port端口
        */
        public connect(host: string, port: number): void {
            if (this.connected) {
                console.warn("socket had connected, please try to connect again after colse");
                return;
            }
            if(this.mHost == host && this.mPort == port){
                console.warn("host&port conflict, socket had connected host&port");
                return;
            }
            this.mHost = host;
            this.mPort = port;
            var caller: any = this;
            this.pomelo.init({ host: this.mHost, port: this.mPort }, function (response: any): void {
                console.log("pomelo init:", response);
                if (response.code === 200) {
                    caller.mConnected = true;
                    caller.event(Socket.OPEN);
                }
            });
        }

        /** 
         * 请求响应 服务端会回执协议响应,socket收到响应并派发socket.data事件
         * @params protocol协议名称
         * @params data 参数【和服务端针对协议约定】
        */
        public request(protocol: string, data: any = null): void {
            if (!this.pomelo) return;
            if(!this.mConnected){
                console.warn("please try to request again after connected");
                return;
            } 
            var caller: any = this;
            this.pomelo.request(protocol, data, function (response: any): void {
                console.log("request - response :", protocol,response);
                caller.event(Socket.DATA, { protocol: protocol, response: response });
            });
        }

        /** 
        * 通知
        * @params protocol协议名称
        * @params data 参数【和服务端针对协议约定】
       */
        public notify(protocol: string, data: any = null): void {
            if (!this.pomelo) return;
            if(!this.mConnected){
                console.warn("please try to notify again after connected");
                return;
            } 
            this.pomelo.notify(protocol, data);
        }

        /** 关闭*/
        public close(): void {
            if (this.pomelo && this.mConnected) this.pomelo.disconnect();
        }

        /** 销毁*/
        public destroy(): void {
            this.close();
            this.pomelo = null;
            if(this.mDecorate)this.mDecorate.destroy();
            this.mDecorate = null;
        }
    }
  /** socket装饰器
     * 处理服务器通知的信息
    */
    export class Decorate {

        private socket: Socket;

        private protocols: Laya.Dictionary;

        constructor(socket: Socket) {
            this.socket = socket;
            this.protocols = new Laya.Dictionary();
        }

        /** 添加*/
        public add(protocol: string): void {
            var index: number = this.protocols.indexOf(protocol);
            if (index > -1) return;
            this.protocols.set(protocol, callBack);
            if (this.socket) this.socket._$pomelo.on(protocol, this.protocols.get(protocol));
            var caller: any = this;
            function callBack(response: any): void {
                if (caller && caller.socket) caller.socket.event(Socket.DATA, { protocol: protocol, response: response });
            }
        }

        /** 移除*/
        public remove(protocol: string): void {
            var index: number = this.protocols.indexOf(protocol);
            if (index == -1) return;
            if (this.socket) this.socket._$pomelo.off(protocol, this.protocols.get(protocol));
            this.protocols.remove(protocol);
        }

        /** 清除所有*/
        public clearAll(): void {
            this.protocols.clear();
        }

        /** 销毁*/
        public destroy(): void {
            this.socket = null;
            this.protocols.clear();
        }
使用方式:
 
 var socket:Socket = new Socket();
 socket.on(Socket.OPEN, this, this._$game_open_handler);
 socket.on(Socket.CLOSE, this, this._$game_close_handler);
 socket.on(Socket.IO_ERROR, this, this._$game_io_error_handler);
 socket.on(Socket.DATA, this, this._$game_data_handler);
 
  private _$game_open_handler(): void {
    console.log("game connection success");
    //游戏服连接成功后,拿取账号的密钥请求进入游戏
  }
  
  private _$game_close_handler(response: any): void {
    console.log("game connection close:", response);
  }

  private _$game_io_error_handler(response: any): void {
    console.log("game io-error:", response);
  }

  private _$game_data_handler(response: any): void {
    //this.receive(response);
    //根据个人与服务器的约定处理协议即可
  }
可将协议注册在Decorate中便于socket.data事件的消息池处理
连接服务器socket.connect(ip,port);
发送请求消息socket.request(this.protocol,{uid:uid, playerId: playerId, areaId: areaId});服务器回执消息可通过监听事件SOCKET.DATA来获取对应的协议号内容;
发送通知消息(服务器无须回执)socket.notify(this.protocol,{});
对,借用下socket名称,是不是看上去熟悉而又简单。

6.NetConnection
as3的人应该熟悉这个http通讯了,对laya的urlRequest做的封装处理,由于urlRequest的一个实例在请求中未回执时不可重复使用,这样当需要多个请求时必须再次实例化对象,于是做对象池封装处理,其中传递的参数需要和http协商格式,我的封装仅限参考
export class NetConnection extends Laya.EventDispatcher {
        /** 地址*/
        private mUrl: string;
        /** 数据池*/
        private mItems: laya.utils.Dictionary;

        /** 请求集*/
        private static requests: URLRequest = ;
        /** 计数*/
        private static count: lamborghini.utils.Counter = new lamborghini.utils.Counter(Number.MAX_VALUE);

        constructor(url: string = "") {
            super();
            this.mUrl = url;
            this.mItems = new laya.utils.Dictionary();
        }

        /** 设置或获取远程地址*/
        public get url(): string { return this.mUrl }
        public set url(value: string) { this.mUrl = value }

        /** 
         * 调用远程的命令或方法
         * @params command 命名或方法名
         * @params handler 回执
         * @params args 传递的参数
        */
        public call(command: string, handler: Laya.Handler = null, ...args): void {
            if (this.mUrl.length == 0) {
                console.warn("netconnection url is null");
                return;
            }

            //生成请求和事件监听并放入对象池
            var request: URLRequest = NetConnection._$fromPool(command, handler);
            if (this.mItems.indexOf(request.adpter) == -1) this.mItems.set(request.adpter, request);
            this._$on(request);

            //生成固定的http消息模式
            var url: string = this.mUrl.charAt(this.mUrl.length - 1) == "/" ? this.mUrl : this.mUrl + "/";
            var data: any = new Object();
            data.command = request.command;
            data.adpter = request.adpter;
            data.params = args;
            console.log("netconnection call:", url + command, JSON.stringify(data));
            request.send(url + command, JSON.stringify(data), URLRequestMethod.POST, URLResponseType.JSON, ["Content-Type", "application/json"]);
        }

        private completeHandler(e: any): void {
            console.log("netconnection complete:", e);
            if (e) {
                var adpter: number = e.adpter;
                var request: URLRequest = this.mItems.get(adpter);
                if (request) {
                    if (request.handler) request.handler.runWith(e);
                    this.mItems.remove(adpter);
                    this._$off(request);
                    NetConnection._$toPool(request);
                }
                this.event(Laya.Event.COMPLETE, e);
            }
        }

        private progressHandler(e: any): void {
            console.log("netconnection progress:", e);
            this.event(Laya.Event.PROGRESS, e);
        }

        private errorHandler(e: any): void {
            console.log("netconnection error:", e);
            this.event(Laya.Event.ERROR, e);
        }

        /** 添加监听*/
        private _$on(request: URLRequest): void {
            if (!request) return;
            request.once(Laya.Event.COMPLETE, this, this.completeHandler);
            request.once(Laya.Event.PROGRESS, this, this.progressHandler);
            request.once(Laya.Event.ERROR, this, this.errorHandler);
        }

        /** 移除监听*/
        private _$off(request: URLRequest): void {
            if (!request) return;
            request.off(Laya.Event.COMPLETE, this, this.completeHandler);
            request.off(Laya.Event.PROGRESS, this, this.progressHandler);
            request.off(Laya.Event.ERROR, this, this.errorHandler);
        }

        public destroy(): void {
            if (this.mItems) {
                var items: any = this.mItems.values;
                for (var index = 0; index < items.length; index++) {
                    var element: URLRequest = items[index];
                    this._$off(element);
                    NetConnection._$toPool(element);
                }
                this.mItems.clear();
                this.mItems = null;
            }
        }

        /** 从对象池获取*/
        private static _$fromPool(command: string, handler: Laya.Handler = null): URLRequest {
            var request: URLRequest = NetConnection.requests.length ? NetConnection.requests.pop() : new URLRequest();
            request.command = command;
            request.handler = handler;
            request.adpter = NetConnection.count.count;
            NetConnection.count.next();
            return request;
        }

        /** 放入对象池*/
        private static _$toPool(request: URLRequest): void {
            request.destroy();
            NetConnection.requests.push(request);
        }
    }
使用方法也很简单
netConnection.call("login", Laya.handler.create(null,function():void{}), name, password);//向http请求登录。

7.tiledmap
laya吐槽的东西很多,但tiledmap已经是我不想在吐槽的东西了,完全不适合rpg的地图处理,所以我用as的源码改写成了ts,并做相关修改和优化
a.45°角地图非正菱形会导致计算的格子点坐标错误
tiledmap.png

菱形ABEF是正菱形,而我的地图大小只有ABCD的大小,那么问题来了,
来看下laya的TiledMap类(as版源码),在加载配置完成后获取地图宽的逻辑onJsonComplete方法中的处理是
/**
         * json文件读取成功后,解析里面的纹理数据,进行加载
         * @param    e JSON数据
         */
        private function onJsonComplete(e:*):void {
            _mapSprite = new Sprite();
            Laya.stage.addChild(_mapSprite);
            var tJsonData:* = _jsonData = e;
            
            _properties = tJsonData.properties;
            _orientation = tJsonData.orientation;
            _renderOrder = tJsonData.renderorder;
            _mapW = tJsonData.width;
            _mapH = tJsonData.height;
            
            _mapTileW = tJsonData.tilewidth;
            _mapTileH = tJsonData.tileheight;
            
            _width = _mapTileW * _mapW;
            _height = _mapTileH * _mapH;
            
            .
            .
            .
        }
_width = _mapTileW * _mapW;
_height = _mapTileH * _mapH;
width的逻辑是格子的横向数量*单元格的宽度,我能说这个算的是菱形ABEF中BF的长度么,而我的实际地图的宽应该是BG段的长度,难道不是么?并且细心的同学会发现在maplayer计算坐标的位置都会用这个width/2方式计算格子和屏幕坐标如方法
/**
         * 通过地图坐标得到屏幕坐标
         * @param    tileX 格子坐标X
         * @param    tileY 格子坐标Y
         * @param    screenPos 把计算好的屏幕坐标数据,放到此对象中
         */
        public function getScreenPositionByTilePos(tileX:Number, tileY:Number, screenPos:Point = null):void {
            if (screenPos) {
                switch (_map.orientation) {
                case TiledMap.ORIENTATION_ISOMETRIC: 
                    screenPos.x = _map.width / 2 - (tileY - tileX) * _tileWidthHalf;
                    screenPos.y = (tileY + tileX) * _tileHeightHalf;
                    break;
                case TiledMap.ORIENTATION_STAGGERED:
                    tileX = Math.floor(tileX);
                    tileY = Math.floor(tileY);
                    screenPos.x = tileX * _map.tileWidth + (tileY & 1) * _tileWidthHalf;
                    screenPos.y = tileY * _tileHeightHalf;
                    break;
                case TiledMap.ORIENTATION_ORTHOGONAL: 
                    screenPos.x = tileX * _map.tileWidth;
                    screenPos.y = tileY * _map.tileHeight;
                    break;
                case TiledMap.ORIENTATION_HEXAGONAL: 
                    tileX = Math.floor(tileX);
                    tileY = Math.floor(tileY);
                    var tTileHeight:Number = _map.tileHeight * 2 / 3;
                    screenPos.x = (tileX * _map.tileWidth + tileY % 2 * _tileWidthHalf) % _map.gridWidth;
                    screenPos.y = (tileY * tTileHeight) % _map.gridHeight;
                    break;
                }
                //地图坐标转换成屏幕坐标
                screenPos.x = (screenPos.x + _map.viewPortX) * _map.scale;
                screenPos.y = (screenPos.y + _map.viewPortY) * _map.scale;
            }
        }
        

getScreenPositionByTilePos中screenPos.x = _map.width / 2 - (tileY - tileX) * _tileWidthHalf;其中用到了_map.width / 2,这样会导致坐标值错误。
这个使用位置很多,我不一一举例,下面我给出修改方案,修改后需要所有涉及到的地方同步修改逻辑
修改方案入下面的代码(ts版):
/**
         * json文件读取成功后,解析里面的纹理数据,进行加载
         * @param    e JSON数据
         */
        private onJsonComplete(e: any): void {

            this._mapSprite = new Laya.Sprite();
            this.addChild(this._mapSprite);
            var tJsonData: any = this._jsonData = e;

            this._orientation = tJsonData.orientation;
            this._renderOrder = tJsonData.renderorder;
            this._mapW = tJsonData.width;
            this._mapH = tJsonData.height;

            this._mapTileW = tJsonData.tilewidth;
            this._mapTileH = tJsonData.tileheight;

            
            this._h = this._mapTileH * this._mapH;

            switch (this.orientation) {
                case TiledMap.ORIENTATION_ISOMETRIC://45度角
                    //45°菱形算宽,由于单个菱形是正菱形由2个等边三角形组合,相当于单元块的高就是这个三角形的边长
                    this._w = this._mapTileH * this._mapH + this._mapTileH * this._mapW;
                    this._offsetX = this._mapTileH * this._mapH;
                break;
                case TiledMap.ORIENTATION_STAGGERED://45度交错地图
                case TiledMap.ORIENTATION_ORTHOGONAL://直角
                case TiledMap.ORIENTATION_HEXAGONAL://六边形
                    this._w = this._mapTileW * this._mapW;
                    this._offsetX = this._w / 2;
                break;
            }
            .
            .
            .
            
        }
我这里只针对45°地图出现的问题进行修改,其他图像地图暂无解答

this._w = this._mapTileH * this._mapH + this._mapTileH * this._mapW; 
this._offsetX = this._mapTileH * this._mapH;
_w :地图的实际宽度,利用正菱形2个等边三角形组合而成的原理,纵向的格子数*纵向的单元格高度就会是
AD之间的横向距离,同理计算AB段的横向距离,两者和及是整个地图的宽
_offsetX:横向偏移数(即计算格子点位置时的便宜值),也是AD之间的横向距离
还是以maplayer中的getScreenPositionByTilePos为例修正坐标如下(ts版):
    public getScreenPositionByTilePos(tileX: number, tileY: number, screenPos: Laya.Point = null): void {
            if (screenPos) {
                switch (this._map.orientation) {
                    case TiledMap.ORIENTATION_ISOMETRIC:
                        screenPos.x = this._map.offsetX - (tileY - tileX) * this._tileWidthHalf - this._map.viewPortX;
                        screenPos.y = (tileY + tileX) * this._tileHeightHalf - this._map.viewPortY;
                        break;
                        .
                        .
                }
            }
    }
这样就修正坐标ok。

b.很多人问tiledmap我怎么格子转屏幕坐标,怎么屏幕坐标转格子,api藏的太深了,如果我真的写逻辑我需要做如下操作:
tiledMap.getLayerByIndex(1).getScreenPositionByTilePos();大多人估计要了解熟悉下才知道怎么用,并且在maplayer中getScreenPositionByTilePos并没有对malpayer特定数值依赖,单纯用的tiledmap中的数值和关系,所以完全可以提取出来使用,这时添加TileUtils类。
export class TileUtils {
        /** 地图对象引用*/
        public static map: TiledMap = null;

         /**
         * 通过格子坐标得到屏幕坐标
         * @param    tileX 格子坐标X
         * @param    tileY 格子坐标Y
         * @param    screenPos 把计算好的屏幕坐标数据,放到此对象中
         */
        public static getScreenPositionByTilePos(tileX: number, tileY: number, screenPos: Laya.Point = null): void {
            if (screenPos && TileUtils.map) {
                var tTileW:number = TileUtils.map.TileWidth;
                var tTileH:number = TileUtils.map.TileHeight;
                var tTileWHalf:number = tTileW/2;
                var tTileHHalf:number = tTileH/2;
                switch (TileUtils.map.orientation) {
                    case TiledMap.ORIENTATION_ISOMETRIC:
                        screenPos.x = TileUtils.map.offsetX - (tileY - tileX) * tTileWHalf - TileUtils.map.viewPortX;
                        screenPos.y = (tileY + tileX) * tTileHHalf - TileUtils.map.viewPortY;
                        break;
                    case TiledMap.ORIENTATION_STAGGERED:
                        tileX = Math.floor(tileX);
                        tileY = Math.floor(tileY);
                        screenPos.x = tileX * TileUtils.map.TileWidth + (tileY & 1) * tTileWHalf;
                        screenPos.y = tileY * tTileHHalf;
                    break;        
                    case TiledMap.ORIENTATION_ORTHOGONAL:
                        screenPos.x = tileX * tTileW - TileUtils.map.viewPortX;
                        screenPos.y = tileY * tTileH - TileUtils.map.viewPortY;
                        break;
                    case TiledMap.ORIENTATION_HEXAGONAL:
                        tileX = Math.floor(tileX);
                        tileY = Math.floor(tileY);
                        var tTileHeight: number = tTileH * 2 / 3;
                        screenPos.x = (tileX * tTileW + tileY % 2 * tTileWHalf) % TileUtils.map.gridWidth - TileUtils.map.viewPortX;
                        screenPos.y = (tileY * tTileHeight) % TileUtils.map.gridHeight - TileUtils.map.viewPortY;
                        break;
                }
                // screenPos = TileUtils.map.localToGlobal(screenPos);
                screenPos.x = screenPos.x >> 0;
                screenPos.y = screenPos.y >> 0;
            }
        }
        
        /**
         * 通过格子坐标得到地图相对于屏幕坐标
         * @param    tileX 格子坐标X
         * @param    tileY 格子坐标Y
         * @param    relativePos 把计算好的屏幕相对坐标数据,放到此对象中
         */
        public static getRelativePositionByTilePos(tileX: number, tileY: number, relativePos: Laya.Point = null): void {
        
            if (relativePos && TileUtils.map) {
                TileUtils.getScreenPositionByTilePos(tileX,tileY,relativePos);
                relativePos.x += TileUtils.map.viewPortX;
                relativePos.y += TileUtils.map.viewPortY;
            }
        }

        /**
         * 通过屏幕坐标来获取选中格子的索引
         * @param    screenX 屏幕坐标x
         * @param    screenY 屏幕坐标y
         * @param    result 把计算好的格子坐标,放到此对象中
         * @return
         */
        public static getTilePositionByScreenPos(screenX: number, screenY: number, result: Laya.Point = null): Boolean {
            if (result && TileUtils.map) {
                screenX = screenX + TileUtils.map.viewPortX;
                screenY = screenY + TileUtils.map.viewPortY;
                var tTileW: number = TileUtils.map.TileWidth;
                var tTileH: number = TileUtils.map.TileHeight;
                var tTileWHalf: number = tTileW / 2;

                var tV: number = 0;
                var tU: number = 0;
                switch (TileUtils.map.orientation) {
                    case TiledMap.ORIENTATION_ISOMETRIC://45度角
                        var tDirX: number = screenX - TileUtils.map.width / 2;
                        var tDirY: number = screenY;
                        tV = -(tDirX / tTileW - tDirY / tTileH);
                        tU = tDirX / tTileW + tDirY / tTileH;
                        if (result) {
                            result.x = tU>>0;
                            result.y = tV>>0;
                        }
                        return true;
                    case TiledMap.ORIENTATION_STAGGERED://45度交错地图
                        if (result) {
                            var cx:number, cy:number, rx:number, ry:number;
                            cx = Math.floor(screenX / tTileW) * tTileW + tTileW / 2;        //计算出当前X所在的以tileWidth为宽的矩形的中心的X坐标
                            cy = Math.floor(screenY / tTileH) * tTileH + tTileH / 2;//计算出当前Y所在的以tileHeight为高的矩形的中心的Y坐标
                            
                            rx = (screenX - cx) * tTileH / 2;
                            ry = (screenY - cy) * tTileW / 2;
                            
                            if (Math.abs(rx) + Math.abs(ry) <= tTileW * tTileH / 4) {
                                tU = Math.floor(screenX / tTileW);
                                tV = Math.floor(screenY / tTileH) * 2;
                            } else {
                                screenX = screenX - tTileW / 2;
                                tU = Math.floor(screenX / tTileW) + 1;
                                screenY = screenY - tTileH / 2;
                                tV = Math.floor(screenY / tTileH) * 2 + 1;
                            }
                            result.x = tU - (tV & 1);
                            result.y = tV;
                        }
                        return true;        
                    case TiledMap.ORIENTATION_ORTHOGONAL://直角
                        tU = screenX / TileUtils.map.TileWidth;
                        tV = screenY / TileUtils.map.TileHeight;
                        if (result) {
                            result.x = tU>>0;
                            result.y = tV>>0;
                        }
                        return true;
                    case TiledMap.ORIENTATION_HEXAGONAL://六边形
                        var tTileHeight: number = TileUtils.map.TileHeight * 2 / 3;
                        tV = screenY / tTileHeight;
                        tU = (screenX - tV % 2 * tTileWHalf) / TileUtils.map.TileWidth;
                        if (result) {
                            result.x = tU>>0;
                            result.y = tV>>0;
                        }
                        return true;
                }
            }

            return false;
        }
        
        /**
         * 通过地图坐标得到屏幕相对中心点坐标
         * @param    tileX 格子坐标X
         * @param    tileY 格子坐标Y
         * @param    relativePos 把计算好格子的屏幕相对坐标中心点数据,放到此对象中
         */
        public static getTileCenterRelativePositionByTilePos(tileX: number, tileY: number, relativePos: Laya.Point = null):void {
            if (relativePos && TileUtils.map) {
               TileUtils.getRelativePositionByTilePos(tileX,tileY,relativePos);
               relativePos.x += TileUtils.map.TileWidth/2;
               relativePos.y += TileUtils.map.TileHeight/2;
            }
        }
        
        /**
         * 通过屏幕坐标点得到地图相对准确的坐标点
         * @param screenX 屏幕点x
         * @param screenY 屏幕点y
         * @param relativePos 把计算好的屏幕相对坐标中心点数据,放到此对象中
        */
        public static getRelativePositionByScreenPos(screenX:number,screenY:number,relativePos:Laya.Point = null):void {
            if (relativePos && TileUtils.map) {
                var p = TileUtils.map.globalToLocal(new Laya.Point(screenX,screenY));
                relativePos.x = (p.x + TileUtils.map.viewPortX) >> 0;
                relativePos.y = (p.y + TileUtils.map.viewPortY) >> 0;
            }
             console.log("^^^:",relativePos);
        }

          /**
         * 通过相对坐标点得到地图相对的格子点
         * @param relativeX 相对坐标点x
         * @param relativeY 相对坐标点y
         * @param tilePos 把计算好的格子点数据,放到此对象中
        */
        public static getTilePositionByRelativePos(relativeX:number,relativeY:number,tilePos:Laya.Point = null):void {
             if (tilePos && TileUtils.map) {
                 var screenX: number = relativeX - TileUtils.map.viewPortX, screenY: number = relativeY - TileUtils.map.viewPortY;
                 TileUtils.getTilePositionByScreenPos(screenX,screenY,tilePos);
             }
        }
    }
持有当前map的引用即可,并增加更多接口便于调用。

c.在地图层中添加自定义显示对象,并跟随地图视图改变时同步视图,实现相对地图的相对位置移动,包括object对象与地图配置显示对象层级关系(这个后面再讲解)我跟官方提过很多次,但是给我的回复我这就不说了。。。
那么脑洞打开的我自己动手吧,已经改成ts的源码还怕不能处理么,根据gridSprite的相同逻辑,实现自动定义显示对象在tiledmap中相对显示和移动:
c1.添加接口IMapUnit
 /** 地图独立单元接口*/
    export interface IMapUnit {
        /**相对于地图X轴的坐标*/
        relativeX: number;
        /**相对于地图Y轴的坐标*/
        relativeY: number;
        /** 单元唯一id*/
        id: any;
        /** 单元格id*/
        gridXY: Laya.Point;
        /** 所在图层的深度*/
        z:number;

        initData(map: TiledMap): void;

        /** 设置相对位置*/
        relativeXY(px: number, py: number): void;

        /** 地图相对更新坐标*/
        updatePos(): void;

        /** 深度排序*/
        sortZorder():void;

        clearAll(): void;

        destroy():void;
    }
c2.在maplayer中增加_unitDics,单元对象管理,并添加相关方法,tiledmap类中开放接口调用maplayer的方法
/** 添加地图单元*/
        public addUnit(unit: IMapUnit): void {
            var temp: any =  this._unitDics.get(unit.id);
            if (!temp) {
                temp = unit;
                unit.initData(this._map);
                unit.z = this.layerIndex;
                this._unitDics.set(unit.id,unit);
                if (<Laya.Sprite>temp) this.addChild(<Laya.Sprite>temp);
            }else{
                console.warn("addUnit Repeat:",unit.id);
            }
        }

        /** 移除地图单元*/
        public removeUnit(unit: IMapUnit): boolean {
            var id:any = unit.id;
            var temp: any =  this._unitDics.get(id);
            if (temp) {
                if (<Laya.Sprite>temp) this.removeChild(<Laya.Sprite>temp);
                unit.clearAll();
                this._unitDics.remove(id);
                unit = null;
                temp = null;
                return true;
            }else{
                console.warn("removeUnit undefined:",unit.id);
            }
            return false;
        }

        /** 通过id移除地图单元*/
        public removeUnitById(id: any): boolean {
            var temp: any =  this._unitDics.get(id);
            if (temp) {
                if (<Laya.Sprite>temp) this.removeChild(<Laya.Sprite>temp);
                temp.clearAll();
                temp = null;
                this._unitDics.remove(id);
                return true;
            }else{
                console.warn("removeUnitById undefined:",id);
            }
            return false;
        }

        /** 移除所有地图单元*/
        public removeAllUnit(): void {
            if(!this._unitDics)return;
            var unit: IMapUnit;
            for (var i = 0; i < this._unitDics.values.length; i++) {
                unit = this._unitDics.values;
                var temp: any = unit;
                if (<Laya.Sprite>temp) this.removeChild(<Laya.Sprite>temp);
                unit.clearAll();
                unit = null;
            }
            this._unitDics.clear();
        }

        /** 通过id获取地图单元*/
        public getUnitById(id: any): IMapUnit {
            return   this._unitDics.get(id);
        }
/** 添加地图单元*/
        public addUnit(unit:IMapUnit,layerName:string = "object"):void {
            var mapLayer:MapLayer = this.getLayer(layerName);
            if(mapLayer){
                mapLayer.addUnit(unit);
            }
        }
        /** 移除地图单元*/
        public removeUnit(unit:IMapUnit,layerName:string = "object"):boolean {
            var mapLayer:MapLayer = this.getLayer(layerName);
            if(mapLayer){
                return mapLayer.removeUnit(unit);
            }
            return false;
        }
        /** 通过id移除地图单元*/
        public removeUnitById(id:any,layerName:string = "object"):boolean {
            var mapLayer:MapLayer = this.getLayer(layerName);
            if(mapLayer){
                return mapLayer.removeUnitById(id);
            }
            return false;
        }

        /** 移除所有地图单元*/
        public removeAllUnit(layerName:string = "object"):void {
            var mapLayer:MapLayer = this.getLayer(layerName);
            if(mapLayer){
                mapLayer.removeAllUnit();
            }
        }

        /** 通过id获取地图单元*/
        public getUnitById(id:any,layerName:string = "object"):IMapUnit {
            var mapLayer:MapLayer = this.getLayer(layerName);
            if(mapLayer){
                return mapLayer.getUnitById(id);
            }
            return null;
        }
并在maplayer的updateGridPos中添加更新逻辑
    /**
         * 更新此层中块的坐标
         * 手动刷新的目的是,保持层级的宽和高保持最小,加快渲染
         */
        public updateGridPos(): void {
            var tSprite: GridSprite;
            for (var i: number = 0; i < this._gridSpriteArray.length; i++) {
                tSprite = this._gridSpriteArray[i];
                if ((tSprite.visible || tSprite.isAloneObjectKey) && tSprite.drawImageNum > 0) {
                    tSprite.updatePos();
                }
            }
            for (var j: number = 0; j < this._unitDics.values.length; j++) {
                unit = this._unitDics.values[j];
                if (unit) {
                    unit.updatePos();
                }
            }
        }

c3.MapUnit,自定义显示对象基类
 /** 地图基础元素
     * 实现 entity,effect,...等元素在地图上显示逻辑
    */
    export class MapUnit extends Laya.Sprite implements lamborghini.map.IMapUnit {
        /**相对于地图的坐标*/
        protected mRelative: Laya.Point;
        /** 单元唯一id*/
        protected mId: any;
        /** 地图引用*/
        protected mMap: lamborghini.map.TiledMap;

        /** 所在图层的深度*/
        public z: number;

        constructor() {
            super();

            this.mRelative = new Laya.Point();
        }

        public set id(value: any) {
            this.mId = value;
        }
        public get id(): any { return this.mId }

        public set relativeX(value: number) {
            this.mRelative.x = value;
            this.updatePos();
        }
        public get relativeX(): number { return this.mRelative.x }

        public set relativeY(value: number) {
            this.mRelative.y = value;
            this.updatePos();
        }
        public get relativeY(): number { return this.mRelative.y }

        protected get map(): lamborghini.map.TiledMap { return this.mMap }

        /**
         * 传入必要的参数,用于裁剪,跟确认此对象类型
         * @param    map    把地图的引用传进来,参与一些裁剪计算
         */
        public initData(map: lamborghini.map.TiledMap): void {
            this.mMap = map;
        }

        /** 
         * 相对坐标
         * @param px 地块相对x
         * @param py 地块相对y
        */
        public relativeXY(px: number, py: number): void {
            if (this.map) {
                this.mRelative.setTo(px, py);
                this.updatePos();
            } else {
                // console.warn("please frist addUnit...");
            }
        }

        /** 格子坐标点*/
        public get gridXY(): Laya.Point {
            var p: Laya.Point = new Laya.Point();
            lamborghini.map.TileUtils.getTilePositionByScreenPos(this.x, this.y, p);
            return p;
        }

        public updatePos(): void {
            if (this.map) {
                this.x = this.relativeX - this.map.viewPortX;
                this.y = this.relativeY - this.map.viewPortY;
            }
            var t: any = new Laya.Rectangle(0, 0, this.width, this.height);
            if (this.x + t.width / 2 < 0 || this.x - t.width / 2 > Laya.Browser.width || this.y < 0 || this.y - t.height > Laya.Browser.height) {
                this.visible = false;
            } else {
                this.visible = true;
                this.sortZorder();
            }
        }

        /** 深度排序*/
        public sortZorder(): void {
            var z = this.z;
            var p: Laya.Point = this.gridXY;
            var gridX = p.x;
            var gridY = p.y;
            var totalGridNum = this.map.totalGridNum;
            var maplayer = this.map.getLayerByIndex(z);
            switch (this.map.orientation) {
                case lamborghini.map.TiledMap.ORIENTATION_ISOMETRIC://45度角        
                    this.zOrder = z * totalGridNum + gridY * this.map.gridW + gridX + (this.relativeY >> 0);
                    var a1 = maplayer.getTileData(gridX + 1, gridY + 1);
                    var a2 = maplayer.getTileData(gridX, gridY + 1);
                    var a3 = maplayer.getTileData(gridX + 1, gridY);
                    var a4 = maplayer.getTileData(gridX + 2, gridY + 2);
                    // this.alpha = a1+a2+a3+a4 ? 0.5 : 1;
                    if (a1) {
                        this.alpha = 0.3;
                    } else if (a2 + a3 + a4) {
                        this.alpha = 0.7;
                    } else {
                        this.alpha = 1;
                    }
                    break;
                case lamborghini.map.TiledMap.ORIENTATION_ORTHOGONAL://直角
                case lamborghini.map.TiledMap.ORIENTATION_HEXAGONAL://六边形

                    switch (this.map.renderOrder) {
                        case lamborghini.map.TiledMap.RENDERORDER_RIGHTDOWN:
                            this.zOrder = z * totalGridNum + gridY * this.map.gridW + gridX + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_RIGHTUP:
                            this.zOrder = z * totalGridNum + (this.map.gridH - 1 - gridY) * this.map.gridW + gridX + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_LEFTDOWN:
                            this.zOrder = z * totalGridNum + gridY * this.map.gridW + (this.map.gridW - 1 - gridX) + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_LEFTUP:
                            this.zOrder = z * totalGridNum + (this.map.gridH - 1 - gridY) * this.map.gridW + (this.map.gridW - 1 - gridX) + (this.relativeY >> 0);
                            break;
                    }
                    break;
            }
        }

        public clearAll(): void {
            this.mId = 0;
            this.mMap = null;
            this.mRelative.setTo(0, 0);
        }

        public destroy(destroyChild: boolean = true): void {
            this.clearAll();
            this.mRelative = null;
            super.destroy(destroyChild);
        }
这样,那看看具体怎么使用:
 Var entity:MapUnit = MapUnit();
 entity.id = id;
 tiledmap.addUnit(entity);
 entity.relativeXY(vx, vy);//相对地图位置显示,移动的时候改变relativeXY
 //如果需要站立在对应的格子上,通过tiledUtils.getRelativePositionByTilePos获取格子点的相对坐标即可
c4.自定义显示对象与地图配置生成的object层级处理
这个可能有点绕口,官方的处理是通过单元格的格子关系进行zOrder排序,下面的格子比上面的格子zOrder大,当然我觉得这个没问题,但问题是你只针对自身地图对象或平面的排序关系,假如格子上的图像的高度高于单元格的高度呢?2.5D的呢?那样我的模型不是站在了树顶,房檐上了么?
做rpg的同学都该知道,地形建筑与人物遮挡,传统的方式是半透明处理,对,我这里只是检测地形临时做了透明度的处理(请原谅我偷懒哦),就如c3中mapunit中的sortZorder中的处理
  /** 深度排序*/
        public sortZorder(): void {
            var z = this.z;
            var p: Laya.Point = this.gridXY;
            var gridX = p.x;
            var gridY = p.y;
            var totalGridNum = this.map.totalGridNum;
            var maplayer = this.map.getLayerByIndex(z);
            switch (this.map.orientation) {
                case lamborghini.map.TiledMap.ORIENTATION_ISOMETRIC://45度角        
                    this.zOrder = z * totalGridNum + gridY * this.map.gridW + gridX + (this.relativeY >> 0);
                    var a1 = maplayer.getTileData(gridX + 1, gridY + 1);
                    var a2 = maplayer.getTileData(gridX, gridY + 1);
                    var a3 = maplayer.getTileData(gridX + 1, gridY);
                    var a4 = maplayer.getTileData(gridX + 2, gridY + 2);
                    // this.alpha = a1+a2+a3+a4 ? 0.5 : 1;
                    if (a1) {
                        this.alpha = 0.3;
                    } else if (a2 + a3 + a4) {
                        this.alpha = 0.7;
                    } else {
                        this.alpha = 1;
                    }
                    break;
                case lamborghini.map.TiledMap.ORIENTATION_ORTHOGONAL://直角
                case lamborghini.map.TiledMap.ORIENTATION_HEXAGONAL://六边形

                    switch (this.map.renderOrder) {
                        case lamborghini.map.TiledMap.RENDERORDER_RIGHTDOWN:
                            this.zOrder = z * totalGridNum + gridY * this.map.gridW + gridX + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_RIGHTUP:
                            this.zOrder = z * totalGridNum + (this.map.gridH - 1 - gridY) * this.map.gridW + gridX + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_LEFTDOWN:
                            this.zOrder = z * totalGridNum + gridY * this.map.gridW + (this.map.gridW - 1 - gridX) + (this.relativeY >> 0);
                            break;
                        case lamborghini.map.TiledMap.RENDERORDER_LEFTUP:
                            this.zOrder = z * totalGridNum + (this.map.gridH - 1 - gridY) * this.map.gridW + (this.map.gridW - 1 - gridX) + (this.relativeY >> 0);
                            break;
                    }
                    break;
            }
        }
45°里面我只做了最简单的透明处理逻辑,这样模型在高地形背后时半透明呈现。
当然有更好的逻辑方式处理,如找到遮挡地形格子数据,并获取到格子地块的地形配置,获取地形的高度,通过自己当前的格子位置 < 遮挡格子位置 - 遮挡格子配置地形的高度及偏移 获取真实的层级关系。
但我没有这样做,一个层面考虑到效率问题,显示对象多的情况下我需要大量执行该操作,另一个层面是大的建筑物后面,可能完全遮挡人物模型了,体验不好。
.
.
.
边写边整理,边举例,边对比,边分析,就已经从早上10点到17:30了,呵呵,看上去也没写多少东西,不过快下班了,就写到这里吧【小孩病了,下班我的快点回家,虽然不是个好父亲,但我正在努力去做】,很多功能的细节没来得及讲解,已介绍的也没说的很详细,有什么了解的群里@我,或者加我Q(474594087)私聊我吧!当然,我爱听民谣,爱看球赛,爱打dota1(11id:一元方程式),虽然很菜,不过还是喜欢有时间去休闲2把,有共同爱好的也可以一起交流。
 
 再次感谢下玉笛冰萧,虽然只负责ide的工作,但是也帮助我询问和解答很多问题,私下也经常聊很多无关的话题(哈哈,希望他boss看到不会扣他绩效)。
 
 每天内心里都有两个小人在跟我说话,一个说:“算了,何必呢,何苦,做出来干撒?”,另一个却在说:“加油,再走几步你就能看见未来!”
 未来,未来,未来你在哪里?
 天色渐黑.路还很遥远.我却心急如焚......
[/i]
已邀请:

zoom

赞同来自: cuixueying 非文 AREX

抱歉:demo的链接云盘地址发布的时候有问题 
http://pan.baidu.com/s/1bpA8NL1   用这个地址

yung

赞同来自: jweilan

这么多支持的,git开源吧

cuixueying

赞同来自:

受教,厉害,感谢!!!

weixiaonan

赞同来自:

链接地址用不了了呢

mylore

赞同来自:

能有这方面的教程就好了。小白非常缺乏这类知识。

aaawanxiao

赞同来自:

PomeloClient.d.ts  我怎么找不到, 我就是因为没找到 才用的js版本

woody1720596907

赞同来自:

链接不存在了,老铁。 
感恩的心,好人一生平安: 求: 1720596907@qq.com

hyzjzcy

赞同来自:

麻烦发我一份demo,学习一下 332627767@qq.com 地址也行,感恩的心,好人一生平安!!

qwer535305054

赞同来自:

麻烦发一份demo,学习一下 535305054@qq.com 感谢

AREX

赞同来自:

求Demo学习,1103268534@qq.com,好人一生平安

awong

赞同来自:

求demo学习,1833413570@qq.com,感谢

mackay

赞同来自:

地址无法下载了啊

y1234

赞同来自:

求一份demo学习,958171338@qq.com,非常感谢

matthew_liu

赞同来自:

您好,我是刚接触游戏开发的小白,最近在研究layabox,能给我一份源码吗? (email: qingbo_matthew@163.com) 我想好好学习学习。 万分感谢。

chunjine

赞同来自:

哥们,求份源码学习学习。chunjine@163.com

knight

赞同来自:

哥们,求demo学习,1501970808@qq.com,非常感谢啊
 

kvssb

赞同来自:

同求demo学习,学习,现在刚好有搞类似的!!1648714954@qq.com,感谢!!!

137*****926

赞同来自:

同求demo学习,576689404@qq.com   万分感谢!!!

jacksing888

赞同来自:

同求demo学习,842256713@qq.com   万分感谢!!!

AiLiner

赞同来自:

刚好在rpg 求个demo yuandyoga@gmail.com 万分感谢!!

Rickshao

赞同来自:

 放github上比较好

jcd404552157

赞同来自:

求大佬源码 404552157@qq.com  感谢感谢

jyg0124

赞同来自:

求大佬源码 4109968@qq.com  感谢感谢

137*****926

赞同来自:

大神,求源码学习!万分感谢! 576689404@qq.com

zhengguojin101

赞同来自:

大神,求源码学习!非常感谢!zhangtianhua2018@qq.com

hyzjzcy

赞同来自:

大神,求源码学习!非常感谢! [email]zjzcy@qq.com[/email]

176*****867

赞同来自:

求demo,感谢大神1976442140@qq.com

136*****541

赞同来自:

383765662@qq.com 感谢大神仙

何小威

赞同来自:

求一份demo学习,1007941801@qq.com,非常感谢

赞同来自:

学习中,求源码,万分感谢,514562048@qq.com

layawq123

赞同来自:

1065668986@qq.com求一份demo学习。膜拜大佬

Six.Sir

赞同来自:

求来一份源码学习下 [email]415244214@qq.com[/email]

谌鹏飞

赞同来自:

求来一份源码学习下81913811@qq.com

代号9527

赞同来自:

学习中,求源码,万分感谢!1640069164@qq.com

 渡

赞同来自:

同求一份源码学习!!!2576663552@qq.com

Jessica

赞同来自:

同求一份源码学习!!![email]2576663552@qq.com[/email]

152*****450

赞同来自:

同求一份源码学习!!!421708419@qq.com

要回复问题请先

商务合作
商务合作