有关游戏进入动更机制流程的思考 (Unity)

主要流程状态

  • Init
  • LoadManifestVersion
  • CompareManifestVersion
  • LoadManifest
  • CompareManifest
  • Upgrade
  • AfterUpgrade
  • Unzip
  • AfterUnzip
  • Done

主要接口

m_AssetUpdateOp = Miaokids.Modules.ResourceDownloader.Instance.UpdateAssets (Miaokids.Utilities.Constants.updateManifestFile,
                settingManager.resourceUrl,
                false, false, true);
            yield return m_AssetUpdateOp;

Init

初始化

1 初始化下载路径

m_TemporaryPath = "/Projects/miaokidsmathpublic/miaokids/tmp/";
m_InstallPath = "/Users/tomyuan/Projects/miaokidsmathpublic/miaokids/install/"
m_ResourcePath = "/Users/tomyuan/Projects/miaokidsmathpublic/miaokids/resource/main/"
m_ManifestVersionFileName = "manifest_version/main.json"
m_ManifestFileName = "manifest/main.json"
m_DownloadUrl = "https://miaokids-xxxxxxx.aliyuncs.com/android/1.0.0/"

2 初始化参数

IgnoreCompareManifestVersion = false
KeepAlive = keepAlive;
EnablePing = enablePing;

LoadManifestVersion

m_DownloadSpeedCalculator.Reset ();

m_LoadLocalManifestVersionOp = FileManager.Instance.LoadFile (m_ManifestVersionFileName);

m_LoadRemoteManifestVersionOp = new AssetHttpDownloadOperation (m_DownloadUrl + m_ManifestVersionFileName);
m_LoadRemoteManifestVersionOp.EnableWriteFile = false;
m_LoadRemoteManifestVersionOp.RetainData = true;
m_LoadRemoteManifestVersionOp.Start ();

Unity下的Slider ScrollView

Slider ScrollView

如下图所示,主要制作可以左右滑动的ScrollView切屏幕效果

主要功能和设计思路

功能效果非常类似手机横滑切屏幕的效果,我们主要还是依托Unity的ScrollRect组件,然后水平方向我们通过计算滑动的距离和时间,判断是否进行切屏操作,再执行切屏过程中,滑动区域下方的导航点也会根据当前展示页面显示不同的激活状态。

Git push出现Could not resolve host问题解决

Could not resolve host:xxx 问题


当我们在使用git进行推送的时候有时会遇到Could not resolve host的问题,一般我们都会尝试重启网络再尝试push,这样无法从根本解决问题。目前还有一种更为有效的方式:通过改host配置文件来完成。

具体错误如下

git push origin channels/xxxx
fatal: unable to access 'https://git.dev.tencent.com/xxxx/xxxx.git/': Could not resolve host: git.dev.tencent.com

首先我们ping无法resolv的host

ping git.dev.tencent.com
PING git.tencentcloud.coding.net (118.25.166.124): 56 data bytes
64 bytes from 118.25.166.124: icmp_seq=0 ttl=51 time=34.578 ms
64 bytes from 118.25.166.124: icmp_seq=1 ttl=51 time=36.573 ms

然后修改host文件即可

sudo vi /etc/hosts
# 文件增加一行 118.25.166.124 git.tencentcloud.coding.net #git.tencentcloud.coding.net

如果还不可以需要执行如下命令

git config --global --unset http.proxy
git config --global --unset https.proxy 

搞定

补充
有朋友留言如下:
感觉是coding被收购后内部在维护,

git.coding.net和git.dev.tencent.com原来是指向相同的服务器,

试了下面这样,发现也可以

git remote add coding https://git.coding.net/xxxx/xxxx.git/
git push coding master

LRU、LFU、FIFO、ARC、MRU

概述

  • LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。LRU是最近最少使用页面置换算法(Least Recently Used),也就是首先淘汰最长时间未被使用的页面。

  • LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小,LFU是最近最不常用页面置换算法(Least Frequently Used),也就是淘汰一定时期内被访问次数最少的页。

  • FIFO (Fist in first out) 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉。

  • ARC 自适应缓存替换算法,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。

  • MRU 这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。

LRU

这个是目前常见的缓存方式,浏览器和memcached的缓存策略均为此项,LRU主要的方式如下图所示:

file

一般采用的是双向链表+Hash的方式,主要基于以下几个方面考虑:

  • 采用普通的单链表,则删除节点的时候需要从表头开始遍历查找,效率为O(n),采用双向链表可以直接改变节点的前驱的指针指向进行删除达到O(1)的效率

  • 使用Map来保存节点的key、value值便于能在O(logN)的时间查找元素

#include "stdio.h"
#include "stdlib.h"

#define OK 1
#define ERROR 0
#define OVERFLOW -1

typedef int status;
typedef int ElemType;

typedef struct {
    ElemType data;
    int nums;
}Node;

typedef struct 
{
    Node *base;
    int length;
    int listsize;
}Cache;

status InitCache(Cache &L);
status ListInsert_Sq(Cache &L , int i, ElemType e);
status Push(Cache &L ,  ElemType e);
status Pop(Cache &L);
status EQ(ElemType a,ElemType b);
status LT(ElemType a,ElemType b);
status ShowCache(Cache L);
int LocateCache_nums(Cache L,ElemType e);
int LocateCache_data(Cache L,ElemType e);
status start(Cache &L , ElemType e);

int main(int argc, char const *argv[])
{
    Cache L;
    int arr[] = { 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6};
    int count;
    int i;
    count = sizeof(arr)/sizeof(int);
    InitCache(L);
    for(i=0;i<count;i++){
        printf("-----%d\n",i+1);
        start(L,arr[i]);
        ShowCache(L);
    }
    return 0;
}

status InitCache(Cache &L){
    if(!L.base) exit(OVERFLOW);
    printf("please input cache nums:");
    scanf("%d",&L.listsize);
    L.base = (Node*)malloc(sizeof(Node)*L.listsize);
    L.length = 0;
    return OK;
}
/**
 * 1.先查找是否在缓存里面
 * 2.在找出位置,返回位置数
 * 3.然后查出的相同次数缓存的第一个位置
 * 4.把缓存调到该位置的前一个。:1.取出该元素,2该元素的原来位置到第一个元素的位置之间元素向后移一位,插入元素到第一个位置
 * 5.不在则判断是否已满
 * 6.不满直接插入元素到最后位置,否则删除最后元素,然后插入元素
 * @param  L [description]
 * @param  e [description]
 * @return   [description]
 */
status start(Cache &L , ElemType e){
    int site,nums,firstElem,temp;
    Node p;
    // 查找有该元素
    if((site=LocateCache_data(L,e))!=-1){
        nums = L.base[site].nums;
        firstElem = LocateCache_nums(L,nums);
        printf("%d %d\n",nums,firstElem );
        p = L.base[site];
        // 把顺序表的结点一个个向后移一位
        for(temp=firstElem;temp<site;temp++){
            L.base[temp+1] = L.base[temp];
            printf("fuckfuck\n");
        }
        // 顺序表该增加调用的改为第一个
        L.base[firstElem] = p;
        L.base[firstElem].nums++;
    }else{
        // 没有该元素,直接删除最后一位,增加新的元素
        if(L.length==L.listsize)
            Pop(L);
        Push(L,e);
    }
}

// 查找第一个出现的对应元素,折半查找元素(根据使用次数)
int LocateCache_nums(Cache L,int nums){
    int low,high,mid;
    low = L.length-1;
    high = 0;
    // printf("%d\n", mid);exit(-1);
    while(low >= high){
        mid = (high+low)/2;
        if(EQ(L.base[mid].nums,nums)){
            // 如果出现了相同的次数,查找第一个出现的次数
            while(mid!=0){
                // 查找前一个元素是否符合次数
                if(!EQ(L.base[--mid].nums,nums)){
                    mid++;
                    break;
                }
            }
            return mid;
        }else {
            if(LT(L.base[mid].nums,nums)) low = mid-1;
            else high = mid+1;
        }
    }
    return -1;
}

// 查找元素
int LocateCache_data(Cache L,ElemType e){
    int i=0;
    // 设置哨兵
    L.base[L.length].data = e;
    for(;!EQ(L.base[i].data,e);i++);
    if(i==L.length) return -1;
    else return i;
}

// 在尾部插入元素,调用次数为1
status Push(Cache &L , ElemType e){
    Node *p;
    if(L.length>=L.listsize) return ERROR;
    p = (Node*)malloc(sizeof(Node));
    p->data = e;
    p->nums = 1;
    L.base[L.length] = *p;
    L.length++;
}

// 删除尾部元素
status Pop(Cache &L){
    if(L.length<1) return ERROR;
    L.length--;
}

status ShowCache(Cache L){
    int i=0;
    while(i<L.length){
        printf("cache Id nums:%d %d\n",L.base[i].data,L.base[i].nums);
        i++;
    }   
}

status EQ(ElemType a,ElemType b){
    return a==b?OK:ERROR;
}
status LT(ElemType a,ElemType b){
    return a<b?OK:ERROR;
}

LFU

LFU的每个数据块都有一个引用计数,所有数据块按照引用计数排序,具有相同引用计数的数据块则按照时间排序。一般实现有以下几步。

  • 新加入数据插入到队列尾部(因为引用计数为1)
  • 队列中的数据被访问后,引用计数增加,队列重新排序
  • 当需要淘汰数据时,将已经排序的列表最后的数据块删除

由于每个数据都需要维护引用计数,同时需要针对引用计数排序,所以性能消耗比较高。

#include "stdio.h"
#include "stdlib.h"

#define OK 1
#define ERROR 0
#define OVERFLOW -1

typedef int status;
typedef int ElemType;

typedef struct {
    ElemType data;
    int nums;
}Node;

typedef struct 
{
    Node *base;
    int length;
    int listsize;
}Cache;

status InitCache(Cache &L);
status ListInsert_Sq(Cache &L , int i, ElemType e);
status Push(Cache &L ,  ElemType e);
status Pop(Cache &L);
status EQ(ElemType a,ElemType b);
status LT(ElemType a,ElemType b);
status ShowCache(Cache L);
int LocateCache_nums(Cache L,ElemType e);
int LocateCache_data(Cache L,ElemType e);
status start(Cache &L , ElemType e);

int main(int argc, char const *argv[])
{
    Cache L;
    int arr[] = { 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6};
    int count;
    int i;
    count = sizeof(arr)/sizeof(int);
    InitCache(L);
    for(i=0;i<count;i++){
        printf("-----%d\n",i+1);
        start(L,arr[i]);
        ShowCache(L);
    }
    return 0;
}

status InitCache(Cache &L){
    if(!L.base) exit(OVERFLOW);
    printf("please input cache nums:");
    scanf("%d",&L.listsize);
    L.base = (Node*)malloc(sizeof(Node)*L.listsize);
    L.length = 0;
    return OK;
}
/**
 * 1.先查找是否在缓存里面
 * 2.在找出位置,返回位置数
 * 3.然后查出的相同次数缓存的第一个位置
 * 4.把缓存调到该位置的前一个。:1.取出该元素,2该元素的原来位置到第一个元素的位置之间元素向后移一位,插入元素到第一个位置
 * 5.不在则判断是否已满
 * 6.不满直接插入元素到最后位置,否则删除最后元素,然后插入元素
 * @param  L [description]
 * @param  e [description]
 * @return   [description]
 */
status start(Cache &L , ElemType e){
    int site,nums,firstElem,temp;
    Node p;
    // 查找有该元素
    if((site=LocateCache_data(L,e))!=-1){
        nums = L.base[site].nums;
        firstElem = LocateCache_nums(L,nums);
        printf("%d %d\n",nums,firstElem );
        p = L.base[site];
        // 把顺序表的结点一个个向后移一位
        for(temp=firstElem;temp<site;temp++){
            L.base[temp+1] = L.base[temp];
            printf("fuckfuck\n");
        }
        // 顺序表该增加调用的改为第一个
        L.base[firstElem] = p;
        L.base[firstElem].nums++;
    }else{
        // 没有该元素,直接删除最后一位,增加新的元素
        if(L.length==L.listsize)
            Pop(L);
        Push(L,e);
    }
}

// 查找第一个出现的对应元素,折半查找元素(根据使用次数)
int LocateCache_nums(Cache L,int nums){
    int low,high,mid;
    low = L.length-1;
    high = 0;
    // printf("%d\n", mid);exit(-1);
    while(low >= high){
        mid = (high+low)/2;
        if(EQ(L.base[mid].nums,nums)){
            // 如果出现了相同的次数,查找第一个出现的次数
            while(mid!=0){
                // 查找前一个元素是否符合次数
                if(!EQ(L.base[--mid].nums,nums)){
                    mid++;
                    break;
                }
            }
            return mid;
        }else {
            if(LT(L.base[mid].nums,nums)) low = mid-1;
            else high = mid+1;
        }
    }
    return -1;
}

// 查找元素
int LocateCache_data(Cache L,ElemType e){
    int i=0;
    // 设置哨兵
    L.base[L.length].data = e;
    for(;!EQ(L.base[i].data,e);i++);
    if(i==L.length) return -1;
    else return i;
}

// 在尾部插入元素,调用次数为1
status Push(Cache &L , ElemType e){
    Node *p;
    if(L.length>=L.listsize) return ERROR;
    p = (Node*)malloc(sizeof(Node));
    p->data = e;
    p->nums = 1;
    L.base[L.length] = *p;
    L.length++;
}

// 删除尾部元素
status Pop(Cache &L){
    if(L.length<1) return ERROR;
    L.length--;
}

status ShowCache(Cache L){
    int i=0;
    while(i<L.length){
        printf("cache Id nums:%d %d\n",L.base[i].data,L.base[i].nums);
        i++;
    }   
}

status EQ(ElemType a,ElemType b){
    return a==b?OK:ERROR;
}
status LT(ElemType a,ElemType b){
    return a<b?OK:ERROR;
}

FIFO

FIFO也是我们最常见的队列方式,如下图所示

file

一般开发步骤如下

  • 新访问的数据插入FIFO队列尾部,数据在FIFO队列中顺序移动
  • 淘汰FIFO队列头部的数据
#include "stdio.h"
#include "stdlib.h"

#define OK 1
#define ERROR 0
#define OVERFLOW -1

typedef int status;
typedef int ElemType;
typedef status (*fp)(ElemType a,ElemType b);

typedef struct QNode{
    ElemType data;
    QNode* next;
}QNode;

typedef struct Queue
{
    // 缓存个数
    int nums;
    // 当前个数
    int totals;
    // 头结点
    QNode *front;
    // 尾结点
    QNode *rear;
}Queue;

status InitQueue(Queue &Q);
status Locate_QNode(Queue Q,ElemType e,fp compare);
status equal(ElemType a,ElemType b);
status Add_QNode(Queue &Q,ElemType newQ,ElemType &oldQ);
status EnQueue(Queue &Q,ElemType e);
status DeQueue(Queue &Q,ElemType e);

status InitQueue(Queue &Q){
    int nums;
    printf("please input cache nums:");
    scanf("%d",&nums);
    if(nums<1) return ERROR;
    Q.nums = nums;
    Q.totals = 0;
    Q.front = (QNode*)malloc(sizeof(QNode));
    Q.front->next =  NULL;
    Q.rear = Q.front;
    return OK;
}

// 查找队列是否有对应的缓存
status Locate_QNode(Queue Q,ElemType e,fp compare){
    QNode *q1=Q.front;
    while(q1){
        if(compare(q1->data,e)) return OK;
        q1 = q1->next;
    }
    return ERROR;
}

status Add_QNode(Queue &Q,ElemType newQ,ElemType &oldQ){
    // 在尾部添加新的结点
    EnQueue(Q,newQ);
    // 缓存个数尚未满,个数增加
    if(Q.totals<Q.nums){
        Q.totals++;
    }else{
    // 缓存个数已满,去除头结点
        DeQueue(Q,oldQ);
    }
    return OK;
}

status EnQueue(Queue &Q,ElemType e){
    QNode *p;
    p = (QNode*)malloc(sizeof(QNode));
    if(!p) exit(OVERFLOW);
    p->data = e;
    p->next = NULL;
    Q.rear->next = p;
    Q.rear = p;
    return OK;
}

status DeQueue(Queue &Q,ElemType e){
    QNode *p;
    if(!Q.front->next) return ERROR;
    p = Q.front->next;
    Q.front->next = p->next;
    e = p->data;
    free(p);
    // 最后一个结点了
    if(!Q.front->next) Q.rear = Q.front;
    return OK;
}

status equal(ElemType a,ElemType b){
    return a==b?OK:ERROR;
}

int main(int argc, char const *argv[])
{
    // 测试缓存页面队列,不同数字代表不同的缓存页面
    ElemType a[] = { 1,2,3,4,2,1,5,6,2,1,2,3,7,6,3,2,1,2,3,6};
    int count = sizeof(a)/sizeof(a[0]);
    // j为置换次数,temp为置换出来的页面
    int i, j=0,temp;
    Queue Q;
    InitQueue(Q);
    for(i = 0; i < count;i++){
        if(!Locate_QNode(Q,a[i],equal)){
            Add_QNode(Q,a[i],temp);
            j++;
        }
    }
    printf("页面置换次数%d\n",j);
    return 0;
}

MiaoKids Artificial Intelligence Circuit

芯片智能交互技术


MAIC主要实现了视觉、听觉、触觉的多重感官刺激学习的功能设计,让儿童在学习过程中始终保持兴趣。
优学猫整个研发团队以孩子为中心,以兴趣为导向;遵循技术是服务于孩子;尊重孩子的天性同时培养孩子对于学习的兴趣。

优学猫视觉体验

鲜艳的色彩,丰富的动作对于儿童有非常大的吸引力,不管是看动画片也好,还是卡通故事也好都很好的证明了这个事实;而在科技日新月异的今天,孩子所能接受到的信息早已不是已为父辈的我们儿时接受的那么般匮乏。以《优学猫数学》为例,优学猫团队为了将小朋友带入一个美妙的数学王国,我们采用了目前市场化非常成熟的Unity引擎进行整个产品的客户端开发工作,针对低年龄段儿童的特性,我们放弃了3D,选择2D开发;Unity在当时针对2D和Texture2D的很多功能都不尽完善,所以整个团队针对各种需求写出了一些专用的Unity工具和一些提升效果和纹理功能的Shader;为了使得动画展示更加丰富,使用Spine的骨骼动画,可以在使用较少资源的情况下,将效果最大化,骨骼动画其中用到了不少矩阵运算的知识,主要为了可以向量化的支持关节父子节点直接的变化。然后通过Unity将图片与动画有机的结合在一起,最终将美妙的数学王国呈现给小朋友。

优学猫听觉体验

除了色彩和动画,声音对于儿童也是必不可少的,舒缓的背景音乐和亲和的提示音可以给予小朋友安全感;而有趣的音效可以带给小朋友快乐。优学猫系列产品从声音体验这个角度而言,也做了很多的功课。在一些需要的场景,我们使用到了3D立体音效果,3D立体声和2D声不同的地方是它是会随着距离衰减的,距离越近声音越大,距离越远声音越小。而在进行游戏题目的过程中,每一道题目我们会根据小朋友答题的具体情况提示不同的声音,时而鼓励,时而启发。中国的家长一般非常重视孩子的双语教育,而优学猫为了满足这个需要,增加了语言切换的功能,在游戏内部可以无缝的在中文和英语下进行切换。整个技术开发的过程中,为了协调背景声音、旁白语音以及音效等多类声音,保证他们之间不会混淆,在多种声音覆盖的情况下突出重点,我们处理了音高和音量;在录音的部分,为了提高声音品质,也对所录的音频文件进行了音高和去噪处理(一般是采用一些滤波的方法,比如高斯滤波);除了目前所展现出来的,还有很多会继续开发和完善的部分,比如我们可以让家长录制自己鼓励声音,然后对在游戏的过程中播放家长的鼓励音,小朋友必定会更加高兴。有了家长的音频如果数据足够时,我们可以使用WaveNet进行语音合成功能的开发,一般语音合成主要使用“长短期记忆网络”(LSTM),而WaveNet是通过空洞卷积神经网络来实现的。

优学猫触觉体验

小朋友一般都有很多自己的玩具,一个起到了陪伴作用,另外就是小朋友可以通过接触感受到触感,触觉是人类七大感官之一,也是人类大脑重要的信息通道,从小培养孩子的触觉,有助于大脑的发育。优学猫是一个有实体积木的产品,不管从本身硬件板子的设计还是到数学积木、英语积木,我们考虑到每一个细节,比如工业设计模型的倒角,再到每个积木块的大小和材质,甚至每一条愣,每一个角度都经过认真打磨,在保证小朋友安全使用为第一前提的基础上,也仅最大的可能提高积木的触感。小朋友在进行优学猫学习和游戏的过程中,眼睛在屏幕和实体积木中来回切换,小朋友也会主动的进行思考,促进大脑的发育,同时声音和画面也会源源不断的传输信息给小朋友,优学猫从而做到增强小朋友手眼协调,脑力锻炼的能力,也更多元化的提供信息给小朋友。

优学猫智能

当今中国是一个人工智能大国,人工智能作为当今社会非常先进的生产力已经渗透到了各个领域,同样教育领域也不例外。优学猫也做到与时俱进,为幼儿教育的智能化出一份力,优学猫从下面几个角度做到了智能化,同样也会继续提高和努力。

  • 大数据与数据智能
    用户每天都会累计使用优学猫完成近千万级别的交互,做数据挖掘的一般常说:Data is rich, Information is pool,大概意思就是数据很多,但是有用的信息却很少,所以对这些数据进行挖掘是非常有必要的,不管为了家长更好的了解孩子还是为了企业的发展方向做参考。而在做具体分析的时候,我们一般会经过数据的收集和整理,再通过各种算法对已经收集好的信息做处理。

  • 语音智能
    优学猫英语在语音智能方面做过实践,目前人工智能语音方面有几个方向:语音识别、语音合成、自然语音处理等等,而优学猫英语使用了语音识别的技术,这里采用了CMU的sphinx开源算法,我们会对小朋友的录音先进行预处理,比如降噪,判别声音端点等等;然后对预处理后的声音进行特征提取,去除对于语音识别无用的信息,特征提取一般采用Mel频率倒谱系数(MFCC)来完成,首先将时域通过快速傅立叶变换转成频域,然后卷积处理;有了特征量之后,优学猫根据训练语音库的特征参数训练出声学模型参数,一般都采用隐马尔可夫模型进行声学模型建模,最终我们可以将声音处理成音素,再针对所给的音素集合和需要配对的声音进行匹配算法进行打分。

Git合并

一般情况下,我们使用merge命令即可合并两个branch,比如现在有两个Branch,一个是branchA,一个是branchB。现在我正处在BranchA,需要合并BranchB,只需要执行

git branch merge BranchB

解决冲突


我们使用代码管理工具的时候,总不可避免的会遇到代码冲突,有关代码冲突的相关介绍可以这里了解冲突时的分支合并,对于更复杂的冲突合并,我们可以借助Source Tree进行解决。
当我们遇到下面信息的时候,就需要手动进行冲突的解决。

CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.

中断一次合并

当然遇到了冲突,我们也可以选择中断这次合并,通过下面命令,在工作目录中有未储藏、未提交的修改时它不能完美处理,除此之外它都工作地很好。

git merge --abort

如果因为某些原因你发现自己处在一个混乱的状态中然后只是想要重来一次,可以执行

git reset --hard HEAD

回到之前的状态或其他你想要恢复的状态。 请牢记这会将清除工作目录中的所有内容,所以确保你不需要保存这里的任意改动。

如果你想要在最终提交前看一下我们这边与另一边之间实际的修改,你可以使用 git diff 来比较将要提交作为合并结果的工作目录与其中任意一个阶段的文件差异。具体使用方法可以参考git-diff

同时我们可以使用下面指令来来清理我们为手动合并而创建但不再有用的额外文件。

git clean -f

撤销合并


我们已经知道如何创建一个合并提交,但有时出错是在所难免的。
我们有两种方法来解决这个问题,这取决于你想要的结果是什么
方法1
reset –hard
这个方法的缺点是它会重写历史,在一个共享的仓库中这会造成问题的。 用简单的话说就是如果其他人已经有你将要重写的提交,你应当避免使用 reset。 如果有任何其他提交在合并之后创建了,那么这个方法也会无效;移动引用实际上会丢失那些改动。
方法2 还原提交
$ git revert -m 1 HEAD

合并指定分支单个文件


# git checkout --patch branchB xxxx

合并指定commit


  • cherry pick 合并单个 commit
    git checkout master
    git cherry-pick xxxxx

Unity快速切换平台

不同平台下的Unity工程可以公用一个Asset,只需要为不同平台缓存Library就可以,只要我们通过symbolic link的方式来完成

symbolic link


Windows平台

Mac平台

Lua重要概念梳理

元表的概念


首先我们先看下面的代码

local t = {}
t.prototype = {
    x = 100,
    y = 100,
    width = 50,
    height = 50
}
t.mt = {}
function t.new(value)
    setmetatable(value, t.mt)
    return value
end
t.mt.__index = t.prototype

t.mt.__newindex = function(table, key, value)
    if key == "tomyuan" then
        rawset(table, key, "Yes")
    end
end
-- 处理
local instance = t.new({x = 33, y = 33})
instance.tomyuan = "YuanBo"
print(instance.tomyuan)
print(instance.x)
print(instance.y)
print(instance.width)
print(instance.height)

运行结果如下

Yes
33
33
50
50

上述我们就更改了原表的行为方法

__index

当我们访问表中的一个元素不存在时,则会去寻找 __index 元方法,如果存在则会返回结果,否则返回nil

local t = {}
t.prototype = {
    x = 100,
    y = 100,
    width = 50,
    height = 50
}
t.mt = {}
function t.new(tb)
    setmetatable(tb, t.mt)
    return tb
end
--
t.mt.__index = function(tb, key)
    if key == "TomYuan" then
        return "YuanBo"
    else
        return "Other"
    end
end
--
local instance = t.new({
    x = 100,
    y = 100
})
print(instance.TomYuan)
print(instance.x)
print(instance.width)

运行结果如下
YuanBo
100
Other

__newindex

当给你的表中不存在的key进行赋值时,lua解释器则会寻找__newindex 元方法,发现存在该方法,则执行该方法进行赋值,通过rawset来进行赋值操作

local t = {}
t.prototype = {
    x = 100,
    y = 100,
    width = 50,
    height = 50
}
t.mt = {}
function t.new(tb)
    setmetatable(tb, t.mt)
    return tb
end

t.mt.__newindex = function(tb, key, value)
    if key == "TomYuan" then
        rawset(tb, key, "Yuan Bo")
    end
end

--
local instance = t.new({
    x = 100,
    y = 100
})
instance.TomYuan = "Who"
print(instance.TomYuan)

输出Yuan Bo

rawget和rawset

rawget是为了绕过__index而出现的,还是上面的代码

local t = {}
t.prototype = {
    x = 100,
    y = 100,
    width = 50,
    height = 50
}
t.mt = {}
function t.new(tb)
    setmetatable(tb, t.mt)
    return tb
end

t.mt.__newindex = function(tb, key, value)
    if key == "TomYuan" then
        rawset(tb, key, "Yuan Bo")
    end
end

--
local instance = t.new({
    x = 100,
    y = 100
})
instance.TomYuan = "Who"
print(rawget(instance ,instance.TomYuan))

输出的nil
针对rawset,如果我们上面代码这么写

local t = {}
t.prototype = {
    x = 100,
    y = 100,
    width = 50,
    height = 50
}
t.mt = {}
function t.new(tb)
    setmetatable(tb, t.mt)
    return tb
end

t.mt.__newindex = function(tb, key, value)
    if key == "TomYuan" then
        tb.key = "Yuan Bo"
    end
end

--
local instance = t.new({
    x = 100,
    y = 100
})
instance.TomYuan = "Who"
print(instance.TomYuan)

此时输出了nil,如果__newindex里面这样的呢?

t.mt.__newindex = function(tb, key, value)
    tb.key = "Yuan Bo"
end

则会报如下错误

stack traceback:
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    ...
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:15: in function '__newindex'
    /usercode/file.lua:23: in main chunk
__index 和 __newindex的区别,__index是直接取表的值,没有对应的key,则根据__index返回,__newindex主要用于表不存在的key的赋值操作。

Unity资源打包

什么是AssetBundle


  • AssetBundle是一个压缩包包含模型、贴图、预制体、声音、场景等资源,在游戏运行过程中被加载
  • 这个压缩包可以认为是一个文件夹,里面包含了多个文件。这些文件可以分为两类:serialized file 和 resource files(序列化文件和源文件)serialized file:资源被打碎放在一个对象中,最后统一被写进一个单独的文件(只有一个);resource files:某些二进制资源(图片、声音)被单独保存,方便快速加载

AssetBundle的主要作用


  • 游戏运行过程中可以被加载
  • AssetBundle自身保存着互相的依赖关系
  • 压缩包可以使用LZMA和LZ4压缩算法,减少包大小,更快的进行网络传输
  • 把一些可以下载内容放在AssetBundle里面,可以减少安装包的大小

AssetBundle打包


如下图所示,我们针对资源在Asset Labels中设置Asset Bundle名字

file

下面我们开始构建AssetBundle包

  • 创建一个文件夹命名Editor,创建一个编辑器扩展类CreateAssetbundles
using UnityEditor;
using System.IO;

public class CreateAssetbundles  {

    [MenuItem("AssetsBundle/Build AssetBundles")]
     static void BuildAllAssetBundles()//进行打包
    {
        string dir = "AssetBundles";
        // 判断该目录是否存在
        if (Directory.Exists(dir) == false)
        {
            // 在工程下创建AssetBundles目录
            Directory.CreateDirectory(dir);
        }
        // 参数一为打包到哪个路径,参数二压缩选项  参数三 平台的目标
        BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None,BuildTarget.StandaloneWindows64);
    }
}
BuildAssetBundleOptions.None:使用LZMA算法压缩,压缩的包更小,但是加载时间更长。使用之前需要整体解压。一旦被解压,这个包会使用LZ4重新压缩。使用资源的时候不需要整体解压。在下载的时候可以使用LZMA算法,一旦它被下载了之后,它会使用LZ4算法保存到本地上。
BuildAssetBundleOptions.UncompressedAssetBundle:不压缩,包大,加载快
BuildAssetBundleOptions.ChunkBasedCompression:使用LZ4压缩,压缩率没有LZMA高,但是我们可以加载指定资源而不用解压全部。
注意使用LZ4压缩,可以获得可以跟不压缩想媲美的加载速度,而且比不压缩文件要小。

所以一般我们选择LZ4的打包压缩方式

AssetBundle加载


  • 从内存加载使用LoadFromMemoryAsync
  • 从本地文件加载可以使用LoadFromFile
  • 从服务器上Web上加载可以使用UnityWbRequest

我们根据上面所给的三种方式,分别进行说明

LoadFromMemoryAsync方式

using UnityEngine;
using System.IO;
using System.Collections;
public class LoadFromFileExample : MonoBehaviour {

    IEnumerator Start () {
        string path = "AssetBundles/iOS/common_icons";
        // 第一种加载AB的方式 LoadFromMemoryAsync
        // 异步加载
        AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(File.ReadAllBytes(path));
        yield return request;
        AssetBundle ab = request.assetBundle;
        // 同步方式
        // AssetBundle ab =  AssetBundle.LoadFromMemory(File.ReadAllBytes(path));

         // 使用里面的资源
        Object[] obj = ab.LoadAllAssets<GameObject>();//加载出来放入数组中
        // 创建出来
        foreach (Object o in obj)
        {
            Instantiate(o);
        }
    }
}

LoadFromFile方式

using UnityEngine;
using System.Collections;

public class LoadFromFileExample : MonoBehaviour {

    IEnumerator Start () {
        string path = "AssetBundles/iOS/common_icons";
        // 第二种加载方式 LoadFromFile
        // 异步加载
        AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(path);
        yield return request;
        AssetBundle ab = request.assetBundle;
        // 同步加载
        // AssetBundle ab = AssetBundle.LoadFromFile(path);

        // 使用里面的资源, 加载出来放入数组中
        Object[] obj = ab.LoadAllAssets<GameObject>();
        // 创建出来
        foreach (Object o in obj)
        {
            Instantiate(o);
        }
     }
}

UnityWbRequest方式

using UnityEngine;
using System.Collections;
using UnityEngine.Networking;

public class LoadFromFileExample : MonoBehaviour {

    IEnumerator Start () {
        // 第三种加载方式使用UnityWbRequest服务器加载使用http本地加载或远程加载使用file
        string uri = @"http://127.0.0.1:8080/AssetBundles/iOS/common_icons";
        UnityWebRequest request = UnityWebRequest.GetAssetBundle(uri);
        yield return request.Send();
        AssetBundle ab = DownloadHandlerAssetBundle.GetContent(request);
        // 使用里面的资源, 加载出来放入数组中
        Object[] obj = ab.LoadAllAssets<GameObject>();
        // 创建出来
        foreach (Object o in obj)
        {
            Instantiate(o);
        }
    }
}

AssetBundle分组策略


  • 把经常更新的资源放在一个单独的包里面,跟不经常更新的包分离
  • 把需要同时加载的资源放在一个包里面
  • 可以把其他包共享的资源放在一个单独的包里面
  • 把一些需要同时加载的小资源打包成一个包
  • 如果对于一个同一个资源有两个版本,可以考虑通过后缀来区分

AssetBundle中的依赖


什么叫做依赖呢?比如我们两个预设(Prefab)需要用到同样的纹理(Texture),那么Prefab和Texture之间就存在着依赖关系,如果我们对两个预设分别打包,Texture就会存在两份,包体就会变大,我们该如何解决这个问题呢?

  • 首先将用到的同样的纹理图打包成AssetBundle
  • 然后分别打包两个预设

这样就可以缩减打包出来的体积大小
此时我们再加载AssetBundle中的Prefab,同时需要将他们依赖的纹理也加载到内存中,否则会出现纹理丢失的情况。如下所示

using UnityEngine;
public class LoadFromFileExample : MonoBehaviour {
    void Start () {
        AssetBundle ab = AssetBundle.LoadFromFile("AssetBundles/iOS/Prefab1");
        AssetBundle textureAB = AssetBundle.LoadFromFile("AssetBundles/iOS/texture");
        // GameObject go = ab.LoadAsset<GameObject>("Prefab1");
        // Instantiate(go);
        Object[] obj = ab.LoadAllAssets<GameObject>();
        // 创建出来
        foreach (Object o in obj)
        {
            Instantiate(o);
        }
    }
}

在打包完AssetBundle之后会出现AssetBundlesAssetBundles.manifest两个文件,所有打包出来的AssetBundle都会放在其中;所以我们可以读取这两个文件,从而达到获取所有AssetBundle的目的。

AssetBundle manifesAB = AssetBundle.LoadFromFile("AssetBundles/iOS/AssetBundles");
AssetBundleManifest manifest= manifesAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
foreach (string name in manifest.GetAllAssetBundles())
{
    print(name);
}

然后我们利用manifest文件加载某个AssetBundle的依赖

...
AssetBundle manifesAB = AssetBundle.LoadFromFile("AssetBundles/AssetBundles");
AssetBundleManifest manifest= manifesAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
foreach (string name in manifest.GetAllAssetBundles())
{
    print(name);
}
string []strs = manifest.GetAllDependencies("Prefab1");
foreach (var name in strs)
{
    AssetBundle.LoadFromFile("AssetBundles/" + name);
}
...

AssetBundle的卸载

一般我们使用AssetBundle.Unload对资源进行卸载工作

  • AssetBundle.Unload(true) 卸载所有资源,即使有资源被使用着
  • AssetBundle.Unload(false)卸载所有没用被使用的资源
  • Resources.UnloadUnusedAssets

文件校验

文件校验可以在文件传输的时候保证文件的完整性,例如A在给我传输了一个文件之前会生成一个校验码,对于这个文件只会生成这一个唯一的校验码,只要传输给我的文件有一点不一样那么校验码就会完全不同。所以A在传输给我文件的时候会把文件和校验码都传输给我,当我取到这个文件的时候我也会使用和A同样一个算法去生成这个文件的校验码,然后拿这个值和A传输给我的校验码比对,如果一样说明这个文件是完整的,如果不一样那么就重新传输。

CRC MD5 SHA1几种方式对比

  • 算法不同。CRC采用多项式除法,MD5和SHA1使用的是替换、轮转等方法
  • 校验值的长度不同。CRC校验位的长度跟其多项式有关系,一般为16位或32位;MD5是16个字节(128位);SHA1是20个字节(160位)
  • 校验值的称呼不同。CRC一般叫做CRC值;MD5和SHA1一般叫做哈希值(Hash)或散列值
  • 安全性不同
  • 效率不同,CRC的计算效率很高;MD5和SHA1比较慢
  • 用途不同。CRC一般用作通信数据的校验;MD5和SHA1用于安全(Security)领域

Unity Asset Bundle Browser Tool

下载地址:
https://github.com/Unity-Technologies/AssetBundles-Browser

参考资料

https://www.jianshu.com/p/5d4145cd900c

Markdown基本语法

标题


在想要设置为标题的文字前面加#来表示
一个#是一级标题,二个#是二级标题,以此类推。支持六级标题。

# 这是一级标题
## 这是二级标题
### 这是三级标题
#### 这是四级标题
##### 这是五级标题
###### 这是六级标题

字体


  • 加粗
    要加粗的文字左右分别用两个*号包起来
  • 斜体
    要倾斜的文字左右分别用一个*号包起来
  • 斜体加粗
    要倾斜和加粗的文字左右分别用三个*号包起来
  • 删除线
    要加删除线的文字左右分别用两个~~号包起来
**这是加粗的文字**
*这是倾斜的文字*`
***这是斜体加粗的文字***
~~这是加删除线的文字~~

引用


在引用的文字前加>即可。引用也可以嵌套,如加两个>>三个>>>
n个等等

>这是引用的内容
>>这是引用的内容
>>>>>>>>>>这是引用的内容

分割线


三个或者三个以上的 – 或者 * 都可以。

---
----
***
*****

图片


![图片alt](图片地址 ''图片title'')
图片alt就是显示在图片下面的文字,相当于对图片内容的解释。
图片title是图片的标题,当鼠标移到图片上时显示的内容。title可加可不加
例如
![blockchain](http://tomyuan.cn/blog/wp-content/uploads/2017/04/0068KyMWly8fdm3fcgzpzj30hr0dcmz1.jpg "TomYuan")

超链接


[超链接名](超链接地址 "超链接title")
title可加可不加
[简书](http://jianshu.com)
[百度](http://baidu.com)

列表


无序列表用 – + * 任何一种都可以

- 列表内容
+ 列表内容
* 列表内容

注意:- + * 跟内容之间都要有一个空格

表格


表头|表头|表头
---|:--:|---:
内容|内容|内容
内容|内容|内容

第二行分割表头和内容。
- 有一个就行,为了对齐,多加了几个
文字默认居左
-两边加:表示文字居中
-右边加:表示文字居右
注:原生的语法两边都要用 | 包起来。此处省略

代码


为了防止转译,前后三个反引号处加了小括号,实际是没有的。这里只是用来演示,实际中去掉两边小括号即可

(```)
  代码...
  代码...
  代码...
(```)

Unity下使用HTTPS

背景说明


Google表示,为保证用户数据和设备的安全,针对下一代 Android 系统(Android P) 的应用程序,将要求默认使用加密连接,这意味着 Android P 将禁止 App 使用所有未加密的连接,因此运行 Android P 系统的安卓设备无论是接收或者发送流量,未来都不能明码传输,需要使用下一代(Transport Layer Security)传输层安全协议,而 Android Nougat 和 Oreo 则不受影响。
因此在Android P 中使用Http请求会遇到如下错误

W/System.err: java.io.IOException: Cleartext HTTP traffic to **** not permitted

使用OKHttp请求则出现

java.net.UnknownServiceException: CLEARTEXT communication ** not permitted by network security policy

此处需要注意的是即便是Web View也是需要https请求。
针对Android而言有一下3中解决方法

  • APP改用https请求
  • targetSdkVersion 降到27以下
  • 在 res 下新增一个 xml 目录,然后创建一个名为:network_security_config.xml 文件(名字自定) ,内容如下,大概意思就是允许开启http请求
    <?xml version="1.0" encoding="utf-8"?>
    <network-security-config>
    <base-config cleartextTrafficPermitted="true" />
    </network-security-config>

    然后在APP的AndroidManifest.xml文件下的application标签增加以下属性

    <application
    ...
    android:networkSecurityConfig="@xml/network_security_config"
    ...
    />

    Unity使用HTTPS


    #if !UNITY_WSA_10_0 && !UNITY_WINRT_8_1 && !UNITY_WSA && !UNITY_WEBPLAYER
    #define SUPPORT_SSL
    #endif
    ...
    using System.Net.Security;
    using System.Security.Cryptography.X509Certificates;
    using System.Threading;
    ...
    #if SUPPORT_SSL
    private bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
    {
    bool isOk = true;
    // If there are errors in the certificate chain,
    // look at each error to determine the cause.
    /*if (sslPolicyErrors != SslPolicyErrors.None)
    {
        for (int i = 0; i < chain.ChainStatus.Length; i++)
        {
            if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown)
            {
                continue;
            }
            chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
            chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
            chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
            // Note: change flags to X509VerificationFlags.AllFlags to skip all security checks
            chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
            bool chainIsValid = chain.Build((X509Certificate2)certificate);
            if (!chainIsValid)
            {
                isOk = false;
                break;
            }
        }
    }*/
    return isOk;
    }
    #endif
    ...
    #if SUPPORT_SSL
    if (m_DownloadUrl.ToLower().StartsWith("https://"))
    {
    ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
    }
    #endif

    针对WWW的GET请求我们也需要进行处理,我们可以使用WebClient().DownloadString的方法来完成

    ...
    #if SUPPORT_SSL
    if (m_DownloadUrl.ToLower().StartsWith("https://"))
    {
    ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
    }
    #endif
    try {
    mes = new System.Net.WebClient().DownloadString(m_DownloadUrl);
    } catch (Exception e) {
    m_Error = e.Message;
    }
    ...

iOS .a文件生成指令

xcodebuild clean -project bluetooth.xcodeproj
xcodebuild -configuration Release -sdk iphonesimulator -arch i386 -project bluetooth.xcodeproj
mv build/Release-iphonesimulator/libbluetooth.a my/libbluetooth_i386.a

xcodebuild clean -project bluetooth.xcodeproj
xcodebuild -configuration Release -sdk iphonesimulator -arch x86_64 -project bluetooth.xcodeproj
mv build/Release-iphonesimulator/libbluetooth.a my/libbluetooth_x86_64.a

xcodebuild clean -project bluetooth.xcodeproj
xcodebuild -configuration Release -sdk iphoneos -arch armv7 -project bluetooth.xcodeproj
mv build/Release-iphoneos/libbluetooth.a my/libbluetooth_armv7.a

xcodebuild clean -project bluetooth.xcodeproj
xcodebuild -configuration Release -sdk iphoneos -arch armv7s -project bluetooth.xcodeproj
mv build/Release-iphoneos/libbluetooth.a my/libbluetooth_armv7s.a

xcodebuild clean -project bluetooth.xcodeproj
xcodebuild -configuration Release -sdk iphoneos -arch arm64 -project bluetooth.xcodeproj
mv build/Release-iphoneos/libbluetooth.a my/libbluetooth_arm64.a

lipo -create my/libbluetooth_arm64.a my/libbluetooth_armv7.a my/libbluetooth_armv7s.a my/libbluetooth_x86_64.a -output my/libbluetooth.a

BLE中writeValue的方式

BLE特征属性

这里提到的特征属性在iOS的BLE中值得是CBCharacteristic类型的properties的属性,我们参考下面的代码看

/*!
 *  @enum CBCharacteristicProperties
 *
 *  @discussion Characteristic properties determine how the characteristic value can be used, or how the descriptor(s) can be accessed. Can be combined. Unless
 *              otherwise specified, properties are valid for local characteristics published via @link CBPeripheralManager @/link.
 *
 *  @constant CBCharacteristicPropertyBroadcast                     Permits broadcasts of the characteristic value using a characteristic configuration descriptor. Not allowed for local characteristics.
 *  @constant CBCharacteristicPropertyRead                          Permits reads of the characteristic value.
 *  @constant CBCharacteristicPropertyWriteWithoutResponse          Permits writes of the characteristic value, without a response.
 *  @constant CBCharacteristicPropertyWrite                         Permits writes of the characteristic value.
 *  @constant CBCharacteristicPropertyNotify                        Permits notifications of the characteristic value, without a response.
 *  @constant CBCharacteristicPropertyIndicate                      Permits indications of the characteristic value.
 *  @constant CBCharacteristicPropertyAuthenticatedSignedWrites     Permits signed writes of the characteristic value
 *  @constant CBCharacteristicPropertyExtendedProperties            If set, additional characteristic properties are defined in the characteristic extended properties descriptor. Not allowed for local characteristics.
 *  @constant CBCharacteristicPropertyNotifyEncryptionRequired      If set, only trusted devices can enable notifications of the characteristic value.
 *  @constant CBCharacteristicPropertyIndicateEncryptionRequired    If set, only trusted devices can enable indications of the characteristic value.
 *
 */
typedef NS_OPTIONS(NSUInteger, CBCharacteristicProperties) {
    CBCharacteristicPropertyBroadcast                                               = 0x01,
    CBCharacteristicPropertyRead                                                    = 0x02,
    CBCharacteristicPropertyWriteWithoutResponse                                    = 0x04,
    CBCharacteristicPropertyWrite                                                   = 0x08,
    CBCharacteristicPropertyNotify                                                  = 0x10,
    CBCharacteristicPropertyIndicate                                                = 0x20,
    CBCharacteristicPropertyAuthenticatedSignedWrites                               = 0x40,
    CBCharacteristicPropertyExtendedProperties                                      = 0x80,
    CBCharacteristicPropertyNotifyEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0)   = 0x100,
    CBCharacteristicPropertyIndicateEncryptionRequired NS_ENUM_AVAILABLE(10_9, 6_0) = 0x200
};

所以如何使用characteristicWriteType通过如下代码即可控制

// 通过characteristic判断
if (self.characteristic.properties & CBCharacteristicPropertyWriteWithoutResponse) {
    self.characteristicWriteType = CBCharacteristicWriteWithoutResponse;
}
if (self.characteristic.properties & CBCharacteristicPropertyWrite) {
    self.characteristicWriteType = CBCharacteristicWriteWithResponse;
}

此处我们以一个设备的特征进行举例,特征具体为

<CBCharacteristic: 0xxxxxxxxx, UUID = XXXX, properties = 0x18, value = <aaxxxxx0 00xxxx00 xx>, notifying = YES>

然后我们拿到properties为0x18,我们进行特征计算得到如下效果:

>>> 0x18 & 4
0
>>> 0x18 & 8
8

所以当前设备需要使用CBCharacteristicPropertyWrite而不是without的WriteValue方式

CodeVS Problem 01 – Cantor表

Cantor表
现代数学的著名证明之一是Georg Cantor证明了有理数是可枚举的。他是用下面这一张表来证明这一命题的: 1/1 1/2 1/3 1/4 1/5 … 2/1 2/2 2/3 2/4 … 3/1 3/2 3/3 … 4/1 4/2 … 5/1 … … 我们以Z字形给上表的每一项编号。第一项是1/1,然后是1/2,2/1,3/1,2/2,…

输入描述 Input Description
整数N(1≤N≤10000000)

输出描述 Output Description
表中的第N项

样例输入 Sample Input
7

样例输出 Sample Output
1/4

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
// 制定移动规律
enum Direct {
	RIGHT = 1,
	LEFTDOWN = 2,
	DOWN = 3,
	RIGHTUP = 4
};

std::vector Locus(int step) {
	std::vector directs;
	int currentTime = 1;
	int l = 1;
	for (int i = 1; i <= step;) {
		if (l % 4 == 1) {
			// 右
			directs.push_back(RIGHT);
			i = i + 1;
		} else if (l % 4 == 3) {
			// 下
			directs.push_back(DOWN);
			i = i + 1;
		} else if (l % 4 == 2) {
			// 左下
			for (int j = 0; j < currentTime; j++) {
				directs.push_back(LEFTDOWN);
				i = i + 1;
				if (i == step + 1) {
					break;
				}
			}
			currentTime = currentTime + 1;
		} else if (l % 4 == 0) {
			// 右上
			for (int j = 0; j < currentTime; j++) { directs.push_back(RIGHTUP); i = i + 1; if (i == step + 1) { break; } } currentTime = currentTime + 1; } l = l + 1; } return directs; } int main() { // 定义默认 X = 1, Y = 1 int X = 1, Y = 1; int step = 0; cin >> step;
	std::vector directs = Locus(step - 1);
	for (int i = 0; i < directs.size(); i++) {
		Direct direct = directs[i];
		if (direct == RIGHT) {
			X = X + 1;
		} else if (direct == LEFTDOWN) {
			X = X - 1;
			Y = Y + 1;
		} else if (direct == DOWN) {
			Y = Y + 1;
		} else if (direct == RIGHTUP) {
			X = X + 1;
			Y = Y - 1;
		}
	}
	cout << Y << "/" << X;
}




游戏引擎开发 – 1

从今天开始,想连载一段时间游戏引擎开发的相关内容,这里想先说下,由于服务器配置真的比较低,所以由于带宽和性能问题,造成的博客打不开的情况,敬请谅解,等有盈利了,再来改善服务器的配置情况。

  • 游戏引擎开篇
  • 一个游戏引擎包括了很多个部分,这里我们首先从渲染以及跨平台考虑,所以选择了OpenGLES 2.0,3.0版本虽然在性能上、以及纹理处理上有非常多的优化,但是目前很多设备都不兼容,所以考虑兼容性的情况,选择OpenGLES 2.0。我这次希望完成的这个引擎主要想可以依托它可以较快速的实现SLG类型的游戏,当然可能开发的过程是艰辛而漫长的阶段,但是还是希望可以最终完成一个自研的跨平台的游戏引擎。

  • iOS opengles使用
  • // .h
    #ifndef MainViewController_h
    #define MainViewController_h
    #import 
    #import 
    
    
    
    @interface MainViewController : GLKViewController {
        
    }
    @property (nonatomic , strong) GLKBaseEffect* mEffect;
    @property (nonatomic, retain) EAGLContext *mContext;
    @end
    
    #endif /* MainViewController_h */
    
    // .m
    #import "MainViewController.h"
    
    @implementation MainViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        //
        
    }
    
    - (void)loadView {
        self.view = [[ GLKView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
        self.view.backgroundColor = [UIColor blackColor];
        self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        GLKView *glkView = (GLKView *)self.view;
        glkView.context = self.mContext;
        glkView.drawableColorFormat = GLKViewDrawableColorFormatRGBA8888;
        [EAGLContext setCurrentContext:self.mContext];
        // 顶点数据,前三个是顶点坐标,后面两个是纹理坐标
        GLfloat squareVertexData[] =
        {
            0.5, -0.5, 0.0f,    1.0f, 0.0f, // 右下
            0.5, 0.5, -0.0f,    1.0f, 1.0f, // 右上
            -0.5, 0.5, 0.0f,    0.0f, 1.0f, // 左上
            
            0.5, -0.5, 0.0f,    1.0f, 0.0f, // 右下
            -0.5, 0.5, 0.0f,    0.0f, 1.0f, // 左上
            -0.5, -0.5, 0.0f,   0.0f, 0.0f, // 左下
        };
        
        GLuint buffer;
        glGenBuffers(1, &buffer);
        glBindBuffer(GL_ARRAY_BUFFER, buffer);
        glBufferData(GL_ARRAY_BUFFER, sizeof(squareVertexData), squareVertexData, GL_STATIC_DRAW);
        glEnableVertexAttribArray(GLKVertexAttribPosition); // 顶点数据缓存
        glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 0);
        glEnableVertexAttribArray(GLKVertexAttribTexCoord0); // 纹理
        glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, (GLfloat *)NULL + 3);
        
        NSString* filePath = [[NSBundle mainBundle] pathForResource:@"yuan" ofType:@"png"];
        NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:@(1), GLKTextureLoaderOriginBottomLeft, nil]; // GLKTextureLoaderOriginBottomLeft 纹理坐标系是相反的
        GLKTextureInfo* textureInfo = [GLKTextureLoader textureWithContentsOfFile:filePath options:options error:nil];
        
        //着色器
        self.mEffect = [[GLKBaseEffect alloc] init];
        self.mEffect.texture2d0.enabled = GL_TRUE;
        self.mEffect.texture2d0.name = textureInfo.name;
    }
    
    
    /**
     *  渲染场景代码
     */
    - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        //启动着色器
        [self.mEffect prepareToDraw];
        glDrawArrays(GL_TRIANGLES, 0, 6);
        
        // 执行逻辑
        
    }
    
    
    - (void)didReceiveMemoryWarning {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    @end
    

    Unix Network Programming – Big or little endian Que

    #include "header.h"
    #include "unp.h"
    int main(int argc, char** argv) 
    {
        union {
            short s;
            char c[sizeof(short)];
        } un;
        un.s = 0x0102;
        printf("%s: ", CPU_VENDOR_OS);
        if (sizeof(short) == 2) {
            if (un.c[0] == 1 && un.c[1] == 2) {
                printf("big-endian\n");
            } else if (un.c[0] == 2 && un.c[1] == 1) {
                printf("little-endian\n");
            } else {
                printf("unknown\n");
            }
        } else {
            printf("sizeof(short)=%d\n", (int)sizeof(short));
        }
        exit(0);
    }
    

    Unity 技术积累

  • Unity中Android和iOS的StreamingAsset路径
  • string StreamAssetPath {
        get {
            string filePath = "";
            #if UNITY_ANDROID && !UNITY_EDITOR
                filePath = "jar:file://" + Application.dataPath + "!/assets/" + flodername + "/";
            #elif UNITY_IPHONE && !UNITY_EDITOR
                filePath = Application.dataPath + "/Raw/";
            #elif UNITY_STANDALONE_WIN || UNITY_EDITOR
                filePath = "file://" + Application.dataPath + "/StreamingAssets" + "/" + flodername + "/";
            #else
                // TODO
            #endif
            return filePath;
        }
    }
    

    iOS – BLE开发笔记

    CB Means CoreBluetooth
    Refercene To:About Core Bluetooth
    The Core Bluetooth framework provides the classes needed for your iOS and Mac apps to communicate with devices that are equipped with Bluetooth low energy wireless technology.
    意思是说CoreBluetooth库提供了必要的类来满足iOS和Mac的App与低耗能蓝牙设备进行数据交互。

    Central and Peripheral Devices

    使用BLE的时候,我们需要区分好什么是中心设备?什么是外围设备?以及两者在蓝牙技术中扮演的角色意义。

    如图1-1所示,我们一般使用的手机、电脑、IPad是中心设备,他们需要蓝牙设备传过来的数据;而蓝牙设备、便携式设备等是外围设备,他们可以发送数据给中心设备。
    Centrals Discover and Connect to Peripherals That Are Advertising(中心设备发现和连接外围设备是通过广播的方式进行)外围设备会广播数据信息。
    我们来看看外围设备的数据组成
    中心设备(Central)如何和外围设备(Peripherals)进行数据交互
    当中心设备与外围设备连接成功之后,外围设备的数据通过服务和特征传过来,同样的中心设备可以修改和设置特征的值。

    Local Centrals and Remote Peripherals

    在iOS中,中心设备通过CBCentralManager对象来表示。主要作用:发现和连接远程外围设备。而外围设备使用CBPeripheral对象来表示。如下图1-4所示
    而远程外围设备的数据格式主要通过CBServiceCBCharacteristic两个对象来表示,如下图1-5所示
    当然除了有【本地中心设备 – 远程外围设备】的模式还有一种就是【本地外围设备 – 远程中心设备】,此时需要CBPeripheralManager和CBCentral对象。当然目前不需要。上述模式如下图所示

    以及【本地外围设备 – 远程中心设备】的数据表示方式如下图所示
    在BLE中,中心设备有一组需要完成的任务,主要有:
    1 discovering and connecting to available peripherals (发现和连接外围设备)
    2 exploring and interacting with the data that peripherals have to offer (完成和外围设备的数据交互)
    再BLE中,外围设备同样有一组不同于中心设备需要完成的任务
    1 publishing and advertising services (分发或者广播服务)
    2 responding to read, write, and subscription requests from connected centrals (回复中心设备的各类请求)
    下面我们说一下如何使用Core Bluetooth Framework完成最普通的BLE的中心设备功能

  • Start up a central manager object.
  • Discover and connect to peripheral devices that are advertising.
  • Explore the data on a peripheral device after you’ve connected to it.
  • Send read and write requests to a characteristic value of a peripheral’s service.
  • Subscribe to a characteristic’s value to be notified when it is updated.
  • 在程序中,我们如何完成这几步呢?

    机器学习 – 分类与逻辑回归

    Classification

    To attempt classification, one method is to use linear regression and map all predictions greater than 0.5 as a 1 and all less than 0.5 as a 0. However, this method doesn’t work well because classification is not actually a linear function.

    The classification problem is just like the regression problem, except that the values y we now want to predict take on only a small number of discrete values. For now, we will focus on the binary classification problem in which y can take on only two values, 0 and 1. (Most of what we say here will also generalize to the multiple-class case.) For instance, if we are trying to build a spam classifier for email, then x(i) may be some features of a piece of email, and y may be 1 if it is a piece of spam mail, and 0 otherwise. Hence, y∈{0,1}. 0 is also called the negative class, and 1 the positive class, and they are sometimes also denoted by the symbols “-” and “+.” Given x(i), the corresponding y(i) is also called the label for the training example.

    Hypothesis Representation

    Decision Boundary

    In order to get our discrete 0 or 1 classification, we can translate the output of the hypothesis function as follows:

    The way our logistic function g behaves is that when its input is greater than or equal to zero, its output is greater than or equal to 0.5:

    Remember.

    From these statements we can now say:

    The decision boundary is the line that separates the area where y = 0 and where y = 1. It is created by our hypothesis function.

    机器学习 – 正规方程法(Unity版本)

    // @author TomYuan
    using UnityEngine;
    using System.Collections;
    using LinearAlgebra;
    using UnityEngine.UI;
    
    public class Main : MonoBehaviour {
    
    	void Start () {
    		// For Example
    		// Predict The House Value
    		// Training Data Set
    		/**
    		 *  RoomNumber    Feet(2)    Coordinates   Cost
    		 *      3          2000           1        250,000
    		 *      2          800            2        300,000
    		 *      2          850            1        150,000
    		 *      2          550            1         78,000
    		 *      4          2000           3        150,000
    		 */
    		/**
    		 *  Cal RoomNumber(3)   Feets(2000)  Coordinates(2)
    		 */
    		// Min = (X(T)*X)(-1)*X(T)*y
    		// f(x) = a + bx1 + cx2 + dx3
    		Matrix X = Matrix.Create (5, 4, 
    			           new double[] {
    							1, 3, 2000, 1,
    							1, 2, 800, 2,
    							1, 2, 850, 1,
    							1, 2, 550, 1,
    							1, 4, 2000, 3
    						});
    		Matrix y = Matrix.Create (5, 1, 
    			            new double[] {
    							250000,
    							300000,
    							150000,
    							78000,
    							150000
    						});
    		Matrix result = ((X.Transpose () * X).Inverse ()) * X.Transpose () * y;
    		// Cal Result
    		Matrix example = Matrix.Create(1, 4, new double[] {
    			1, 3, 2000, 2
    		});
    		// double cost = result.Elements[0, 0] + result.Elements[1, 0] * 3 + result[2, 0] * 2000 + result[3, 0] * 2;
    		// Debug.Log ("RoomNumber(3)   Feets(2000)  Coordinates(2)'s Cost:" + Mathf.Ceil((float)cost));
    		Matrix cost_matrix = example * result;
    		Debug.Log ("RoomNumber(3)   Feets(2000)  Coordinates(2)'s Cost:" + Mathf.Ceil((float)cost_matrix.Elements[0, 0]));
    		GameObject.Find ("Text").GetComponent ().text = "RoomNumber(3)   Feets(2000)  Coordinates(2)'s Cost:" + Mathf.Ceil ((float)cost_matrix.Elements [0, 0]);
    			
    	}
            ......
    }
    

    机器学习 – 多特征研究

    Multiple Features

    Linear regression with multiple variables is also known as “multivariate linear regression”.

    We now introduce notation for equations where we can have any number of input variables.

    The multivariable form of the hypothesis function accommodating these multiple features is as follows:

    hθ(x)=θ0+θ1x1+θ2x2+θ3x3++θnxn

    In order to develop intuition about this function, we can think about θ0 as the basic price of a house, θ1 as the price per square meter, θ2 as the price per floor, etc. x1 will be the number of square meters in the house, x2 the number of floors, etc.

    Using the definition of matrix multiplication, our multivariable hypothesis function can be concisely represented as:

    This is a vectorization of our hypothesis function for one training example; see the lessons on vectorization to learn more.

    Remark: Note that for convenience reasons in this course we assume x(i)0=1 for (i1,,m). This allows us to do matrix operations with theta and x. Hence making the two vectors ‘θ‘ and x(i) match each other element-wise (that is, have the same number of elements: n+1).]

    Gradient Descent For Multiple Variables

    Gradient Descent for Multiple Variables

    The gradient descent equation itself is generally the same form; we just have to repeat it for our ‘n’ features:

    The following image compares gradient descent with one variable to gradient descent with multiple variables:

    Gradient Descent in Practice I – Feature Scaling

    We can speed up gradient descent by having each of our input values in roughly the same range. This is because θ will descend quickly on small ranges and slowly on large ranges, and so will oscillate inefficiently down to the optimum when the variables are very uneven.

    The way to prevent this is to modify the ranges of our input variables so that they are all roughly the same. Ideally:

    −1 ≤ x(i) ≤ 1

    or

    −0.5 ≤ x(i) ≤ 0.5

    These aren’t exact requirements; we are only trying to speed things up. The goal is to get all input variables into roughly one of these ranges, give or take a few.

    Two techniques to help with this are feature scaling and mean normalization. Feature scaling involves dividing the input values by the range (i.e. the maximum value minus the minimum value) of the input variable, resulting in a new range of just 1. Mean normalization involves subtracting the average value for an input variable from the values for that input variable resulting in a new average value for the input variable of just zero. To implement both of these techniques, adjust your input values as shown in this formula:

    Where μi is the average of all the values for feature (i) and si is the range of values (max – min), or si is the standard deviation.

    Note that dividing by the range, or dividing by the standard deviation, give different results. The quizzes in this course use range – the programming exercises use standard deviation.

    For example, if xi represents housing prices with a range of 100 to 2000 and a mean value of 1000, then,

    Gradient Descent in Practice II – Learning Rate

    Debugging gradient descent. Make a plot with number of iterations on the x-axis. Now plot the cost function, J(θ) over the number of iterations of gradient descent. If J(θ) ever increases, then you probably need to decrease α.

    Automatic convergence test. Declare convergence if J(θ) decreases by less than E in one iteration, where E is some small value such as 10(3). However in practice it’s difficult to choose this threshold value.

    It has been proven that if learning rate α is sufficiently small, then J(θ) will decrease on every iteration.

    To summarize:

    If α is too small: slow convergence.

    If α is too large: may not decrease on every iteration and thus may not converge.

    Features and Polynomial Regression

    We can improve our features and the form of our hypothesis function in a couple different ways.

    We can combine multiple features into one. For example, we can combine x1 and x2 into a new feature x3 by taking x1x2.

    Polynomial Regression

    Our hypothesis function need not be linear (a straight line) if that does not fit the data well.

    We can change the behavior or curve of our hypothesis function by making it a quadratic, cubic or square root function (or any other form).

    机器学习 – 线性代数基础

    1 基本概念和符号

    线性代数可以对一组线性方程进行简洁地表示和运算。例如,对于这个方程组:

    在矩阵表达中,我们可以简洁的写作:

    Ax = b其中

    以下是我们要使用符号:

    • 符号A ∈ Rm×n表示一个m行n列的矩阵,并且矩阵A中的所有元素都是实数。
    • 符号x ∈ Rn表示一个含有n个元素的向量。通常,我们把n维向量看成是一个n行1列矩阵,即列向量。如果我们想表示一个行向量(1行n列矩阵),我们通常写作x(xT表示x的转置,后面会解释它的定义)。
    • 一个向量x的第i个元素表示为xi

    • 我们用aij (或Aij,Ai,j,等) 表示第i行第j列的元素:


    2 矩阵乘法

    矩阵 ∈ Rm×∈ Rn×的乘积为矩阵 :

    其中:

    矩阵转置

    逆矩阵

    方程AX=B是线性方程组的矩阵表达形式,称为矩阵方程。其中A称为方程组的系数矩阵X称为未知矩阵B称为常数项矩阵

    这样,解线性方程组的问题就变成求矩阵方程中未知矩阵X的问题。类似于一元一次方程ax=ba≠0)的解可以写成x=a-1b,矩阵方程AX=B的解是否也可以表示为X=A-1B的形式?如果可以,则X可求出,但A-1的含义和存在的条件是什么呢?下面来讨论这些问题。

    定义11  对于n阶方阵A,如果存在n阶方阵C,使得AC=CA=EEn阶单位矩阵),则把方阵C称为A逆矩阵(简称逆阵)记作A-1,即C=A-1

    机器学习 – 梯度下降法

    Gradient Descent

    So we have our hypothesis function and we have a way of measuring how well it fits into the data. Now we need to estimate the parameters in the hypothesis function. That’s where gradient descent comes in.

    Imagine that we graph our hypothesis function based on its fields θ0 and θ1 (actually we are graphing the cost function as a function of the parameter estimates). We are not graphing x and y itself, but the parameter range of our hypothesis function and the cost resulting from selecting a particular set of parameters.

    We put θ0 on the x axis and θ1 on the y axis, with the cost function on the vertical z axis. The points on our graph will be the result of the cost function using our hypothesis with those specific theta parameters. The graph below depicts such a setup.

    We will know that we have succeeded when our cost function is at the very bottom of the pits in our graph, i.e. when its value is the minimum. The red arrows show the minimum points in the graph.

    The way we do this is by taking the derivative (the tangential line to a function) of our cost function. The slope of the tangent is the derivative at that point and it will give us a direction to move towards. We make steps down the cost function in the direction with the steepest descent. The size of each step is determined by the parameter α, which is called the learning rate.

    For example, the distance between each ‘star’ in the graph above represents a step determined by our parameter α. A smaller α would result in a smaller step and a larger α results in a larger step. The direction in which the step is taken is determined by the partial derivative of J(θ0,θ1). Depending on where one starts on the graph, one could end up at different points. The image above shows us two different starting points that end up in two different places.

    The gradient descent algorithm is:

    repeat until convergence:

    where

    j=0,1 represents the feature index number.

    At each iteration j, one should simultaneously update the parameters θ1,θ2,...,θn. Updating a specific parameter prior to calculating another one on the j(th) iteration would yield to a wrong implementation.

    Gradient Descent Intuition

    In this video we explored the scenario where we used one parameter θ1 and plotted its cost function to implement a gradient descent. Our formula for a single parameter was :

    Repeat until convergence:

    Regardless of the slope’s sign for, θ1 eventually converges to its minimum value. The following graph shows that when the slope is negative, the value of θ1 increases and when it is positive, the value of θ1 decreases.

    On a side note, we should adjust our parameter α to ensure that the gradient descent algorithm converges in a reasonable time. Failure to converge or too much time to obtain the minimum value imply that our step size is wrong.

    How does gradient descent converge with a fixed step size α?

    Gradient Descent For Linear Regression

    When specifically applied to the case of linear regression, a new form of the gradient descent equation can be derived. We can substitute our actual cost function and our actual hypothesis function and modify the equation to :

     

    The point of all this is that if we start with a guess for our hypothesis and then repeatedly apply these gradient descent equations, our hypothesis will become more and more accurate.

    So, this is simply gradient descent on the original cost function J. This method looks at every example in the entire training set on every step, and is called batch gradient descent. Note that, while gradient descent can be susceptible to local minima in general, the optimization problem we have posed here for linear regression has only one global, and no other local, optima; thus gradient descent always converges (assuming the learning rate α is not too large) to the global minimum. Indeed, J is a convex quadratic function. Here is an example of gradient descent as it is run to minimize a quadratic function.

    The ellipses shown above are the contours of a quadratic function. Also shown is the trajectory taken by gradient descent, which was initialized at (48,30). The x’s in the figure (joined by straight lines) mark the successive values of θ that gradient descent went through as it converged to its minimum.

    机器学习 – 代价函数


    Cost Function – Intuition I

    If we try to think of it in visual terms, our training data set is scattered on the x-y plane. We are trying to make a straight line (defined by hθ(x)) which passes through these scattered data points.

    Our objective is to get the best possible line. The best possible line will be such so that the average squared vertical distances of the scattered points from the line will be the least. Ideally, the line should pass through all the points of our training data set. In such a case, the value of J(θ0,θ1) will be 0. The following example shows the ideal situation where we have a cost function of 0.

    When θ1=1, we get a slope of 1 which goes through every single data point in our model. Conversely, when θ1=0.5, we see the vertical distance from our fit to the data points increase.

    This increases our cost function to 0.58. Plotting several other points yields to the following graph:

    Cost Function – Intuition II

    A contour plot is a graph that contains many contour lines. A contour line of a two variable function has a constant value at all points of the same line. An example of such a graph is the one to the right below.

    Taking any color and going along the ‘circle’, one would expect to get the same value of the cost function. For example, the three green points found on the green line above have the same value for J(θ0,θ1) and as a result, they are found along the same line. The circled x displays the value of the cost function for the graph on the left when θ0 = 800 and θ1= -0.15. Taking another h(x) and plotting its contour plot, one gets the following graphs:

    When θ0 = 360 and θ1 = 0, the value of J(θ0,θ1) in the contour plot gets closer to the center thus reducing the cost function error. Now giving our hypothesis function a slightly positive slope results in a better fit of the data.

    The graph above minimizes the cost function as much as possible and consequently, the result of θ1 and θ0 tend to be around 0.12 and 250 respectively. Plotting those values on our graph to the right seems to put our point in the center of the inner most ‘circle’.

    机器学习 – 基本概念

    Machine Learning Honor Code

    We strongly encourage students to form study groups, and discuss the lecture videos (including in-video questions). We also encourage you to get together with friends to watch the videos together as a group. However, the answers that you submit for the review questions should be your own work. For the programming exercises, you are welcome to discuss them with other students, discuss specific algorithms, properties of algorithms, etc.; we ask only that you not look at any source code written by a different student, nor show your solution code to other students.

    Guidelines for Posting Code in Discussion Forums

    Scenario 1: Code to delete

    Learner Question/Comment: “Here is the code I have so far, but it fails the grader. Please help me fix it.”

    Why Delete?: The reason is that if there is a simple fix provided by a student, a quick copy and paste with a small edit will provide credit without individual effort.

    Learner Question: A student substitutes words for the math operators, but includes the variable names (or substitutes the equivalent greek letters (θ for ‘theta’, etc). This student also provides a sentence-by-sentence, line by line, description of exactly what their code implements. “The first line of my script has the equation “hypothesis equals theta times X”, but I get the following error message…”.

    Why Delete?: This should be deleted. “Spelling out” the code in English is the same as using the regular code.

    Scenario 2: Code not to delete

    Learner Question: How do I subset a matrix to eliminate the intercept?

    Mentor Response: This probably would be okay, especially if the person posting makes an effort to not use familiar variable names, or to use a context which has nothing to do with the contexts in the assignments.

    It is clearly ok to show examples of Octave code to demonstrate a technique. Even if the technique itself is directly applicable to a programming problem at hand. As long as what is typed cannot be “cut and pasted” into the program at hand.

    E.g. how do I set column 1 of a matrix to zero? Try this in your Octave work area:

    >> A = magic(3)

    >> A(:,1) = 0

    The above is always acceptable (in my understanding). Demonstrating techniques and learning the language/syntax are important Forum activities.

    What is Machine Learning?

    Two definitions of Machine Learning are offered. Arthur Samuel described it as: “the field of study that gives computers the ability to learn without being explicitly programmed.” This is an older, informal definition.

    Tom Mitchell provides a more modern definition: “A computer program is said to learn from experience E with respect to some class of tasks T and performance measure P, if its performance at tasks in T, as measured by P, improves with experience E.”

    Example: playing checkers.

    E = the experience of playing many games of checkers

    T = the task of playing checkers.

    P = the probability that the program will win the next game.

    In general, any machine learning problem can be assigned to one of two broad classifications:

    Supervised learning and Unsupervised learning.

    Supervised Learning

    In supervised learning, we are given a data set and already know what our correct output should look like, having the idea that there is a relationship between the input and the output.

    Supervised learning problems are categorized into “regression” and “classification” problems. In a regression problem, we are trying to predict results within a continuous output, meaning that we are trying to map input variables to some continuous function. In a classification problem, we are instead trying to predict results in a discrete output. In other words, we are trying to map input variables into discrete categories.

    Example 1:

    Given data about the size of houses on the real estate market, try to predict their price. Price as a function of size is a continuous output, so this is a regression problem.

    We could turn this example into a classification problem by instead making our output about whether the house “sells for more or less than the asking price.” Here we are classifying the houses based on price into two discrete categories.

    Example 2:

    (a) Regression – Given a picture of a person, we have to predict their age on the basis of the given picture

    (b) Classification – Given a patient with a tumor, we have to predict whether the tumor is malignant or benign.

    Unsupervised Learning

    Unsupervised learning allows us to approach problems with little or no idea what our results should look like. We can derive structure from data where we don’t necessarily know the effect of the variables.

    We can derive this structure by clustering the data based on relationships among the variables in the data.

    With unsupervised learning there is no feedback based on the prediction results.

    Example:

    Clustering: Take a collection of 1,000,000 different genes, and find a way to automatically group these genes into groups that are somehow similar or related by different variables, such as lifespan, location, roles, and so on.

    Non-clustering: The “Cocktail Party Algorithm”, allows you to find structure in a chaotic environment. (i.e. identifying individual voices and music from a mesh of sounds at a cocktail party).

    Model Representation

    To establish notation for future use, we’ll use x(i) to denote the “input” variables (living area in this example), also called input features, and y(i) to denote the “output” or target variable that we are trying to predict (price). A pair (x(i),y(i)) is called a training example, and the dataset that we’ll be using to learn—a list of m training examples (x(i),y(i));i=1,...,m—is called a training set. Note that the superscript “(i)” in the notation is simply an index into the training set, and has nothing to do with exponentiation. We will also use X to denote the space of input values, and Y to denote the space of output values. In this example, X = Y = ℝ.

    To describe the supervised learning problem slightly more formally, our goal is, given a training set, to learn a function h : X → Y so that h(x) is a “good” predictor for the corresponding value of y. For historical reasons, this function h is called a hypothesis. Seen pictorially, the process is therefore like this:

    When the target variable that we’re trying to predict is continuous, such as in our housing example, we call the learning problem a regression problem. When y can take on only a small number of discrete values (such as if, given the living area, we wanted to predict if a dwelling is a house or an apartment, say), we call it a classification problem.

    机器学习 – octave

    octave机器学习利器:
    下载

    >> 5+6
    ans =  11
    >> 3-2
    ans =  1
    >> 5*8
    ans =  40
    >> 1/2
    ans =  0.50000
    >> 2^6
    ans =  64
    
    >> 1 == 2  %false
    ans = 0
    >> 1 ~= 2  %true
    ans =  1
    >> 8>1 && 0  %AND
    ans = 0
    >> 9>1 || 1  %OR
    ans =  1
    >> xor(1,0)
    ans =  1
    
    >> PS1('>>>');
    >>>
    
    >>>a = 3
    a =  3
    >>>a = 3;  #分号抑制打印
    >>>
    
    >>>a = 3.14;
    >>>a
    a =  3.1400
    >>>disp(a);
     3.1400
    >>>disp(sprintf('2 decimals: %0.2f', a));
    decimals: 3.14
    
    >>>a=pi
    a =  3.1416
    >>>format long
    >>>a
    a =  3.14159265358979
    >>>format short
    >>>a
    a =  3.1416
    
    >>>A = [1 2; 3 4; 5 6]
    A =
      2
      4
      6
    
    >>>a = [1 2;
    4;
    6]
    a =
      2
      4
      6
    
    >>>v = [1 2 3]
    v =
      2   3
    
    >>>v = [1; 2; 3]
    v =
       2
    
    >>>v = 1:0.1:2
    v =
    
     Columns 1 through 4:
    
        1.0000    1.1000    1.2000    1.3000
    
     Columns 5 through 8:
    
        1.4000    1.5000    1.6000    1.7000
    
     Columns 9 through 11:
    
        1.8000    1.9000    2.0000
    
    >>>v = 1:6
    v =
    
       1   2   3   4   5   6
    
    >>>ones(2,3)
    ans =
      1   1
      1   1
    
    >>>w = ones(1,3)
    w =
      1   1
    
    >>>w = rand(3,3)
    w =
    
       0.91025   0.82671   0.14067
       0.90400   0.34350   0.51289
       0.25501   0.24975   0.80750
    
    // 高斯分布 (正态分布)
    >>>w = randn(1,3)
    w =
    
      -0.052546  -1.786869   0.754202
    
    w = -6 + sqrt(10)*(randn(1,10000));
    hist(w)
    hist(w, 50)
    
    >> eye(4)
    ans =
    
    Diagonal Matrix
      0   0   0
      1   0   0
      0   1   0
      0   0   1
    
    >> help
    
      For help with individual commands and functions type
    
        help NAME
    
      (replace NAME with the name of the command or function you would
      like to learn more about).
    
      For a more detailed introduction to GNU Octave, please consult the
      manual.  To read the manual from the prompt type
    
        doc
    
      GNU Octave is supported and developed by its user community.
      For more information visit http://www.octave.org.
    
    >> A = [1 2; 3 4; 5 6]
    A =
      2
      4
      6
    
    >> size(A)
    ans =
      2
    
    >> sz = size(A)
    sz =
      2
    
    >> size(sz)
    ans =
      2
    
    >> size(A,1)
    ans =  3
    >> size(A,2)
    ans =  2
    
    >> V = [1 2 3 4]
    V =
      2   3   4
    
    >> length(V)
    ans =  4
    >> length(A)
    ans =  3
    
    >> pwd
    ans = C:\Users\xin
    >> cd 'E:\TEMPsrc\octave'
    >> pwd
    ans = E:\TEMPsrc\octave
    >> ls
    >> load featuresX.dat
    >> load priceY.dat
    >> load('featuresX.dat')
    >> who
    Variables in the current scope:
    
    a    ans  b    c
    >> whos
    Variables in the current scope:
    
       Attr Name        Size                     Bytes  Class
    
       ==== ====        ====                     =====  =====
    
            a           1x1                          8  doubl
    e
            ans         1x17                        17  char
            b           1x1                          8  doubl
    e
            c           1x1                          8  doubl
    e
            d           3x2                         48  doubl
    e
    
    Total is 26 elements using 89 bytes
    >> who
    Variables in the current scope:
    
    a    ans  b    c    d
    
    >> clear a
    >> who
    Variables in the current scope:
    
    ans  b    c    d
    >> save hello.mat d
    >> clear
    >> who
    >> v = [1 2; 3 4; 5 6; 7 8; 9 0]
    v =
      2
      4
      6
      8
      0
    
    < -ascii  %save as text(ASCII)
    >> A = [1 2; 3 4; 5 6]
    A =
      2
      4
      6
    
    >> A(3,2)
    ans =  6
    >> A(2,:)
    ans =
      4
    
    >> A(:,2)
    ans =
       4
    
    >> A([1 3], 🙂
    ans =
    
       1   2
       5   6
    
    >> A(:,2) = [10;11;12]
    A =
      10
      11
      12
    
    >> A = [A, [100;101;102]]
    A =
       10   100
       11   101
       12   102
    
    >> A(:)
    ans =
         3
        10
        12
       101
    
    >> A = [1 2; 3 4; 5 6];
    >> B = [11 12; 13 14; 15 16];
    >> C = [A B]
    C =
       2   11   12
       4   13   14
       6   15   16
    
    >> C = [A; B]
    C =
       2
       4
       6
      12
      14
      16
    
    >> A = [1 2; 3 4; 5 6];
    >> B = [11 12; 13 14; 15 16];
    >> C = [1 1; 2 2];
    >> A*C
    ans =
       5
      11
      17
    
    >> A .* B
    ans =
      24
      56
      96
    
    >> A .^ 2
    ans =
       4
      16
      36
    
    >> V = [1; 2; 3];
    >> 1 ./ V
    ans =
    
       1.00000
       0.50000
       0.33333
    
    >> 1 ./ A
    ans =
    
       1.00000   0.50000
       0.33333   0.25000
       0.20000   0.16667
    
    >> log(V)
    ans =
    
       0.00000
       0.69315
       1.09861
    
    >> exp(V)
    ans =
    
        2.7183
        7.3891
       20.0855
    
    >> abs(V)
    ans =
       2
    
    >> v = [1;2;3]
    v =
       2
    
    >> v + ones(length(v), 1)
    ans =
       3
    
    >> v + ones(3,1)
    ans =
       3
    
    >> v + 1
    ans =
       3
    
    >> A
    A =
      2
      4
      6
    
    >> A'
    ans =
      3   5
      4   6
    
    >> a = [1 15 2 0.5]
    a =
    
        1.00000   15.00000    2.00000    0.50000
    
    >> val = max(a)
    val =  15
    >> [val, ind] = max(a)
    val =  15
    ind =  2
    
    a =
    
        1.00000   15.00000    2.00000    0.50000
    
    >> a < 3
    ans =
      0   1   1
    
    >> find(a < 3)
    ans =
      3   4
    
    >> A = magic(3)
    A =
      1   6
      5   7
      9   2
    
    >> [r, c] = find(A >= 7)
    r =
       3
    
    c =
       2
    
    >> a
    a =
    
        1.00000   15.00000    2.00000    0.50000
    
    >> sum(a)
    ans =  18.500
    >> prod(a)
    ans =  15
    >> floor(a)
    ans =
      15    2    0
    
    >> ceil(a)
    ans =
      15    2    1
    
    >> max(rand(3), rand(3))
    ans =
    
       0.957477   0.083887   0.459507
       0.799441   0.975439   0.927632
       0.888604   0.942436   0.612661
    
    >> A
    A =
      1   6
      5   7
      9   2
    
    >> max(A, [], 1)
    ans =
      9   7
    >> max(max(A))
    ans =  9
    >> max(A(:))
    ans =  9
    
    >> A = magic(5)
    A =
      24    1    8   15
       5    7   14   16
       6   13   20   22
      12   19   21    3
      18   25    2    9
    
    >> sum(A,1)
    ans =
      65   65   65   65
    
    >> sum(A,2)
    ans =
       65
       65
    >> sum(sum(A.*eye(5)))
    ans =  65
    
    >> eye(9)
    ans =
    
    Diagonal Matrix
      0   0   0   0   0   0   0   0
      1   0   0   0   0   0   0   0
      0   1   0   0   0   0   0   0
      0   0   1   0   0   0   0   0
      0   0   0   1   0   0   0   0
      0   0   0   0   1   0   0   0
      0   0   0   0   0   1   0   0
      0   0   0   0   0   0   1   0
      0   0   0   0   0   0   0   1
    
    >> flipud(eye(9))
    ans =
    
    Permutation Matrix
      0   0   0   0   0   0   0   1
      0   0   0   0   0   0   1   0
      0   0   0   0   0   1   0   0
      0   0   0   0   1   0   0   0
      0   0   0   1   0   0   0   0
      0   0   1   0   0   0   0   0
      0   1   0   0   0   0   0   0
      1   0   0   0   0   0   0   0
      0   0   0   0   0   0   0   0
    
    >> A = magic(3)
    A =
      1   6
      5   7
      9   2
    
    >> pinv(A)
    ans =
    
       0.147222  -0.144444   0.063889
      -0.061111   0.022222   0.105556
      -0.019444   0.188889  -0.102778
    
    >> temp = pinv(A)
    temp =
    
       0.147222  -0.144444   0.063889
      -0.061111   0.022222   0.105556
      -0.019444   0.188889  -0.102778
    
    >> temp * A
    ans =
    
       1.00000   0.00000  -0.00000
      -0.00000   1.00000   0.00000
       0.00000   0.00000   1.00000
    
    >> t=[0:0.01:0.98];
    >> y1 = sin(2*pi*4*t);
    >> plot(t,y1);
    >> t=[0:0.01:0.98];
    >> y2 = cos(2*pi*4*t);
    >> plot(t,y2);
    >> plot(t, y1);
    >> hold on;
    >> plot(t, y2, 'r');
    >> xlabel('time')
    >> ylabel('value')
    >> legend('sin', 'cos')
    >> title('my plot')
    >> print -dpng 'myplot.png'
    >> close
    >> figure(1); plot(t, y1);
    >> figure(2); plot(t, y2);
    >> subplot(1,2,1);
    >> plot(t, y1);
    >> subplot(1,2,2);
    >> plot(t, y2);
    >> axis([0.5 1 -1 1])
    >> clf;
    >> A = magic(5);
    >> imagesc(A)
    >> imagesc(A), colorbar, colormap gray;
    

    iOS开发 – 静态库.a文件制作

    开发XCode工程选项如下所示

    我们选择静态库

    工程创建完成后如下图所示

    // .h
    #import 
    @interface MyPrint : NSObject
    + (void) print;
    @end
    
    // .m
    #import "MyPrint.h"
    
    @implementation MyPrint
    
    + (void) print {
        NSLog(@"测试输出");
    }
    
    @end
    

    添加头文件

    之后设置编译目标为8.0,这样向上兼容
    编译模拟器和真机版本的静态库,并进行合并,如下图所示

    合并

    bogon:Tool tomcomputer$ lipo -create ./Debug-iphone
    Debug-iphoneos/        Debug-iphonesimulator/ 
    bogon:Tool tomcomputer$ lipo -create ./Debug-iphoneos/libMyPrint.a  ./Debug-iphonesimulator/libMyPrint.a -output libMyPrint.a
    bogon:Tool tomcomputer$ 
    

    将合并的.a文件和.h声明文件加入到一个新项目中进行测试
    测试成功
    同理unity中使用的bundle文件和.so文件分别用Mac和Android可以进行编译出来使用

    Yii2.0 – Word上传下载和预览功能

    我们这篇来说下怎么上传Word文档以及预览功能,下载功能之前有所提到,这里我先单独将下载功能单独提出来。

    下载

    /**
     * 下载单独文件接口
     * @param file_name 需要下载的文件
     * @param ext 文件扩展名
     * @example http://127.0.0.1/basic/web/index.php?r=my/downfile&file_name=1.doc
     */
    public function actionDownfile($file_name, $ext = "doc") {
        // 
        if (file_exists($file_name)) {
            // 文件存在
            ob_start();
            $download_filename = date("YmdHis") . "." . $ext;
            header("Content-type:application/octet-stream"); 
            header("Accept-Ranges:bytes");
            header("Content-Disposition:attachment;filename=" . $download_filename);
            $size = readfile($file_name);
            header("Accept-Length:" . $size);
        }
    }
    

    效果如下图所示

    上传功能

    在前面php文件上传中,我们使用ajax完成了文件上传功能。这里我们使用Yii2.0来完成文件上传功能。
    由于前端使用ajax来传文件,所以我们这里依旧展示服务器的代码即可

    /**
     * 上传文件接口
     * 前端Ajax调用
     */
    public function actionUploadfile() {
        if (isset($_FILES) && isset($_FILES['myfile'])) {
            $err = "";
            $picname = $_FILES['myfile']['name'];   // 这里和提交的file表单名字相同, < input id="fileupload" type="file" name="myfile" >
            $picsize = $_FILES['myfile']['size'];
            if ($picname != "") { 
                if ($picsize > 512000 * 2) { // 限制上传大小 
                    $err = '文件不能超过1M'; 
                } 
                $type = strstr($picname, '.'); //限制上传格式 
                if ($type != ".doc") { 
                    $err = '文件格式不对!'; 
                }
                if ($err == "") {
                    $rand = rand(100, 999); 
                    $pics = date("YmdHis") . $rand . $type; //命名图片名称 
                    
                    //上传路径 
                    $pic_path = "DocDir/" . $pics; 
                    move_uploaded_file($_FILES['myfile']['tmp_name'], $pic_path);
                }
            } 
            $size = round($picsize / 1024, 2); //转换成kb 
            $arr = array( 
                'name' => $picname, 
                'filename' => $pics, 
                'size' => $size 
            ); 
            echo json_encode($arr); //输出json数据 
        }
    }
    

    在线预览功能

    我们来看看在线预览怎么做
    我们使用flexPaper + swftool的方式来做
    首先我们先下载flexPaper,然后安装swftool
    swftool参考:http://blog.csdn.net/zhizaibide1987/article/details/28901511
    最终展示效果:http://47.94.156.112/FlexPaper
    同时参考:http://blog.chinaunix.net/uid-25512152-id-3077202.html

    Yii2.0 – 文件打包接口

    这篇文章我们通过Yii2.0框架来完成文件打包下载功能,相信这个功能会在很多的地方有所使用。

    Yii 2.0

    开篇我们简单说下Yii框架,这个框架以MVC框架为基础,主要我们知道Controller、Model和View在哪里写就可以,我这里以basic为基础,同样的这篇文章不处理其他事情,比如路由什么的,只关注多文件打包zip以及下载功能。
    比如这样的URL:http://127.0.0.1/basic/web/index.php?r=my/index
    表示MyController的index方法
    Controller文件写在controllers中
    MyController.php

    request->isPost) {
                $user_name = Yii::$app->request->post("name", "");
                echo $user_name;
            }
        }
    }
    

    这里我们新建一个action来作为多个文件打包下载的接口

    /**
     * 多文件打包下载
     * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
     * @example file_list [1.txt, 2.txt, 3.txt]
     */
    public function actionMutifiledown($file_list) {
        // TODO
        var_dump(json_decode($file_list, true));
    }
    


    现在我们的web目录下存在1.doc、2.doc、3.doc、4.doc几个文件,我们怎么将他们打包下载呢?

    打包Zip

    我们使用PHP的ZipArchive扩展,直接上代码

    /**
     * 多文件打包下载
     * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
     * @example file_list [1.txt, 2.txt, 3.txt]
     */
    public function actionMutifiledown($file_list) {
        // 现在存在1.doc;2.doc;3.doc;4.doc几个文件
        $zip = new \ZipArchive;    // 存在Zip扩展
        if ($zip->open("1.zip", \ZIPARCHIVE::CREATE)!==TRUE) {
            // 创建1.zip 确保存在权限
            exit();
        }
        $zip->addFile("1.doc");
        $zip->addFile("2.doc");
        $zip->addFile("3.doc");
        $zip->addFile("4.doc");
        $zip->close();
        // 这样我们就打包好了
    }
    

    Zip下载

    我们知道已经生成了zip文件,下面直接下载到本地,代码如下:

    /**
     * 多文件打包下载
     * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
     * @example file_list [1.txt, 2.txt, 3.txt]
     */
    public function actionMutifiledown($file_list) {
        // 现在存在1.doc;2.doc;3.doc;4.doc几个文件
        $zip = new \ZipArchive;    // 存在Zip扩展
        if ($zip->open("1.zip", \ZIPARCHIVE::CREATE)!==TRUE) {
            // 创建1.zip 确保存在权限
            exit();
        }
        $zip->addFile("1.doc");
        $zip->addFile("2.doc");
        $zip->addFile("3.doc");
        $zip->addFile("4.doc");
        $zip->close();
        // 这样我们就打包好了
        ob_start();
        $filename = "1.zip";
        header("Content-type:application/octet-stream"); 
        header("Accept-Ranges:bytes");
        header("Content-Disposition:attachment;filename=1.zip");
        $size = readfile($filename);
        header("Accept-Length:" . $size);
    }
    

    完善接口

    我们上面只是完成了功能,这里还有一些内容需要完善。
    接口:http://127.0.0.1/basic/web/index.php?r=my/mutifiledown&file_list=[“1.doc”,”2.doc”,”3.doc”]

    /**
     * 多文件打包下载
     * @param file_list 表示下载文件的列表,数据格式可以多种,这里以json为例
     * @example file_list [1.txt, 2.txt, 3.txt]
     * @param $ext 文件可打包扩展名,默认doc
     */
    public function actionMutifiledown($file_list, $ext = "doc") {
        //
        $file_arr = json_decode($file_list, true);
        if (!empty($file_arr)) {
            $zipComp = new \ZipArchive;    // Zip扩展
            $zipName = strtotime(date("Y-m-d H:i:s")) . ceil(rand() % 100) . "zip";
            if ($zipComp->open($zipName, \ZIPARCHIVE::CREATE) !== TRUE) {
                exit();
            }
            foreach ($file_arr as $file) {
                $fileArr = explode(".", $file);
                $fileExt = $fileArr[count($fileArr) - 1];
                if ($fileExt == $ext) {
                    if (file_exists($file)) {
                        $zipComp->addFile($file);
                    }
                }
            }
            $zipComp->close();
            // 开始下载
            if (file_exists($zipName)) {
                ob_start();
                $download_filename = date("YmdHis") . "_archive.zip";
                header("Content-type:application/octet-stream"); 
                header("Accept-Ranges:bytes");
                header("Content-Disposition:attachment;filename=" . $download_filename);
                $size = readfile($zipName);
                header("Accept-Length:" . $size);
            }
        }
    }
    

    Unity开发框架整理

    ToLua

    此Unity框架支持Lua,所使用lua为ToLua,这里介绍了有关uLua以及toLua的相关内容。当然除了uLua和toLua意外,还有腾讯开发的XLua,xLua更加灵活,支持直接给C#进行打补丁的操作。
    使用toLua开发的游戏很多,如下图所示



    这里面包括了最火的王者荣耀
    https://github.com/topameng/CsToLua

    Unity


    一般成熟项目的Unity工程至少包含
    Editor
    Plugins
    Resources
    StreamingAssets
    这几个目录
    然后框架中Game作为C#核心库

    开发框架

  • 主入口Main
  • GameMain作为单例一直存在与整个游戏的生命周期内,主要完成初始化工作。

  • 基本类关系
  • Facade
    作为模块管理器存在,主要作用完成模块的初始化添加、获取与删除。
    主要接口有AddModule、GetModule、RemoveModule,而我们的框架使用的是继承于Facade的AppFacade。
    在Main中除了初始化AppFacade还有就是初始化状态机。

  • GameStateMachine
  • GameStateMachine stateMachine = AppFacade.Instance.AddModule ();
    

    状态机通过调用StateTransition方法来完成游戏状态的转变以及执行相应的代码

    public T StateTransition () where T : GameStateBase {
        GameStateBase oldState = currentState;
        currentState = gameObject.AddComponent ();
        if (oldState != null)
            oldState.PrepareExit ();
    
        currentState.PrepareEnter ();
        if (oldState != null)
            oldState.Exit ();
    
        currentState.Enter ();
        if (oldState != null)
            oldState.DoneExit ();
    
        currentState.DoneEnter ();
        Object.Destroy (oldState);      // 销毁旧的状态(组件)
        return currentState as T;
    }
    

    大致的流程分
    1 旧状态->PrepareExit
    2 旧状态->Exit
    3 新状态->Enter
    4 旧状态->DoneExit
    5 新状态->DoneExit

    状态机初始化完成操作:
    <状态:Initializing>
    1 添加FileHelper模块,主要用于文件操作。同时配置文件搜索路径。
    2 添加AssetBundleLoader模块,主要用于AssetBundle的相关操作。
    3 状态转变为Upgrading。
    <状态:Upgrading>
    1 添加ResourceDownloader模块,用于资源和代码下载。
    2 完成Upgrade工作。
    3 初始化AssetBundleLoader。
    4 添加并初始化ResourceManager,主要用于资源管理。
    5 添加并初始化LuaManager,主要用于Lua管理。
    Lua初始化调用

    protected virtual void OnLoadFinished()
    {
        luaState.Start();
        StartLooper();
        StartMain();
    }
    
    protected virtual void StartMain()
    {
        luaState.DoFile("Main.lua");
        levelLoaded = luaState.GetFunction("OnLevelWasLoaded");
        CallMain();
    }
    

    此时已经运行到Main.lua,前提一定完成Lua Binding。
    6 状态转变为Ready。
    此时完成了框架初始化的工作。这里需要特别注意的是在执行MainLua之前,我们需要加载完成需要使用的Lua,通过接口:DoLoadAssetBundleAsync。

    IEnumerator DoLoadAssetBundleAsync (Action onFinish) {
        // Get lua manifest
        string path = GameSettings.AppContentPath + GameSettings.GetOS () + "/" + GameConsts.luaBundleFile;
        string json = File.ReadAllText (path);
    
        LuaBundleList manifest = JsonUtility.FromJson (json);
    
        for (int i = 0; i < manifest.items.Count; i++) {
            string abName = manifest.items [i];
    
            yield return StartCoroutine (DoLoadAssetBundleAsync (abName, delegate (AssetBundle ab) {
                if (!ab) {
                    Debug.Log ("[LuaManager] DoLoadAssetBundleAsync : Can't find the assetbundle " + abName);
                    return;
                }
    
                // Add the bundle to searched bundles.
                abName = abName.Replace ("lua/", "");
                abName = abName.Replace (GameSettings.ExtName, "");
                LuaFileUtils.Instance.AddSearchBundle (abName.ToLower (), ab);
            }));
        }
            
        onFinish.Invoke ();
    }
    
    IEnumerator DoLoadAssetBundleAsync (string abName, Action onFinish) {
        string abPath = FileHelper.Instance.FindFile (abName);
        if (abPath == null) {
            onFinish (null);
            yield break;
        }
    
        AssetBundle ab = null;
        if (GameSettings.LuaEncryptMode) {
            abPath = "file://" + abPath;
    
            WWW www = new WWW (abPath);
            yield return www;
    
            byte[] encData = www.bytes;
            byte[] decData = CryptoUtil.Decrypt (encData, GameSettings.Secret);
    
            var loadRequest = AssetBundle.LoadFromMemoryAsync (decData);
            yield return loadRequest;
    
            ab = loadRequest.assetBundle;
        } else {
            var loadRequest = AssetBundle.LoadFromFileAsync (abPath);
            yield return loadRequest;
    
            ab = loadRequest.assetBundle;
        }
    
        onFinish (ab);
    }
    

    故此我们知道LuaBundles.json是我们需要初始化的luaBundle。

  • Lua
  • CustomSettings是我们需要绑定C#接口给Lua调用定义的地方。
    Main.Lua是LuaManager会初始化调用的文件,而Main函数是最开始调用的。

    require "init"
    ...
    local app = nil
    function Main()
        // 这里GameMain是游戏整个生命周期的都存在的
        local gameMain = UGameObject.Find("GameMain")
        app = App:instance(gameMain)
        app:registerScene(MainScene())
        app:registerScene(DinningScene())
        app:registerScene(LoadScene())
        app:registerTransition(MaskTransition())
        app:runScene(SceneConsts.Main)
    end
    

    init主要完成了lua脚本以及运行框架的所有初始化,大致完成了如下功能定义和初始化:
    1 async(Lua异步接口)
    async.iterator = function(fn_list)
    async.waterfall = function(fn_list, cb)
    async.forEach = function(arr, it, callback)
    async.forEachSeries = function(arr, it, callback)
    参考:Github Async

    2 class(类定义)

    function clone(object)
        local lookup_table = { }
        local function _copy(object)
            if type(object) ~= "table" then
                return object
            elseif lookup_table[object] then
                return lookup_table[object]
            end
            local new_table = { }
            lookup_table[object] = new_table
            for key, value in pairs(object) do
                new_table[_copy(key)] = _copy(value)
            end
            return setmetatable(new_table, getmetatable(object))
        end
        return _copy(object)
    end
    function class(base)
        local c = { }
        if type(base) == 'table' then
            -- our new class is a shallow copy of the base class!
            for i, v in pairs(base) do
                c[i] = v
            end
            c._base = base
        end
        -- the class will be the metatable for all its objects,
        -- and they will look up their methods in it.
        c.__index = c
    
        -- expose a constructor which can be called by ()
        local mt = { }
        mt.__call = function(class_tbl, ...)
            local obj = { }
            setmetatable(obj, c)
            if class_tbl.init then
                class_tbl.init(obj, ...)
            else
                -- make sure that any stuff from the base class is initialized!
                if base and base.init then
                    base.init(obj, ...)
                end
            end
            return obj
        end
        c.is_a = function(self, klass)
            local m = getmetatable(self)
            while m do
                if m == klass then return true end
                m = m._base
            end
            return false
        end
        setmetatable(c, mt)
        return c
    end
    -- A = class()
    -- function A:init(x)
    --     self.x = x
    -- end
    -- function A:test()
    --     print(self.x)
    -- end
    
    -- B = class(A)
    -- function B:init(x, y)
    --     A.init(self, x)
    --     self.y = y
    -- end
    
    -- b = B(1, 2)
    -- b:test()
    
    -- print(b:is_a(A))
    

    3 function(公用函数合集)
    4 const常量合集,用于MVC分层的常量定义。
    5 Utility
    6 Core主要包括App、Controller、Scene、View以及消息机制和管理器
    7 game中主要是逻辑代码和逻辑的MVC分层

    Python – 简易识别

    有如下类似训练数据多组

    主要目的是通过某种算法实现计算机可以学习认识数字0
    比如有如下算法(暂时不合理)

    # -*- TomYuan
    import numpy as np
    import os
    import matplotlib.pyplot as plt
    '''
    0000000000000000000000000
    0000000000000000000000000
    0000000001111111000000000
    0000000010000000100000000
    0000000100000000010000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000001000000000001000000
    0000000100000000010000000
    0000000010000000100000000
    0000000001111111000000000
    0000000000000000000000000
    0000000000000000000000000
    '''
    def readData(fileName):
        #define array (25 * 22) {m * n}
        m = 25
        n = 22
        matrix = [0] * n
        fr = open(fileName)
        l = 0;
        for line in fr.readlines():
            #put all number into array
            matrix[l] = [0] * (len(line) - 1)
            for i in range(1, len(line)):
                # print (line[i - 1])
                matrix[l][i - 1] = line[i - 1]
            l = l + 1
        return matrix
    
    def calMatrix(matrix):
        ret = 0;
        for i in range(1, 22):
            for j in range(1, 25):
                if matrix[i][j] == '1':
                    ret = ret + 1
                    #
                    if matrix[i - 1][j] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    #
                    if matrix[i - 1][j - 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    #
                    if matrix[i][j - 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    ##
                    #
                    if matrix[i + 1][j] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    #
                    if matrix[i + 1][j + 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    #
                    if matrix[i][j + 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    ##
                    #
                    if matrix[i - 1][j + 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
                    #
                    if matrix[i + 1][j - 1] == '1':
                        ret = ret + 2
                    else:
                        ret = ret - 2
        return ret
    print(calMatrix(readData("data5.set")))
    

    我们来思考下如何实现一个非常简单的聚类,类似这样

    Python最小二乘法

    # -*- TomYuan
    import numpy as np
    import os
    import matplotlib.pyplot as plt
    
    xcord = [];
    ycord = [];
    def drawScatterDiagram(fileName):
        fr = open(fileName)
        for line in fr.readlines():
            line_arr = line.split(",")
            xcord.append(float(line_arr[0]));
            ycord.append(float(line_arr[1]));
    
    drawScatterDiagram("ml.txt")
    
    def leastSquare(xcord, ycord):
        t1 = 0; t2 = 0; t3 = 0; t4 = 0
        for num in range(0, len(xcord)):
            t1 += xcord[num] * xcord[num];
            t2 += xcord[num];
            t3 += xcord[num] * ycord[num];
            t4 += ycord[num];
        a = (t3 * len(xcord) - t2 * t4) / (t1 * len(xcord) - t2 * t2);
        b = (t1 * t4 - t2 * t3) / (t1 * len(xcord) - t2 * t2)
        return a, b
    
    def lastDraw():
        plt.scatter(xcord, ycord, s=30, c='red')
        print(ycord[len(ycord) - 1])
        a, b = leastSquare(xcord, ycord)
        
        x1 = float(xcord[0])
        x2 = 1.0
    
        y1 = a * x1 + b;
        y2 = a * x2 + b;
    
        plt.plot([x1, x2],
                 [y1, y2])
        plt.show()
    
    lastDraw()
    

    Web开发中的调试

    Chrome

    C++调试功能强大,我们在开发HTML5的时候也需要掌握一定的调试方法才可以更加从容的面对Bug。
    我们这里以Chrome为例对调试方法进行说明。

  • 控制台
  • 为了让之前没有接触过Web的朋友也有所了解,我先介绍下控制台是什么,我们打开Chrome,按下F12。如下图所示:

    Elements主要展示当前页面的DOM元素组成,右侧是样式表和事件监听器。
    Network主要用来抓取HTTP数据流

    开启Record,可以抓取当前的网络流,包括网络中所有的文件流和所用的资源。

    HTTP状态码

    通过这些状态码我们可以很容易知道是服务器端的问题还是客户端的问题。
    Preview可以预览对应文件编译后的代码。

  • JS断点功能
  • Source中我们可以使用断点功能来对js进行调试


    如上图所示,这里的断点调试类似VS中的断点调试。

  • Console

  • Console中我们不仅可以测试对应的js代码,而且如果我们的代码中有错误这里也会有所提示。
    点击我们就可以找到对应的错误了。

    游戏开发讨论(持续更新)

    前言

    这里我将会做为一个专门的区域和读者反馈区一样的,这里多多的说一些技巧方面的内容,当然作为开发者,这里不限游戏引擎,也不限任何技术,希望大家多多留言!也希望多多反馈意见。

  • Unity中yield的意义
  • Unity中视频播放
  • MovieTexture和MobileMovieTexture
    MovieTexture可以非常的方便的在Unity中播放影片(ogv格式),但是只是支持PC端,对于Iphone和Android并不支持,这是我们有一个新的选择可以实现在移动端视频播放的相关功能(MobileMovieTexture),这个插件支持视频的基本操作,包括视频的播放(Play)、停止(Stop)、暂停(Pause)以及快放、慢放等,通过设置shader(着色器)的信息,我们还可以将视频变成老相片格式或者信号差模式以及重影模式等等。整体来说做Unity的视频相关功能还是不错的选择。

    Egret打包

    HTML5

    egret publish

    iOS

    egret create_app PKingIOS -f PKingDuckling -t ./egret-ios-support-4.0.3

    Android

    ~$ egret create_app PKingAndroid -f PKingDuckling -t ./egret-android-support-4.0.3/egret-android-support-4.0.3/
    您正在使用的引擎版本为 4.0.2
    > copy from project template …
    > replace all configure elements …
    > rename project name …
    native拷贝共计耗时:0.33秒
    native拷贝共计耗时:0.149秒
    创建完毕,共计耗时:0.996秒
    ~$

    注意事项

    1. 修改横屏
    2. 去掉egret.assert以及window和document相关函数接口,否则会报错

    Java代码翻写成OC样例

    Java代码

    // TranslateData
    public void translateData(byte[] data, int size) {
        for (int i = 0; i < size; i++) {
            byte b = data[i];
            if (head_count < HEAD_LENGTH) {
                switch (head_count) {
                    case 0:
                        if (b == HOST) {
                            head_count++;
                            commandBean.setHost(b);
                        } else {
                            head_count = 0;
                        }
                        break;
                    case 1:
                        commandBean.setCommand(b);
                        head_count++;
                        break;
                    case 2:
                        commandBean.setSequence(b);
                        head_count++;
                        break;
                    case 3:
                        commandBean.setOption(b);
                        head_count++;
                        break;
                    case 4:
                        commandBean.setLength(b);
                        head_count++;
                        data_count = 0;
                        break;
                    default:
                        break;
                }
            } else {
                if (data_count < commandBean.getLength()) {
                    temp[data_count++] = b;
                } else {
                    byte bcc = Common.bcc(temp, 0, commandBean.getLength());
                    if (bcc == b) {
                        commandBean.setData(temp);
                        doCommand(commandBean);
                    }
                    head_count = 0;
                    data_count = 0;
                }
            }
        }
    }
    // commandBean
    public class CommandBean
    implements Serializable
    {
        private byte command;
        private byte[] data;
        private byte host;
        private byte length;
        private byte option;
        private byte sequence;
        
        public byte getCommand()
        {
          return command;
        }
        
        public byte[] getData()
        {
          return data;
        }
        
        public byte getHost()
        {
          return host;
        }
        
        public int getLength()
        {
          return Common.byte2Int(length);
        }
        
        public byte getOption()
        {
          return option;
        }
        
        public byte getSequence()
        {
          return sequence;
        }
        
        public void setCommand(byte paramByte)
        {
          command = paramByte;
        }
        
        public void setData(byte[] paramArrayOfByte)
        {
          data = paramArrayOfByte;
        }
        
        public void setHost(byte paramByte)
        {
          host = paramByte;
        }
        
        public void setLength(byte paramByte)
        {
          length = paramByte;
        }
        
        public void setOption(byte paramByte)
        {
          option = paramByte;
        }
        
        public void setSequence(byte paramByte)
        {
          sequence = paramByte;
        }
    }
    // 有关调用方法
    translateData(buffer, bytes);
    

    我们来看看buffer的具体样例:
    a0780000 02040004
    a0800000 13140080 30005555 33b2ddd9 01400000 0000be92 4c
    a0800000 130d0080 30005555 33b2ddd9 01400000 0000be92 55
    a0800000 13120080 30005555 33b2ddd9 01400000 0000be92 4aa08000 00130500 803000bb bb33b2dd d9014000 00000083 3ac8
    a0800000 130e0080 3000bbbb 33b2ddd9 01400000 0000833a c3a08000 00131100 80300055 5533b2dd d9014000 000000be 9249
    我们用a0780000 02040004来进行举例

    // a0|78|00|00|02|04|0004| 我们这样来进行拆分
    // 首先我们先来翻写translateData的Objective-C
    #pragma mark -执行命令
    - (void) doCommand:(struct DataBean) cmd {
        // int result;
        switch (cmd.command) {
            case CMD_GET_VERSION:
                [TopToast showToptoastWithText:@"获得版本!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_ERROR:
                [TopToast showToptoastWithText:@"系统错误!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_READ_LABEL:
                [TopToast showToptoastWithText:@"读取标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_WRITE_LABEL:
                [TopToast showToptoastWithText:@"写入标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_INVENTORY:
                [TopToast showToptoastWithText:@"查询标签!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_CLEAR_CACHE:
                [TopToast showToptoastWithText:@"清除缓存!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_SET_OUTPUT:
                [TopToast showToptoastWithText:@"设置功率!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_GET_OUTPUT:
                [TopToast showToptoastWithText:@"获取功率!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_BATTERY:
                [TopToast showToptoastWithText:@"获取电量!" duration:1 height:64 backGroundColor:[UIColor blackColor] alpha:1.0];
            break;
            case CMD_CHARGING:
                // TODO
            break;
            default:
                break;
        }
    }
    
    #pragma mark- 数据解析
    - (void) translateData: (Byte[])data size:(long)size {
        for (int i = 0; i < size; i++) {
            Byte b = data[i];
            if (_head_count < HEAD_LENGTH) {
                switch (_head_count) {
                    case 0:
                        if (b == HOST) {
                            _head_count++;
                            _dataBean.host = b;
                        } else {
                            _head_count = 0;
                        }
                    break;
                    case 1:
                        _dataBean.command = b;
                        _head_count++;
                    break;
                    case 2:
                        _dataBean.sequence = b;
                        _head_count++;
                    break;
                    case 3:
                        _dataBean.option = b;
                        _head_count++;
                    break;
                    case 4:
                        _dataBean.length = b;
                        _head_count++;
                        _data_count = 0;
                    break;
                }
            } else {
                if (_data_count < _dataBean.length) {
                    _temp[_data_count++] = b;
                } else  {
                    // 暂时不进行BCC校验
                    _dataBean.data = _temp;
                    [self doCommand:_dataBean];
                    _head_count = 0;
                    _data_count = 0;
                }
            }
        }
    }
    

    Unity – IOS与Unity交互

  • Unity部署IOS配置简况

  • 接口信息
  • BlueToothManagerInterface.h

    #import 
    @interface BlueToothManagerInterface : NSObject
    @end
    

    BlueToothManagerInterface.mm

    #import "BlueToothManagerInterface.h"
    #import "BlueToothManager.h"
    extern "C"
    {
        //初始化
        void InitBlueToothManager(){
            // TODO
        }
    
        // 运行
        void RunBlue() {
            [[BlueToothManager shareInstance] RunBlue];
        }
    }
    

    将上述文件放置Unity的Plugins/iOS中

  • Unity接口信息
  • using UnityEngine;
    using System.Collections;
    using System.Collections.Generic;
    using System.Runtime.InteropServices;
    using LuaInterface;
    public class IOSManager {
        private static IOSManager sInstance = null;
        public static IOSManager getInstance() {
            if (sInstance == null) {
                sInstance = new IOSManager ();
            }
            return sInstance;
        }
        #region
        // 注意是两个下划线
        [DllImport("__Internal")]
        private static extern void RunBlue ();
        /// 
        /// 开起蓝牙
        /// 
        public void RunBlueNative() {
            #if UNITY_IOS && !UNITY_EDITOR
            RunBlue();
            #endif
        }
        #endregion
        // 上面即完成了Unity调用iOS的过程
    }
    

    下面我们看下iOS如何调用Unity.
    这里和Android类似,主要利用Unity的UnitySendMessage函数

  • UnitySendMessage接口
  • 同理其中参数1是场景中接受消息的对象,参数2是要执行的函数名,参数3为传入参数.
    具体参考如下,我们在iOS蓝牙模块中定义

    // .h
    /**
     * iOS To Unity
     */
    - (void) testUnity;
    // .m
    - (void) testUnity {
        UnitySendMessage("ScriptObject", "setString", "Test...");
    }
    

    上述经过测试没有问题

    IOS开发(3) – Block

    基本概念

    Block是一个C Level的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似,但是其运行需要编译器和运行时支持,从IOS4.0开始就很好地支持Blocks。Blocks主要用在一些回调函数的用法上,2个对象通信需要一些回调的时候,比如说2个对象在运行,我们需要知道其中一个对象什么时候完成,我们就需要一个回调函数,之前我们用代理,但是有些小的地方用代理大材小用,Blocks可以用来做一些代理的很好地支撑,可以用来做边界或者一些地方的回调函数。
    本质上说:Blocks实质其实就是回调函数。

    IOS开发(2) – 函数与参数

    IOS奇葩的函数与参数

  • 多个参数的形式
  • (方法的数据类型)函数名:(参数1数据类型)参数1的数值的名字 参数2的名字: (参数2数据类型) 参数2值的名字 …;
    这里我们同样按照上一篇文章的形式来举例
    Java中多参数函数的写法如下:

    public void JavaMethod(int a1, String a2, byte[] a3, Object a4) {
        // TODO
        int a_1 = a1;
        String a_2 = a2;
        ...
    }
    // 调用
    this.JavaMethod(10, "HelloWorld", new byte[10], null);
    

    C++中多参数的写法如下

    int max(int a, int b);
    int max(int a, int b) {
        int ret = 0;
        if (a > b) {
    
        } else {
    
        }
        return ret;
    }
    int c = max(100, 200);
    

    Objective-C中

    // 定义
    /**
     * 测试函数
     */
    -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName;
    // 实现
    -(void) setKids: (NSString *)myOldestKidName secondKid: (NSString *) mySecondOldestKidName thirdKid: (NSString *) myThirdOldestKidName
    
    {
        NSString* firstSon = myOldestKidName;
        NSString* secondSon = mySecondOldestKidName;
        NSString* thirdSon = myThirdOldestKidName;
    }
    // 调用
    [self setKids:@"firstSon" secondKid:@"secondSon" thirdKid:@"thirdSon"];
    

    截图如下:


    IOS开发(1) – Objective-C基础

    Objective-C

  • 函数比较
  • 以HelloWorld为例

    // Java
    public void helloWorld(bool ishelloworld) {  
        //干点啥  
    }
    // C++
    void HelloWorld(bool ishelloworld) {
    }
    // Objective-C
    -(void) HelloWorld:(BOOL)ishelloworld {
        // 干点啥
    }
    

    说明:
    前面带有减号(-) 的方法为实例方法,必须使用类的实例才可以调用的。
    对应的有(+)号, 代表是类的静态方法,不需要实例化即可调用。

  • 消息
  • 向对象发送信息,消息是ios的运行时环境特有的机制,和C++,Java下的类,或实例调用类或实例的方法类似。我这说的是类似,他们的机制实际上是有很大的差别。
    举例

    // Objective-C
    [object  message:param1 withParameter:param2];
    NSString *string;
    string = [[NSString alloc] initWithString:@"Hello"];
    // 转化Java和C++
    object.message();
    object.message(param1, param2);
    char* str;
    str = new string("Hello world");
    
  • Import
  • import "Class.h"
    import 
    import 
    // C++和Java类似include和import
    
  • Property和Synthesize
  • @property 声明用于自动创建property属性变量的getter和setter.
    @Synthesize声明实现了property属性变量的getter和setter.

  • 头文件函数定义
  • -(returnType)method  
    -(returnType)method:(dataType)param1  
    -(returnType)method:(dataType)param1 withParam:(dataType)param2
    // 类似C++
    returnType method();
    returnType method(param1);
    returnType method(param1, param2);
    
  • self
  • [self method];
    this.method();
    
  • 继承关系和接口实现
  • ClassA:ParentA  
    ClassA:ParentA  
    ClassA < Protocol >
    // C++和Java类似
    ClassA extends ParentA  
    ClassA extends ParentA implements interface  
    ClassA implements interface 
    

    objective-c的 Protocol和c++、java的接口类似.

  • 空指针
  • id obj = nil;
    NSString *hello = nil;
    // 类似Java和C++的空指针
    
  • id
  • objective-c的和C++里的(void*)类似
    

    蓝牙BLE – IOS开发

    IOS制作类似Android的Toast功能

  • Toast.h
  • #import 
    #import 
    @interface TopToast : NSObject
    // API
    + (void)showToptoastWithText:(NSString *)text;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color;
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color alpha:(float)alpha;
    @end
    

    @interface、@protocal和@implementation
    在Java中interface是接口、class是类.
    例如如下

    interface BaseInterface {
        void print();
    };
    
    class BaseObject {
        
    };
    

    两者可以通过implement继承

    class BaseObject implement BaseInterface {
        void print() {
            system.out.println("HelloWorld");
        }
    };
    

    那么在OC中是什么情况呢?
    1 @protocal相当于Java的interface
    2 @interface和@implementation两者结合相当于Java中的class
    OC中类包括interface和implementation两个部分
    成员变量和成员方法的声明放置interface部分中
    成员变量和成员方法的实现放置implementation部分中

  • Toast.m
  • #import "TopToast.h"
    #define ToastDispalyDuration 2.0f
    #define ToastBackgroundColor [UIColor purpleColor]
    #define ToastHeight 64
    //
    @interface TopToast ()
    @property (nonatomic, strong) UIButton *contentView;
    @property (nonatomic, assign) CGFloat duration;
    @property (nonatomic, assign) CGFloat height;
    @end
    // 实现
    @implementation TopToast
    //多次点击缓存,逐个显示
    static NSMutableArray *_toastArr;
    - (void)dealloc{
    }
    
    - (id)initWithText:(NSString *)text{
        if (self = [super init]) {
            _duration = ToastDispalyDuration;
            _height = ToastHeight;
            if (_toastArr.count == 0) {
                _toastArr = [[NSMutableArray alloc]init];
            }
            
            _contentView = [[UIButton alloc] initWithFrame:CGRectMake(0, -_height, [UIScreen mainScreen].bounds.size.width, _height)];
            _contentView.backgroundColor = ToastBackgroundColor;
            [_contentView addGestureRecognizer:[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(hide)]];
    
            UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0,[UIScreen mainScreen].bounds.size.width, _height)];
            textLabel.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, _height);
            textLabel.backgroundColor = [UIColor clearColor];
            textLabel.textColor = [UIColor whiteColor];
            textLabel.textAlignment = NSTextAlignmentCenter;
            textLabel.font = [UIFont boldSystemFontOfSize:15];
            textLabel.text = text;
            textLabel.numberOfLines = 0;
            [_contentView addSubview:textLabel];
     
        }
        return self;
    }
    //设置停留时间
    - (void)setDuration:(CGFloat)duration{
        _duration = duration;
    }
    //设置背景色
    - (void)setBackgroundColor:(UIColor *)color{
        _contentView.backgroundColor = color;
    }
    //设置高度
    - (void)setHeigh:(float)height{
        _height = height;
    }
    //设置透明度
    - (void)setBackgroundAlpha:(float)alpha{
        _contentView.alpha = alpha;
    }
    //出现动画
    -(void)showAnimationWithToast:(TopToast *)toast{
        [UIView beginAnimations:@"show" context:NULL];
        [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
        [UIView setAnimationDuration:0.3];
        toast.contentView.frame = CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, toast.height);
        [UIView commitAnimations];
    }
    //影藏动画
    -(void)hideAnimationWithToast:(TopToast *)toast{
        [UIView animateWithDuration:0.3 animations:^{
            toast.contentView.frame = CGRectMake(0, -toast.height, [UIScreen mainScreen].bounds.size.width, toast.height);
        } completion:^(BOOL finished) {
            [toast.contentView removeFromSuperview];
            //影藏后检查是否有toast缓存
            if (_toastArr.count > 0) {
                [_toastArr removeObjectAtIndex:0];
                if (_toastArr.count > 0) {
                    [self showWithToast:_toastArr[0]];
                }
            }
        }];
    }
    //触摸隐藏
    - (void)hide{
        [UIView animateWithDuration:0.3 animations:^{
            _contentView.frame = CGRectMake(0, -_height, [UIScreen mainScreen].bounds.size.width, _height);
        }completion:^(BOOL finished) {
            [_contentView removeFromSuperview];
        }];
    }
    
    - (void)showWithToast:(TopToast *)toast{
        [[UIApplication sharedApplication].keyWindow addSubview:toast.contentView];
        [self showAnimationWithToast:toast];
        
        dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_duration * NSEC_PER_SEC));
        dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
            [self hideAnimationWithToast:toast];
        });
    }
    
    + (void)showToptoastWithText:(NSString *)text{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:ToastDispalyDuration];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        [toast setBackgroundColor:color];
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
    }
    + (void)showToptoastWithText:(NSString *)text duration:(CGFloat)duration height:(float)height backGroundColor:(UIColor *)color alpha:(float)alpha{
        TopToast *toast = [[TopToast alloc] initWithText:text];
        [toast setDuration:duration];
        [toast setHeigh:height];
        [toast setBackgroundColor:color];
        [toast setBackgroundAlpha:alpha];
        
        if (_toastArr.count == 0) {
            [toast showWithToast:toast];
        }
        [_toastArr addObject:toast];
        
    }
    @end
    

    蓝牙BLE4.0开发流程

    1 建立中心角色 (硬件盒子)

    #import 
    CBCentralManager *manager;
    manager = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
    

    2 扫描外设 (外设指的是你的IPhone和IPad)

    [manager scanForPeripheralsWithServices:nil options:options];
    

    3 连接外设
    指的是手机设备和硬件连接过程

    - (void)centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber*)RSSI {
        if ([peripheral.name isEqualToString:BLE_SERVICE_NAME]) {
            [self connect:peripheral];
        }
    }
    - (BOOL)connect:(CBPeripheral*) peripheral {
        self.manager.delegate = self;
        [self.manager connectPeripheral:peripheral options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:CBConnectPeripheralOptionNotifyOnDisconnectionKey]];
    }
    

    4 扫描外设中的服务和特征

    - (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
        [peripheral setDelegate:self];
        [peripheral discoverServices:nil];
    }
    
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
        if (error) {
            NSLog(@"Discovered services for %@ with error: %@", peripheral.name, [error localizedDescription]);
            if ([self.delegate respondsToSelector:@selector(DidNotifyFailConnectService:withPeripheral:error:)])
                [self.delegate DidNotifyFailConnectService:nil withPeripheral:nil error:nil];
            return; 
        }
        for (CBService *service in peripheral.services) {
            // 发现服务
            if ([service.UUID isEqual:[CBUUID UUIDWithString:UUIDSTR_ISSC_PROPRIETARY_SERVICE]]) {
                NSLog(@"Service found with UUID: %@", service.UUID);    // 查找特征
                [peripheral discoverCharacteristics:nil forService:service];
                break; 
            }
        }
    }
    
    - (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(NSError *)error {
        if (error) {
            NSLog(@"Discovered characteristics for %@ with error: %@", service.UUID, [error localizedDescription]);
            [self error];
            return;
        }
        NSLog(@"服务:%@",service.UUID);
        for (CBCharacteristic *characteristic in service.characteristics) {
            //发现特征
            if ([characteristic.UUID isEqual:[CBUUID UUIDWithString:@"xxxxxxx"]]) {
                NSLog(@"监听:%@",characteristic); //监听特征
                [self.peripheral setNotifyValue:YES forCharacteristic:characteristic];
            }
        }
    }
    

    5 与外设做数据交互,包括读和写

  • 读操作
  • - (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
        if (error) {
            NSLog(@"Error updating value for characteristic %@ error: %@", characteristic.UUID, [error localizedDescription]);
            self.error_b = BluetoothError_System;
            [self error];
            return;
        }
        [self decodeData:characteristic.value];
    }
    
  • 写操作
  • NSData *d2 = [[PBABluetoothDecode sharedManager] HexStringToNSData:@"0x02"];
    [self.peripheral writeValue:d2 forCharacteristic:characteristic type:CBCharacteristicWriteWithoutResponse];
    

    一阶微分方程

    微分方程

    微分方程指的是这样的方程,其中未知的是单变量或者多变量(一个或者多个)函数,而且在方程中不仅含有函数本身而且含有它们的导函数。如果方程中得未知函数是多变量函数,则称这类方程为偏微分方程,若未知函数是单变量函数,称这样的方程为常微分方程。
    我们知道我们经历的所有的变化过程都是连续的,大部分都以时间(t)为自变量,而x,y,z等表示未知函数,这里t的导数我们这么表示:x(1)= (dx) / (dt) 和 x(2) = (d2x) / (dt2)…,则n阶导数表示为x(n) = (dnx) / (dtn)

    一阶微分方程

    只含有未知函数一阶导数的方程,一般式为:

    F(t, x, x(1)) = 0;

    这里t为自变量,x是t的未知函数,x(1)是未知函数的导数;函数F是三个变量的给定函数。
    举几个例子来说明
    例1:
    现在的北京正值夏季,酷暑难耐,然后我们从超市买了一瓶水,不幸的是超市没有冷冻好的水了,所以我们将热烘烘的水拿回家放到冰箱里,希望凉一凉然后再进行饮用。这里就涉及了一个数学问题,假设放入冰箱前水的温度是C1,只有水的温度到了C2,我们才会取出来进行饮用,那么我们需要等多久?
    这里时间(t)就是自变量,什么是自变量?
    在数学等式中能够影响其他变量的一个变量叫做自变量。
    未知函数就是过t时间水温的温度,故此一定存在一个等式保证F(t, f(t), f'(t)) = 0

    卷积神经网络

    卷积神经网络(CNN)

    首先我们需要明确下Deep Learning是全部深度学习算法的总成,而CNN则是深度学习算法在图像处理领域的一个应用。
    一般的CNN基本结构包括两层:

  • 特征提取层
  • 每个神经元的输入与前一层的局部接受域相连,并提取该局部的特征。一旦该局部特征被提取后,它与其它特征间的位置关系也随之确定下来

  • 特征映射层
  • 网络的每个计算层由多个特征映射组成,每个特征映射是一个平面,平面上所有神经元的权值相等。
    在想了解卷积神经网络之前,我们需要对神经网络有一个清晰的认识,请参考博客中有关神经网络的内容,在了解神经网络之后,我们继续下面的学习和讨论。

    技术备忘录

    1 Unity调试技巧:Android下主要通过Android工程的Log来进行调试

    然后通过这里进行观察即可

    IOS中Unity调试方式也是一样,在XCode中观察输出的调试信息进行调试。
    2 有关Lua使用的注意事项,注意Git更新代码后,无论是否有新文件或者新类的添加都需要进行Clear Lua操作,确定保证Lua中Binding没有问题。

    3 Unity插件 – Haste

    PHP – Solr查询方法

    Solr

    Solr采用Lucene搜索库为核心,提供全文索引和搜索开源企业平台,提供REST的HTTP/XML和JSON的API。
    下载Solr的jar包
    执行java -jar start.jar
    访问http://localhost:8983/solr/
    可以通过java -jar post.jar solr.xml monitor.xml添加文档。
    solr.xml

    
    
     SOLR1000
     Solr, the Enterprise Search Server
     Apache Software Foundation
     software
     search
     Advanced Full-Text Search Capabilities using Lucene
     Optimized for High Volume Web Traffic
     Standards Based Open Interfaces - XML and HTTP
     Comprehensive HTML Administration Interfaces
     Scalability - Efficient Replication to other Solr Search Servers
     Flexible and Adaptable with XML configuration and Schema
     Good unicode support: h&#xE9;llo (hello with an accent over the e)
     0
     10
     true
     2006-01-17T00:00:00.000Z
    
    

    数据的处理无外乎增加、删除、修改、查找。
    我们来看看怎么操作?

    • 数据导入

    可以使用DIH(DataImportHandler)从数据库导入数据
    支持CSV文件导入,因此Excel数据也能轻松导入
    支持JSON格式文档
    二进制文档比如:Word、PDF
    还能以编程的方式来自定义导入

    • 更新数据

    如果同一份文档solr.xml重复导入会出现什么情况呢?实际上solr会根据文档的字段id来唯一标识文档,如果导入的文档的id已经存在solr中,那么这份文档就被最新导入的同id的文档自动替换。

    • 删除数据

    java -Ddata=args -jar post.jar “SOLR1000”
    java -Ddata=args -jar post.jar “name:DDR”

    • 查询数据

    查询数据都是通过HTTP的GET请求获取的,搜索关键字用参数q指定,另外还可以指定很多可选的参数来控制信息的返回
    http://localhost:8983/solr/collection1/select?q=solr&fl=name&wt=json&indent=true
    排序:q=video&sort=price desc

    全文检索基本原理

    首先我们需要知道什么是索引。
    想想字典,查一个字,我们用过拼音,比如“翘楚”这个词语,如果我们使用字典顺序扫描的话,需要很久很久,但是如果我们先找到Q对应的位置,然后依次查找,就会很快,这个步骤就是利用了索引的规则,那么字典中的A-Z就是索引,同理,我们处理文本将结果也利用索引的方式存储起来,更加的方便查找。
    对于全文检索也是类似的原理,它可以归结为两个过程:1.索引创建(Indexing)2. 搜索索引(Search)。那么索引到底是如何创建的呢?索引里面存放的又是什么东西呢?搜索的的时候又是如何去查找索引的呢?带着这一系列问题继续往下看。

    Solr/Lucene采用的是一种反向索引,所谓反向索引:就是从关键字到文档的映射过程,保存这种映射这种信息的索引称为反向索引。

    左边保存的是字符串序列
    右边是字符串的文档(Document)编号链表,称为倒排表(Posting List)
    字段串列表和文档编号链表两者构成了一个字典。现在想搜索”lucene”,那么索引直接告诉我们,包含有”lucene”的文档有:2,3,10,35,92,而无需在整个文档库中逐个查找。如果是想搜既包含”lucene”又包含”solr”的文档,那么与之对应的两个倒排表去交集即可获得:3、10、35、92。

    • 索引创建

    假设有如下两个原始文档:
    文档一:Students should be allowed to go out with their friends, but not allowed to drink beer.
    文档二:My friend Jerry went to school to see his students but found them drunk which is not allowed.

    一:把原始文档交给分词组件(Tokenizer)
    将文档分成一个一个单独的单词
    去除标点符号
    去除停词(stop word),所谓停词(Stop word)就是一种语言中没有具体含义,因而大多数情况下不会作为搜索的关键词,这样一来创建索引时能减少索引的大小。英语中停词(Stop word)如:”the”、”a”、”this”,中文有:”的,得”等。不同语种的分词组件(Tokenizer),都有自己的停词(stop word)集合。经过分词(Tokenizer)后得到的结果称为词汇单元(Token)。上例子中,便得到以下词汇单元(Token):
    “Students”,”allowed”,”go”,”their”,”friends”,”allowed”,”drink”,”beer”,”My”,”friend”,”Jerry”,”went”,”school”,”see”,”his”,”students”,”found”,”them”,”drunk”,”allowed”

    二:词汇单元(Token)传给语言处理组件(Linguistic Processor)
    变为小写(Lowercase)
    将单词缩减为词根形式,如”cars”到”car”等。这种操作称为:stemming
    将单词转变为词根形式,如”drove”到”drive”等。这种操作称为:lemmatization

    三:得到的词(Term)传递给索引组件(Indexer)
    创建字典

    Term    Document ID
    student     1
    allow       1
    go          1
    their       1
    friend      1
    allow       1
    drink       1
    beer        1
    my          2
    friend      2
    jerry       2
    go          2
    school      2
    see         2
    his         2
    student     2
    find        2
    them        2
    drink       2
    allow       2
    

    对字典按字母顺序排序

    Term    Document ID
    allow       1
    allow       1
    allow       2
    beer        1
    drink       1
    drink       2
    find        2
    friend      1
    friend      2
    go          1
    go          2
    his         2
    jerry       2
    my          2
    school      2
    see         2
    student     1
    student     2
    their       1
    them        2
    

    合并相同的词(Term)成为文档倒排(Posting List)链表

  • 搜索步骤
  • 一:对查询内容进行词法分析、语法分析、语言处理
    词法分析:区分查询内容中单词和关键字,比如:english and janpan,”and”就是关键字,”english”和”janpan”是普通单词
    根据查询语法的语法规则形成一棵树

    语言处理,和创建索引时处理方式是一样的。比如:leaned–>lean,driven–>drive
    二:搜索索引,得到符合语法树的文档集合
    三:根据查询语句与文档的相关性,对结果进行排序
    我们把查询语句也看作是一个文档,对文档与文档之间的相关性(relevance)进行打分(scoring),分数高比较越相关,排名就越靠前。当然还可以人工影响打分,比如百度搜索,就不一定完全按照相关性来排名的。
    如何评判文档之间的相关性?一个文档由多个(或者一个)词(Term)组成,比如:”solr”, “toturial”,不同的词可能重要性不一样,比如solr就比toturial重要,如果一个文档出现了10次toturial,但只出现了一次solr,而另一文档solr出现了4次,toturial出现一次,那么后者很有可能就是我们想要的搜的结果。这就引申出权重(Term weight)的概念。
    权重表示该词在文档中的重要程度,越重要的词当然权重越高,因此在计算文档相关性时影响力就更大。通过词之间的权重得到文档相关性的过程叫做空间向量模型算法(Vector Space Model)
    影响一个词在文档中的重要性主要有两个方面:
    Term Frequencey(tf),Term在此文档中出现的频率,ft越大表示越重要
    Document Frequency(df),表示有多少文档中出现过这个Trem,df越大表示越不重要
    物以希为贵,大家都有的东西,自然就不那么贵重了,只有你专有的东西表示这个东西很珍贵,权重的公式:

    空间向量模型
    文档中词的权重看作一个向量
    Document = {term1, term2, …… ,term N}
    Document Vector = {weight1, weight2, …… ,weight N}
    把欲要查询的语句看作一个简单的文档,也用向量表示:
    Query = {term1, term 2, …… , term N}
    Query Vector = {weight1, weight2, …… , weight N}
    把搜索出的文档向量及查询向量放入N维度的空间中,每个词表示一维:

    夹角越小,表示越相似,相关性越大

    Solr查询串 PHP样例

    $requestArray = array();
    $requestArray['filterList'][0] = ['attribute' => 'webSiteId', "valueArray" => [1, 2, 3]];
    $requestArray['filterList'][1] = ['attribute' => 'sourceType', "valueArray" => [4]];
    $requestArray['minTime']       = $startStamp;
    $requestArray['maxTime']       = $endStamp;
    $keyword = '\"测试\"' OR '\"测试2\"';
    $requestArray['qValue'] = $keyword;
    $requestArray['limit'] = 1000;
    $requestStr = json_encode($requestArray);
    $ret = \SoapService::queryByJson('ARTICLE_URL', ["arg0" => $requestStr, "arg1" =>0, "arg2"=>0]);
    $content = $ret->return;
    

    Egret – WebGL下显示错误的问题

    Canvas中setTransform

    绘制一个矩形,通过 setTransform() 重置并创建新的变换矩阵,再次绘制矩形,重置并创建新的变换矩阵,然后再次绘制矩形。
    setTransform() 允许您缩放、旋转、移动并倾斜当前的环境。

    
    // a 表示水平旋转绘图
    // b 表示水平倾斜绘图
    // c 表示垂直倾斜绘图
    // d 表示垂直缩放绘图
    // e 表示水平移动绘图
    // f 表示垂直移动绘图
    context.setTransform(a,b,c,d,e,f);
    

    drawTexture

    
    /**
     * 绘制材质
     */
    WebGLRenderContext.prototype.drawTexture = function (texture, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight, textureWidth, textureHeight, meshUVs, meshVertices, meshIndices, bounds) {
        var buffer = this.currentBuffer;
        if (this.contextLost || !texture || !buffer) {
            return;
        }
        if (meshVertices && meshIndices) {
            if (this.vao.reachMaxSize(meshVertices.length / 2, meshIndices.length)) {
                this.$drawWebGL();
            }
        }
        else {
            if (this.vao.reachMaxSize()) {
                this.$drawWebGL();
            }
        }
        if (meshUVs) {
            this.vao.changeToMeshIndices();
        }
        var transform = buffer.globalMatrix;
        var alpha = buffer.globalAlpha;
        var count = meshIndices ? meshIndices.length / 3 : 2;
        // 应用$filter,因为只可能是colorMatrixFilter,最后两个参数可不传
        this.drawCmdManager.pushDrawTexture(texture, count, this.$filter);
        this.vao.cacheArrays(transform, alpha, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight, textureWidth, textureHeight, meshUVs, meshVertices, meshIndices);
    };
    

    texture, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight, textureWidth, textureHeight, meshUVs, meshVertices, meshIndices, bounds

    Python基础 – 基本绘制与基本程序设计

    基本绘制

    >>> import turtle
    >>> turtle.showturtle()
    >>> turtle.write("Welcome TomYuan")
    >>> turtle.forward(100);
    >>> turtle.right(90)
    >>> turtle.forward(100);
    >>> turtle.color("red");
    >>> turtle.right(90)
    >>> turtle.forward(100);
    >>> turtle.goto(200, 200);
    >>> turtle.penup();
    >>> turtle.goto(100, 100);
    >>> turtle.pendown();
    >>> turtle.circle(50);
    

     >>> 4 / 2.5
    1.6
    >>> 4 // 2.5
    1.0
    >>> 4 * 2.5
    10.0
    >>> 4 ** 2.5
    32.0
    >>> 4 % 7
    4
    >>> int(10.5)
    10
    >>> round(10.5)
    11.0
    >>> 
    

    代码抓取

    这里来玩玩吧
    代码都是抓取的!随意的看看,没准有意外的收获。参考代码如下:

    ...
    $root = "https://github.com/";
    $urls = [];
    foreach ($dats as $dat) {
        $url_item = $root . $dat->children(0)->children(0)->find("a")[0]->href;
        $item_html = new simple_html_dom();
        $item_html->load_file($url_item);
        $T1 = $item_html->find(".repository-content");
        if ($T1 && is_array($T1) && count($T1) > 0) {
            $files = $T1[0]->find(".file-wrap");
            $sources = $files[0]->find(".js-navigation-item");
            foreach ($sources as $source) {
                $T2 = $source->find(".css-truncate-target");
                if ($T2 && is_array($T2) && count($T2) > 0) {
                    $T3 = $T2[0]->find("a");
                    if ($T3 && is_array($T3) && count($T3) > 0) {
                        $url = $T3[0]->href;
                        $url_arr = explode(".", $url);
                        if (count($url_arr) > 1) {
                            $urls[] = $root . $url;
                        }
                    }
                }
            }
        } 
    
        $item_html->clear();
    }
    $html->clear();
    // 随机选取
    $url = $urls[array_rand($urls)];
    $detail_html = new simple_html_dom();
    $detail_html->load_file($url);
    $T5 = $detail_html->find(".repository-content")[0];
    if ($T5 && is_array($T5) && count($T5) > 0) {
        $detail_content = $T5->find(".file")[0]->find(".blob-wrapper")[0];
        echo $detail_content->innertext;
        $data = '';
        $data = $data . $detail_content->innertext;
        $detail_html->clear();
        file_put_contents("a" . rand() * (rand() % 99) . ".html", $data);
    }
    $detail_html->clear();
    

    C# – Socket开发

    using System;
    using System.Net.Sockets;
    using System.Net;  
    using System.Threading;
    using System.Text;
    
    namespace TsetC001
    {
    	class MainClass
    	{
    		// 服务器
    		private static byte[] result = new byte[1024];  
    		private static int myProt = 8885;   //端口  
    		static Socket serverSocket;  
    		static void Main(string[] args)  
    		{  
    			//服务器IP地址  
    			IPAddress ip = IPAddress.Parse("127.0.0.1");  
    			serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
    			serverSocket.Bind(new IPEndPoint(ip, myProt));  //绑定IP地址:端口  
    			serverSocket.Listen(10);    //设定最多10个排队连接请求  
    			Console.WriteLine("启动监听{0}成功", serverSocket.LocalEndPoint.ToString());  
    			//通过Clientsoket发送数据  
    			Thread myThread = new Thread(ListenClientConnect);  
    			myThread.Start();  
    			Console.ReadLine();  
    		}  
    
    		///   
    		/// 监听客户端连接  
    		///   
    		private static void ListenClientConnect()  
    		{  
    			while (true)  
    			{  
    				Socket clientSocket = serverSocket.Accept();  
    				clientSocket.Send(Encoding.ASCII.GetBytes("Server Say Hello"));  
    				Thread receiveThread = new Thread(ReceiveMessage);  
    				receiveThread.Start(clientSocket);  
    			}  
    		}  
    
    		///   
    		/// 接收消息  
    		///   
    		///   
    		private static void ReceiveMessage(object clientSocket)  
    		{  
    			Socket myClientSocket = (Socket)clientSocket;  
    			while (true)  
    			{  
    				try  
    				{  
    					//通过clientSocket接收数据  
    					int receiveNumber = myClientSocket.Receive(result);  
    					Console.WriteLine("接收客户端{0}消息{1}", myClientSocket.RemoteEndPoint.ToString(), Encoding.ASCII.GetString(result, 0, receiveNumber));  
    				}  
    				catch(Exception ex)  
    				{  
    					Console.WriteLine(ex.Message);  
    					myClientSocket.Shutdown(SocketShutdown.Both);  
    					myClientSocket.Close();  
    					break;  
    				}  
    			}  
    		}
    
    		// 客户端
    		/*private static byte[] result = new byte[1024];  
    		static void Main(string[] args)  
    		{  
    			//设定服务器IP地址  
    			IPAddress ip = IPAddress.Parse("127.0.0.1");  
    			Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);  
    			try  
    			{  
    				clientSocket.Connect(new IPEndPoint(ip, 8885)); //配置服务器IP与端口  
    				Console.WriteLine("连接服务器成功");  
    			}  
    			catch  
    			{  
    				Console.WriteLine("连接服务器失败,请按回车键退出!");  
    				return;  
    			}  
    			//通过clientSocket接收数据  
    			int receiveLength = clientSocket.Receive(result);  
    			Console.WriteLine("接收服务器消息:{0}",Encoding.ASCII.GetString(result,0,receiveLength));  
    			//通过 clientSocket 发送数据  
    			for (int i = 0; i < 10; i++)  
    			{  
    				try  
    				{  
    					Thread.Sleep(1000);    //等待1秒钟  
    					string sendMessage = "client send Message Hellp" + DateTime.Now;  
    					clientSocket.Send(Encoding.ASCII.GetBytes(sendMessage));  
    					Console.WriteLine("向服务器发送消息:{0}" + sendMessage);  
    				}  
    				catch  
    				{  
    					clientSocket.Shutdown(SocketShutdown.Both);  
    					clientSocket.Close();  
    					break;  
    				}  
    			}  
    			Console.WriteLine("发送完毕,按回车键退出");  
    			Console.ReadLine();  
    		}*/
    	}
    }
    

    Install Mono on Linux

    Mono

    官方网站上的解释为:Cross platform, open source .NET framework。既然他是支持跨平台的.NET框架,那么我们可以通过这个将C#做的服务器运行到Linux上。
    这里我们可以对应系统所需要的Mono版本。

  • CentOS 7, Fedora 19 (and later), and derivatives
  • Add the Mono Project GPG signing key and the package repository in a root shell with:

    yum install yum-utils
    rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"
    yum-config-manager --add-repo http://download.mono-project.com/repo/centos/
    

    日志参考如下

    # yum install yum-utils
    已加载插件:fastestmirror, langpacks
    Repository epel is listed more than once in the configuration
    Repodata is over 2 weeks old. Install yum-cron? Or run: yum makecache fast
    epel                                                     | 4.3 kB     00:00     
    extras                                                   | 3.4 kB     00:00     
    os                                                       | 3.6 kB     00:00     
    updates                                                  | 3.4 kB     00:00     
    (1/4): epel/7/x86_64/updateinfo                            | 791 kB   00:00     
    (2/4): extras/7/x86_64/primary_db                          | 151 kB   00:00     
    (3/4): updates/7/x86_64/primary_db                         | 4.8 MB   00:00     
    (4/4): epel/7/x86_64/primary_db                            | 4.7 MB   00:00     
    Determining fastest mirrors
    正在解决依赖关系
    --> 正在检查事务
    ---> 软件包 yum-utils.noarch.0.1.1.31-34.el7 将被 升级
    ---> 软件包 yum-utils.noarch.0.1.1.31-40.el7 将被 更新
    --> 正在处理依赖关系 yum >= 3.4.3-143,它被软件包 yum-utils-1.1.31-40.el7.noarch 需要
    --> 正在检查事务
    ---> 软件包 yum.noarch.0.3.4.3-132.el7.centos.0.1 将被 升级
    ---> 软件包 yum.noarch.0.3.4.3-150.el7.centos 将被 更新
    --> 正在处理依赖关系 python-urlgrabber >= 3.10-8,它被软件包 yum-3.4.3-150.el7.centos.noarch 需要
    --> 正在检查事务
    ---> 软件包 python-urlgrabber.noarch.0.3.10-7.el7 将被 升级
    ---> 软件包 python-urlgrabber.noarch.0.3.10-8.el7 将被 更新
    --> 解决依赖关系完成
    
    依赖关系解决
    
    ================================================================================
     Package                 架构         版本                       源        大小
    ================================================================================
    正在更新:
     yum-utils               noarch       1.1.31-40.el7              os       116 k
    为依赖而更新:
     python-urlgrabber       noarch       3.10-8.el7                 os       108 k
     yum                     noarch       3.4.3-150.el7.centos       os       1.2 M
    
    事务概要
    ================================================================================
    升级  1 软件包 (+2 依赖软件包)
    
    总下载量:1.4 M
    Is this ok [y/d/N]: y
    Downloading packages:
    Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
    (1/3): python-urlgrabber-3.10-8.el7.noarch.rpm             | 108 kB   00:00     
    (2/3): yum-utils-1.1.31-40.el7.noarch.rpm                  | 116 kB   00:00     
    (3/3): yum-3.4.3-150.el7.centos.noarch.rpm                 | 1.2 MB   00:00     
    --------------------------------------------------------------------------------
    总计                                                11 MB/s | 1.4 MB  00:00     
    Running transaction check
    Running transaction test
    Transaction test succeeded
    Running transaction
      正在更新    : python-urlgrabber-3.10-8.el7.noarch                         1/6 
      正在更新    : yum-3.4.3-150.el7.centos.noarch                             2/6 
      正在更新    : yum-utils-1.1.31-40.el7.noarch                              3/6 
      清理        : yum-utils-1.1.31-34.el7.noarch                              4/6 
      清理        : yum-3.4.3-132.el7.centos.0.1.noarch                         5/6 
      清理        : python-urlgrabber-3.10-7.el7.noarch                         6/6 
      验证中      : yum-utils-1.1.31-40.el7.noarch                              1/6 
      验证中      : yum-3.4.3-150.el7.centos.noarch                             2/6 
      验证中      : python-urlgrabber-3.10-8.el7.noarch                         3/6 
      验证中      : yum-utils-1.1.31-34.el7.noarch                              4/6 
      验证中      : yum-3.4.3-132.el7.centos.0.1.noarch                         5/6 
      验证中      : python-urlgrabber-3.10-7.el7.noarch                         6/6 
    
    更新完毕:
      yum-utils.noarch 0:1.1.31-40.el7                                              
    
    作为依赖被升级:
      python-urlgrabber.noarch 0:3.10-8.el7    yum.noarch 0:3.4.3-150.el7.centos   
    
    完毕!
    # rpm --import "http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF"
    # yum-config-manager --add-repo http://download.mono-project.com/repo/centos/
    已加载插件:fastestmirror, langpacks
    Repository epel is listed more than once in the configuration
    adding repo from: http://download.mono-project.com/repo/centos/
    
    [download.mono-project.com_repo_centos_]
    name=added from: http://download.mono-project.com/repo/centos/
    baseurl=http://download.mono-project.com/repo/centos/
    enabled=1
    

    同样我们从http://download.mono-project.com/sources/mono/mono-4.2.1.102.tar.bz2这里地址下载源码,然后进行编译运行!
    我们直接运行yum install mono-devel
    安装完成后可以进行验证,如下即可

    # mono -V
    Mono JIT compiler version 4.8.1 (Stable 4.8.1.0/22a39d7 Wed Apr 12 11:42:39 UTC 2017)
    Copyright (C) 2002-2014 Novell, Inc, Xamarin Inc and Contributors. www.mono-project.com
    	TLS:           __thread
    	SIGSEGV:       altstack
    	Notifications: epoll
    	Architecture:  amd64
    	Disabled:      none
    	Misc:          softdebug 
    	LLVM:          supported, not enabled.
    	GC:            sgen
    [root@VM_74_138_centos mono-4.2.1]# 
    

    下面我们测试一下运行.NET的exe程序

    # mono ConsoleApplication1.exe 
    Hello World!
    
    # 

    Egret – 对象池例子

    对象池主要好处是让内存管理工作更容易。 如果内存利用率无限制地增长,对象池可预防该问题。 此技术通常可提高性能和减少内存的使用。

    class ObjectPool {
    
    
        constructor() {
            egret.Ticker.getInstance().register(this.onEnterFrame, this);
        }
    
        private onEnterFrame(advancedTime:number):void {
            var list = this._list.concat();
            for (var i = 0 , length = list.length; i < length; i++) {
                var obj:GameObject = list[i];
                obj.onEnterFrame(advancedTime);
            }
        }
    
        private _pool = {};
    
        private _list:Array = [];
    
        public createObject(classFactory:any):GameObject {
            var result;
            var key = classFactory.key;
            var arr = this._pool[key];
            if (arr != null && arr.length) {
                result = arr.shift();
            }
            else {
                result = new classFactory();
                result.key = key;
            }
            result.onCreate();
            this._list.push(result);
            return result;
        }
    
        public destroyObject(obj:GameObject) {
            var key = obj.key;
            if (this._pool[key] == null) {
                this._pool[key] = [];
            }
            this._pool[key].push(obj);
            obj.onDestroy();
            var index = this._list.indexOf(obj);
            if (index != -1) {
                this._list.splice(index, 1);
            }
        }
    
        private static instance:ObjectPool;
    
        public static getInstance():ObjectPool {
            if (ObjectPool.instance == null) {
                ObjectPool.instance = new ObjectPool();
            }
            return ObjectPool.instance;
        }
    }

    PHP – 页面抓取与分析

    一般我们收集资料的时候,尤其在网站中发现比较好的内容,我们需要另存为或者复制粘贴的方式来进行。我们的效率就会变的很低下,这次我们就来自己编写爬虫和页面的抓取并慢慢的进行优化。
    比如我们拿CSDN来进行测试http://blog.csdn.net/mindfloating/article/details/71076570

    Curl和simple_html_dom

    我们通过PHP的Curl来进行远程页面内容的抓取。

    <?php
    #加载页面
    function curl_get($url){
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 1);
        $result = curl_exec($ch);
        $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($code != '404' && $result){
            curl_close($ch);
            return $result;
        }
    }
    $URL = "http://blog.csdn.net/mindfloating/article/details/71076570";
    $content = curl_get($URL);
    

    查看源码我们知道文章的HTML格式。
    我们使用simple_html_dom来完成,Github下载地址

    <?php
    include('simple_html_dom.php');
    $URL = "http://blog.csdn.net/mindfloating/article/details/71076570";
    $html = file_get_html($URL);
    // 我们解析出来Title和正文内容
    // Title link_title
    // Content article_content
    $title_arr = $html->find('.link_title');
    $title_dom = new simple_html_dom();
    $title = "";
    foreach ($title_arr as $item) {
        $title_nodes = $title_dom->load($item->innertext);
        $title_link_arr = $title_nodes->find('a');
        if (count($title_link_arr) > 0) {
            $title = $title_link_arr[0]->innertext;
        }
    }
    echo "<h3>$title</h3>";
    $article_content = "";
    $article_content_arr = $html->find("#article_content");
    foreach ($article_content_arr as $item) {
        $article_content = $item->innertext;
    }
    echo $article_content;
    

    显示结果内容如下:

    更换一个网址再次进行访问也是OK的http://blog.csdn.net/qq_25827845/article/details/52932234

    页面外链获取与分析

    我们获取页面HTML之后,可以过滤取的我们所需要的内容,例如:图片、URL这两个应该是比较常用的。我们来用另外一个小说网站来进行说明。

    下图是这个网站的具体内容

    我们这里主要抓取一下排行榜

    通过分析页面我们知道百度小说月票榜单的id为monthTicketRankList。

    <?php
    include('simple_html_dom.php');
    $URL = "http://www.zongheng.com/";
    $html = file_get_html($URL);
    $articles = $html->find("#monthTicketRankList");
    foreach ($articles as $item) {
        $article_dom = new simple_html_dom();
        $article_dom->load($item->innertext);
        $urls = $article_dom->find("a");
        foreach ($urls as $url) {
            // 每一篇的文章
            $truely_url = $url->href;
            $article_html = file_get_html($truely_url);
            // 获取阅读按钮
            $start_url = $article_html->find("#readRecord");
            foreach ($start_url as $start_item) {
                $every_article_url = $start_item->href;
                $every_article_html = file_get_html($every_article_url);
                // 得到标题 (.tc txt)
                $arr_title = $every_article_html->find('.tc');
                // 得到内容 (.readtext)
                $arr_content = $every_article_html->find('.readtext');
                if (count($arr_title) > 0 && count($arr_content) > 0) {
                    echo $arr_title[0]->innertext;
                    echo $arr_content[0]->innertext;
                }
                $every_article_html->clear();
            }
            $article_html->clear();
        }
        $article_dom->clear();
    }
    $html->clear();
    

    PHP – 设计模式之单例模式

    < ?php
    class User {
        //静态变量保存全局实例
        private static $_instance = null;
        //私有构造函数,防止外界实例化对象
        private function __construct() {
        }
        //私有克隆函数,防止外办克隆对象
        private function __clone() {
        }
        //静态方法,单例统一访问入口
        static public function getInstance() {
            if (is_null ( self::$_instance ) || isset ( self::$_instance )) {
                self::$_instance = new self ();
            }
            return self::$_instance;
        }
        public function getName() {
            echo 'hello world!';
        }
    }
    

    PHP权限系统

    权限系统

    一个系统存在N个模块,我们称之为F(1),F(2),F(3),…,F(N),每个模块存在读权限和写权限。
    若现在系统用户表设计如下:

    
    -----------------------------------------------------------------------
     | id     | username  | password  | type  | write |  desc |  email |
    -----------------------------------------------------------------------
      1001      tomyuan      xxxxxx      0       null    null    null
    

    我们假设现在存在3个模块分别称之为N1,N2,N3,然后存在“读”和“写”权限,该怎么更改这个数据表让其支持权限系统呢?
    方法很多

  • 方法1 权限json化
  • 新增字段permissions表示权限,类别varchar(255)
    {“N1”:[0, 0], “N2”:[1, 0], “N3”:[1, 1]} 这样表示tomyuan对于N1模块没有读和写的权限,对于N2模块有读的权限,对于N3模块有读和写权限即可。

  • 方法2 进制判断法
  • 我们根据权限类别数量的平方作进制数,而模块类别作为位数。则上述问题可以转化成生成一个3位的4进制数,数据表中存放一个字段permissions表示权限,类别int(10)即可,1表示没有读和写权限,2表示只有读权限,3表示只有写权限,4表示有读和写权限,该字段是124表示和上面的json表示的结果一样。

    PHP中的解决方案

  • 方法1 权限json化
  • 
    $user_data = User::find()->where(
        ["username" => $username, "password" => $password]
    )->one();
    $user_exist = User::find()->where(
        ["username" => $username]
    )->one();
    if (isset($user_data)) {
        // 检测权限 (无论是那种方法都这样)
        $permissions = $user_data["permissions"];
        $session->set('permissions', $permissions);
        ....
    }
    // 权限判断
    function getWritablePermissions() {
    
        $session = \Yii::$app->session;
        if ($session->isActive) {
            // Error
            $session->open();
        }
    
        return $session->get("permissions");
    }
    //
    $permissions = $this->getWritablePermissions();
    $permissions_arr = json_decode($permissions, true);
    if ($permissions_arr) {
        if (!isset($permissions_arr[$this->_modId]) || $permissions_arr[$this->_modId][0] == 0) 
        {
            // 没有读权限
            echo "< script >window.location.href='/';< /script >";
            return FALSE;
        }
    }  
    
  • 方法2 进制判断法
  • 这里和json唯一的区别就是解析权限,json的方法我们使用json_encode和json_decode就可以进行分析,进制这里我们使用取余的方法即可。

    $Permissions = [
    	"没有读写权限",
    	"读权限",
    	"写权限",
    	"读写权限"
    ];
    $permission_number = 1234;
    $n = $permission_number;
    $index = 4;
    while ($n > 0) {
    	$e = $n % 10;
    	$n = floor($n / 10);
    	echo "第" . $index . "模块有" . $Permissions[$e - 1] . "\n";
    	$index = $index - 1;
    }
    

    运行结果如下所示:

    Android开发之BroadcastReceiver

    BroadCastReceiver

    广播接收者( BroadcastReceiver )用于接收广播 Intent ,广播 Intent 的发送是通过调用Context.sendBroadcast() 、 Context.sendOrderedBroadcast() 来实现的。通常一个广播 Intent 可以被订阅了此 Intent 的多个广播接收者所接收。
    广播是一种广泛运用的在应用程序之间传输信息的机制 。而 BroadcastReceiver 是对发送出来的广播进行过滤接收并响应的一类组件;

  • BroadCastReceiver的机制
  • 在 Android 里面有各种各样的广播,比如电池的使用状态,电话的接收和短信的接收都会产生一个广播,应用程序开发者也可以监听这些广播并做出程序逻辑的处理。
    广播的步骤

    public class MyBroadcastReceiver extends BroadcastReceiver {
        // action 名称
        String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED" ;
        public void onReceive(Context context, Intent intent) {
     
           if (intent.getAction().equals( SMS_RECEIVED )) {
               // 相关处理 : 地域变换、电量不足、来电来信;
           }
        }
    }
    < receiver android:name = ".MyBroadcastReceiver" >
    < intent-filter android:priority = "1000" >      
    < action android:name = " android.provider.Telephony.SMS_RECEIVED" />
    
     当然了需要权限 :
    < uses-permission android:name = "android.permission.RECEIVE_SMS" />
    < uses-permission android:name = "android.permission.SEND_SMS" />
    
    private BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
           @Override
           public void onReceive(Context context, Intent intent) {
               // 相关处理,如收短信,监听电量变化信息
           }
    };
    IntentFilter intentFilter = new IntentFilter( "android.provider.Telephony.SMS_RECEIVED " );
    registerReceiver( mBatteryInfoReceiver , intentFilter);
    

    php文件上传

    jQuery+php实现ajax文件即时上传

    https://cdn.bootcss.com/jquery.form/4.2.1/jquery.form.min.js
    //cdn.bootcss.com/jquery/3.2.1/jquery.min.js

    添加附件

     < input id="fileupload" type="file" name="myfile" >

    我们用于上传文件的html中并没有出现form表单,而正常的上传功能是要依赖form表单的。我们的form表单是动态插入的,这样可以避免一个大表单中出现多个form。
    对该按钮进行样式修改

    .btn{position: relative;overflow: hidden;margin-right: 4px;display:inline-block; 
    *display:inline;padding:4px 10px 4px;font-size:14px;line-height:18px; 
    *line-height:20px;color:#fff; 
    text-align:center;vertical-align:middle;cursor:pointer;background:#5bb75b; 
    border:1px solid #cccccc;border-color:#e6e6e6 #e6e6e6 #bfbfbf; 
    border-bottom-color:#b3b3b3;-webkit-border-radius:4px; 
    -moz-border-radius:4px;border-radius:4px;} 
    .btn input{position: absolute;top: 0; right: 0;margin: 0;border:solid transparent; 
    opacity: 0;filter:alpha(opacity=0); cursor: pointer;} 
    

    首先定义各种变量,注意动态将表单元素form插入到上传按钮部位,并且form的属性enctype必须设置为:multipart/form-data。然后当点击“上传附件”按钮,选择要上传的文件后,调用jquery.form插件的ajaxSubmit方法,如下代码说明。

    $(function () { 
        var files = $(".files"); 
        var btn = $(".btn span"); 
        $("#fileupload").wrap("< form id='myupload' action='uploadfile.php'  
        method='post' enctype='multipart/form-data' >< /form >"); 
        $("#fileupload").change(function(){ //选择文件 
            $("#myupload").ajaxSubmit({ 
                dataType:  'json', //数据格式为json 
                beforeSend: function() { //开始上传 
                    btn.html("上传中..."); //上传按钮显示上传中 
                }, 
                uploadProgress: function(event, position, total, percentComplete) { 
                    // TODO
                }, 
                success: function(data) { //成功 
                    //获得后台返回的json数据,显示文件名,大小,以及删除按钮 
                    // file_name
                    btn.html("添加附件"); //上传按钮还原 
                }, 
                error:function(xhr){ //上传失败 
                    btn.html("上传失败"); 
                } 
            }); 
        }); 
    }); 
    


    需要通过PHP文件来完成文件上传。图片上传时需要验证格式和大小,然后通过move_uploaded_file()方法上传图片,最后返回json格式的数据。为了安全起见,这里文件上传就不做说明了!

    Egret – 资源管理简述

    Egret入口

    data-entry-class=”Main”
    入口主函数是Main
    this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
    这个是Egret最开始的回调函数,执行onAddToStage
    这里我们主要进行显示游戏初始的Loading界面以及这节重点要说的加载资源
    加载资源大致分为以下几个步骤,主要包括

  • 步骤1
  • 添加config以及相关监听事件
    RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigCompleteHandle, this);
    RES.loadConfig(“resource/default.res.json”, “resource/”);

  • 步骤2
  • 设置加载组信息以及相关回调事件
    RES.addEventListener(RES.ResourceEvent.GROUP_COMPLETE, this.onResourceLoadComplete, this);
    RES.addEventListener(RES.ResourceEvent.GROUP_PROGRESS, this.onResourceLoadProgress, this);
    RES.addEventListener(RES.ResourceEvent.GROUP_LOAD_ERROR, this.onResourceLoadError, this);
    RES.loadGroup(“preload”);

    蓝牙2.0 Demo

    Manifest文件综述

    // 权限说明
    uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
    uses-permission android:name="android.permission.BLUETOOTH"
    uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"
    uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" 
    uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"
    

    存在扫描设备和操作蓝牙设置操作时,需要BLUETOOTH_ADMIN权限和BLUETOOTH权限.
    MOUNT_UNMOUNT_FILESYSTEMS允许允许挂载和反挂载文件系统可移动存储.
    WRITE_EXTERNAL_STORAGE使得SD卡获得写的权限.

    蓝牙存在的问题

    蓝牙一直在等待搜索,但是蓝牙列表却不弹出来。
    android 6.0之后要用蓝牙还需要添加一个模糊定位的权限android.permission.ACCESS_COARSE_LOCATION(GPS是精确定位)。
    在android 6.0(targetSdkVersion小于23)之前,安装新的app时系统会提示应用将要获取某某权限,如果同意安装,系统会默认为应用授予所申请的所有权限,而不同意的话,就不能安装应用;所以如果你以前的APP设置的targetSdkVersion低于23,在运行时是不会崩溃的。而在android 6.0(targetSdkVersion>=23)之后,我们会直接安装,不过当app需要获取不恰当权限的时候我们再决定是确定还是拒绝。

    android 6.0权限分类

  • Normal Permissions
  • Normal Permissions一般不涉及用户隐私,是不需要用户授权的.

    ACCESS_LOCATION_EXTRA_COMMANDS
    ACCESS_NETWORK_STATE
    ACCESS_NOTIFICATION_POLICY
    ACCESS_WIFI_STATE
    BLUETOOTH
    BLUETOOTH_ADMIN
    BROADCAST_STICKY
    CHANGE_NETWORK_STATE
    CHANGE_WIFI_MULTICAST_STATE
    CHANGE_WIFI_STATE
    DISABLE_KEYGUARD
    EXPAND_STATUS_BAR
    GET_PACKAGE_SIZE
    INSTALL_SHORTCUT
    INTERNET
    KILL_BACKGROUND_PROCESSES
    MODIFY_AUDIO_SETTINGS
    NFC
    READ_SYNC_SETTINGS
    READ_SYNC_STATS
    RECEIVE_BOOT_COMPLETED
    REORDER_TASKS
    REQUEST_INSTALL_PACKAGES
    SET_ALARM
    SET_TIME_ZONE
    SET_WALLPAPER
    SET_WALLPAPER_HINTS
    TRANSMIT_IR
    UNINSTALL_SHORTCUT
    USE_FINGERPRINT
    VIBRATE
    WAKE_LOCK
    WRITE_SYNC_SETTINGS
    
  • Dangerous Permissions
  • Dangerous Permissions涉及到用户隐私,在使用时需要用户实时授权才行

    group:android.permission-group.CONTACTS
      permission:android.permission.WRITE_CONTACTS
      permission:android.permission.GET_ACCOUNTS
      permission:android.permission.READ_CONTACTS
    group:android.permission-group.PHONE
      permission:android.permission.READ_CALL_LOG
      permission:android.permission.READ_PHONE_STATE
      permission:android.permission.CALL_PHONE
      permission:android.permission.WRITE_CALL_LOG
      permission:android.permission.USE_SIP
      permission:android.permission.PROCESS_OUTGOING_CALLS
      permission:com.android.voicemail.permission.ADD_VOICEMAIL
    group:android.permission-group.CALENDAR
      permission:android.permission.READ_CALENDAR
      permission:android.permission.WRITE_CALENDAR
    group:android.permission-group.CAMERA
      permission:android.permission.CAMERA
    group:android.permission-group.SENSORS
      permission:android.permission.BODY_SENSORS
    group:android.permission-group.LOCATION
      permission:android.permission.ACCESS_FINE_LOCATION
      permission:android.permission.ACCESS_COARSE_LOCATION
    group:android.permission-group.STORAGE
      permission:android.permission.READ_EXTERNAL_STORAGE
      permission:android.permission.WRITE_EXTERNAL_STORAGE
    group:android.permission-group.MICROPHONE
      permission:android.permission.RECORD_AUDIO
    group:android.permission-group.SMS
      permission:android.permission.READ_SMS
      permission:android.permission.RECEIVE_WAP_PUSH
      permission:android.permission.RECEIVE_MMS
      permission:android.permission.RECEIVE_SMS
      permission:android.permission.SEND_SMS
      permission:android.permission.READ_CELL_BROADCASTS
    

    android部分知识学习

    蓝牙适配器
    主要功能包括:
    1 开关蓝牙设备
    2 扫描蓝牙设备
    3 设置/获取蓝牙状态信息
    蓝牙状态值
    蓝牙Name
    蓝牙Mac地址
    BluetoothAdapter.getDefaultAdapter();
    BluetoothAdapter STATE
    STATE_OFF 蓝牙已经关闭
    STATE_ON 蓝牙已经打开
    STATE_TURNING_OFF 蓝牙处于关闭过程中
    STATE_TURNING_ON 蓝牙处于打开过程中
    BluetoothAdapter SCAN_MOD
    SCAN_MODE_CONNECTABLE 表明该蓝牙可以扫描其他蓝牙设备
    SCAN_MODE_CONNECTABLE_DISCOVERABLE 该蓝牙设备同时可以扫码其他蓝牙设备,并且可以被其他蓝牙设备扫描到
    SCAN_MODE_NONE 该蓝牙不能扫描以及被扫描

  • 获取蓝牙适配器
  • 
    BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
    
  • 打开蓝牙
  • 
    _bluetooth.enable();
    

    requestWindowFeature
    DEFAULT_FEATURES 系统默认状态,一般不需要指定
    FEATURE_CONTEXT_MENU 启用ContextMenu,默认该项已启用,一般无需指定
    FEATURE_CUSTOM_TITLE 自定义标题。当需要自定义标题时必须指定。如:标题是一个按钮时
    FEATURE_INDETERMINATE_PROGRESS 不确定的进度
    FEATURE_LEFT_ICON 标题栏左侧的图标
    FEATURE_NO_TITLE 无标题
    FEATURE_OPTIONS_PANEL 启用“选项面板”功能,默认已启用
    FEATURE_PROGRESS 进度指示器功能
    FEATURE_RIGHT_ICON 标题栏右侧的图标

  • 浏览搜索
  • 
    setProgressBarIndeterminateVisibility(true);
    setTitle("搜索中...");
    if (mBtAdapter.isDiscovering()) {
        mBtAdapter.cancelDiscovery();
    }
    mBtAdapter.startDiscovery();
    
  • getRemoteDevice
  • 给定Mac地址创建一个BluetoothDevice类的实力(代表远程蓝牙实力),并连接

    _device = _bluetooth.getRemoteDevice(address);
    // createRfcommSocketToServiceRecord创建一个BluetoothSocket实例
    _socket = _device.createRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
    _socket.connect();
    

    剩余的通信均依靠_socket来完成,附录核心代码

    public void onSendButtonClicked(View v){
        int i=0;
        int n=0;
        try{
            OutputStream os = _socket.getOutputStream();
            byte[] bos = edit0.getText().toString().getBytes();
            for(i=0;i < bos.length;i++){
                if(bos[i]==0x0a)n++;
            }
            byte[] bos_new = new byte[bos.length+n];
            n=0;
            for(i=0;i < bos.length;i++){
                if(bos[i]==0x0a){
                    bos_new[n]=0x0d;
                    n++;
                    bos_new[n]=0x0a;
                }else{
                    bos_new[n]=bos[i];
                }
                n++;
            }
            
            os.write(bos_new);  
        }catch(IOException e){          
        }   
    }
    
    Thread ReadThread=new Thread(){     
        public void run(){
            int num = 0;
            byte[] buffer = new byte[1024];
            byte[] buffer_new = new byte[1024];
            int i = 0;
            int n = 0;
            bRun = true;
            while(true){
                try{
                    while(is.available()==0){
                        while(bRun == false){}
                    }
                    while(true){
                        num = is.read(buffer);
                        n=0;
                        
                        String s0 = new String(buffer,0,num);
                        fmsg+=s0;
                        for(i=0;i < num;i++){
                            if((buffer[i] == 0x0d)&&(buffer[i+1]==0x0a)){
                                buffer_new[n] = 0x0a;
                                i++;
                            }else{
                                buffer_new[n] = buffer[i];
                            }
                            n++;
                        }
                        String s = new String(buffer_new,0,n);
                        smsg+=s;
                        if(is.available()==0)break;  //
                    }
                        handler.sendMessage(handler.obtainMessage());                       
                    }catch(IOException e){
                    }
            }
        }
    };
    

    遗传算法(2)

    遗传算法核心表示

    /**
     * 交叉遗传的概率Pc
     * 遗传变异的概率Pm
     * 种群规模M
     * 终止进化代数G
     * 当产生出一个个体的适应度超过给定Tf,则终止进化
     */
     步骤1
     初始化产生 Pc Pm M G Tf参数并随机生成第一代种群population,简称P1
     初始化P = P1
     do {
     	计算P中每一个个体的适应度F(i)
     	初始化空种群newP
     	do {
     		根据适应度比例选择算法选择出2个个体
     		if (rnd1 < Pc) {
     			进行交叉操作
     		}
     		if (rnd2 < Pm) { 进行变异操作 } 将两个操作后的个体放进newP中,即产生的新个体进入新的种群 } until (M个个体被创建) P = newP } until(任何染色体满足适应度或者繁殖代数>G)
    

    在这里我们看到了,这个随机选择以及产生后代的方式需要斟酌,如果设定的不好,那么很有可能这个种族最后就灭绝了,算个说话也就是我们没有得到我们的解。
    大自然这里还有一个规律叫做:物竞天择 适者生存
    在我们这里也需要对算法进行优化:
    择优 为了防止进化过程中产生的最优解被交叉和变异所破坏,可以将每一代中的最优解原封不动的复制到下一代中。

    具体实例

  • 理解实例
  • 求 f(x1, x2) = x1^3 + x2^2 + (x1 * (x1 – x2))的最大值,其中x1属于{-5, -3, 1, 3, 5}, x2属于{0, 2, 4}
    当然这个题目解法很多,穷举方法也非常的迅速。这个例子为了更加透彻的理解遗传算法。
    步骤1 编码
    我们此处定义隐射关系为
    [
    [0] = -5,
    [1] = -3,
    [2] = 0,
    [3] = 1,
    [4] = 2,
    [5] = 3,
    [6] = 4,
    [7] = 5
    ]
    8可以用4位二进制表示,而x1和x2则用8位二进制即可完成验证比如{0110|0110}则表示[x1 = 3, x2 = 4]
    步骤2 生成种群,注意生成种群的数量以及作用域关系,写一段js代码来进行测试

    步骤3 随机选择父代进行通过交叉和变异生成子代(选出适应度较高的进行)

    代码示意,因为没有变异以及编码是否可以有更好的办法,所以只为显示整体过程

    console.log("遗传算法");
    var everyone = [];
    var number = 200;
    function in_array(search, array){
        for(var i in array){
            if(array[i]==search){
                return true;
            }
        }
        return false;
    }
    var genChromosome = function(scope) {
        var timestamp = new Date().getTime();
        var index = Math.ceil(Math.random() * timestamp) % scope.length;
        var chromosome = scope[index].toString(2);
        while (chromosome.length < 4) {
            chromosome = "0" + chromosome;
        }
        return chromosome;
    }
    
    // 计算每个的适应度
    var calFitness = function(omo) {
        var codes = [-5, -3, 0, 1, 2, 3, 4, 5];
        var arr1 = [-5, -3, 1, 3, 5];
        var arr2 = [0, 2, 4];
        var x1 = codes[parseInt(omo.substr(0, 4), 2)];
        var x2 = codes[parseInt(omo.substr(4, 4), 2)];
        if (x1 != undefined && x2 != undefined && in_array(x1, arr1) && in_array(x2, arr2)) {
            return x1 * x1 * x1 + x2 * x2 + (x1 * (x1 - x2));
        }
        return -9999;
    }
    
    function sortNumber(a,b) 
    { 
        return a - b 
    } 
    
    $('#genUnit').click(function() {
        $('#geti').html('');
        var scope1 = [0, 1, 3, 5, 7];
        var scope2 = [2, 4, 6];
        // 生成50组个体
        everyone = [];
        for (var i = 0; i < number; i++) {
            var new_omo = genChromosome(scope1) + genChromosome(scope2);
            everyone.push (new_omo);
        }
        for (var i = 0; i < everyone.length; i++) {
            $('#geti').append(everyone[i] + " ");
            if ((i + 1) % 10 == 0) {
                $('#geti').append("
    "); } } }); // 交叉函数 var cross = function(omo1, omo2) { // 针对这个,四位是一个染色体特征控制 var ret = ""; var timestamp = new Date().getTime(); var rnd = Math.ceil(Math.random() * timestamp) % 4; if (rnd <= 1) { // 互换前四位 for (var i = 0; i < 4; i++) { var tmp = omo1[i]; omo1[i] = omo2[i]; omo2[i] = tmp; } } else if (rnd <= 3) { // 互换后四位 for (var i = 4; i < 8; i++) { var tmp = omo1[i]; omo1[i] = omo2[i]; omo2[i] = tmp; } } var rnd_next = Math.ceil(Math.random() * timestamp) % 2; if (rnd_next == 0) { ret = omo1; } else { ret = omo2; } return ret; } // 变异函数 var variation = function(omo1, omo2) { // 变异某一位,然后做交叉运算 // 这里暂时不需要,所以直接进行选择 var timestamp = new Date().getTime(); var rnd_next = Math.ceil(Math.random() * timestamp) % 2; if (rnd_next == 0) { ret = omo1; } else { ret = omo2; } return ret; } // 判断结束 var finish = function() { // 这里直接看第五十代 } $('#genNextUnit').click(function() { if (everyone.length == 0) { return } // 至少5代且满足best适应值占75%或最多50代 var g_num = 0; while (g_num < 50) { // 假设淘汰20%,最优的保留,剩下随机 var fitness_score = []; for (var i = 0; i < everyone.length; i++) { fitness_score.push(parseInt(calFitness(everyone[i]))); } fitness_score.sort(sortNumber); var over = Math.ceil(fitness_score.length * 0.2) for (var i = 0; i < over; i++) { fitness_score.shift(); } var best = fitness_score[fitness_score.length - 1]; // 生成子代 var new_generation = []; while (new_generation.length < number) { var curr_unit; // 选择 var timestamp = new Date().getTime(); var choose_fitness1 = everyone[Math.ceil(Math.random() * timestamp) % everyone.length]; var choose_fitness2 = everyone[Math.ceil(Math.random() * timestamp) % everyone.length]; if (calFitness(choose_fitness1) == best && calFitness(choose_fitness2) == best) { // 进行交叉 curr_unit = cross(choose_fitness1, choose_fitness2) if (Math.ceil(Math.random() * timestamp) % 100 < 2) { // 进行变异 curr_unit = variation(choose_fitness1, choose_fitness2) } } else if (Math.ceil(Math.random() * timestamp) % 100 > 50) { // 进行交叉 curr_unit = cross(choose_fitness1, choose_fitness2) // 进行变异 if (Math.ceil(Math.random() * timestamp) % 100 < 2) { // 进行变异 curr_unit = variation(choose_fitness1, choose_fitness2) } } if (curr_unit != undefined) { if (calFitness(curr_unit) > -9999) { new_generation.push(curr_unit); } } } everyone = new_generation; g_num = g_num + 1; } var fitness_score = []; for (var i = 0; i < everyone.length; i++) { fitness_score.push(parseInt(calFitness(everyone[i]))); } console.log(everyone[0]); fitness_score.sort(sortNumber); var best_number = fitness_score[fitness_score.length - 1]; $('#zidai').html(best_number); // 01110010 });

    Egret2D跑酷之龙骨换装研究

    全局换装

    全局换装的原理其实就是完全的替换骨骼对应的纹理集合。

    //
    var dragonBonesFactory:DragonBonesFactory = new DragonBonesFactory();
    dragonBonesFactory.initArmatureFile(RES.getRes("role_pkd_ske_json"),
                                        RES.getRes("role_pkd_tex_png"),
                                        RES.getRes("role_pkd_tex_json"));
    // 生成骨骼
    this._animation = dragonBonesFactory.makeArmature("role_pkd_animation");
    // 
    

    对应的纹理集如下图所示


    生成骨骼动画效果如下

    我们整体改变纹理集合的颜色,来进行测试

    //
    this._animation.getArmature().replaceTexture(RES.getRes("role_pkd_tex_wb_png"));
    

    效果如下图


    我们如果纹理集变成这样是不可以的,需要我们使用局部换肤

    运行效果如下

    局部换装

    我们主要通过dragonBonesFactory.getFactory().replaceSlotDisplay的接口完成。
    具体参数意义如下

    
     * @param dragonBonesName 指定的龙骨数据名称。 - 一般自己没有设置就设置null
     * @param armatureName 指定的骨架名称。 - 替换后的骨架名称
     * @param slotName 指定的插槽名称。 - 替换后骨骼的插槽名称
     * @param displayName 指定的显示对象名称。 - 替换后插槽的Display纹理
     * @param slot 指定的插槽实例。 - 现在正在使用的Slot
     // 举例
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body"));
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    

    效果如下:


    这个时候我们看到换装效果已经OK,但是在新的换装之后,红色框记的区域,层级发生了改变,这里我们需要手动进行调整下。

    
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body"));
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", 
        "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    this._animation.name = "DXY";
    this._animation.getArmature().display.scaleX = 2.0
    this._animation.getArmature().display.scaleY = 2.0
    const displayA = this._animation.getArmature().getSlot("pking_neck2").display as egret.DisplayObject;
    const displayB = this._animation.getArmature().getSlot("pking_body").display as egret.DisplayObject;
    const displayC = this._animation.getArmature().getSlot("ss_pking_hat").display as egret.DisplayObject;
    
    displayA.parent.addChildAt(displayA, displayA.parent.getChildIndex(displayB) - 1);
    displayC.parent.addChildAt(displayC, displayC.parent.getChildIndex(displayB) - 1);
    

    这个可以使用的前提就是动画中没有控制过层级的顺序(ZOrder)
    动画设置ZOrder了,可以多设置几个卡槽,然后程序控制显示和隐藏即可,代码如下

    
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "pking_body", 
        "ss_pking_body", this._animation.getArmature().getSlot("pking_body_top"));
    const display:egret.DisplayObject = this._animation.getArmature().getSlot("pking_body").display
    display.visible = false
    dragonBonesFactory.getFactory().replaceSlotDisplay(null, "role_pkd_animation1", "ss_pking_hat", 
        "ss_pking_hat", this._animation.getArmature().getSlot("ss_pking_hat"));
    this._animation.name = "DXY";
    this._animation.getArmature().display.scaleX = 2.0
    this._animation.getArmature().display.scaleY = 2.0
    

    最后效果如下视频


    欢迎各位留言!

    Egret – 2D跑酷元素编辑器更新版



    之前有过一篇写有关金币编辑器的文章,现在为了满足策划的需求,打算更新一篇有关跑酷综合编辑器的内容。
    编辑器说明(宽度 * 高度):
    * 障碍物 (2 * 2)
    * 弹板 (3 * 1)

    元素编辑器


    金币(1*1):
    弹板(3*1):
    地面弹板(3*1):
    普通衣服(2*2):
    宇航服(2*2):
    吸金币(2*2):
    小萝卜(1*1):
    大萝卜(2*2):
    加速(2*2):
    浮空的鸟(2*2):
    飞行的鸟(2*2):
    电池(2*2):
    泥泞道路(3*1):
    大号金币(2*2):
    浮空平台(3*1):
    恐龙衣服(2*2):
    运动衣服(2*2):
    轮胎(2*2):
    轮胎(2*3):
    箱子(2*2):
    箱子(2*4):
    盒子(2*1):
    盒子(2*3):
    球(3*3):
    飞行器(2*2):
    烽火台(9*13):

    导入关卡文件
    粘贴到此处



    Egret – 2D跑酷道具吸金币

    在众多跑酷游戏中,除了跑酷元素以外还包括很多其他的道具效果,其中比较场景的包括:吸金币、加速、无敌等,这一章我们来做一下吸金币的效果。

  • 检索金币与豆小鸭的位置关系
  • 我们通过圆形扫描就可以,这里比较容易


    实现方式如下

    // 
    public AttractCoin() {
        var view:egret.DisplayObjectContainer = (this.app.viewManager.getView(ViewConst.Player));
        var dxy = view.getChildByName("DXY");
        var coins:Array> = new Array>();
        var width = 0;
        var height = 0;
        var num = this.coinContent.numChildren;
        var id_index = 0;
        for (var i = 0; i < num; i++) {
            var c_content:egret.DisplayObjectContainer = this.coinContent.getChildAt(i);
            for (var j = 0; j < c_content.numChildren; j++) {
                var coin = c_content.getChildAt(j);
                width = coin.width
                height = coin.height;
                coins.push([coin.x + c_content.x + this.coinContent.x, 
                    coin.y + c_content.y + this.coinContent.y]);
                /*App.DebugUtils.debugDraw(coin, "coin" + id_index, "0x00ff00", c_content.x + this.coinContent.x, 
                    c_content.y + this.coinContent.y, false);*/
                id_index = id_index + 1
            }
        }
        this._ctr.coinModel.checkCoinsInObject(coins, [dxy.x, dxy.y], 500, dxy.width, dxy.height);
    }
    // 
    public checkCoinsInObject(coins:Array>, player_position:Array, r:number, width?:number, height?:number) {
        // 遍历所有的Coins
        if (width == undefined) {
            width = 0;
        }
        if (height == undefined) {
            height = 0;
        }
        for (var key in coins) {
            var coin:Array = >coins[key];
            var lenQt = App.MathUtils.getDistanceQt(coin[0], coin[1], player_position[0] - width / 2, player_position[1] - height / 2);
            // App.DebugUtils.debugDrawPosition("test01", "0xff0000", coin[0] - width / 2, coin[1] - height / 2, width, height);
            if (lenQt <= r * r) {
                console.log("Attract");
            }
        }
    }
    

    这里的遍历方式都是可以进行优化的,比如碰撞检测时候我们也无需遍历所有的,可以进行优化。

  • 金币的移动
  • // 
    public checkCoinsInObject(player, coins:Array, coins_pos:Array>, player_position:Array, r:number, width?:number, height?:number) {
        // 遍历所有的Coins
        if (width == undefined) {
            width = 0;
        }
        if (height == undefined) {
            height = 0;
        }
        // 定义金币移动速度 (单位时间位移即可)
        var speed = [13, 13];
        // 设定最小距离
        var min_distance_sq = 10 * 10;
        var del_coins:Array = Array();
        for (var key in coins_pos) {
            var coin_pos:Array = >coins_pos[key];
            var coin = coins[key];
            var lenQt = App.MathUtils.getDistanceQt(coin_pos[0], coin_pos[1], player_position[0] - width / 2, player_position[1] - height / 2);
            if (lenQt <= r * r) {
                if (lenQt < min_distance_sq) {
                    // 吃掉金币
                    del_coins.push(coin);
                } else {
                    // 移动
                    if (coin_pos[0] < player.x) {
                        coin.x = coin.x + speed[0];
                    } else {
                        coin.x = coin.x - speed[0];
                    }
    
                    if (coin_pos[1] < player.y) {
                        coin.y = coin.y + speed[1];
                    } else {
                        coin.y = coin.y - speed[1];
                    }
                }
            }
        }
        for (var key in del_coins) {
            var del_coin = del_coins[key];
            del_coin.parent.removeChild(del_coin);
        }
    }
    

    大致效果如下:


    我们还是可以对吸金币进行一些效果的优化

    Egret – 2D跑酷金币功能

    我们已经可以开始跑酷了,现在我们需要一个金币层,用来显示跑酷地图上的金币,这里需要注意的是金币的显示需要与地图背景、道路相吻合。金币层如下图所示

    普通加金币

  • 点与矩形碰撞
  • /** 
     *  
     * @param x1 点 
     * @param y1 点 
     * @param x2 矩形view x 
     * @param y2 矩形view y 
     * @param w  矩形view 宽 
     * @param h  矩形view 高 
     * @return 
     */ 
    public isCollisionPoint(x1:number, y1:number, x2:number, y2:number, w:number, h:number): boolean {
        if (x1 >= x2 && x1 <= x2 + w && y1 >= y2 && y1 <= y2 + h) {  
            return true;
        }
        return false;
    }
    
  • 检测两个矩形是否碰撞
  • /** 
     * 检测两个矩形是否碰撞 
     * @return 
     */ 
    public isCollisionWithRect(x1:number, y1:number, w1:number, h1:number,
            x2:number, y2:number, w2:number, h2:number) : boolean{
        if (x1 >= x2 && x1 >= x2 + w2) {  
            return false;  
        } else if (x1 <= x2 && x1 + w1 <= x2) {  
            return false;  
        } else if (y1 >= y2 && y1 >= y2 + h2) {  
            return false;  
        } else if (y1 <= y2 && y1 + h1 <= y2) {  
            return false;  
        }
        return true;
    }
    

    我们需要判断逗小鸭和金币的外边矩形碰撞,我们需要绘制下对象外边框

    /*
     * Drag Object
     */
    public debugDraw(obj:egret.DisplayObject, name, color, offx:number, offy:number, is_center:boolean) {
        if (this.app.root.getChildByName(name) != undefined) {
            this.app.root.removeChild(this.app.root.getChildByName(name));
        }
        var shp:egret.Shape = new egret.Shape();
        shp.name = name;
        shp.graphics.beginFill( color, 1);
        if (is_center) {
            shp.graphics.drawRect( (obj.x + offx) - obj.width / 2, (obj.y + offy) - obj.height / 2, 
                obj.width, obj.height );
        } else {
            shp.graphics.drawRect( (obj.x + offx), (obj.y + offy), 
                obj.width, obj.height );
        }
        
        shp.graphics.endFill();
        shp.alpha = 0.5
        this.app.root.addChildAt(shp, this.app.root.numChildren);
    }
    //////
    public CheckCollision() {
        var view:egret.DisplayObjectContainer = (this.app.viewManager.getView(ViewConst.Player));
        var dxy = view.getChildByName("DXY");
        App.DebugUtils.debugDraw(dxy, "dxy", "0xff0000", 0, 0, true);
        var num = this.coinContent.numChildren;
        var id_index = 0;
        for (var i = 0; i < num; i++) {
            var c_content:egret.DisplayObjectContainer = this.coinContent.getChildAt(i);
            for (var j = 0; j < c_content.numChildren; j++) {
                var coin = c_content.getChildAt(j);
                App.DebugUtils.debugDraw(coin, "coin" + id_index, "0x00ff00", c_content.x + this.coinContent.x, 
                    c_content.y + this.coinContent.y, false);
                id_index++;
            }
        }
    }
    

    运行结果如下图所示


    在跳起来的时候两个矩形发生了碰撞,下一步我们就来处理下

    ...
    var dxy_rect = App.MathUtils.getRect(dxy, 0, 0, true);
    var coin_rect = App.MathUtils.getRect(coin, c_content.x + this.coinContent.x, 
        c_content.y + this.coinContent.y, false);
    if (App.MathUtils.isCollisionWithRect(dxy_rect[0], dxy_rect[1], dxy_rect[2], dxy_rect[3],
    coin_rect[0], coin_rect[1], coin_rect[2], coin_rect[3])) {
        coin.parent.removeChild(coin);
    }
    ...
    


    下一章来完成加吸金币工具的功能

    Egret – 2D跑酷背景循环讨论

    跑酷场景参考如下:
    让我们一步步来实现并对这个进行优化

    跑酷地图分层原理

    首先我们需要确认跑酷游戏元素的组成部分,主要包括:

  • 游戏场景 (Scene)
  • 游戏控制角色 (Role)
  • 游戏道具 (Tool)
  • 道路障碍 (Obstacles)
  • 称为SRTO。

    游戏场景

    游戏场景我们可以理解为跑酷地图。首先我们需要知道跑酷地图分为多少层?每一层都是什么?
    不同的2D跑酷游戏,地图包含的层级也有所不同,一般分为以下几层:
    1 背景底衬
    2 天空层
    3 远景层
    4 近景层
    5 道路层
    6 前景层
    而对于2D没有摄像机的,一般通过控制道路移动来实现,这里为了实现景深感,需要对于不同的层级做不同的速度处理,1层作为底衬不动,2层作为天空层,移动很缓慢处于基本不动的情况,而3层移动缓慢,但是移动可以感受到,4、5、6三层移动速度一致,一般称之为跑酷的移动速度。除了景深效果,还有就是地图的无限循环,这里就涉及到一些方法,我们一起讨论下,如果不加循环效果,就会出现如下效果:


    除去绘制的物理体效果,背景层、天空层还在,但是远景、近景前景基本都已经不复存在。
    我们一层一层的来进行处理,首先来添加近景层。

    一般需要背景循环,我们的步骤如下

  • 初始化
  • public InitNear():void {
    	// 
    	this.nearContent = new egret.DisplayObjectContainer();
    	this.addChildAt(this.nearContent, this.numChildren);
    
    	// 添加草皮
    	var grass_content:egret.DisplayObjectContainer = this.AddGrass();
    	// 添加该草皮的元素
    	this.AddGrassElement(grass_content);
    }
    

  • 检查更新后位置,如果位置超过左边界, 则进行Append
  • public update(_targetVelocity:number[]) {
    	// 近景
    	this.nearContent.x += _targetVelocity[0] * 0.84;
    	this.nearContent.y += _targetVelocity[1] * 0.84;
    	this.farHillContent.x += _targetVelocity[0] * 0.84 * 0.33;
    	this.farHillContent.y += _targetVelocity[1] * 0.84 * 0.33;
    	this.cloudContent.x += _targetVelocity[0] * 0.84 * 0.1;
    	this.cloudContent.y += _targetVelocity[1] * 0.84 * 0.1;
    	this.CheckNear();
    	this.CheckFar();
    	this.CheckCloud();
    }
    public CheckNear() {
        var cNum = this.nearContent.numChildren;
        var all_numbers = 0;
        var over_numbers = 0;
        var remove_objs = [];
        for (var i = 0; i < cNum; i++) {
            var child = this.nearContent.getChildAt(i);
            if (child.name.substr(0, 1) == "G") {
                all_numbers = all_numbers + 1;
                var offx = child.x + this.nearContent.x;
                if (offx < -100) {
                    over_numbers = over_numbers + 1;
                } else if (offx < -1000) {
                    remove_objs.push(child);
                }
            }
        }
    
        for (var i = 0; i < remove_objs.length; i++) {
            var grass:egret.Bitmap = remove_objs[i];
            grass.parent.removeChild(grass);
        }
    
        if (over_numbers == all_numbers) {
            // Create New
            this.AppendNear();
        }
    }
    public AppendNear() {
        // 附加近景
        // 添加草皮
        var grass_content:egret.DisplayObjectContainer = this.AddGrass();
        // 添加该草皮的元素
        this.AddGrassElement(grass_content);
        var offx = this.GetNearRight(3);
        grass_content.x = offx;
    }
    // GetRight
    public GetNearRight(type:number) {
        var ret = 0;
        var cNum = 0;
        if (type == 1) {
            // 云
            cNum = this.cloudContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.cloudContent.getChildAt(i);
                if (child.name.substr(0, 1) == "C") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        } else if (type == 2) {
            // 远山
            cNum = this.farHillContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.farHillContent.getChildAt(i);
                if (child.name.substr(0, 1) == "H") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        } else if (type == 3) {
            // 草地
            cNum = this.nearContent.numChildren;
            for (var i = 0; i < cNum; i++) {
                var child = this.nearContent.getChildAt(i);
                if (child.name.substr(0, 1) == "G") {
                    if (child.x + child.width > ret) {
                        ret = child.x + child.width;
                    }
                }
            }
        }
        return ret;
    }
    



    从上面视频我们知道,需要修正部分装饰物的位置,我们设置一个偏移数组来完成。
    经过位置和随机优化,我们效果如下

    现在只剩下道路的和场景的匹配,我们调整一下道路即可。下一章我们将开始制作吃金币和吸金币的功能。

    遗传算法(1)

    遗传算法也成进化算法,该算法受到达尔文进化论的启发提出的一种启发式搜索算法。

    进化论

    种群

    生物的进化以群体的形式进行,这样的一个群体称为种群。

    个体

    组成种群的单个生物。

    基因

    一个遗传因子。

    染色体

    包含一组的基因。

    生存竞争,适者生存

    对环境适应度高的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。

    遗传与变异

    新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。

    综述

    繁殖过程,会发生基因交叉,基因突变 ,适应度低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的,其中很可能包含史上产生的适应度最高的那个个体。

    遗传算法

    遗传算法将要解决的问题模拟成一个生物进化的过程,通过复制、交叉、突变等操作产生下一代的解,并逐步淘汰掉适应度函数值低的解,增加适应度函数值高的解。这样进化N代后就很有可能会进化出适应度函数值很高的个体。
    下面以0-1背包问题来讲解下遗传算法的步骤

  • 编码
  • 需要将问题的解编码成字符串的形式才能使用遗传算法。最简单的一种编码方式是二进制编码,即将问题的解编码成二进制位数组的形式。例如,问题的解是整数,那么可以将其编码成二进制位数组的形式。将0-1字符串作为0-1背包问题的解就属于二进制编码。

  • 选择
  • 选择一些染色体来产生下一代。一种常用的选择策略是比例选择。也就是轮盘赌算法,如下图所示


    群体中每一染色体指定饼图中一个小块。块的大小与染色体的适应性分数成比例,适应性分数愈高,它在饼图中对应的小块所占面积也愈大。为了选取一个染色体,要做的就是旋转这个轮子,直到轮盘停止时,看指针停止在哪一块上,就选中与它对应的那个染色体。

    若产生随机数为0.81,则6号个体被选中。

    // 轮盘赌代码示意
    /*
    * 按设定的概率,随机选中一个个体
    * P[i]表示第i个个体被选中的概率
    */
    int RWS()
    {
        m =0;
        r =Random(0,1); //r为0至1的随机数
        for(i=1;i<=N; i++)
        {
            /* 产生的随机数在m~m+P[i]间则认为选中了i
             * 因此i被选中的概率是P[i]
             */
            m = m + P[i];
            if(r<=m) return i;
        }
    }
    
  • 交叉
  • 2条染色体交换部分基因,来构造下一代的2条新的染色体。
    父辈染色体
    00000|011100000000|10000
    11100|000001111110|00101
    随机交叉遗传
    00000|000001111110|10000
    11100|011100000000|00101

  • 变异
  • 新产生的染色体中的基因会以一定的概率出错,称为变异。
    变异前: 000001110000000010000
    变异后: 000001110000100010000
    我们认为染色体交叉的概率为Pc,染色体变异的概率为Pm。
    适应度函数:用于评价某个染色体的适应度,用f(x)表示。有时需要区分染色体的适应度函数与问题的目标函数。例如:0-1背包问题的目标函数是所取得物品价值,但将物品价值作为染色体的适应度函数可能并不一定适合。适应度函数与目标函数是正相关的,可对目标函数作一些变形来得到适应度函数。

    多维数组排序

  • 多维数组排序
  •     // 多维数组排序
    	public function multi_array_sort($multi_array, $sort_key, $sort = SORT_ASC){ 
    		if (is_array($multi_array)) { 
    			foreach ($multi_array as $row_array) { 
    				if(is_array($row_array)) { 
    					$key_array[] = $row_array[$sort_key]; 
    				} else { 
    					return false; 
    				} 
    			} 
    		} else { 
    			return false; 
    		} 
    		array_multisort($key_array,$sort,$multi_array); 
    		return $multi_array; 
    	} 
    

    神经网络(1)

  • 生物学中的神经网络
  • 人类的大脑有白质和灰质,简单的说,灰质负责接收指令,发出指令,而白质负责传递指令!而最小的处理单元我们认为是神经细胞,如下图所示:


    神经细胞利用电-化学过程交换信号。输入信号来自另一些神经细胞。这些神经细胞的轴突末梢(也就是终端)和本神经细胞的树突相遇形成突触,信号就从树突上的突触进入本细胞。我们可以把这个过程看成现代计算机信号处理过程,利用一系列的0和1来进行操作,大脑的神经细胞也只有两种状态:兴奋和不兴奋(即抑制)。发射信号的强度不变,变化的仅仅是频率。神经细胞利用一种我们还不知道的方法,把所有从树突突触上进来的信号进行相加,如果全部信号的总和超过某个阀值,就会激发神经细胞进入兴奋状态,这时就会有一个电信号通过轴突发送出去给其他神经细胞。如果信号总和没有达到阀值,神经细胞就不会兴奋起来。
    由于人体的神经细胞数量巨大,而且每个细胞都以独立处理单元的形式并行工作着!使人类大脑具备了如下的特点:
    1 能实现无监督的学习
    2 对损伤有冗余性
    3 处理信息的效率极高
    4 善于归纳推广
    5 具有意识

  • 数字神经网络
  • 上一节我们了解了生物学的神经网络是由神经细胞进行信息传导的,那么我们的人工神经网络也需要一个信息传导介质,我们称之为人工神经细胞,其示意图如下:

    如图所示进入人工神经细胞的每一个input(输入)都与一个权重w相联系,正是这些权重将决定神经网络的整体活跃性。你现在暂时可以设想所有这些权重都被设置到了-1和1之间的一个随机小数。因为权重可正可负,故能对与它关联的输入施加不同的影响,如果权重为正,就会有激发作用,权重为负,则会有抑制作用。
    当输入信号进入神经细胞时,它们的值将与它们对应的权重相乘,作为图中大圆的输入。而大圆的本质是一个函数,我们成为激励函数,它把所有这些新的、经过权重调整后的输入全部加起来,形成单个的激励值。激励值也是一浮点数,且同样可正可负。然后,再根据激励值来产生函数的输出也即神经细胞的输出:如果激励值超过某个阀值,就会产生一个值为1的信号输出;如果激励值小于阀值,则输出一个0。
    

    有了上面的了解,下面我们将人工神经细胞统一称之为神经细胞。神经细胞可以有一个或者多个输入,我们通过数学表达式x1, x2, x3, x4, …, xn(n表示输入总数),每个输入对应的权值为w1, w2, w3, w4, …, wn。而此时我们的激励值即为A = x1*w1 + x2*w2 + x3*w3 + … + xn*wn和图中所表示意义一致。我们此时可以将(x1, w1)、(x2, w2)…看成一个n维向量。下一步我们知道,如果激励值超过阀值,神经细胞的output则为1,否则为0。

  • 神经细胞可以用来干什么?
  • 我们有了上面对神经细胞的了解,下面就让我们来看看它到底用来干什么?大脑里的生物神经细胞和其他的神经细胞是相互连接在一起的。为了创建一个人工神经网络,人工神经细胞也要以同样方式相互连接在一起。虽然连接方式有很多种,我们一般通过下图所示的方式,一层一层的将神经细胞连接起来,这一种类型的神经网络就叫前馈网络,因为网络的每一层神经细胞的输出都向前馈送到了它们的下一层,直到获得整个网络的输出为止。


    下面我们通过一个实例来具体说明,我们知道神经网络经常用作模式识别。说的具体一点就是神经网络善于把一种输入状态(它所企图识别的模式)映射到一种输出状态(它曾被训练用来识别的模式)。
    我们以字符识别作为例子。设想有一个由8×8个格子组成的一块面板。每一个格子里放了一个小灯,每个小灯都可独立地被打开(格子变亮)或关闭(格子变黑),这样面板就可以用来显示十个数字符号。如下图所示表示数字4

    现在为了识别显示内容,我们设计如下神经网络:这个面板作为输入,1或0作为输出,1代表神经网络确认已显示了数字“4”,而输出0表示没有显示数字“4”。神经网络需要有64个输入,即识别图中的每个方格。以及由许多神经细胞组成的一个隐藏层,还有仅有一个神经细胞的输出层,隐藏层的所有输出都馈送到它。
    一旦神经网络体系创建成功后,它必须接受训练来认出数字“4”。为此可用这样一种方法来完成:先把神经网的所有权重初始化为任意值。然后给它一系列的输入,在本例中,就是代表面板不同配置的输入。对每一种输入配置,我们检查它的输出是什么,并调整相应的权重。如果我们送给网络的输入模式不是“4”, 则我们知道网络应该输出一个0。因此每个非“4”字符时的网络权重应进行调节,使得它的输出趋向于0。当代表“4”的模式输送给网络时,则应把权重调整到使输出趋向于1。这种类型的训练称作有监督的学习,用来训练的数据称为训练集。调整权重可以采用许多不同的方法。对本类问题最常用的方法就是反向传播方法,后面我们会阐述到。除了这种需要监督的学习训练还有一种不需要任何导师来监督的训练,或称无监督学习。

    composer

    1 安装方法
    参考:https://pkg.phpcomposer.com/#how-to-install-composer

    下载 Composer

    安装前请务必确保已经正确安装了 PHP。打开命令行窗口并执行 php -v 查看是否正确输出版本号。

    执行如下命令

    php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"  // 下载安装脚本 - composer-setup.php - 到当前目录。
    php composer-setup.php   // 执行安装过程。
    php -r "unlink('composer-setup.php');"  // 删除安装脚本。
    

    运行结果如下

    Do not run Composer as root/super user!

    这个是因为composer为了防止非法脚本在root下执行,解决办法随便切换到非root用户即可

    Egret2D跑酷之金币编辑器


    在《天天酷跑》游戏中,我们可以吃各种不同的金币,其中金币的样式非常的多样化,有箭头、爱心、动物等等多种形式,这些金币的摆放,需要我们使用一个编辑器来完成,一般的方式就是我们设计一个(n*m)二维矩阵进行,天天酷跑中金币样式如下:

    二维矩阵如下所示

    其中a(m*n)表示第n行、第m列的元素,此处我们设定一些元素类型,以后也可以根据这个进行扩展。
    N表示空元素
    C表示普通金币
    于是下面这个矩阵
    C N N N C
    N C N C N
    N N C N N
    N C N C N
    C N N N C
    金币构图如下

    除了每个元素的行列坐标,还有就是行间距和列间距也影响显示结果,于是一般的我们认为每个金币显示的内容包括一个行间距、一个列间距、一个二维矩阵以及n(行数)和m(列数)。
    为了使用数据更佳通用,这边将使用json数据类型,json样例表示如下:

    
    {
        "name": "coin1",
        "width": 5,
        "height": 5,
        "content": [
            [
                "C",
                "N",
                "N",
                "N",
                "C"
            ],
            [
                "N",
                "C",
                "N",
                "C",
                "N"
            ],
            [
                "N",
                "N",
                "C",
                "N",
                "N"
            ],
            [
                "N",
                "C",
                "N",
                "C",
                "N"
            ],
            [
                "C",
                "N",
                "N",
                "N",
                "C"
            ]
        ],
        "width_distance": 5,
        "height_distance": 5
    }
    

    coins.json点击下载

    • Egret读取数据生成
    
            // 测试金币 
            var coins = RES.getRes("coins_json");
            var maxW = coins['width'];
            var maxH = coins['height'];
            var disW = coins['width_distance'];
            var disH = coins['height_distance'];
            
            // 得到Coin
            var coin_texture = RES.getRes("cc01_png");
            var coin:egret.Bitmap = new egret.Bitmap(coin_texture);
            var coin_width = coin.width;
            var coin_height = coin.height;
            coin = null;
    
            // 开始位置
            var start_x = 300;
            var start_y = 300;
            for (var i = 0; i < maxW; i++) {
                for (var j = 0; j < maxH; j++) {
                    if (coins.content[i][j] == "C") {
                        var coin_texture = RES.getRes("cc01_png");
                        var coin:egret.Bitmap = new egret.Bitmap(coin_texture);
                        this.app.root.addChildAt(coin, 5);
                        coin.x = start_x;
                        coin.y = start_y;
                        coin.scaleX = 0.33;
                        coin.scaleY = 0.33;
                    }
                    start_y = start_y + coin_height * 0.33 + disH;
                }
                start_y = 300;
                start_x = start_x + coin_width * 0.33 + disW;
            }
    

    运行结果如下图所示:

    我们通过参数可以改变样式,比如修改列间距为-10,显示结果如下图所示

    当然也可以修改行间距为-10如下图所示

    • 金币编辑器

    下面我们就制作一下这个金币编辑器,我们直接使用js配合服务器进行完成。
    行数:
    列数:
    元素行宽:
    元素列宽:


    选中效果如下:

    程序运行结果如图

    Egret2D有关Camera模拟的思考

    上文说到在开发跑酷游戏中遇到了些问题,比如摄像机,我再思考Egret中主要通过渲染Stage,然后实现显示,我们是否完全可以通过控制Stage或者根目录级别的容器的x、y以及ScaleX和ScaleY来进行模拟摄像机的(Camera)情况,因为这里主要是2D开发的游戏,所以我们不考虑Y轴方向旋转。3D的可以直接使用Egret3D直接参考上篇文章。
    下面我们来做一个测试,首先我们准备好Texture和一个简单的对象纹理图。

    然后设置一个CameraController来实现模拟摄像机的效果如下:

    
    class CameraController extends BaseController {
        public constructor() {
            super();
            this.registerFunc(ActionConst.Create, this.onCreate, this);
        }
    
        private onCreate():void {
            
        }
    
        public Left() {
            this.app.root.$setX(this.app.root.$getX() - 0.33 * this.app.timerManager.dt);
        }
    
        public Right() {
            this.app.root.$setX(this.app.root.$getX() + 0.33 * this.app.timerManager.dt);
        }
    
        public Up() {
            this.app.root.$setY(this.app.root.$getY() - 0.33 * this.app.timerManager.dt);
        }
    
        public Down() {
            this.app.root.$setY(this.app.root.$getY() + 0.33 * this.app.timerManager.dt);
        }
    
        public ScaleBig() {
            this.app.root.$setScaleX(this.app.root.$getScaleX() * 0.99);
            this.app.root.$setScaleY(this.app.root.$getScaleY() * 0.99);
        }
    
        public ScaleSmall() {
            this.app.root.$setScaleX(this.app.root.$getScaleX() / 0.99);
            this.app.root.$setScaleY(this.app.root.$getScaleY() / 0.99);
        }
    }
    

    其中app.root表示根节点,然后对象与root显示节点位于不同的层级,然后通过控制root节点位置来模拟移动如下:


    然后其中道具金币均在root层级上,然后为了实现景深,可以指定多层root,每层root移动速率不一样,然后所有障碍及碰撞均位于最外层的root层面上即可。

    Egret – 3D(例子)

    由于要开始制作H5跑酷类的游戏,所以需要不少游戏引擎不可缺少的元素,比如摄像机,所以花点时间来看看Egret3D,Egret官方例子运行如下(当然还有其它的例子,我们一个一个看下)。

  • 基础样例
  • /**
     * 立方体模型
     * 
     */
    class CubeSample {
    
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private init() {
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(50, 100, 10);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial();
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 颜色材质样例
  • /**
     * 颜色材质
     * 
     */
    class ColorMaterialSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private init() {
    
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(128, 128, 128);
            let mat: egret3d.ColorMaterial = new egret3d.ColorMaterial(0xffff00);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 40;
            this.cameraCtl.rotationY = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 光照样例
  • class DirectLightSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/texture/earth.png");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private light: egret3d.DirectLight;
        private _lightDir: egret3d.Vector3D = new egret3d.Vector3D(0, -1, 0);
        private _rotationX: number = 0.01;
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.SphereGeometry = new egret3d.SphereGeometry(200, 30, 30);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let earth: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(earth);
    
            this.light = new egret3d.DirectLight(this._lightDir);
            this.light.diffuse = 0xffffff;
            this.light.intensity = 0.1;
            this.light.ambient = 0xffffffff;
    
            let group: egret3d.LightGroup = new egret3d.LightGroup();
            group.addLight(this.light);
            earth.material.lightGroup = group;
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 0;
        }
    
        private _lightIntensity = 0.01;
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
            if (this.light.intensity >= 0.5) {
                this._lightIntensity = -0.01;
            }
            if (this.light.intensity <= 0.1) {
                this._lightIntensity = 0.01;
            }
            this.light.intensity += this._lightIntensity;
    
            if (this.light.dir.x >= 1) {
                this._rotationX = -0.01;
            }
            if (this.light.dir.x <= -1) {
                this._rotationX = 0.01;
            }
            this.light.dir.x += this._rotationX;
        }
    }
    
  • 高度图模型样例
  • /**
     * 高度图模型
     * 
     */
    class ElevationSample {
    
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/elevation/timg.jpg");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.ElevationGeometry = new egret3d.ElevationGeometry(img, 512, 100, 512);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 天空盒样例
  • /**
     * 天空盒
     * close
     */
    class SkyboxSample {
        public constructor() {
            StageMgr.Instance().init();
    
            this.init();
        }
    
        private queueLoader: egret3d.QueueLoader;
    
        private init() {
            this.queueLoader = new egret3d.QueueLoader();
    
            this.queueLoader.load("resource/skybox/cloudy_noon_BK.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_DN.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_FR.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_LF.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_RT.png");
            this.queueLoader.load("resource/skybox/cloudy_noon_UP.png");
    
            this.queueLoader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onQueueLoader, this);
    
            //this.loader = new egret3d.UnitLoader("resource/skybox/cloudy_noon_BK.png");
            //this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onQueueLoader, this);
        }
    
        private loader: egret3d.UnitLoader;
    
        private onQueueLoader2(e: egret3d.LoaderEvent3D) {
            console.log("ede", this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png"));
        }
    
        private onQueueLoader(e: egret3d.LoaderEvent3D) {
    
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_FR.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_BK.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_LF.png"));
            //console.log(this.queueLoader.getAsset("resource/skybox/cloudy_noon_RT.png"));
    
            let cubeTexture: egret3d.CubeTexture = egret3d.CubeTexture.createCubeTextureByImageTexture(
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_FR.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_BK.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_LF.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_RT.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png"),
                this.queueLoader.getAsset("resource/skybox/cloudy_noon_DN.png")
            );
    
            let mat: egret3d.CubeTextureMaterial = new egret3d.CubeTextureMaterial(cubeTexture);
            let cube: egret3d.CubeGeometry = new egret3d.CubeGeometry(50, 50, 50);//10000, 10000, 10000);
            //cube.buildGeomtry(false);
            let sky: egret3d.Sky = new egret3d.Sky(cube, mat, StageMgr.Instance().view3d.camera3D);
            StageMgr.Instance().view3d.addChild3D(sky);
    
            console.log("load")
            let img: egret3d.ImageTexture = this.queueLoader.getAsset("resource/skybox/cloudy_noon_UP.png");
            let mat2: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let a: egret3d.Mesh = new egret3d.Mesh(cube, mat2);
            StageMgr.Instance().view3d.addChild3D(a);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 纹理样例
  • /**
     * 纹理材质
     * 
     */
    class TextureMaterialSample {
        public constructor() {
            StageMgr.Instance().init();
            this.init();
        }
    
        private loader: egret3d.UnitLoader;
        private init() {
    
            this.loader = new egret3d.UnitLoader("resource/texture/Icon.png");
            this.loader.addEventListener(egret3d.LoaderEvent3D.LOADER_COMPLETE, this.onLoader, this);
        }
    
        private onLoader(e: egret3d.LoaderEvent3D) {
            let img: egret3d.ImageTexture = e.target.data;
    
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(128, 128, 128);
            let mat: egret3d.TextureMaterial = new egret3d.TextureMaterial(img);
            let cube: egret3d.Mesh = new egret3d.Mesh(geom, mat);
            StageMgr.Instance().view3d.addChild3D(cube);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 1000;
            this.cameraCtl.rotationX = 40;
            this.cameraCtl.rotationY = 40;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    
  • 线框渲染样例
  • /**
     * 线框渲染
     * 
     */
    class WireframeSample {
        public constructor() {
            StageMgr.Instance().init();
    
            this.init();
        }
    
        private init() {
            let geom: egret3d.CubeGeometry = new egret3d.CubeGeometry(100, 100, 100);
            let wf: egret3d.Wireframe = new egret3d.Wireframe();
            wf.fromGeometry(geom);
            wf.fromVertexs(egret3d.VertexFormat.VF_POSITION);
            StageMgr.Instance().view3d.addChild3D(wf);
    
            this.InitCameraCtl();
            StageMgr.Instance().stage3d.addEventListener(egret3d.Event3D.ENTER_FRAME, this.update, this);
        }
    
        private cameraCtl: egret3d.LookAtController;
        private InitCameraCtl() {
            this.cameraCtl = new egret3d.LookAtController(StageMgr.Instance().view3d.camera3D, new egret3d.Object3D());
            this.cameraCtl.distance = 300;
            this.cameraCtl.rotationX = 0;
        }
    
        public update(e: egret3d.Event3D) {
            this.cameraCtl.update();
        }
    }
    

    Egret – ts(Egret) 与 js 的调用

    ts 是 js 的超集,因此只要是 js 与 js 可以互相调用的,ts 均可以调用,只不过需要增加声明来解决编译时报错。
    ts 最终生成的文件为 js,因此 js 调用 ts 其实就是 js 调用 js。
    而在互相调用之前,需要进行声明!即生成d.ts文件。
    下面我们重点来说下如何生成d.ts文件

    
    (function(p2){
    
        // exports
        p2.KinematicCharacterController = KinematicCharacterController;
    
        // imports
        var vec2 = p2.vec2;
        var Ray = p2.Ray;
        var RaycastResult = p2.RaycastResult;
        var AABB = p2.AABB;
        var EventEmitter = p2.EventEmitter;
    
        // constants
        var ZERO = vec2.create();
        var UNIT_Y = vec2.fromValues(0,1);
    
        // math helpers
        function sign(x){
            return x >= 0 ? 1 : -1;
        }
        function lerp(factor, start, end){
            return start + (end - start) * factor;
        }
        function clamp(value, min, max){
            return Math.min(max, Math.max(min, value));
        }
        function angle(a, b){
            return Math.acos(vec2.dot(a, b));
        }
        function expandAABB(aabb, amount){
            var halfAmount = amount * 0.5;
            aabb.lowerBound[0] -= halfAmount;
            aabb.lowerBound[1] -= halfAmount;
            aabb.upperBound[0] += halfAmount;
            aabb.upperBound[1] += halfAmount;
        }
        ...
        /**
         * Should be executed after each physics tick, using the physics deltaTime.
         * @param {number} deltaTime
         */
        KinematicCharacterController.prototype.update = (function(){
            var scaledVelocity = vec2.create();
            return function (deltaTime) {
                var input = this.input;
                var velocity = this.velocity;
                var controller = this;
    
                var wallDirX = (controller.collisions.left) ? -1 : 1;
                var targetVelocityX = input[0] * this.moveSpeed;
    
                var smoothing = this.velocityXSmoothing;
                smoothing *= controller.collisions.below ? this.accelerationTimeGrounded : this.accelerationTimeAirborne;
                var factor = 1 - Math.pow(smoothing, deltaTime);
                velocity[0] = lerp(factor, velocity[0], targetVelocityX);
                if(Math.abs(velocity[0]) < this.velocityXMin){
                    velocity[0] = 0;
                }
    
                var wallSliding = false;
                if ((controller.collisions.left || controller.collisions.right) && !controller.collisions.below && velocity[1] < 0) {
                    wallSliding = true;
    
                    if (velocity[1] < -this.wallSlideSpeedMax) {
                        velocity[1] = -this.wallSlideSpeedMax;
                    }
    
                    if (this.timeToWallUnstick > 0) {
                        velocity[0] = 0;
    
                        if (input[0] !== wallDirX && input[0] !== 0) {
                            this.timeToWallUnstick -= deltaTime;
                        } else {
                            this.timeToWallUnstick = this.wallStickTime;
                        }
                    } else {
                        this.timeToWallUnstick = this.wallStickTime;
                    }
                }
    
                if (this._requestJump) {
                    this._requestJump = false;
    
                    if (wallSliding) {
                        if (wallDirX === input[0]) {
                            velocity[0] = -wallDirX * this.wallJumpClimb[0];
                            velocity[1] = this.wallJumpClimb[1];
                        } else if (input[0] === 0) {
                            velocity[0] = -wallDirX * this.wallJumpOff[0];
                            velocity[1] = this.wallJumpOff[1];
                        } else {
                            velocity[0] = -wallDirX * this.wallLeap[0];
                            velocity[1] = this.wallLeap[1];
                        }
                    }
    
                    if (controller.collisions.below) {
                        velocity[1] = this.maxJumpVelocity;
                    }
                }
    
                if (this._requestUnJump) {
                    this._requestUnJump = false;
                    if (velocity[1] > this.minJumpVelocity) {
                        velocity[1] = this.minJumpVelocity;
                    }
                }
    
                velocity[1] += this.gravity * deltaTime;
                vec2.scale(scaledVelocity, velocity, deltaTime);
                controller.move(scaledVelocity, input);
    
                if (controller.collisions.above || controller.collisions.below) {
                    velocity[1] = 0;
                }
            };
        })();
    
    })(this.p2);
    

    我们来写下p2.KinematicCharacterController的声明文件如下

    
    declear module p2 {
        export function sign(x:number);
        export function lerp(factor:number, start:number, end:number);
        export function clamp(value:number, min:number, max:number);
        export function angle(a:number, b:number);
        export function expandAABB(aabb:{}, amount:number);
        //
        export class RaycastController {
            constructor(options: {});
            updateRaycastOrigins();
            calculateRaySpacing();
        }
    
        export class Controller {
            constructor(options: {});
            resetCollisions(velocity:number);
            moveWithZeroInput(velocity:number, standingOnPlatform:boolean);
            move(velocity:number, input:number, standingOnPlatform:boolean);
            emitRayCastEvent():{};
            horizontalCollisions(velocity:number);
            verticalCollisions(velocity:number, input:any);
            climbSlope(velocity:number, slopeAngle:number);
            descendSlope(velocity:number);
            resetFallingThroughPlatform();
        }
    
        export class KinematicCharacterController {
            constructor(options: {});
            setJumpKeyState(isDown:boolean);
            update():any;
        }
    }
    

    点击下载
    欢迎提问

    PHPMailer实现找回密码功能

    整体流程大致是:
    1 用户点击”密码找回”,跳转密码找回页面。
    2 密码找回页面需要输入”用户名”,然后根据用户名取得对应的邮箱地址。

    
    $user_exist = User::find()->where(["username" => $username]);
       ->one();
    

    3 当用户存在时,给对应邮箱发送邮件,邮件拼装方式如下图所示:

    主要包括设置Email相关参数,以及设置Email主题和内容,我们需要对内容进行Token加密,加密算法使用authcode,Token中主要包括email、username以及时间戳,用来后面的验证。具体的authcode如下:

    
        // 加密算法
        public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {   
            $ckey_length = 4;   
              
            $key = md5($key ? $key : UC_KEY);   
            $keya = md5(substr($key, 0, 16));   
            $keyb = md5(substr($key, 16, 16));   
            $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';   
              
            $cryptkey = $keya.md5($keya.$keyc);   
            $key_length = strlen($cryptkey);   
              
            $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;   
            $string_length = strlen($string);   
              
            $result = '';   
            $box = range(0, 255);   
              
            $rndkey = array();   
            for($i = 0; $i <= 255; $i++) {   
                $rndkey[$i] = ord($cryptkey[$i % $key_length]);   
            }   
              
            for($j = $i = 0; $i < 256; $i++) {   
                $j = ($j + $box[$i] + $rndkey[$i]) % 256;   
                $tmp = $box[$i];   
                $box[$i] = $box[$j];   
                $box[$j] = $tmp;   
            }   
              
            for($a = $j = $i = 0; $i < $string_length; $i++) {   
                $a = ($a + 1) % 256;   
                $j = ($j + $box[$a]) % 256;   
                $tmp = $box[$a];   
                $box[$a] = $box[$j];   
                $box[$j] = $tmp;   
                $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));   
            }   
              
            if($operation == 'DECODE') {   
                if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {   
                    return substr($result, 26);   
                } else {   
                        return '';   
                    }   
            } else {   
                return $keyc.str_replace('=', '', base64_encode($result));   
            }     
        }
    

    4 发送邮件后,用户点击邮件链接后跳转链接到指定页面,这里我们需要对Token进行验证,验证项目包括username和email是否对应、时间戳是否在一个小时内,如果验证通过,设置session并显示密码重置页面,否则显示验证错误页面。
    5 在密码重置页面中,通过session得到需要重置密码的用户名,然后输入新密码并再次输入验证,然后通过接口设置工程,完成整个密码找回过程。

    Unity3d中SendMessage – Android和Unity的交互

      • UnityPlayer的SendMessage

    SendMessage

    
    // paramString1 表示挂载脚本的对象
    // paramString2 表示脚本中调用的方法名
    // paramString3 传参数
    UnityPlayer.UnitySendMessage(paramString1, paramString2, paramString3);
    
      AndroidJavaClass和AndroidJavaObject
    
    using UnityEngine;
    using System.Collections;
    public static class AndroidUtils {
    	public static AndroidJavaClass unityOverrideClass;
    	public static AndroidJavaClass UnityOverrideClass {
    		get {
    			if (unityOverrideClass == null) {
    				unityOverrideClass = new AndroidJavaClass ("com.xxx.xxxx.xxxxActivity");
    			}
    			return unityOverrideClass;
    		}
    	}
    
    	public static AndroidJavaObject SDK {
    		get {
    			return UnityOverrideClass.CallStatic ("GetSDK", new object[0]);
    		}
    	}
    }
    

    同时需要我们Android封装Jar包,以及AndroidManifest.xml,XML中定义的Activity所需要的res也需要。将这些内容放置到Plugins中:

    Unity调用Android的方法如下

    
    AndroidUtils.SDK.Call("AndroidMethodName", args);
    

    AndroidManifest.xml示例

    <?xml version="1.0" encoding="utf-8" standalone="no"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" package="com.xx.xxxx" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727">
     <supports-screens android:largeScreens="true" android:normalScreens="false" android:requiresSmallestWidthDp="600" android:smallScreens="false" android:xlargeScreens="true"/>
     <uses-feature android:glEsVersion="0x00020000"/>
     <supports-gl-texture android:name="GL_OES_compressed_ETC1_RGB8_texture"/>
     <uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen.multitouch" android:required="false"/>
     <uses-feature android:name="android.hardware.touchscreen.multitouch.distinct" android:required="false"/>
     <uses-permission android:name="com.android.vending.CHECK_LICENSE"/>
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.CAMERA"/>
     <android:uses-permission android:name="android.permission.READ_PHONE_STATE"/>
     <application android:allowBackup="true" android:icon="@drawable/app_icon" android:isGame="true" android:label="@string/app_name" android:largeHeap="true" android:name="com.xxx.xxxx.BLApplication" android:supportsRtl="true" android:theme="@style/UnityThemeSelector">
     <activity android:configChanges="locale|fontScale|keyboard|keyboardHidden|mcc|mnc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|touchscreen|uiMode" android:exported="true" android:label="@string/app_name" android:launchMode="singleTask" android:name="com.xx.xxx.SDKNativeActivity" android:screenOrientation="sensorLandscape">
     <meta-data android:name="unityplayer.UnityActivity" android:value="true"/>
     <meta-data android:name="unityplayer.ForwardNativeEventsToDalvik" android:value="false"/>
     <intent-filter>
     <action android:name="android.intent.action.MAIN"/>
     <category android:name="android.intent.category.LAUNCHER"/>
     </intent-filter>
     </activity>
     </application>
    </manifest> 
    

    记录一次代码分析(2)

    续接上节,我们这次主要分析的是ManagePlayerActivity

  • 成员变量
  • 
      private static LoginListener listener;
      private Button btnAddPlayer;
      private Button btnBack;
      private Button btnEditPlayer;
      private boolean isManagePlayer = true;
      private LinearLayout llFilters;
      private PlayersAdapter mPlayersAdapter;
      private GridView mPlayersGridView;
      private List playersList;
      private RelativeLayout rlRootView;
      private SharedPrefUtil sharedPrefUtil;
      ...
    
  • 主要成员函数
    • onCreate
    
      protected void onCreate(Bundle paramBundle)
      {
        super.onCreate(paramBundle);
        setContentView(R.layout.activity_manage_player);
        sharedPrefUtil = new SharedPrefUtil(this);
        ...
        try
        {
          ...
          mPlayersGridView.setOnItemClickListener(new AdapterView.OnItemClickListener()
          {
            public void onItemClick(AdapterView paramAnonymousAdapterView, View paramAnonymousView, int paramAnonymousInt, long paramAnonymousLong)
            {
              if (isManagePlayer)
              {
                paramAnonymousAdapterView = mPlayersAdapter.getItem(paramAnonymousInt);
                paramAnonymousView = new Intent(ManagePlayerActivity.this, AddEditPlayerActivity.class);
                paramAnonymousView.addFlags(65536);
                paramAnonymousView.putExtra("EXTRA_PLAYER", paramAnonymousAdapterView);
                startActivityForResult(paramAnonymousView, 0);
              }
              ...
              if (BluetoothUtil.isPlaysetConnected())
              {
                ...
                return;
              }
              paramAnonymousAdapterView = new Intent(ManagePlayerActivity.this, PlaysetScanActivity.class);
              ...
              startActivityForResult(paramAnonymousAdapterView, 0);
            }
          });
          ...
          return;
        }
        catch (Exception paramBundle)
        {
          for (;;)
          {
            paramBundle.printStackTrace();
          }
        }
      }
    

    下面来看下PlaysetScanActivity主要用来Scan!
    PlaysetScanActivity同样继承BluetoothBaseActivity。

  • 成员变量
  • 
      private static final int REQUEST_ENABLE_BT = 1;
      private static final long SCAN_PERIOD = 10000L;
      private static final String TAG = PlaysetScanActivity.class.getName();
      private BluetoothStateReceiver bluetoothStateReceiver;
      ...
      private FlowLayout flowLayout;
      private boolean isAudioPlayed = false;
      private boolean isLettersRead = false;
      private boolean isPaused;
      private boolean isPermissionGranted = true;
      private boolean isPlaysetSelected = false;
      ...
      private BluetoothAdapter mBluetoothAdapter;
      private String mDeviceAddress;
      private String mDeviceName;
      private Handler mHandler;
      // 蓝牙设备列表
      private ArrayList mLeDevices = new ArrayList();
    

    这个类也非常重要,我想我们离成功就更近一步了。有关蓝牙(BLE)的相关内容,博客的文章也有所提及,我这边回慢慢完善。

  • 重要成员函数
    • onCreate
    
      public void onCreate(Bundle paramBundle)
      {
        ...
        // 蓝牙接收器
        bluetoothStateReceiver = new BluetoothStateReceiver();
        // 判断是否支持BLE
        if (!getPackageManager().hasSystemFeature("android.hardware.bluetooth_le"))
        {
          ...
          finish();
        }
        // 获取蓝牙适配器
        mBluetoothAdapter = ((BluetoothManager)getSystemService("bluetooth")).getAdapter();
        if (mBluetoothAdapter == null)
        {
          // 蓝牙不支持
          return;
        }
        if (sharedPrefUtil.getBoolean("IS_IDLE_TIME"))
        {
          //
        }
        paramBundle = new IntentFilter("android.bluetooth.adapter.action.STATE_CHANGED");
        // 注册蓝牙广播
        registerReceiver(bluetoothStateReceiver, paramBundle);
      }
    
      onResume
    
      protected void onResume()
      {
        ...
        isPaused = false;
        // 蓝牙关闭
        if (!mBluetoothAdapter.isEnabled())
        {
          ...
        }
        // 蓝牙没有连接
        if ((!BluetoothUtil.isPlaysetConnected()) && (!BluetoothUtil.getFirmwareUpdateState()) && (!BluetoothUtil.isBootModeEnabled()))
        {
          isPlaysetSelected = false;
          isAudioPlayed = false;
          if (!mScanning) {
            new Handler().postDelayed(new Runnable()
            {
              public void run()
              {
                PlaysetScanActivity.this.scanLeDevice(true);
              }
            }, 1000L);
          }
        }
      }
    

    开始扫描设备进行连接scanLeDevice

    
      private void scanLeDevice(boolean paramBoolean)
      {
        if (mBluetoothAdapter == null) {
          return;
        }
        if (paramBoolean)
        {
          mScanning = true;
          // 清空当前列表
          clearScanList();
          mHandler.postDelayed(new Runnable()
          {
            public void run()
            {
              if ((mScanning) && (!isPlaysetSelected))
              {
                ...
              }
            }
          }, 10000L);
          mHandler.postDelayed(new Runnable()
          {
            public void run()
            {
              // 设备不存在
              if ((mLeDevices.size() == 0) && (!isPlaysetSelected) && (!BluetoothUtil.isPlaysetConnected())) {
                ...
              }
              BluetoothDevice localBluetoothDevice;
              ArrayList localArrayList;
              do
              {
                do
                {
                  return;
                } while ((getIntent().hasExtra("EXTRA_CHOOSE_PLAYSET")) || (isPlaysetSelected) || (sharedPrefUtil.getStringSet("REMEMBER_PLAYSET").size() <= 0) || (flowLayout.getChildCount() != 1));
                localBluetoothDevice = (BluetoothDevice)flowLayout.getChildAt(0).getTag();
                localArrayList = new ArrayList();
                localArrayList.addAll(sharedPrefUtil.getStringSet("REMEMBER_PLAYSET"));
              } while ((!localArrayList.contains(localBluetoothDevice.getAddress())) || (isPaused));
              // 自动调用Click事件
              flowLayout.getChildAt(0).performClick();
            }
          }, 2000L);
          BluetoothAdapter localBluetoothAdapter = mBluetoothAdapter;
          UUID localUUID = Constants.FILTER_UUID_FOR_SCAN;
          // 设置回调
          BluetoothAdapter.LeScanCallback localLeScanCallback = mLeScanCallback;
          localBluetoothAdapter.startLeScan(new UUID[] { localUUID }, localLeScanCallback);
          return;
        }
        mScanning = false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
      }
    

    下面看下回调mLeScanCallback

    
      public void onLeScan(final BluetoothDevice paramAnonymousBluetoothDevice, int paramAnonymousInt, byte[] paramAnonymousArrayOfByte)
        {
          runOnUiThread(new Runnable()
          {
            public void run()
            {
              PlaysetScanActivity localPlaysetScanActivity;
              if (!isAudioPlayed)
              {
                localPlaysetScanActivity = PlaysetScanActivity.this;
                ...
              }
              for (boolean bool = true;; bool = false)
              {
                ...
                PlaysetScanActivity.this.addDevice(paramAnonymousBluetoothDevice);
                ...
                return;
              }
            }
          });
        }
      };
    

    我们此时加入了新的蓝牙连接设备对象addDevice

    
      private void addDevice(BluetoothDevice paramBluetoothDevice)
      {
        ...
        // 判断当前是否存在该设备
        if (!mLeDevices.contains(paramBluetoothDevice))
        {
          mLeDevices.add(paramBluetoothDevice);
          localView = getLayoutInflater().inflate(R.layout.device_item, null);
          localTextView = (TextView)localView.findViewById(R.id.tvDeviceName);
          String str = paramBluetoothDevice.getName();
          // 不存在设备则break;
          if ((str == null) || (str.length() <= 0)) {
            ...
          }
          localTextView.setText(str);
        }
        // 设置点击回调事件
        for (;;)
        {
          localView.setTag(paramBluetoothDevice);
          localView.setOnClickListener(new View.OnClickListener()
          {
            public void onClick(View paramAnonymousView)
            {
              // 用户选择了则return
              if (isPlaysetSelected) {
                return;
              }
              ...
              BluetoothDevice localBluetoothDevice = (BluetoothDevice)paramAnonymousView.getTag();
              if ((localBluetoothDevice == null) || (BluetoothUtil.isPlaysetConnected()))
              {
                ...
              }
              ...
              // 终止扫描
              SPPlaysetScanActivity.this.scanLeDevice(false);
              // 连接设备!!!
              boolean bool = BluetoothUtil.connectPlayset(mDeviceAddress, mDeviceName);
              ...
            }
          });
          ...
        }
      }
    

    该Activity中还包括检测硬件升级(checkFirmwareUpdate)、初始化UI(initUI)、bleLettersFromPlayset等函数。

    记录一次代码分析(1)


    入口Activity如下(由于有些涉及隐私,所以不展示):

    
      protected void onCreate(Bundle paramBundle)
      {
        super.onCreate(paramBundle);
        setContentView(R.layout.activity_splash_screen);
        getDeviceDimensions(this);
        sharedPrefUtil = new SharedPrefUtil(getApplicationContext());
        PlayerUtil.setSelectedPlayer(this, null);
        new Handler().postDelayed(new Runnable()
        {
          public void run()
          {
            if (sharedPrefUtil.getBoolean("LOGIN_USER_STATUS"))
            {
              localIntent = new Intent(SplashScreenActivity.this, ManagePlayerActivity.class);
              localIntent.putExtra("EXTRA_SELECT_PLAYER", true);
              localIntent.putExtra("EXTRA_IS_FIRST_TIME", true);
              startActivityForResult(localIntent, 0);
              return;
            }
            if (!sharedPrefUtil.getBoolean("HAVE_PLAYSET"))
            {
              localIntent = new Intent(SplashScreenActivity.this, BeforeStartActivity.class);
              startActivityForResult(localIntent, 0);
              return;
            }
            if (!sharedPrefUtil.getBoolean("DO_YOU_HAVE_ACCOUNT"))
            {
              localIntent = new Intent(SplashScreenActivity.this, HaveAccountActivity.class);
              startActivityForResult(localIntent, 0);
              return;
            }
            Intent localIntent = new Intent(SplashScreenActivity.this, LoginActivity.class);
            startActivityForResult(localIntent, 0);
          }
        }, SPLASH_TIME_OUT);
      }
    

    其中getDeviceDimensions为适配屏幕分辨率:

    
      public static void getDeviceDimensions(Activity paramActivity)
      {
        paramActivity = paramActivity.getWindowManager().getDefaultDisplay();
        Point localPoint = new Point();
        paramActivity.getSize(localPoint);
        Constants.setDeviceWidth(x);
        Constants.setDeviceHeight(y);
      }
    

    sharedPrefUtil用来本地存储,主要通过SharedPreferences来实现的,该程序主要包含的本地存储对象为:

    
    BATTER_SAVER
    DO_YOU_HAVE_ACCOUNT
    FILE_PATH
    FIRMWARE_RIVISION
    FIRMWARE_VERSION
    HARDWARE_RIVISION
    HAVE_PLAYSET
    IS_ALL_SET
    IS_IDLE_TIME
    LOGIN_USER_ID
    LOGIN_USER_INFO
    LOGIN_USER_STATUS
    LOGIN_USER_TOKEN
    PLAYERS_LIMIT
    PLAYSET_ADDRESS
    PLAYSET_IDLE_TIME
    PLAYSET_MANUFACTURER
    PLAYSET_MODEL
    PLAYSET_NAME
    PRIVACY
    REMEMBER_PLAYSET
    SELECTED_PLAYER
    TERMS_AND_CONDITIONS
    USER_ID
    USER_NAME
    USER_PASSWORD
    

    如果存在LOGIN_USER_STATUS则跳转ManagePlayerActivity,并传递参数EXTRA_SELECT_PLAYER和EXTRA_IS_FIRST_TIME参数。如果没有用户则需要创建用户,有用户需要登录。下一步主要来看下ManagePlayerActivity。

  • ManagePlayerActivity
  • 类定义及继承关系

    
      public class ManagePlayerActivity
        extends BluetoothBaseActivity
        implements View.OnClickListener, ApiCallListener {
         ...
      }
    

    接口ApiCallListener定义如下

    
    public abstract interface ApiCallListener
    {
      public abstract void onFailure(Object paramObject);
      public abstract void onResponse(Object paramObject);
    }
    

    ManagePlayerActivity继承BluetoothBaseActivity

  • BluetoothBaseActivity
  • 
    public abstract class BluetoothBaseActivity
      extends AppCompatActivity
      implements BluetoothCharacteristicListener, LoginListener, DialogInterface.OnDismissListener, PlaysetConnectionListener
    {
      ...
    }
    

    继承AppCompatActivity,可以说是新版的Activity(support v7中更新的),主要继承的借口有
    BluetoothCharacteristicListener

    
    public abstract interface BluetoothCharacteristicListener
    {
      public abstract void bleAvailableServices();
      public abstract void bleBatteryLevel(String paramString);
      public abstract void bleConnectedPlayset();
      public abstract void bleDisconnectedPlayset();
      public abstract void bleFirmwareUpdateStatus(int paramInt);
      public abstract void bleLcdsStates(byte[] paramArrayOfByte);
      public abstract void bleLettersFromPlayset(String paramString, byte[] paramArrayOfByte);
      public abstract void blePlaysetFirmwareRevision(String paramString);
      public abstract void blePlaysetHardwareRevision(String paramString);
      public abstract void blePlaysetManufacturerName(String paramString);
      public abstract void blePlaysetModelNumber(String paramString);
      public abstract void blePlaysetName(String paramString);
    }
    

    LoginListener

    
    public abstract interface LoginListener
    {
      public abstract void didFinishSdkUserConfiguration();
      public abstract void didSelectedChild(Player paramPlayer);
    }
    

    Player就是包含用户多种属性的序列化类

    
      @SerializedName("activityDate")
      private String activityDate;
      @SerializedName("avatar")
      private String avatar;
      @SerializedName("createdAt")
      private String createdAt;
      @SerializedName("currentLevel")
      private String currentLevel;
      @SerializedName("dateofBirth")
      private String dateofBirth;
      @SerializedName("firstName")
      private String firstName;
      @SerializedName("game")
      private String game;
      @SerializedName("gender")
      private String gender;
      @SerializedName("id")
      private String id;
      @SerializedName("imgbase64")
      private String imgbase64;
      @SerializedName("owner")
      private String owner;
      @SerializedName("profileURL")
      private String profileURL;
      @SerializedName("status")
      private String status;
      @SerializedName("updatedAt")
      private String updatedAt;
    

    PlaysetConnectionListener

    
    public abstract interface PlaysetConnectionListener
    {
      public abstract void availableServices();
      public abstract void batteryLevel(String paramString);
      public abstract void connectedPlayset();
      public abstract void didClearTheCharctersOnBoard();
      public abstract void disconnectedPlayset();
      public abstract void firmwareUpdateStatus(int paramInt);
      public abstract void gamePaused(boolean paramBoolean);
      public abstract void lcdsStates(byte[] paramArrayOfByte);
      public abstract void lettersFromPlayset(String paramString, byte[] paramArrayOfByte);
      public abstract void playsetFirmwareRevision(String paramString);
      public abstract void playsetHardwareRevision(String paramString);
      public abstract void playsetManufacturerName(String paramString);
      public abstract void playsetModelNumber(String paramString);
      public abstract void playsetName(String paramString);
    }
    

    下面我们重点看BluetoothBaseActivity的类实现,这个对我们理解所有的工作流程有非常的帮助!

    成员变量

    
      // 用户Connection监听器
      private static PlaysetConnectionListener playsetConnectionListener;
      // 是否锁屏
      private boolean isScreenLocked = true;
      private View mDecorView;
      private ProgressDialog progressDialog;
      // 本地存储
      private SharedPrefUtil sharedPrefUtil;
    

    关键成员函数

      onCreate
    
      protected void onCreate(Bundle paramBundle)
      {
        requestWindowFeature(1);
        super.onCreate(paramBundle);
        getWindow().setFlags(1024, 1024);
        mDecorView = getWindow().getDecorView();
        hideSystemUI();
        Util.IS_APP_RUNNING = true;
        // 蓝牙初始化,有关BluetoothUtil我们之后说
        if (!BluetoothUtil.isInitialized()) {
          BluetoothUtil.initialize(getApplicationContext());
        }
        // 设置蓝牙监听器
        BluetoothUtil.setListener(this);
        // 设置本地存储
        sharedPrefUtil = new SharedPrefUtil(this);
        if (progressDialog == null)
        {
          progressDialog = new ProgressDialog(this, R.style.SPProgressDialogTheme);
          progressDialog.setOnDismissListener(this);
        }
        // 未设置分辨率,设置分辨率,用于屏幕适配
        if (Constants.getDeviceWidth() == 0) {
          SPSplashScreenActivity.getDeviceDimensions(this);
        }
        new Handler().postDelayed(new Runnable()
        {
          public void run()
          {
            // 配置UI
            ViewGroup localViewGroup = (ViewGroup)((ViewGroup)findViewById(16908290)).getChildAt(0);
            BluetoothBaseActivity.this.setupUI(localViewGroup);
          }
        }, 1000L);
      }
    
      onResume

    Android生命周期图如下:

    
      protected void onResume()
      {
        super.onResume();
        // 设置监听器
        BluetoothUtil.setListener(this);
        ...
        // 下面这里非常非常重要!!!
        try
        {
    
          Class localClass = Class.forName(getString(R.string.game_activity));
          if (getClass().equals(localClass))
          {
            LoginActivity.setListener(this);
            ManagePlayerActivity.setListener(this);
            setplaysetConnectionListener(this);
          }
          ...
          return;
        }
        catch (Exception localException)
        {
          ...
        }
      }
    

    我们需要分析上面这段内容

    getString:获得定义的字符串在res/string.xml中。满足当前运行类是指定game_activity时,配置监听器!

      LoginListener中接口的实现
    
    // 空实现
    public void didFinishSdkUserConfiguration() {}
    public void didSelectedChild(Player paramPlayer) {}
    
      PlaysetConnectionListener中接口的实现
    
    // 空实现
    public void availableServices() {}
    public void didClearTheCharctersOnBoard() {}
    
      BluetoothCharacteristicListener中接口的实现
    
      public void bleAvailableServices()
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.availableServices();
        }
      }
      
      // 获取电量等级
      public void bleBatteryLevel(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.batteryLevel(paramString);
        }
        int i = Util.parseInt(paramString);
        if (i < 10)
        {
          String str1 = getString(R.string.Oops);
          String str2 = getString(R.string.LowBatteryMsg);
          if (i == 0) {
            paramString = "1";
          }
          Util.showAlert(this, str1, str2.replace("X", paramString), getString(R.string.Ok), null);
        }
      }
      //
      public void bleConnectedPlayset()
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.connectedPlayset();
        }
      }
      //
      public void bleDisconnectedPlayset()
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.disconnectedPlayset();
        }
        if (...)
        {
          ...
          try
          {
            ...
            PlayerUtil.startPlaysetScanActivity(this);
            return;
          }
          catch (ClassNotFoundException localClassNotFoundException)
          {
            localClassNotFoundException.printStackTrace();
          }
        }
      }
    
      // 
      public void bleFirmwareUpdateStatus(int paramInt)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.firmwareUpdateStatus(paramInt);
        }
      }
      
      public void bleLcdsStates(byte[] paramArrayOfByte)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.lcdsStates(paramArrayOfByte);
        }
      }
    
      public void bleLettersFromPlayset(String paramString, byte[] paramArrayOfByte)
      {
        if (playsetConnectionListener != null)
        {
          playsetConnectionListener.lettersFromPlayset(paramString, paramArrayOfByte);
          if (paramString.trim().length() == 0) {
            playsetConnectionListener.didClearTheCharctersOnBoard();
          }
        }
      }
    
      public void blePlaysetFirmwareRevision(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.playsetFirmwareRevision(paramString);
        }
        sharedPrefUtil.setString("FIRMWARE_RIVISION", paramString);
      }
    
      public void blePlaysetHardwareRevision(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.playsetHardwareRevision(paramString);
        }
        sharedPrefUtil.setString("HARDWARE_RIVISION", paramString);
      }
    
      public void blePlaysetManufacturerName(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.playsetManufacturerName(paramString);
        }
        sharedPrefUtil.setString("PLAYSET_MANUFACTURER", paramString);
      }
    
      public void blePlaysetModelNumber(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.playsetModelNumber(paramString);
        }
        sharedPrefUtil.setString("PLAYSET_MODEL", paramString);
      }
    
      public void blePlaysetName(String paramString)
      {
        if (playsetConnectionListener != null) {
          playsetConnectionListener.playsetName(paramString);
        }
      }
    

    至此我们分析了BluetoothBaseActivity,下一节我们主要分析ManagePlayerActivity

    Android 4.0 BLE开发

    android 4.3(API 18)为BLE核心功能提供了平台支持和API,App可以用这些API来发现设备查询服务读写特征。对比传统蓝牙,BLE的主要特点是低功耗
    关键的术语和概念

  • GATT
  • Generic Attribute Profile GATT配置文件是一个通用规范,用于在BLE连路上链路发送和接收”Attribute”数据块,目前所有BLE应用都基于GATT。蓝牙SIG规定了许多低功耗设备和配置文件。配置文件是设备如何特定的应用程序中工作的规格说明。一个设备可以实现多个配置文件。

  • ATT
  • Attribute Protocol GATT在ATT协议基础上建立起来,被称为GATT/ATT。ATT在BLE设备上进行了优化,使用了尽可能少的字节。每一个属性通过一个唯一的统一标识符(UUID)来标记。每个String类型UUID使用的128 bits标准格式。属性通过ATT格式化称为characteristicsservices

  • Characteristic
  • 每个Characteristic包括一个单一变量和0-n个用来描述characteristic变量的descriptor,characteristic可以认为是一个类。

  • Descriptor
  • Descriptor用来描述characteristic变量的属性。

  • Service
  • Service是characteristic的集合。


    参考:http://blog.csdn.net/ohyeahhhh/article/details/52175596

    匿名方法



    正常我们在使用单例的时候,需要考虑线程安全,代码如下所示:

    private object m_mutex = new object();
    private bool m_initialized = false;
    private BigInstance m_instance = null;
    
    public BigInstance Instance
    {
        get
        {
            if (!this.m_initialized)
            {
                lock (this.m_mutex)
                {
                    if (!this.m_initialized)
                    {
                        this.m_instance = new BigInstance();
                        this.m_initialized = true;
                    }
                }
            }
    
            return this.m_instance;
        }
    }
    

    这样如果有多个单例,就违反了DRY原则
    其实我们可以将它们封装到一处:

    public class Lazy
    {
        public Lazy(Func func)
        {
            this.m_initialized = false;
            this.m_func = func;
            this.m_mutex = new object();
        }
    
        private Func m_func;
    
        private bool m_initialized;
        private object m_mutex;
        private T m_value;
    
        public T Instance
        {
            get
            {
                if (!this.m_initialized)
                {
                    lock (this.m_mutex)
                    {
                        if (!this.m_initialized)
                        {
                            this.m_value = this.m_func();
                            this.m_func = null;
                            this.m_initialized = true;
                        }
                    }
                }
    
                return this.m_value;
            }
        }
    }
    

    然后我们可以简化写成这样

    private Lazy m_lazyInstance =
        new Lazy(delegate { return new BigInstance(); });
    
    public BigInstance Instance { get { return this.m_lazyInstance.Instance; } }
    

    使用Lamabda表达式如下:

    private Lazy m_lazyInstance =
        new Lazy(() => new BigInstance());
    public BigInstance Instance { get { return this.m_lazyInstance.Value; } }
    

    匿名方法的缺点
    匿名方法的优势在于自动形成闭包,而缺点也是让开发人员“不自觉”的创建了闭包,这样会使某些对象的生命周期加长。

    有关委托的进化

    .NET 1.x中的委托

    public delegate int SomeDelegate(string arg1, bool arg2);
    public static int SomeMethod(string arg1, bool arg2) {
        return 0;
    }
    public class SomeClass {
        public int SomeMethod(string a1, bool a2) {
            retrun 0;
        }
        public event SomeDelegate SomeEvent;
    }
    
    static void Main(string[] args) {
        SomeClass someClass = new SomeClass();
        SomeDelegate someDelegate = new SomeDelegate(someClass.SomeMethod);
        someDelegate.SomeEvent += new SomeDelegate(SomeMethod);
    }

    .NET 1x中需要使用new DelegateType(…)的方式来创建一个委托对象。作为委托对象内部的方法既可以是成员方法也可以是静态方法。

    .NET 2.0中委托

    public delegate TResult MyFunc<T1, T2, TResult>(T1 a1, T2 a2);
    public static int SomeMethod(string a1, bool a2) {
        return 0;
    }
    static void Main(string[] args) {
        MyFunc<string, bool, int> myFunc = SomeMethod;
    }

    在2.0中,new DelegateType可以省略,开发人员可以直接将方法赋值给一个委托对象的引用。下面看看新引入的“匿名方法”:

    public static void TestRequest(string url) {
        WebRequest request = HttpWebRequest.Create(url);
        request.BeginGetResponse(delegate(IAsyncResult ar) {
            using (WebResponse response = request.EndGetResponse(ar)) {
                Console.WriteLine("{0}: {1}", url, response.ContentLength);
            }
        },
        null);
    }
    

    匿名方法,简单地说就是内联在方法内部的委托对象,它的关键便在于形成了一个闭包(委托执行时所需的上下文)。如上面的代码中,BeginGetResponse的第一个参数(委托)可以直接使用TestRequest方法的参数url,以及方法内的“局部”变量request。如果没有匿名函数这个特性的话,代码写起来就麻烦了,例如在.NET 1.x中您可能就必须这么写:

    public static void TestRequest(string url)
    {
        WebRequest request = HttpWebRequest.Create(url);
        object[] context = new object[] { url, request };
        request.BeginGetResponse(TestAsyncCallback, context);
    }
    
    public static void TestAsyncCallback(IAsyncResult ar)
    { 
        object[] context = (object[])ar.AsyncState;
        string url = (string)context[0];
        WebRequest request = (WebRequest)context[1];
    
        using (WebResponse response = request.EndGetResponse(ar))
        {
            Console.WriteLine("{0}: {1}", url, response.ContentLength);
        }
    }
    

    可读性增强的匿名写法如下:

    public static void TestRequest(string url)
    {
        WebRequest request = HttpWebRequest.Create(url);
        request.BeginGetResponse(delegate(IAsyncResult ar)
        {
            TestAsyncCallback(ar, request, url);
        }, null);
    }
    
    public static void TestAsyncCallback(IAsyncResult ar, WebRequest request, string url)
    {
        using (WebResponse response = request.EndGetResponse(ar))
        {
            Console.WriteLine("{0}: {1}", url, response.ContentLength);
        }
    }
    

    .NET 3.5中可以使用Lambda表达式

    public static void TestRequest(string url)
    {
        WebRequest request = HttpWebRequest.Create(url);
        request.BeginGetResponse(ar => TestAsyncCallback(ar, request, url), null);
    }
    

    Android基础知识

    1 有关3G/4G技术
    3G:速率一般在几百Kbps,较之前的2G和2.5G在数据传输速度上有很大提升。
    4G:速度可达到100Mbps以上,几乎可以满足人们的所有传输数据的需求。
    3G技术标准:
    WCDMA:全球80%以上的3G网络都是采用此种制式。中国联通运营。
    CDMA2000:目前日韩及北美使用较多。中国电信运营。
    TD-SCDMA:中国自主知识产权的3G通信技术。中国移动运营。

    2 搭建环境
    2.1. 所需资源
    JDK,Java开发环境。下载地址:http://www.oracle.com
    Eclipse,IBM公司开发的一款开源IDE。http://www.eclipse.org
    Android SDK,Android 开发工具,包含开发Android程序所需类库、源码、文档、案例等资源。http://www.android.com
    ADT插件,ADT 是 Eclipse 平台下用来开发 Android 应用程序的插件。http://www.android.com
    3 常用命令行
    列出可以使用的Android
    android list targets
    列出可以使用的虚拟机
    android list avd
    创建虚拟机
    emulator –avd <虚拟机名>
    显示已连接的设备
    adb devices
    导入文件到手机
    adb push <Windows源文件路径> <手机目标路径>
    从手机导出文件
    adb pull <手机源文件路径> <Windows目录路径>
    安装程序
    adb install <apk文件路径>
    卸载程序
    adb uninstall <包名>
    重启adb
    adb kill-server
    abd start-server
    4 创建与代码结构
    src:源代码
    gen:系统自动生成的文件
    java 中记录了项目中各种资源ID
    res:系统资源,所有文件都会在R文件生成资源ID
    drawable:图片
    layout:界面布局
    values:数据
    anim:定义动画的XML
    raw:原生文件
    assets:资源路径,不会在R文件注册
    project.properties:供Eclipse使用,读取该项目使用Android版本号。早期版本名为:default.properties
    AndroidManifest.xml:清单文件,在软件安装的时候被读取Android中的四大组件(Activity、ContentProvider、BroadcastReceiver、Service)都需要在该文件中注册,程序所需的权限也需要在此文件中声明,例如:电话、短信、互联网、访问SD卡。
    bin:二进制文件,包括class、资源文件、dex、apk等
    proguard.cfg:用来混淆代码的配置文件,防止别人反编译。
    5 程序启动过程
    Eclipse将.java源文件编译成.class;使用dx工具将所有.class文件转换为.dex文件;再将.dex文件和所有资源打包成.apk文件;将.apk文件安装到虚拟机完成程序安装;启动程序 – 开启进程 – 开启主线程;创建Activity对象 – 执行OnCreate()方法;按照main.xml文件初始化界面。
    6 查看程序错误
    Android程序中如果出错,错误不会显示在Console中,而是显示在LogCat界面下。可以从window – show view中打开;日志信息分为5个级别:verbose > debug > info > warn > error 高级的包含低级的;可以创建过滤器对日志进行过滤显示,点击绿色加号,可以按照tag、pid、level进行筛选。
    7 短信发送
    搭建界面需要组件:TextView、EditText、Button;给Button添加监听器,当被点击的时候获取号码,获取内容;使用SmsManager发送短信;需要注册短信发送权限
    8 布局
    RelativeLayout(相对布局):android-sdk-windows/docs/guide/topics/ui/layout-objects.html#relativelayout
    FrameLayout(帧布局):android-sdk-windows/docs/guide/topics/ui/layout-objects.html#framelayout
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

    Egret基础 – TypeScript学习之路

    TypeScript学习之路

    为了方便cocos2d-x的朋友,上篇作为引子,今天这篇会就着Egret所使用的Typscript语言学习一下。主要大致分为基本语法和类型条件与循环接口与函数类、包与命名空间泛型 几个部分。希望可以使得朋友尽快入手。

    基本语法和类型

    
       /**
        * 定义变量的方法有 var 、let、const
        */ 
        var number1:number = 100;
        var number2 = 200;
        let number3:number = 300;
        let number4 = 400;
        const number5 = 500;
    

    变量定义

    这里定义出现了var和let,他们之间有什么区别呢?看下段代码你就会有所了解
    1460604178615
    会提示错误src/Main.ts(194,34): error TS2304: Cannot find name ‘num2’.
    当然你要是强行编译,然后运行,你会发现
    1460604271545
    还是显示出来了,因为这个是js定义变量的原理导致的,会将所有的声明提前,但是我们还是要遵循ts这个规则。
    至于const和let的比较可以参考下文
    1460604389668

    基本类型

    
    var b:boolean = false;
    var n:number = 100;
    var s:string = "Hello Egret";
    var name = "TomYuan";
    var h_str: string = `Hello, my name is ${name}`;
    console.log(h_str);
    var arr1:Array = [100, 200];
    var arr2:number[] = [100, 200];
    var p1:[string, number] = ["TomYuan", 100];
    var p2:[string, string, string] = ["China", "USA", "Russia"];
    enum COLOR {Red, Green, Blue};
    var color = COLOR.Red;
    var any_value:any = 100;
    any_value = false;
    any_value = "AnyValue";
    function test(): void {
        // TODO
    }
    

    一般在学习一门语言的路上,必然会学习条件语句和循环语句,这里我们就一起来学习下相关的内容。

    
    var a = Math.random() * 100;
    var b = Math.random() * 100;
    if (a + b > 150) {
        console.log("运气不错,去买彩票吧!");
    } else if (a + b < 10) {
        console.log("运气爆棚,多买几张彩票!");
    } else {
        console.log("今天适合在家睡觉");
    }
    

    通过if…else if..else我们可以完成很多有用的逻辑。当然typescript也同样是支持switch的,而且类型更多样化

    
    enum PlayerStatus { IDLE,MOVE,ATK,SKILL,DEAD};
    var curr_status = PlayerStatus.MOVE;
    switch(curr_status) {
        case PlayerStatus.IDLE:{
            console.log("玩家处于呼吸状态");
            break;
        }
        case PlayerStatus.MOVE:{
            console.log("玩家处于移动状态");
            break;
        }
        case PlayerStatus.ATK:{
            console.log("玩家正在进行攻击");
            break;
        }
        case PlayerStatus.SKILL:{
            console.log("玩家释放了大招");
            break;
        }
        case PlayerStatus.DEAD: {
            console.log("玩家已经死亡");
            break;
        }
        default: {
            console.log("玩家正在发呆");   
        }
    }
    

    有了switch…case…的机制,我们可以很容易实现一些有限状态机的内容。
    学习完了选择,我们就想到了各式各样的循环,看看都有什么的循环等着我们来征服。

    
    var nums: number[] = [100, 200, 312, 405, 771, 512];
    var sum = 0;
    for (var i = 0; i < nums.length; i++) {
        sum = sum + nums[i];
    }
    console.log(`Total:${sum}`);
    for (var k in nums) {
        console.log("n:" + nums[k]);
    }
    

    接口与函数
    我们这里将函数和接口同意称之为接口,让我们看一个简单的接口

    
    function abs(n:number): number {
        if (n >= 0) {
            return n;
        }
        return -n;
    }
    ...
    console.log(abs(-100));        // OUT: 100
    

    下面我们来看下属性接口

    
    interface CostomValue {
        color_r:number;
        color_g:number;
        color_b:number;
    }
    function covertToRGB(v: CostomValue):number {
        var ret:number = 0;
        ret = (v.color_r << 16) + ret;
        ret = (v.color_g << 8) + ret;
        ret = v.color_b + ret;
        return ret;
    }
    ...
    var color:CostomValue = {color_r: 225, color_g: 255, color_b: 155};
    var ret = covertToRGB(color);
    console.log(ret);
    

    上述属性中我们发现都是必须参数,那么可选参数该怎么做呢?

    
    interface CostomValue {
        color_r:number;
        color_g:number;
        color_b:number;
        color_a?:number;
    }
    

    看看那个小小的?就是了
    除了属性接口还有函数接口

    
    interface avg {
        (num1:number, num2:number, ...number):number;
    }
    var avgFunc:avg;
    avgFunc = function(n1:number, n2:number, n3:number, n4:number) {
        return (n1 + n2 + n3 + n4) / 4;
    }
    ...
    var avg_v = avgFunc(100, 200, 300, 400);
    console.log(avg_v);
    

    除了属性和函数还有数组和类接口,数组我这里就不介绍了,一起看下类接口吧!

    
    interface Run {
        Output();
    }
    class People implements Run {
        constructor() {
        }
        Output() {
            console.log("People Running...");
        }
    }
    class Cat implements Run {
        constructor() {
        }
        Output() {
            console.log("Cat Running...");
        }
    }
    ...
    var p = new People();
    p.Output();
    var cat = new Cat();
    cat.Output();
    

    经常玩C++的你一定立刻就懂了,不解释!

  • 类、包与命名空间
  • 
    class Main extends egret.DisplayObjectContainer {
        /**
         * 加载进度界面
         * Process interface loading
         */
        private loadingView:LoadingUI;
        public constructor() {
            super();
            this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this);
        }
        private onAddToStage(event:egret.Event) {
            //设置加载进度界面
            //Config to load process interface
            this.loadingView = new LoadingUI();
            this.stage.addChild(this.loadingView);
            //初始化Resource资源加载库
            //initiate Resource loading library
            RES.addEventListener(RES.ResourceEvent.CONFIG_COMPLETE, this.onConfigComplete, this);
            RES.loadConfig("resource/default.res.json", "resource/");
        }
        ...
    }
    

    官方的代码中这里体现了大部分类的内容,包括继承关系以及封装性
    egret是命名空间。我们在做自己的项目的时候,可以使用一个命名空间来整体规划项目。
    有关包的概念可以参考这里:http://www.typescriptlang.org/docs/handbook/modules.html

    泛型

    
    function identity(arg: T): T {
        return arg;
    }
    

    有了这些准备,我们继续学习之旅吧!

    Egret基础-事件与自定义事件

    游戏中的事件机制

    在游戏开发过程中,我们会不断的使用事件的机制,不管是Touch操作还是网络通信都离不开这些,本篇我们就事件机制做一个整理和学习。

    cocos2d-x中的事件机制

    使用过cocos2d-x的朋友都知道,其事件机制主要由以下几个类控制
    CCEvent CCEventController CCEventCustom CCEventDispatcherCCEventListenerCCEventType
    如果你对cocos2d-x事件了解的非常好,对于Egret中的事件你也会非常容易理解。为了后面的加深理解,先简单的阐述下cocos2d-x中事件的原理。
    cocos2d-x中的Event类型大概分为:

    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        FOCUS,
        GAME_CONTROLLER,
        CUSTOM
     };
    

    而Egret中的事件类型,看目录egret\events,我们知道Egret默认事件的类型包括
    Focus(焦点)Geolocation(定位)HTTPSIOErrorMotion(手势)Orientation(旋转与方向)ProgressStageOrientationTextTimerTouch
    在cocos2d-x中,不同的事件使用不同的事件监听器的创建接口,当然也存在自定义的事件监听器接口。我们这里均以 Touch 和 Custom 来进行举例说明。
    这里我们以cocos2d-x的EventListenerTouchOneByOne举例

    class CC_DLL EventListenerTouchOneByOne : public EventListener
    {
    public:
        ...
        static EventListenerTouchOneByOne* create();
        ...
    public:
        typedef std::function<bool(Touch*, Event*)> ccTouchBeganCallback;
        typedef std::function<void(Touch*, Event*)> ccTouchCallback;
        ccTouchBeganCallback onTouchBegan;
        ccTouchCallback onTouchMoved;
        ccTouchCallback onTouchEnded;
        ccTouchCallback onTouchCancelled;
        ...
    };
    // CPP
    EventListener::init(Type::TOUCH_ONE_BY_ONE, LISTENER_ID, nullptr)
    

    从上述代码我们知道主要执行了父类的init方法,其实主要就是将事件回调、事件类型、事件ID等信息进行了初始化。而添加事件监听的地方在 EventDispatcher ,对应添加事件监听和删除事件监听大致有这几个方法:

    
    // .h
    void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);
    void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
    EventListenerCustom* addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback);
    void removeEventListener(EventListener* listener);
    void removeEventListenersForType(EventListener::Type listenerType);
    void removeEventListenersForTarget(Node* target, bool recursive = false);
    void removeCustomEventListeners(const std::string& customEventName);
    void removeAllEventListeners();
    

    事件这块都涉及了优先级的概念。同样的在Egret中事件分发接口 IEventDispatcher 中有如下定义:

    
    /**
    * @language zh_CN
    * 使用 EventDispatcher 对象注册事件侦听器对象,以使侦听器能够接收事件通知。可以为特定类型的事件、阶段和优先级在显示列表的所有节
    * 点上注册事件侦听器。成功注册一个事件侦听器后,无法通过额外调用 on() 来更改其优先级。要更改侦听器的优先级,必须
    * 先调用 removeEventListener()。然后,可以使用新的优先级再次注册该侦听器。注册该侦听器后,如果继续调用具有不同 type 或 useCapture
    * 值的 on(),则会创建单独的侦听器注册。<br/>
    * 如果不再需要某个事件侦听器,可调用 EventDispatcher.removeEventListener()
    * 删除它;否则会产生内存问题。由于垃圾回收器不会删除仍包含引用的对象,因此不会从内存中自动删除使用已注册事件侦听器的对象。复制
    * EventDispatcher 实例时并不复制其中附加的事件侦听器。(如果新近创建的节点需要一个事件侦听器,必须在创建该节点后附加该侦听器。)
    * 但是,如果移动 EventDispatcher 实例,则其中附加的事件侦听器也会随之移动。如果在正在处理事件的节点上注册事件侦听器,则不会在当
    * 前阶段触发事件侦听器,但会在事件流的稍后阶段触发,如冒泡阶段。如果从正在处理事件的节点中删除事件侦听器,则该事件侦听器仍由当前操
    * 作触发。删除事件侦听器后,决不会再次调用该事件侦听器(除非再次注册以备将来处理)。
    * @param type 事件的类型。
    * @param listener 处理事件的侦听器函数。此函数必须接受 Event 对象作为其唯一的参数,并且不能返回任何结果,
    * 如下面的示例所示: function(evt:Event):void 函数可以有任何名称。
    * @param thisObject 侦听函数绑定的this对象
    * @param useCapture 确定侦听器是运行于捕获阶段还是运行于冒泡阶段。如果将 useCapture 设置为 true,
    * 则侦听器只在捕获阶段处理事件,而不在冒泡阶段处理事件。如果 useCapture 为 false,则侦听器只在冒泡阶段处理事件。
    * 要在两个阶段都侦听事件,请调用 on() 两次:一次将 useCapture 设置为 true,一次将 useCapture 设置为 false。
    * @param  priority 事件侦听器的优先级。优先级由一个带符号的整数指定。数字越大,优先级越高。优先级为 n 的所有侦听器会在
    * 优先级为 n -1 的侦听器之前得到处理。如果两个或更多个侦听器共享相同的优先级,则按照它们的添加顺序进行处理。默认优先级为 0。
    * @see #once()
    * @see #removeEventListener()
    * @version Egret 2.4
    * @platform Web,Native
    */
    addEventListener(type:string, listener:Function, thisObject:any, useCapture?:boolean, priority?:number):void;
    ...
    once(type:string, listener:Function, thisObject:any, useCapture?:boolean, priority?:number):void;
    removeEventListener(type:string, listener:Function, thisObject:any, useCapture?:boolean):void;
    

    文件中的注释也非常清楚,从这个角度说Egret和cocos2d-x的事件使用方法上非常类似。至于原理,我一会儿会在Egret中讲解,在解释cocos2d-x事件原理之前,我们知道有了addEventListener,还有一个不得不说的就是dispatch。dispatch就是分发的意思,意思我们单单addEventListener之后,事件只是被注册,但是什么时候去调用,还是需要我们使用dispatch来分发消息的。在Egret中也是同样的道理。因为本文库主要阐述的是Egret的方方面面,所以其他引擎我会粗略的发表一下自己的意见,有兴趣的朋友可以私下多多交流。
    addEventListener 所做的事情就是就是将注册的事件加入到_listenerMap中,而 removeEventListener 的时候会将事件从_listenerMap中删除。在dispatchEvent的时候,首先会根据优先级对时间监听进行排序,然后执行代码如下:

    
    auto onEvent = [&event](EventListener* listener) -> bool{
        event->setCurrentTarget(listener->getAssociatedNode());
        listener->_onEvent(event);
        return event->isStopped();
    };
    

    我们整体寻找一下默认的dispatch有哪些?当然自定义的事件需要你自己add之后,进行dispatch喔。

    我们搜索了以后得到了很多结果,为了验证找其中一处:
    当我们注册了Touch的Listener,在底层点击的时候,已经加入了dispatch,所以我们无需自己调用。
    在cocos2d-x这里说了这么多,只有一个意思,因为Egret的事件机制和cocos2d-x基本是一致的。

    Egret中的事件机制浅析

    事件机制原理(官方):http://edn.egret.com/cn/docs/page/112
    我这里简单的做一个说明或者概括:

     所有事件机制的原理大多相同:
     基本都有【事件发送方】、【事件接收方】、【事件】三个部分组成。
     事件是连接 发送方 和 接收方的桥梁。
     事件发送方在发送事件之后可以等待回应或者认为事情已经完成。
     事件接收方接到事件会去根据事件做不同的事情,这个事情我们称之为回调。

    有了上面对cocos2d-x的事件分析,我们了解了一些,下面我们就从代码层面开始研究,最后我们会多说几个例子来整体进行巩固。那么我们就从这里开始。


    由于文件个数并不多,所以我们依次看看这些文件的父子级关系和内容。
    Event.ts

    
    // 文件的开始对这个文件有一个非常好的注释:
    /**
    Event 类作为创建事件实例的基类,当发生事件时,Event 实例将作为参数传递给事件侦听器。Event 类的属性包含有关事件的基本信息,例如事件的类型或者是否可以取消事件的默认行为。对于许多事件(如由 Event 类常量表示的事件),此基本信息就足够了。但其他事件可能需要更详细的信息。
    */
    // 简单点说上面我们提到了事件机制的三个部分,这里就是【事件的基类】
    

    下面我们写个例子,测试怎么创建一个事件。

    
    private EventTest(e: egret.Event) {
        console.log(e.data);
    }
    ...
    var e:egret.Event = egret.Event.create(egret.Event, "Test");
    e.data = "TestData";
    
    var p:egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
    p.addEventListener("Test", this.EventTest, this);
    
    var o:egret.DisplayObjectContainer = new egret.DisplayObjectContainer();
    
    var ret = o.dispatchEvent(e);
    console.log(ret);
    

    好吧,我承认上述的代码不漂亮,而且更糟糕的是没有执行,一会儿我们一起看看这个为什么,从这个角度而言也说明,和我们所了解的cocos还是有些区别的。
    我们在编译后的代码中

    这里得到的list是空。如果我们此时直接让p.dispatchEvent(e)是没有问题的。
    上面也说明,一个新的对象直接去dispatchEvent的时候,他的eventMap是没有的。
    当然我们先按照官方的设定来玩这个概念。之后了解清楚原理,可以自己随意扩展。
    现在我们看看那个地方是最重要的呢?毫无疑问 addEventListener 非常重要。
    事件派发器之Add

    下面我们就从Egret的事件派发器说起。
    EventDispatcher继承于IEventDispatcher
    这个接口中的几个方法,需要一个一个详细的聊聊。

    addEventListener

    参数:
    type:string 事件的类型
    listener:Function 处理事件的侦听器函数。此函数必须接受 Event 对象作为其唯一的参数,并且不能返回任何结果
    thisObject:any 侦听函数绑定的this对象
    useCapture?:boolean 确定侦听器是运行于捕获阶段还是运行于冒泡阶段
    priority?:number 事件侦听器的优先级
    通过分析源码我们知道addEventListener主要是判断事件优先级确定index,然后构建eventBin如下:

    
    var eventBin = {
        type: type, listener: listener, thisObject: thisObject, priority: priority,
        target: this, useCapture: useCapture, dispatchOnce: !!dispatchOnce
    };
    

    然后将事件插入到list中,也就说明此时改对象的eventMap就有了对应的事件了。所以该对象在执行dispatchEvent的时候,取得对应的list,然后通过下面一行命令调用回调:
    eventBin.listener.call(eventBin.thisObject, event);
    说完了这个,再重新写个例子,连带整理下Egret事件使用方法:

    
    // 自定义顾客事件
    class CustomerEvent extends egret.Event {
    
        public static BUY:string = "buy";
        public money:number;
        public constructor(type:string, bubbles:boolean = false, cancelable:boolean = false) {
            super(type, bubbles, cancelable);
        }
    }
    // 商品类
    class Good extends egret.DisplayObject {
    
        private _name:string;
        private _value:number;
        private _jin:number;
    
        constructor(name:string, value:number, jin:number) {
            super();
            this._name = name;
            this._value = value;
            this._jin = jin;
        }
    
        public get name() :string {
            return this._name;
        }
    
        public set name(v:string) {
            this._name = v;
        }
    
        public get value():number {
            return this._value;
        }
    
        public set value(v:number) {
            this._value = v;
        }
    
        public get jin():number {
            return this._jin;
        }
        public set jin(v:number) {
            this._jin = v;
        }
    }
    
    // 商店类
    class Shop extends egret.DisplayObject {
    
        public static totalMoney:number;
        private goods:Array<Good>;
        private config:Array<any> = [
            ["苹果", 5.98, 15],
            ["鸭梨", 4.1, 22],
            ["香蕉", 7.2, 17],
            ["猕猴桃", 14, 5]
        ];
        private static _shopInstance: Shop;
        constructor() {
            super();
        }
    
        public AddMoney(e:CustomerEvent) {
            Shop.totalMoney = Shop.totalMoney + e.money;
            console.log("ShopMoney:$" + Shop.totalMoney);
        }
    
        private init():void {
            this.addEventListener(CustomerEvent.BUY, this.AddMoney, this);
            Shop.totalMoney = 0;
            this.goods = new Array<Good>();
            for (var i = 0; i < this.config.length; i++) {
                let name = this.config[i][0];
                let value = this.config[i][1];
                let jin = this.config[i][2];
                this.goods.push(new Good(name, value, jin));
            }
        }
    
        public static getInstance():Shop {
    
            if (!Shop._shopInstance) {
                Shop._shopInstance = new Shop();
                Shop._shopInstance.init();
            }
            return Shop._shopInstance;
        }
    
        public getGoods():Array<Good> {
            return this.goods;
        }
    
        public getRandomGood() {
            var ret = null;
            var index = Math.floor(Math.random() * this.goods.length);
            if (this.goods[index]) {
                ret = this.goods[index];
            }
            return ret;
        }
    
        public allGoodOver() {
            var ret = false;
            var is_zero = 0;
            for (var i = 0; i < this.goods.length; i++) {
                is_zero = is_zero + this.goods[i].jin;
            }
            if (is_zero == 0) {
                ret = true;
            }
            return ret;
        }
    }
    
    // 顾客类
    var family_name:string[] = ["张", "王", "李", "魏", "吴", "袁", "周", "孙"];
    var post_name:string[] = ["博", "洋", "秋水", "长苏"];
    function genName():string {
        var ret = "";
        var index1 = Math.ceil(Math.random() * family_name.length) - 1;
        var index2 = Math.ceil(Math.random() * post_name.length) - 1;
        ret = family_name[index1] + post_name[index2];
        return ret;
    }
    
    class Customer extends egret.DisplayObject {
    
        private _balance:number;
        private c_name:string;
        constructor() {
            super();
            // 自动名字
            this.c_name = genName();
            console.log("Name:" + this.c_name);
            // 自动余额
            this._balance = Math.floor(Math.random() * 100) + 100;
            console.log("Balance:" + this._balance);
            // 添加事件监听
            this.addEventListener(CustomerEvent.BUY, this.buyCallBack, this);
        }
    
        // 购买
        public buyCallBack(e:CustomerEvent) {
            this._balance = this._balance - e.money;
            console.log(this.c_name + "剩余:$" + this._balance);
        }
    
        public set balance(v:number) {
            this._balance = v;
        }
    
        public get balance():number {
            return this._balance;
        }
    
        public buy(index:number, n:number) {
            // 购买
            var shop = Shop.getInstance();
            var goods:Array<Good> = shop.getGoods();
            var e:CustomerEvent = new CustomerEvent(CustomerEvent.BUY);
            e.money = goods[index].value * n;
    
            // 更新数据
            goods[index].jin = goods[index].jin - n;
            shop.dispatchEvent(e);
            this.dispatchEvent(e);
        }
    }
    
    class GameMechine extends egret.DisplayObject {
        public static startGame(customers:Array<any>) {
    
            var shop = Shop.getInstance();
            var max_circle = 100;
            var start_index = 0;
            while (true) {
                // 检测
                if (shop.allGoodOver()) {
                    console.log("Game Over1");
                    break;
                }
    
                if (start_index > max_circle) {
                    console.log("Game Over2");
                    break;
                }
    
                console.log("Nexting..");
                // 选择钱数最多的
                var p_index:[number, number] = [0, 0];
                for (var i = 0; i < customers.length; i++) {
                    let c = <Customer>customers[i];
                    if (p_index[1] < c.balance) {
                        p_index[0] = i;
                        p_index[1] = c.balance;
                    }
                }
    
                // 购买价格 * 斤数最高的,如果没有次之
                var g_index:[number, number] = [0, 0];
                var goods:Array<Good> = shop.getGoods();
                for (var i = 0; i < goods.length; i++) {
                    var value = goods[i].value * goods[i].jin;
                    if (g_index[1] < value) {
                        g_index[0] = i;
                        g_index[1] = value;
                    }
                }
    
                // 默认1
                (<Customer>customers[p_index[0]]).buy(g_index[0], 1);
            }
        }
    }
    //// 具体执行
    var customers:Array<Customer> = new Array<Customer>();
    for (var i = 0; i < 3; i++) {
        // 生成三名顾客
        let c = new Customer();
        customers.push(c);
    }
    
    console.log("===================Start====================");
    GameMechine.startGame(customers);
    

    BLE核心概念

    1 Profile 一种规范,一种通信协议,profile存放在从机中,SIG规定了一些协议包括:心率计、防丢器等。

    2 Service 一种服务,在BLE从机中存在多个服务,包括电量信息服务器,系统信息服务,每个Service中包含多个Characteristic,每个具体的Characteristic的值才是BLE通信的主体。比如当前的电量是80%,会通过电量的Characteristic特征值存在从机的Profile里面,这样,主机就可以通过这个Characteristic值活的从机的80%电量值。

    3 Characteristic BLE主从机通信均是通过Characteristic实现的,可以理解成为一个标签,通过标签可以得到想要的内容。

    4 UUID 唯一识别码,上面的Service和Characteristic均需要通过一个UUID来识别。UUID为128,但是在BLE中,UUID通常用16位,也就是双字节来代替,16位与128位可以相互转化。

    综上所述:

    每个从机由一个或者多个profile构成,每个profile由一个或者多个Service构成,每个Service由多个Characteristic构成,主机和从机通信均是由Characteristic实现。

    有关GATT Profile内容
    现在通用的BLE一般都是建立在GATT(Generic Attribute Profile)协议上。GATT是一个在蓝牙连接之上的发送和接收很短的数据段的通用规范。每个数据段被称为Attribute。

    GAP
    GAP用来控制设备的连接和广播。GAP是你的设备被其它设备可见,并决定了你的设备是否被其它设备可见,以及与其它设备如何交互。
    GAP给设备定义了若干角色,主要包括Peripheral(外围设备)和Central(中心设备)。
    外围设备:一般是非常小或者简单的低功耗的设备,用来提供数据,并连接到一个相对强大的中心设备。
    中心设备:中心设备相对比较强大,用来连接其它外围设备,例如手机等。
    广播数据
    外围设备由两种向外的广播数据:Advertising Data Payload(广播数据)和Scan Response Data Payload(扫描回复),每种数据最长可以包含31byte。这里广播数据是必须的。因为外设必须不停的向外广播,让中心设备知道它的存在。扫描回复是可选的。

    GATT
    GATT(Generic Attribute Profile)普通属性协议,它定义两个BLE设备通过Service和Characterisitic来进行通信,使用ATT(Attribute Protocol)协议,ATT协议把Service、Characterisitic数据保存在查找表中,使用16bitID座位每一项的索引(UUID),一旦两个设备建立连接,GATT开始起作用。同时需要注意的是GATT连接是独占的,也就是说一个BLE外设同时只被一个中心设备连接,一旦外设被连接,它马上就会停止广播,这样它就对其它设备不可见了。当设备断开,它又开始广播。一个中心设备可以连接多个外设。
    GATT结构如下

    关于我

    个人简介


    袁博,男,1990年3月31日,大学本科学历,大学其间多次参加信息竞赛并获奖;毕业后在腾讯(北京)工作,主要从事社交及电商开发工作;2013年加入游戏创业团队担任游戏开发工程师;后加入北京优扬传媒有限公司担任主程开发工作,主要负责前后端架构及功能研发;2018年6月加入北京小嗨乐学科技有限公司担任CTO,主要负责优学猫系列产品的研发和维护工作。

    大事记


    • 2017.06.04
      买车了,最近一直开车,希望可以一路平安,一切以安全为重。最近更多的研究方向偏向人工智能和机器学习了。希望可以慢慢进步,与业内的各位朋友多多交流!
    • 2017.06.14
      NBA勇士冠军!!!!
    • 2017.06.04
      创业进行中,加油!!!
    • 2018.09.26
      马上就要当爸爸了,继续努力!!
    • 2019.02.25
      有贴心小棉袄了,太开心了!
    • 2019.05.08
      妞妞已经百天了,希望你可以健健康康长大,爸爸爱你!

      写于2016年9月,也希望写给多年后的自己!


       在整个之前,我先聊下自己的游戏之旅吧!希望朋友们也可以留言给我聊聊自己的游戏之路。上大学期间,我就一直爱好着游戏开发,学习计算机其实也是因为自己的游戏情节,小时候玩的《仙剑奇侠传》和《红色警戒》,后来玩的《魔兽争霸3》到现在都历历在目,似乎现在玩游戏很难找到曾经的那个感觉了。说回游戏开发,记得刚上大学我我没有接触过什么游戏引擎,只是觉得可以自己做出一个自己的游戏非常的牛掰。那时使用的IDE还是VC6.0(是多么让人回味啊),文档方面主要靠着MSDN以及Windows API,花费很大的力气也就做些小玩意,更不要说理解什么是游戏引擎,那时觉得自己可以学习到事件回调、游戏循环等概念就非常知足了。

       一段时间之后发现,无论怎么写,做的无外乎也就是俄罗斯方块等简单的游戏(其实也不简单)。这些游戏和我自己梦想的游戏,比如说《仙剑奇侠传》、《金庸群侠传》相距甚远。所以查阅了不少资料,了解到DirectX还是非常好的,其实我这里是先接触DirectX的,后来毕业后做cocos2d-x,才开始研究OpenGL的。现在清晰的记得研究DirectX的岁月,那个时候没有什么相关的中文文档,只能硬着头皮看着英文文档,了解了什么是渲染,什么是光照等等。这个学习过程持续了一段时间,自己也按照样例做了些不大不小的东西。

       后来不知是什么时候就喜欢在各种地方下载源码,尤其是游戏相关的,不管是什么语言一概下载下来,说来惭愧,到现在还有很多没有看,也不知道去哪里了,当然也是因为这个习惯,发现了云风的“风魂”的源码,了解到游戏引擎这个奇妙的东西,“风魂”主要是以C来完成的,里面还混合了汇编加速,当时读起来真心觉得非常的痛苦,但是对自己的能力获得或少还是有所提高,看了一段时间差不多就大二了,因为要成为学长了,就有了自己组织学弟学妹来做一个游戏引擎的打算,当然那时候很天真,没想到困难有后面那么多,经过老师的同意,组织了十个下一级的学生,每天晚上聚在一起给他们讲C语言,然后一起聊游戏,聊人生。那段日子可以说痛并快乐着,后来游戏引擎有了一点点模样,但是大家都有自己的事情,自己这方面已经开始在外实习和比赛,游戏引擎的事情就这么搁置了,下面就说说我们比赛和工作经历。

       其实我参加的都是算法方面的编程比赛,那会儿还天真的以为算法玩的很好就会做游戏了,可是后来才明白,只明白算法是远远不够的。那会儿主要参加的就是“百度之星”和“Topcoder”,也拿了一些名次,现在能记住的就是因为时差原因,Topcoder的比赛一般在深夜,我就自己熬着夜,提交代码之后,相互找别人代码的问题(challenge)。做过ACM,反正那阶段就不断的调试代码找题做吧。

       说说自己的工作经历,自己开始的工作和游戏关系也不大,大一下半学期就兼职给一个明星维护网站工作以及相关的网站开发任务。后来由于一些原因就不干了,可能是真心不喜欢,随后在4399实习做了一段时间的服务器,还记得是用erlang,后来就在腾讯做Web开发了,这期间其实一直工作中一直没有做过什么游戏,还是因为自己对游戏的热爱,离开了腾讯。

       之后辗转反侧进了游戏公司,第一家游戏公司是一家创业型的公司,用的就是cocos,还记的那个时候的版本是1.x版本吧,去了之后既没有签合同,薪资也少的可怜。因为我当时在西二旗住,公司坐标是石景山,每天6点不到就出门了,然后基本上每天都快12点到家。但是我依旧是快乐的,那种刚刚进入游戏行业的兴奋感,现在还有印象,但是公司方面似乎就我一个外人,剩下的主程、美术以及策划是一个团队出来的,对于所有东西防我和防狼一样,没有网络,所有的代码也没有对我开源,现在其实我也能够理解当时的情况,但是当时我觉得过的很憋屈,于是帮助他们完成了聊天模块和背包模块之后,一个月吧,我就辞职了。当然最后也没有给我一分钱,但是也是心存感激的,从那时我进入了cocos领域,开始了很长很长的游戏旅途,一直到现在。

       在新的公司,同事们一起完成了适配、架构、网络、支付等等各种工作,也克服了各种困难,经历了整个手游从蓝海到红海的过程。后来H5火了,当然现在依旧火,那是为了配合公司的工作,就用cocos-js版本做过一些项目,大概有3、4个游戏吧,但是Android上都卡的要死,公司就放弃了。之后出来了一款大家都耳熟能详的游戏佳作:刀塔传奇,于是经过我们的努力,将整个的开发流变更为了cocos2d + lua的方式,当然没有用quick,开发速度大大加快,也享受到了技术带来的革新。之后的做了几个游戏,产品技术问题都不大,但是由于挖坑和后期的运营确实不擅长于是结局就是没有成功。

       挣扎了一段时间更换了工作,在新的环境中发现了一个名字叫“Egret(白鹭)”的H5引擎火了,抱着看一看的心态下载了,用户体验确实远远强于cocos,这个也应该至少是一年以前的事情了,经过了一年的体验,当然真正的使用时间也不长,因为公司的业务暂时也不是H5为主,所以在公司即将开展H5项目之机会,想完成一个新的框架和接口,使得类似我这样的cocos2d-x开发者可以更加快速的上手做自己的H5游戏,希望可以和大家一起学习和进步。