C/C++关键字typedef使用指南

搞懂了c++创始人写的中的下面这个例子, 有助于你理解typdef:

typedef int P();
typedef int Q();
class X {
static P(Q); // 等价于`static int Q()`, Q在此作用域中不再是一个类型
static Q(P); // 等价于`static int Q(int ())`, 定义了一个名为Q的function
};

这是一个极好的例子, 先问一下 typedef int P()到底做了什么 ? 其实是 :

declares a function type P as returning an int and taking no arguments.

1. 官方定义

初次接触此类typedef用法的程序员直观上理解这个例子比较困难, 我们来看一下typedef的官方定义 :

Typedef does not work like typedef [type] [new name].The[new name] part does not always come at the end.

You should look at it this way : if[some declaration] declares a variable, typedef [same declaration] would define a type.

总结一下就是 : 任何声明变量的语句前面加上typedef之后,原来是变量的都变成一种类型。不管这个声明中的标识符号出现在中间还是最后。

2. 隐藏技能
typedef 定义的新类型, 使用时可以省略括号。什么意思 ?

typedef int NUM;
NUM a = 10; // 也可写成`NUM(a) = 10;
NUM(b) = 12; // 也可写成`NUM b = 12;

3. 例子(先从初级的开始 :)

int整型

typedef int x; // 定义了一个名为x的int类型

自定义结构体

typedef struct { char c; } s; // 定义名为s的struct类型

指针

typedef int *p; //定义了一个名为p的指针类型, 它指向int

高级的(注意标识符不一定在最后) :

数组

typedef int A[]; // 定义一个名为A的int数组的类型

函数

typedef int f(); // 定义一个名为f, 参数为空, 返回值为int的函数类型

typedef int g(int); // 定义一个名为g, 含一个int参数, 返回值为int行的函数类型

现在回过头看:

typedef int P();
static P(Q);

应该就比较好理解了, P是一个新定义的function类型, 它返回值为int, 无参数
根据我的第2点说明, P(Q); 实际上等价于P Q, 声明Q是一个返回值为int, 无参数的函数.

这玩意有什么用呢 ?
我们都知道C++语言里, 函数都是先声明后使用的(除非在使用之前定义), 看以下例子 :

#include <iostream>
#include <stdio.h>
#include <string>

typedef int P(); // 简单的
typedef void Q(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true); // 复杂的
class X {
public:
P(eat_shit); // 等价于声明`int eat_shit();`
Q(bullshit); // 等价于声明`void bullshit(int *p, const string& s1, const string& s2, size_t size, bool is_true);`
};

int main() {
X *xx;
printf(“shit ret: %d\n”, xx->eat_shit());
int a[] = { 1, 3, 4, 5, 7 };
xx->bullshit(a, “foo”, “bar”, sizeof(a) / sizeof(int), true);
}

int X::eat_shit() {
return 888;
}

void X::bullshit(int *p, const std::string& s1, const std::string& s2, size_t size, bool is_true) {
std::cout << “s1: ” << s1 << “, s2: ” << s2 << “, size: ” << size << std::endl;
printf(“elems:\n”);
for (int i = 0; i < size; i++) {
printf(“%d %s”, *p++, (i == size – 1) ? “” : “,”);
}
printf(“\n”);
}

理解了上面的再看下面这段,理解复杂的定义和声明:

在阅读Linux的内核代码是经常会遇到一些复杂的声明和定义,例如:

  1.  void * (*(*fp1) (int))[10];
  2. float(*(*fp2) (int, int, float)) (int);
  3. typedef double(*(*(*fp3) ())[10]) ();
    1. fp3 a;
  4. int(*(*fp4())[10]) ();

 

刚看到这些声明或者定义时,初学者可能头皮发毛,基于大惑不解。如果缺乏经验和方法来对这些内容进行理解,势必会让我们浪费大量的时间。

我尝试对这些内容进行疏理和总结,为自己和有同样困惑的同学答疑解惑。要理解这些复杂的声明和定义,我觉得首先不能着急,应该由浅而深,逐步突破。下面先看一些简单的定义:

1. 定义一个整型数

int a;

2. 定义一个指向整型数的指针

int *p;

3. 定义一个指向指针的指针,它指向的指针指向一个整型数

int **pp;

我们可以用一些简单的代码把这三条给串起来:

int a;
int *p;
int **pp;

p = &a; // p指向整数a所在的地址
pp = &p; // pp指向指针p

 

4. 定义一个包含10个整型数的数组

int arr[10];

5. 定义一个指向包含10个整型数数组的指针

int(*pArr)[10];

用几行代码将4、5两个定义串起来:

int arr[10];
int(*pArr)[10];

pArr = &arr;

6. 定义一个指向函数的指针,被指向的函数有一个整型参数并返回整型值

int(*pfunc) (int);

7. 定义一个包含10个指针的数组,其中包含的指针指向函数,这些函数有一个整型参数并返回整型值

int(*arr[10]) (int);

用几行代码将6、7两个定义串起来:

int(*pfunc) (int);
int(*arr[10]) (int);

arr[0] = pfunc;

到这一步,似乎就不是那么好理解了。现在需要请出用于理解复杂定义的“右左法则”:

从变量名看起,先往右,再往左,碰到圆括号就调转阅读的方向;括号内分析完就跳出括号,还是先右后左的顺序。如此循环,直到分析完整个定义。

让我们用这个方法来分析上面的第6条定义:int(*pfunc) (int);

  • 找到变量名pfunc,先往右是圆括号,调转方向,左边是一个*号,这说明pfunc是一个指针;
  • 然后跳出这个圆括号,先看右边,又遇到圆括号,这说明(*pfunc)是一个函数,所以pfunc是一个指向这类函数的指针,即函数指针,这类函数具有一个int类型的参数,返回值类型是int。

接着分析第7条定义:int(*arr[10]) (int);

  • 找到变量名arr,先往右是[]运算符,说明arr是一个数组;
  • 再往左是一个*号,说明arr数组的元素是指针(注意:这里的*修饰的不是arr,而是arr[10]。原因是[]运算符的优先级比*要高,arr先与[]结合。);
  • 跳出圆括号,先往右又遇到圆括号,说明arr数组的元素是指向函数的指针,它指向的函数有一个int类型的参数,返回值类型是int。

分析完这两个定义,相信多数人心里面应该有点谱了。

可应该还有人会问:怎么判断定义的是函数指针(定义6),还是数组指针(定义5),或是数组(定义7)?可以抽象出几个模式:

  • type(*var)(…); // 变量名var与*结合,被圆括号括起来,右边是参数列表。表明这是函数指针
  • type(*var)[]; //变量名var与*结合,被圆括号括起来,右边是[]运算符。表示这是数组指针
  • type(*var[])…; // 变量名var先与[]结合,说明这是一个数组(至于数组包含的是什么,由旁边的修饰决定)

至此,我们应该有能力分析文章开始列出来了几条声明和定义:

(1) void * (*(*fp1) (int))[10];

  • 找到变量名fp1,往右看是圆括号,调转方向往左看到*号,说明fp1是一个指针;
  • 跳出内层圆括号,往右看是参数列表,说明fp1是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  • 再跳出外层圆括号,往右看是[]运算符,说明函数返回的是一个数组指针,往左看是void *,说明数组包含的类型是void *。

简言之,fp1是一个指向函数的指针,该函数接受一个整型参数并返回一个指向含有10个void指针数组的指针。

(2) float(*(*fp2) (int, int, float)) (int);

  • 找到变量名fp2,往右看是圆括号,调转方向往左看到*号,说明fp2是一个指针;
  • 跳出内层圆括号,往右看是参数列表,说明fp2是一个函数指针,接着往左看是*号,说明指向的函数返回值是指针;
  • 再跳出外层圆括号,往右看还是参数列表,说明返回的指针是一个函数指针,该函数有一个int类型的参数,返回值类型是float。

简言之,fp2是一个指向函数的指针,该函数接受三个参数(int, int和float),且返回一个指向函数的指针,该函数接受一个整型参数并返回一个float。

(3) typedef double(*(*(*fp3) ())[10]) ();

fp3 a;

  • 如果创建许多复杂的定义,可以使用typedef。这一条显示typedef是如何缩短复杂的定义的。
  • 跟前面一样,先找到变量名fp3(这里fp3其实是新类型名),往右看是圆括号,调转方向往左是*,说明fp3是一个指针;
  • 跳出圆括号,往右看是空参数列表,说明fp3是一个函数指针,接着往左是*号,说明该函数的返回值是一个指针;
  • 跳出第二层圆括号,往右是[]运算符,说明函数的返回值是一个数组指针,接着往左是*号,说明数组中包含的是指针;
  • 跳出第三层圆括号,往右是参数列表,说明数组中包含的是函数指针,这些函数没有参数,返回值类型是double。

简言之,fp3是一个指向函数的指针,该函数无参数,且返回一个含有10个指向函数指针的数组的指针,这些函数不接受参数且返回double值。

这二行接着说明:a是fp3类型中的一个。

(4) int(*(*fp4())[10]) ();

这里fp4不是变量定义,而是一个函数声明。

  • 找到变量名fp4,往右是一个无参参数列表,说明fp4是一个函数,接着往左是*号,说明函数返回值是一个指针;
  • 跳出里层圆括号,往右是[]运算符,说明fp4的函数返回值是一个指向数组的指针,往左是*号,说明数组中包含的元素是指针;
  • 跳出外层圆括号,往右是一个无参参数列表,说明数组中包含的元素是函数指针,这些函数没有参数,返回值的类型是int。

简言之,fp4是一个返回指针的函数,该指针指向含有10个函数指针的数组,这些函数不接受参数且返回整型值。

用typedef简化复杂的声明和定义,以上我们已经看到了不少复杂的声明和定义,这里再举一个例子:

int *(*a[10]) (int, char*);

  • 用前面的“右左法则”,我们可以很快弄清楚:
  • a是一个包含10个函数指针的数组,这些函数的参数列表是(int, char*),返回值类型是int*。
  • 理解已经不成问题,这里的关键是如果要定义相同类型的变量b,都得重复书写:

int *(*b[10]) (int, char*);

这里有没有方便的办法避免这样没有价值的重复?答案就是用typedef来简化复杂的声明和定义。

typedef可以给现有的类型起个别名。这里用typedef给以上a、b的类型起个别名:

typedef int *(*A[10]) (int, char*); // 在之前定义的前面加入typedef,然后将变量名a替换成类型名A

现在要再定义相同类型的变量c,只需要:
A c;

再看一例:

void(*b[10]) (void(*)());

先替换右边括号里面的参数,将void(*)()的类型起个别名pParam:

[源码]会议歌曲播放辅助工具V1

开源一个会议场合音乐播放辅助工具,

开发语言:易语言

工具主要是为了方便操作播放器,全依靠方向键,如下图:

QQ截图20181115150400

软件注册了全局热键:↑、→、↓、←

用户只需要控制方向键即可,音乐可以设定手动播放、自动播放(选中歌曲右键菜单)。

QQ截图20181115150620

以下是源码:

.版本 2
.支持库 shellEx
.支持库 iext
.支持库 eMMedia

.程序集 窗口程序集_启动窗口
.程序集变量 播放热键, 整数型
.程序集变量 暂停热键, 整数型
.程序集变量 上一首热键, 整数型
.程序集变量 下一首热键, 整数型

.子程序 __启动窗口_创建完毕

