[]LayaAir能做RPG吗?不要问我能不能,因为我已经在做 - 杀意来袭
hi.我是zom,非laya的官方人员,之所以写这篇帖子多少有点自己这长时间对制作demo过程中的磕碰一次总结,也跟玉笛冰箫说了是不是写一篇帖子介绍下自己在使用layaair中遇到的坑能让看到的人有些许启发,也是来感谢这一段时间冰萧,小松,梦佳不厌其烦的来处理我提出的问题和解答,就这样,听着布衣乐队的三峰,泡上一杯红茶,写下了laya的故事。
工具及涉及:layaide+typescript+pomelo+tiledmap+其他工具
项目类型:mmorpg
人员:zom(客户端) + arrow(服务端)
进度:单机体验状态,实现 摇杆,切图,释放技能(由于人员和精力有限还需要处理公司正常工作,网络版进度暂时搁置在)
截图:
云盘链接:杀意来袭 密码: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°角地图非正菱形会导致计算的格子点坐标错误
菱形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]
工具及涉及:layaide+typescript+pomelo+tiledmap+其他工具
项目类型:mmorpg
人员:zom(客户端) + arrow(服务端)
进度:单机体验状态,实现 摇杆,切图,释放技能(由于人员和精力有限还需要处理公司正常工作,网络版进度暂时搁置在)
截图:
云盘链接:杀意来袭 密码: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°角地图非正菱形会导致计算的格子点坐标错误
菱形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]
没有找到相关结果
已邀请:
要回复问题请先登录
43 个回复
zoom
赞同来自: cuixueying 、非文 、AREX
http://pan.baidu.com/s/1bpA8NL1 用这个地址
yung
赞同来自: jweilan
cuixueying
赞同来自:
weixiaonan
赞同来自:
mylore
赞同来自:
aaawanxiao
赞同来自:
woody1720596907
赞同来自:
感恩的心,好人一生平安: 求: 1720596907@qq.com
hyzjzcy
赞同来自:
qwer535305054
赞同来自:
AREX
赞同来自:
awong
赞同来自:
mackay
赞同来自:
y1234
赞同来自:
matthew_liu
赞同来自:
chunjine
赞同来自:
knight
赞同来自:
kvssb
赞同来自:
137*****926
赞同来自:
jacksing888
赞同来自:
AiLiner
赞同来自:
Rickshao
赞同来自:
jcd404552157
赞同来自:
jyg0124
赞同来自:
137*****926
赞同来自:
zhengguojin101
赞同来自:
hyzjzcy
赞同来自:
176*****867
赞同来自:
136*****541
赞同来自:
何小威
赞同来自:
钊
赞同来自:
wq
赞同来自:
Six.Sir
赞同来自:
谌鹏飞
赞同来自:
代号9527
赞同来自:
Jessica
赞同来自:
152*****450
赞同来自:
jlike521
赞同来自:
3D 游戏 略粗 欢迎 指点
自然可以做 3D 的都可以
for dream
赞同来自:
liudong95
赞同来自:
coyote
赞同来自:
134*****707
赞同来自:
maikoluolan@163.com
LXP
赞同来自:
131*****569
赞同来自: