TypeScript初试(三)

之前在js中,为了防止全局变量污染,我们经常会声明一个对象,然后将变量或者函数作为对象的一个属性存在。
但是在ts中,我们可以通过关键字namespace来解决这个问题。
这里的namespace相当于es6中的module。

start

下面通过一个例子来说明一下

namespace gameConfig = {

    export const TextColor = {
        valueGolden: 0xf7b44a, //净值数值颜色
        transparent: 0x00000000, //透明色
        btnGolden: 0xffe8a9 //按钮上的文字的颜色
    }

    export enum errorMsg = {
        "网络连接故障,请耐心等待" = 5000,
        "账号在其他位置登录,如不是本人请更改密码" = 5001
    }

    export let isProto: boolean = false;

    let a;

    export function curHeight(): number {
        //当前游戏宽度
        console.log(a);
        return egret.MainContext.instance.stage.stageHeight;
    }

    export class Card{
        console.log('this is a card');
    }

}

class Example {
    const gold = gameConfig.TextColor.valueGolden;
}

在namespace命名空间中,我们可以声明变量,编写函数,编写类等等。
当我们在变量声明或者函数、类的编写之前使用export关键字后,我们就可以在命名空间之外找到它。当然有一些内部使用的,我们没有必要使用export关键字。

TypeScript初试(二)

在js中,变量的声明一般用var,但是在上一次分享中大家可能也看到了,当时采用的变量声明的关键字为let。那么这个let是个什么东西呢?
let 是es6新增的一个变量声明关键字,用来声明局部变量。同时let也是ts中推荐的声明变量的关键字。let声明的变量是局部变量,它的作用于是块级作用域。

有一个比较好的例子可以让我们清楚的了解

for (var i = 0; i < 10; i++) {
    setTimeout(function() { console.log(i); }, 100 * i);
}

上面代码的执行结果为:

10
10
10
10
10
10
10
10
10
10

改为let后:

for (let i = 0; i < 10 ; i++) {
    setTimeout(function() {console.log(i); }, 100 * i);
}

执行结果:

0
1
2
3
4
5
6
7
8
9

这个才是我们想要的结果,因此推荐在ts中或者es6中使用let来代替。

start

let 声明

通过上面的小例子可以看出var声明存在的问题,这也说明了用let声明变量的好处。除了名字不一样以外,let声明变量的写法与var是一样的。
let i: number = 0;
我们都知道,在js中,通过var声明的变量会有一个变量声明提前,即js解析引擎读取到var之后,会将var声明的变量提升至开始并赋值undefined,当用到的时候,才会再进行重新赋值。

变量声明不提前

而使用let声明的变量,不会出现变量声明提前,所以如果在let声明之前使用let声明的变量会报错。

a++; //变量a的作用域在let声明之后,所以此处找不到,报错。
let a: number = 0;
let的作用域

如果是在函数中或者其他块级作用域如for、if等,用let声明的话,函数外面也是找不到的。

function f(input: boolean) {
    let a = 100;

    if (input) {
        // if语句块在变量a的作用域中,可以找到。
        let b = a + 1;
        return b;
    }

    // 变量b的作用域为if语句块,所以此处找不到,报错。
    return b;
}

在上面的例子中,我们通过let分别声明了两个变量a、b。
a的作用域是f函数体内,所以,在if语句中可以找到。
b的作用域是if语句块内,所以外部找不到就会报错。

关于重复声明的问题

之前的js中,无论用var重复声明一个变量多少次,都不会报错,但是还是只有一个。如果重复声明并给同一个变量赋值的话,也不会报错,只是变量的值是最后一次赋值的值。
但是当我们通过let进行声明变量时,在同一个作用域内声明两个相同的变量就会报错。

var a: string = 'hi';
var a: string = 'hehe'; //不会报错,但是a的值为最后一次赋值的值,即为hehe;


let b: number = 0;
let b; //报错,相同作用域内不能使用let重复声明同一个变量。

const 声明

const是声明常量,这个大家在js中应该也有用过。const的作用域与let相同,但是const声明的变量的值不会发生改变。
const还有一个好处就是,采用const声明的变量如果赋值的是一个表达式或者计算,在声明之初,这个值就已经计算出来了。不会像var一样,每次用到这个变量都会在计算一遍。在项目中遇到了很多绘制环形图或者折线图这种的需求,有很多变量是通过传入的数据或者图形来计算大小的,这个时候使用const,在声明之初就将这个变量的值计算了出来,后续的调用会提高效率。

function drawLine(length: number): void {
    //声明完就会将num的值计算出来。
    const num: number = length * 10 + 1;

    //num已经计算好了,直接调用,不会像var声明的变量一样每次调用都会重新计算。
    console.log(num);
}

const变量的内部状态是可以改变的,如:

const zhangshuang = {
    name: zhangshuang,
    age: 26
}

zhangshuang.name = ZhangShuang; //不会报错,内部状态修改了。

end

let VS const

那么我们什么时候用let,什么时候使用const呢?一般来说,变量的声明最好遵循最小特权原则。也就是说,如果这个变量声明之后不会更改,那么我们就用const来声明;如果以后可能会改变,那我们就使用let来声明。

TypeScript初试(一)

跟egret引擎一样,学习typescript也是因为公司的需求。虽然typescript算是JavaScript的超集,按理说应该跟js很像,但是个人感觉ts真的跟java更像一下。遇到有搞不懂的问题都是像公司后端及android童鞋们求助解决的,在这里先谢啦~
当然有一些像interface接口这种的,我的项目中没有用到,而且我问了几次也不是很理解,感觉不用这个也可以操作。
废话不多说了,下面就说一下我通过这个项目总结的一些经验:

start

ts跟js相比,语法上严禁了一些。js动态语言的特性当然也就没了,书写的时候一般都要跟上类型,如:
let isProto: boolean = false;
后面就不会对这种东东再进行赘述。
这里的 : boolean 就是对变量 isProto 类型的说明,每一种语言都会有相应的基本类型,这些网上都有很多,
例如:TypeScript Handbook等,当时学习的时候就是从网上找的这种的一点一点看的。基本的语法在这里就简单一点带过。

布尔值 boolean

最基本的数据类型就是简单的true/false,这个就是布尔类型(boolean)
let isProto: boolean = false;

数字类型 number

ts中的number跟js一样也是浮点数,这个跟java什么的不太一样,没有int或者float整形或浮点型,只有一个number数字类型。这里有一点需要注意,如果想要通过二进制传输的话,js跟ts的number类型最多只支持15位数字,数字位数变多就会不精确。例如我的项目中,前后端数据传输就采用的protobuf这种二进制格式,在定义userID的时候用的long,并且是18位,结果在传输过来时发现末尾不是0的出现了些问题,变得跟原来不一样了。后来查阅各种资料才发现了这个问题,然后就将这种较长的数字也改为string类型替代了。

let i: number = 0;
let colorRed = 0xff0000;
字符串 string

在ts中,string类型跟js中的一样,就不在多说了
let name: string = 'zhangshuang';
TypeScript Handbook中还介绍了模板字符串的用法,如果有需要也可以参考一下。当然我的项目中没有用到这个。

数组

TypeScript中,数组有两种表示方法:
1.元素类型后跟上[ ]
let numList: number[] = [1,2,3];
2.数组泛型
let numList: Array<number> = [1,2,3];

枚举 enum

最初我理解枚举的时候是将enum分成e+num,本质上还是数字,官方的解释就是将一系列数字赋予友好的名字,它的用法也很强大:

enum fontColor = {
    gameRed = 0xff0099, //游戏中的红色
    gameBlue = 0x0099ff //游戏中的蓝色
}
let red: number = fontColor.gameRed;


enum errorMsg = {
    "网络连接故障,请耐心等待" = 5000,
    "账号在其他位置登录,如不是本人请更改密码" = 5001
}
let eMsg: string = errorMsg[5000];

可以说是结合了对象的.跟数组的[number]的用法于一身,具体怎么使用要看自己。比如第一种的fontColor,颜色数值0xff0099比较难记,如果使用fontColor.gameRed就可以解决这个问题;第二种errorMsg,错误提示较长,就可采用errorMsg[5000]来解决。

任意值 any

ts中的任意值就是为了解决在编程时不清楚变量的类型而定义的一种类型,这个就相当于js的隐式类型转换。

let notSure: any = 4;
notSure = '改变了类型';
空值 void

我理解这个void是通过函数来理解的。有的函数有返回值,有的没有,为了代码清晰严谨,有返回值的函数就写返回值的类型,没有返回值的函数直接写void

//有返回值,类型为string
function getName(name: string): string {
    return name;
}

//没有返回值
function alert(): void {
    alert(1);
}
其他的类型

还有一些类型如元组Tuple,Never等因为在项目中没有用到,所以在这里也没再多说,如果有兴趣可以去TypeScript Handbook中寻找一下。

下次分享一下变量声明var、let、const

egret游戏引擎入门(七)

今天分享几个封装的egret方法。
之前也调研过createjs,感觉egret的绘画API跟createjs的相似,或者说,这一类都是大同小异的。虽然这些方法相较原声的canvas简单一点,但是感觉还是有些繁杂,可以将这些方法封装一下进行调用。
1. 根据资源名称创建图片Bitmap
当我们新建一个egret项目的时候,就会在Main.ts中发现这个方法。这个方法直接传入一个在default.res.json中定义好的图片资源名称,就可以返回一个egret.Bitmap对象。其实这也就是在提示我们将文字、绘图等方法进行封装,使用起来会更加高效。

function createBitmapByName(name: string): egret.Bitmap {
    const resource: egret.Bitmap = new egret.Bitmap();
    const texture: egret.Texture = RES.getRes(name);
    resource.texture = texture;
    return resource;
}

  1. 创建文本类TextFiled
/**
 * 创建文本类TextFiled
 *
 * @param {string} text 要显示的文字
 * @param {number} color 文字的颜色
 * @param {number} [size=20] 文字的大小
 * @param {number} [x=0] 文字的x坐标
 * @param {number} [y=0] 文字的y坐标
 */
function createText(text: string, color: number, size: number = 20, x: number = 0, y: number = 0): egret.TextFiled {
    const resource: egret.TextFiled = new egret.TextFiled();
    resource.text = text;
    resource.color = textColor;
    resource.size = size;
    resource.x = x;
    resource.y = y;
}
  1. 创建带有矢量绘制功能的显示容器sprite
    个人感觉这个方法使用的相当频繁,因为我是将sprite作为一个组件去使用的。打个比方来说,我要创建一张卡牌,那么我先创建一个sprite并将其添加到场景上,然后再创建一些卡牌的图案或者文字,添加到sprite上。每次要移动卡牌的时候,只需要操作sprite就好了,其内的文本类后者矢量绘图类都会随之移动。
    值得一提的是,如果是创建一个类继承自egret.Sprite,并且这个类有点击等监听事件,如果无法触发,请设置其touchEnable属性变为true;如果有些地方触发有效果有些地方无效果,就要创建一个同等大小的shape,并且将其的alpha设置为0,放置在sprite上即可。
/**
 * 创建带有矢量绘制功能的显示容器sprite
 *
 * @param {number} width 显示容器的宽
 * @param {number} height 显示容器的高
 * @param {number} [x=0] 显示容器的x坐标
 * @param {number} [y=0] 显示容器的y坐标
 * @returns {egret.Sprite} 返回显示容器对象
 */
function createSprite(width: number, height: number, x: number = 0, y: number = 0): egret.Sprite {
    const resource: egret.Sprite = new egret.Sprite();
    resource.width = width;
    resource.height = height;
    resource.x = x;
    resource.y = y;
    return resource;
}
  1. 创建矢量图Shape
    我在最开始使用的时候,将x跟y直接用在drawRect中,但是通过egret wing自带的调试工具调试后发现,他的实际位置显示的有问题。所以现在在使用drawRect的时候,将x跟y都设置为0,再通过刚刚传入的x跟y坐标设置这个矢量图对象的位置。
    这个只是创建矩形、还有很多如圆弧、圆形等可以参照这个自行封装。
/**
 * 创建矢量图Shape
 *
 * @param {number} width 矢量图的宽
 * @param {number} height 矢量图的高
 * @param {number} color 矢量图的颜色
 * @param {number} [x=0] 矢量图的x坐标
 * @param {number} [y=0] 矢量图的y坐标
 * @param {number} [alpha=1] 矢量图的透明度
 * @returns {egret.Shape} 返回矢量图对象
 */
function createShape(width: number, height: number, color: number, x: number = 0, y: number = 0, alpha: number = 1): egret.Shape {
    const resource: egret.Shape = new egret.Shape();
    resource.graphics.beginFill(color, alpha);
    resource.graphics.drawRect(0, 0, width, height);
    resource.graphics.endFill();
    resource.x = x;
    resource.y = y;
    return resource;
}

另附一个自己封装的环形图代码:
这个实现了传入一个sprite对象以及固定格式的数据,将环形图及图例添加到传入对象的特定方法。

/**
     * 画饼图
     * obj,在哪个对象上绘制
     * data,饼图数据,格式固定
     *
     * @export
     * @param {any} obj
     * @param {any} data
     */
    export function drawCircle(obj, data) {
        const objWidth: number = obj.width;
        const objHeight: number = obj.height;
        const width: number = obj.width * 0.8; //新创建的背景的宽度,宽高相同
        const height: number = obj.height * 0.8; //新创建的背景的高度
        const lineHeight: number = 35;
        const length: number = data.length; //数据长度
        let startDeg = 0; //扇形起始角度
        let endDeg = 0; //结束角度

        let percentBg: egret.Sprite = new egret.Sprite();
        percentBg.width = width;
        percentBg.height = height;
        percentBg.x = (objWidth - width) / 2;
        percentBg.y = 95;
        obj.addChild(percentBg);
        let percentTotalBg: egret.Sprite = new egret.Sprite();
        percentTotalBg.width = width;
        let circleBg: egret.Sprite = new egret.Sprite();
        percentTotalBg.addChild(circleBg);
        circleBg.width = width;
        circleBg.height = width;
        circleBg.y = 0;

        for (let i = 0; i < length; i++) { //环形图浅色部分
            endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
            circleBg.graphics.beginFill(GameConfig.lightColor[i]);
            circleBg.graphics.moveTo(width / 2, width / 2);
            circleBg.graphics.lineTo(width, width / 2);
            circleBg.graphics.drawArc(width / 2, width / 2, width / 2.3, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
            circleBg.graphics.lineTo(width / 2, width / 2);
            circleBg.graphics.endFill();
            startDeg = endDeg;
        }
        for (let i = 0; i < length; i++) { //环形图深色部分
            endDeg = (data[i].industryPercent).toFixed(2) * 3.6 + startDeg;
            circleBg.graphics.beginFill(GameConfig.darkColor[i]);
            circleBg.graphics.moveTo(width / 2, width / 2);
            circleBg.graphics.lineTo(width, width / 2);
            circleBg.graphics.drawArc(width / 2, width / 2, width / 2.6, (startDeg + 2) * Math.PI / 180, endDeg * Math.PI / 180);
            circleBg.graphics.lineTo(width / 2, width / 2);
            circleBg.graphics.endFill();
            startDeg = endDeg;
        }
        circleBg.graphics.beginFill(GameConfig.TextColors.white); //环形图中间白色部分
        circleBg.graphics.moveTo(width / 2, width / 2);
        circleBg.graphics.lineTo(width, width / 2);
        circleBg.graphics.drawArc(width / 2, width / 2, width / 3.5, 0, 360 * Math.PI / 180);
        circleBg.graphics.lineTo(width / 2, width / 2);
        circleBg.graphics.endFill();

        let centerIcon = ZpyGameUtils.createBitmapByName("card_dark_icon_png"); //中心icon
        centerIcon.x = width / 2 - centerIcon.width / 2 * 0.6;
        centerIcon.y = width / 2 - centerIcon.height / 2 * 0.6;
        centerIcon.scaleX = 0.6;
        centerIcon.scaleY = 0.6;
        circleBg.addChild(centerIcon);
        percentTotalBg.addChild(circleBg);

        let labelBg: egret.Sprite = new egret.Sprite();
        labelBg.width = width - 40;
        labelBg.x = 20;
        labelBg.y = width;
        for (let i = 0, len = data.length; i < len; i++) {
            let icon: egret.Shape = new egret.Shape();
            icon.graphics.beginFill(GameConfig.darkColor[i]);
            icon.graphics.drawRect(0, i * lineHeight, 20, 20);
            icon.graphics.endFill();
            labelBg.addChild(icon);

            let label: egret.TextField = ZpyGameUtils.createText(data[i].industryName + '  ' + (data[i].industryPercent).toFixed(2) + '%', GameConfig.TextColors.black, 20);
            label.x = 30;
            label.y = lineHeight * i;
            labelBg.addChild(label);
        }
        percentTotalBg.addChild(labelBg);
        percentTotalBg.height = width + length * lineHeight;

        var scrollView: egret.ScrollView = new egret.ScrollView();
        scrollView.setContent(percentTotalBg);
        scrollView.width = width;
        scrollView.height = height;
        percentBg.addChild(scrollView);
    }

egret游戏引擎入门(六)

今天分享一下通信控制SocketManager。
在egret引擎中,websocket是作为第三方库存在的,需要在创建之初将websocket选项选中,或者参照官网教程,在egret项目的egretProperties.json中编写。官网-如何使用第三方库

通信控制也是采用单例模式,里面也会有几个方法:

当socket连接打开

this.webSocket.addEventListener(egret.Event.CONNECT, this.onSocketOpen, this);

当socket连接关闭,这个地方会有一些处理。如:掉线重连操作等

this.webSocket.addEventListener(egret.Event.CLOSE, this.onSocketClose, this);

当socket连接错误

this.webSocket.addEventListener(egret.IOErrorEvent.IO_ERROR, this.onSocketError, this);

当接收到后端传过来的消息,因为通信是通过protobuf,所以这个地方接收到消息后,会传入SocketDataHandler方法中按照cmd进行对应的解析,如果发送的消息很多的话,这里也要写很多。值得注意的是,书写的顺序必须与后端保持一致。这里我也没大有发言权,毕竟是另一个同事调研编写的。但是个人感觉通过protobuf相当麻烦,需要给每一条消息体都做相应的定义,加上处理数据需要写一遍、通过不同cmd执行不同方法写一遍,加起来就是要在收到消息这里编写三次。

this.webSocket.addEventListener(egret.ProgressEvent.SOCKET_DATA, this.onReceiveMessage, this);

通过url与后端进行连接

this.webSocket.connectByUrl(GlobalData.scUrl);

在消息传输的过程中发现了一个语言层面的巨大无比的坑…
ts中使用protobuf时需要定义一个该条消息的类型,类似string、int32、int64这种的。之前在定义userID的时候,将类型写为int64,结果发现userID尾数不是0的都变为了0,好像是第15为之后。找了各种资料才发现,js只能到15位,最终无奈将userID这个类型变为了string。


下次会分享几个封装的egret方法。