标题 = “会议歌曲播放辅助工具”
.如果真 (文件是否存在 (取运行目录 () + “\sysmp3.ini”) = 假)
写到文件 (取运行目录 () + “\sysmp3.ini”, #ini)
.如果真结束
扫描文件夹 ()
给文件上状态 ()
播放热键 = 注册热键 (_启动窗口.取窗口句柄 (), 标签1.取窗口句柄 (), 0, #右光标键)
暂停热键 = 注册热键 (_启动窗口.取窗口句柄 (), 标签1.取窗口句柄 (), 0, #左光标键)
上一首热键 = 注册热键 (_启动窗口.取窗口句柄 (), 标签1.取窗口句柄 (), 0, #上光标键)
下一首热键 = 注册热键 (_启动窗口.取窗口句柄 (), 标签1.取窗口句柄 (), 0, #下光标键)

.子程序 _自动播放_被选择

写配置项 (取运行目录 () + “\sysmp3.ini”, “MP3”, 超级列表框1.取标题 (超级列表框1.现行选中项, 0), “0”)
超级列表框1.置标题 (超级列表框1.现行选中项, 2, “自动播放”)

.子程序 扫描文件夹, , , MP3只能放在程序目录下

搜索文件夹 (取运行目录 (), “*.mp3”)
标签1.标题 = “就绪!”

.子程序 搜索文件夹
.参数 目录, 文本型
.参数 后缀名, 文本型
.局部变量 temp, 文本型
.局部变量 a, 整数型

.如果真 (取文本右边 (目录, 1) ≠ “\”)
目录 = 目录 + “\”
.如果真结束
输出调试文本 (目录)
temp = 寻找文件 (目录 + 后缀名, )

.判断循环首 (temp ≠ “”)
处理事件 ()
.如果真 (temp ≠ “.” 且 temp ≠ “..”)
处理事件 ()
标签1.标题 = “当前文件:” + temp
a = 超级列表框1.插入表项 (0, temp, , , , )
超级列表框1.置标题 (a, 1, 目录 + temp)
超级列表框1.置标题 (a, 2, “0”)

.如果真结束
temp = 寻找文件 (, )

.判断循环尾 ()

.子程序 _手动播放_被选择

写配置项 (取运行目录 () + “\sysmp3.ini”, “MP3”, 超级列表框1.取标题 (超级列表框1.现行选中项, 0), “1”)
超级列表框1.置标题 (超级列表框1.现行选中项, 2, “手动播放”)

.子程序 给文件上状态
.局部变量 i, 整数型
.局部变量 temp, 整数型

.计次循环首 (超级列表框1.取表项数 (), i)
temp = 到数值 (读配置项 (取运行目录 () + “\sysmp3.ini”, “MP3”, 超级列表框1.取标题 (i - 1, 0), “0”))
.判断开始 (temp = 0)
超级列表框1.置标题 (i - 1, 2, “自动播放”)
.判断 (temp = 1)
超级列表框1.置标题 (i - 1, 2, “手动播放”)
.默认
超级列表框1.置标题 (i - 1, 2, “未知”)
.判断结束

.计次循环尾 ()

.子程序 _超级列表框1_右键单击表项

.如果 (超级列表框1.取标题 (超级列表框1.现行选中项, 2) = “自动播放”)
自动播放.选中 = 真
.否则
自动播放.选中 = 假
.如果结束

弹出菜单 (菜单, , )
.子程序 _按钮1_被单击

_播放_被选择 ()

.子程序 _按钮2_被单击

_暂停_被选择 ()

.子程序 _按钮3_被单击

_下一首_被选择 ()

.子程序 _按钮4_被单击

_上一首_被选择 ()
.子程序 _播放_被选择
.局部变量 文件, 文本型
.如果 (媒体播放1.取状态 () = 1 且 全_继续播放 = 假)
媒体播放1.停止 ()
时钟1.时钟周期 = 0
标签1.标题 = “状态:停止”
按钮1.标题 = “播放”
进度条1.位置 = 0
.否则
全_继续播放 = 假
.如果真 (超级列表框1.现行选中项 = -1)
超级列表框1.现行选中项 = 0
超级列表框1.保证显示 (超级列表框1.现行选中项)
超级列表框1.获取焦点 ()
.如果真结束

文件 = 超级列表框1.取标题 (超级列表框1.现行选中项, 1)
媒体播放1.打开 (文件)
媒体播放1.播放 (-1, )

时钟1.时钟周期 = 1000
进度条1.最大位置 = 媒体播放1.取总时间 ()
分 = 取整 (媒体播放1.取总时间 () ÷ 60)
秒 = 媒体播放1.取总时间 () % 60
标签1.标题 = “正在播放:” + 超级列表框1.取标题 (超级列表框1.现行选中项, 0) + “ ” + 取文本右边 (“0” + 到文本 (分), 2) + “:” + 取文本右边 (“0” + 到文本 (秒), 2)
按钮1.标题 = “停止”
.如果结束

超级列表框1.保证显示 (超级列表框1.现行选中项)
超级列表框1.获取焦点 ()

.子程序 _暂停_被选择

.如果 (媒体播放1.取状态 () = 1)
媒体播放1.暂停 ()
时钟1.时钟周期 = 0
标签1.标题 = “状态:暂停”
按钮2.标题 = “继续”
.否则
媒体播放1.播放 (进度条1.位置 × 1000, )
时钟1.时钟周期 = 1000
按钮2.标题 = “暂停”
标签1.标题 = “正在播放:” + 超级列表框1.取标题 (超级列表框1.现行选中项, 0) + “ ” + 取文本右边 (“0” + 到文本 (分), 2) + “:” + 取文本右边 (“0” + 到文本 (秒), 2)
.如果结束
超级列表框1.保证显示 (超级列表框1.现行选中项)
超级列表框1.获取焦点 ()
.子程序 _下一首_被选择

.如果真 (超级列表框1.取表项数 () = 超级列表框1.现行选中项 + 1)
超级列表框1.现行选中项 = -1
.如果真结束
超级列表框1.现行选中项 = 超级列表框1.现行选中项 + 1
媒体播放1.停止 ()
时钟1.时钟周期 = 0
标签1.标题 = “状态:停止”
按钮1.标题 = “播放”
进度条1.位置 = 0
全_继续播放 = 真
_播放_被选择 ()

.子程序 _上一首_被选择

.如果真 (超级列表框1.现行选中项 = 0)
超级列表框1.现行选中项 = 超级列表框1.取表项数 ()
.如果真结束
超级列表框1.现行选中项 = 超级列表框1.现行选中项 - 1
媒体播放1.停止 ()
时钟1.时钟周期 = 0
标签1.标题 = “状态:停止”
按钮1.标题 = “播放”
进度条1.位置 = 0
全_继续播放 = 真
_播放_被选择 ()

.子程序 _时钟1_周期事件
.局部变量 i, 整数型
.局部变量 temp, 文本型

.如果 (秒 = 0)
秒 = 59
分 = 分 - 1
.否则
秒 = 秒 - 1
.如果结束
标签1.标题 = “正在播放:” + 超级列表框1.取标题 (超级列表框1.现行选中项, 0) + “ ” + 取文本右边 (“0” + 到文本 (分), 2) + “:” + 取文本右边 (“0” + 到文本 (秒), 2)
进度条1.位置 = 进度条1.位置 + 1

.如果真 (进度条1.位置 ≥ 进度条1.最大位置)

进度条1.位置 = 0
时钟1.时钟周期 = 0
temp = 超级列表框1.取标题 (超级列表框1.现行选中项 + 1, 2)

.如果 (temp = “自动播放”)
超级列表框1.现行选中项 = 超级列表框1.现行选中项 + 1
超级列表框1.保证显示 (超级列表框1.现行选中项)
超级列表框1.获取焦点 ()

全_继续播放 = 真
_播放_被选择 ()
.否则
标签1.标题 = “状态:停止”
按钮1.标题 = “播放”

超级列表框1.现行选中项 = 超级列表框1.现行选中项 + 1
超级列表框1.保证显示 (超级列表框1.现行选中项)
超级列表框1.获取焦点 ()
.如果结束

.如果真结束
.子程序 __启动窗口_将被销毁

撤销热键 (_启动窗口.取窗口句柄 (), 播放热键)
撤销热键 (_启动窗口.取窗口句柄 (), 暂停热键)
撤销热键 (_启动窗口.取窗口句柄 (), 上一首热键)
撤销热键 (_启动窗口.取窗口句柄 (), 下一首热键)

.子程序 _标签1_反馈事件, 整数型
.参数 参数一, 整数型
.参数 参数二, 整数型

.判断开始 (参数一 = 播放热键)
_播放_被选择 ()
.判断 (参数一 = 暂停热键)
_暂停_被选择 ()
.判断 (参数一 = 上一首热键)
_上一首_被选择 ()
.判断 (参数一 = 下一首热键)
_下一首_被选择 ()
.默认

.判断结束

 

 

以下是全局变量:

.版本 2

.全局变量 全_打开类型, 整数型
.全局变量 分, 整数型
.全局变量 秒, 整数型
.全局变量 全_继续播放, 逻辑型

文件下载地址:

链接:https://pan.baidu.com/s/1437Q4MCgIHr_RYdAmojXGw
提取码:mbmp

Neural networks made easy

现在谈人工智能已经绕不开“神经网络”这个词了。人造神经网络粗线条地模拟人脑,使得计算机能够从数据中学习。

机器学习这一强大的分支结束了 AI 的寒冬,迎来了人工智能的新时代。简而言之,神经网络可能是今天最具有根本颠覆性的技术。

看完这篇神经网络的指南,你也可以和别人聊聊深度学习了。为此,我们将尽量不用数学公式,而是尽可能用打比方的方法,再加一些动画来说明。

强力思考

AI 的早期流派之一认为,如果您将尽可能多的信息加载到功能强大的计算机中,并尽可能多地提供方法来了解这些数据,那么计算机就应该能够“思考”。比如 IBM 著名的国际象棋 AI Deep Blue 背后就是这么一个思路:通过对棋子可能走出的每一步进行编程,再加上足够的算力,IBM 程序员创建了一台机器,理论上可以计算出每一个可能的动作和结果,以此来击败对手。

通过这种计算,机器依赖于工程师精心预编程的固定规则——如果发生了 A,那么就会发生 B ; 如果发生了 C,就做 D——这并不是如人类一样的灵活学习。当然,它是强大的超级计算,但不是“思考”本身。

教机器学习

在过去十年中,科学家已经复活了一个旧概念,不再依赖大型百科全书式记忆库,而是框架性地进行模拟人类思维,以简单而系统的方式分析输入数据。 这种技术被称为深度学习或神经网络,自20世纪40年代以来一直存在,但是由于今天数据的大量增长—— 图像、视频、语音搜索、浏览行为等等——以及运算能力提升而成本下降的处理器,终于开始显示其真正的威力。

createJS开发小游戏

效果图:(录屏的时候有点卡)

910706-20170707164034456-1593675354

 demo:

https://github.com/beidan/canvas

游戏整体思路实现

1. 实现一个无缝连接的背景图,模拟出汽车在加速的状态

this.backdrop = new createjs.Bitmap(bg);
this.backdrop.x = 0;
this.backdrop.y = 0;
this.stage.addChild(that.backdrop);
this.w = bg.width;
this.h = bg.height;

//创建一个背景副本,无缝连接
var copyy = -bg.height;
this.copy = new createjs.Bitmap(bg);
this.copy.x = 0;
this.copy.y = copyy;  //在画布上y轴的坐标为负的背景图长
//使用createjs的tick函数,逐帧刷新舞台
createjs.Ticker.addEventListener("tick", tick);
function tick(e) {
   if (e.paused !== 1) {
        //舞台逐帧逻辑处理函数
        that.backdrop.y = that.speed + that.backdrop.y;
        that.copy.y = that.speed + that.copy.y;

        if (that.copy.y > -40) {
              that.backdrop.y = that.copy.y + copyy;
        }
        if (that.copy.y > -copyy - 100) {
              that.copy.y = copyy + that.backdrop.y;
        }
        that.stage.update(e);
    }          
}

2. 随机绘制障碍物

由于一条跑道肯定会有很多障碍物,对于超出屏幕的障碍物我们要进行‘资源回收’,否则游戏到后面会越来越卡顿。

// 删除越界的元素
for (var i = 0, flag = true, len = that.props.length; i < len; flag ? i++ : i) {
    if (that.props[i]) {
        if (that.props[i].y > height + 300) {
            that.stage.removeChild(that.props[i]);
            that.props.splice(i, 1);
            flag = false;
        } else {
            flag = true;
        }
    }
}

一共有3条赛道,我们不能出现3个道具同时出现在水平线上,因此我们会随机取1~2个值绘制障碍物。所有游戏我们都应该有参数去控制它的难易程度,免得临上线的时候,老板体验之后觉得游戏太难了……那就非常地尴尬了。 因此,我们会设置加速物体,减速物体,炸弹出现的比例,后期可以调整这个比例来设置游戏的难易程度。

var num = parseInt(2 * Math.random()) + 1, i;
    for (i = 0; i < num; i++) {
        var type = parseInt(10 * Math.random()) + 1;

        // 设置道具出现比例
        if (type == 1) {
            /绘制炸弹
        } else if ((type >= 2) && (type <= 5)) {
            //绘制加速道具
        } else if ((type >= 6) && (type <= 10)) {
            //绘制减速道具
        }
    }

第一次绘制完障碍物之后,会随机时间绘制下一次的障碍物。

var time = (parseInt(3 * Math.random()) + 1);  //随机取1~3整数
// 随机时间绘制障碍物
setTimeout(function () {
    that.propsTmp = [];  //清空
    that.drawObstacle(obj);
}, time * 400);  //400ms ~ 1200ms

 

3.碰撞检测

我们用一个数组来存放汽车占的矩形区域,障碍物占的矩形区域,在每一次tick的时候循环遍历数组,看是否有重叠的,若有重叠,则发生了碰撞。

createjs的一些小知识:

1. 暂停和恢复舞台渲染

createjs.Ticker.addEventListener(“tick”, tick); 
function tick(e) { 
    if (e.paused === 1) { 
    //处理 
    }
}     
createjs.Ticker.paused = 1; //在函数任何地方调用这个,则会暂停tick里面的处理 
createjs.Ticker.paused = 0; //恢复游戏

 

2. 由于汽车会有加速,减速,弹气泡的效果。因此我们把这几个效果绘制在同一个container中,方便统一管理,对这些效果设置name属性,之后可以直接使用getChildByName获取到该对象。

container.name = ‘role’; //设置name值
car = this.stage.getChildByName(“role”);  //使用name值方便获取到该对象

 

3. 预加载 preload (createjs 的 preload 非常的实用)

一开始是自己写的预加载,后来发现createjs里面对图片是有跨域处理的,自己处理跨域的img就比较麻烦,所以直接使用createjs的预加载。

//放置静态资源的数组
var manifest = [
    {src: __uri('./images/car_prop2_tyre@2x.png'), id: 'tyre'}
];
var queue = new createjs.LoadQueue();
queue.on('complete', handleComplete, this);
queue.loadManifest(manifest);
//资源加载成功后,进行处理
function handleComplete() {
   var tyre = queue.getResult('tyre');  //拿到加载成功后的img
}

一般做一个游戏,我们正常都会构建一个游戏类来承载。 下面是一个游戏正常有的接口:

;(function () {
    function CarGame(){}
    CarGame.prototype = {
        init:function(manifest) {
            this.preLoad(manifest);  //资源预加载
            //时间倒计时
            this.prepare(3, 3);  //倒计时3秒
            this.bindEvent(); 
        },
        render:function() {
           this.drawBg(bg1);
           this.drawRole(car, effbomb, effquick);
           this.drawObstacle(obj);
        },
        //在游戏结束的时候批量销毁
        destroy:function(){
            //移除tick事件
            createjs.Ticker.removeEventListener("tick", this.tick);
            //暂停里程,倒计时
            clearInterval(this.changem);
            clearTimeout(this.gametime);
        },
        //由于期间用户可能切出程序进行其他操作,因此都需要一个暂停的接口
        pause:function() {
            //暂停里程,倒计时
            clearInterval(this.changem);
            clearTimeout(this.gametime);

            //暂停页面滚动
            createjs.Ticker.paused = 1;
        },
        //重新开始游戏
        reStart:function(){
           this.destroy();
           this.init(manifest);
        },
        gameOver:function(){
           //显示爆炸效果
           var car = this.stage.getChildByName("role");
           car.getChildByName('bomb').visible = true;
           car.getChildByName('quick').visible = false;
           this.destroy();
        }
    }
})()

 

转载:https://www.cnblogs.com/beidan/p/7122731.html

HTML5声音引擎Howler.js简介

Howler.js是一个不错的HTML5声音引擎。功能强大,性能不错,用起来也很方便。

1. 官网

https://howlerjs.com/ 其代码托管在GitHub上。

2. 兼容性

Howler默认使用Web Audio,但在IE上可以自动转为HTML 5 Audio。这点很是贴心。

3. 声音激活

移动端的Safari和Chrome都禁止网页自动播放声音,必须通过用户的操作,touch, click等触发。Howler可以设置成自动捕捉用户操作激活(解禁)声音播放。

13132333435114
 
Copyright © 2008-2021 lanxinbase.com Rights Reserved. | 粤ICP备14086738号-3 |