一个quintus游戏引擎例子

最近不务正业地研究了一下JavaScript游戏,选择了其中一款游戏引擎quintus。

quintus简介

quintus是一款开源的HTML5游戏引擎,其核心模块包括:

  1. Sprites:通过继承Sprites类,你可以创建你的游戏角色。

  2. Scenes:场景类,是一个可以重复利用的元素,通过继承Scene,你可以设置游戏的关卡等。

  3. Input and Touch: 该模块用来监听键盘、鼠标、触摸事件。

  4. Animation: 动画类,为Sprite定制动画。

  5. Audio: 处理声音。

  6. 2D:2D模块主要是描述一个简单的动力学模型,如你的Sprite加入了2D模块,那么你的Sprite就会按照重力效应自动往下掉。

  7. UI:用来创建按钮等元素。

一个例子

Runner

源代码

这是官网给出的一个例子,通过研究这个例子的源码可以对quintus有个初步的了解。

分析

奔跑的小精灵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Q.Sprite.extend("Player",{
init: function(p) {
this._super(p,{
sheet: "player", //相当于asset
sprite: "player", //动画
collisionMask: SPRITE\_BOX, //检测与type为SPRITE_BOX的对象的碰撞,
x: 40, //起始位置
y: 555,
//碰撞监测区域
standingPoints: [ [ -16, 44], [ -23, 35 ], [-23,-48], [23,-48], [23, 35 ], [ 16, 44 ]],
duckingPoints : [ [ -16, 44], [ -23, 35 ], [-23,-10], [23,-10], [23, 35 ], [ 16, 44 ]],
speed: 500,
jump: -1000
});
//碰撞边缘
this.p.points = this.p.standingPoints;
this.add("2d, animation");
},
step: function(dt) {
//控制横向移动
this.p.vx += (this.p.speed - this.p.vx)/4;
//如果不控制就会下坠,因为加了2D模块
if(this.p.y > 555) {
this.p.y = 555;
this.p.landed = 1;
this.p.vy = 0;
} else {
//在空中
this.p.landed = 0;
}
if(Q.inputs['up'] && this.p.landed > 0) {
this.p.vy = this.p.jump;
}
this.p.points = this.p.standingPoints;
if(this.p.landed) {
//按键控制
if(Q.inputs['down']) {
//play的参数是一个animation
this.play("duck_right");
this.p.points = this.p.duckingPoints;
} else {
this.play("walk_right");
}
} else {
this.play("jump_right");
}
//将palyer固定在屏幕的一个位置上
this.stage.viewport.centerOn(this.p.x + 300, 400 );
}
});

Player继承了Sprite类,覆盖了init方法和step方法,init方法中有很多默认的属性,如x,y,cx,cy等等,值得注意的是sheet,sprite,collisionMask,standingPoints,duckingPoints这几个属性。

sheet相当于图片类,是一种特殊的图片加载器,定义了精灵的外形,此处用到的sheet是player,后面会有介绍。

sprite是动作结合,定义的几个基础的动作,此处用到的sprite是player,后面介绍。

collisionMask是碰撞检测的标识,此处表示与type为SPRITE_BOX的对象的碰撞会被检测到,而后面的箱子的type就是SPRITE_BOX。

standingPoints和duckingPoints是碰撞检测区域,是一个多边形,数组里面的二位坐标分别表示多边形的顶点,顺时针表示,一个是站着时候的多边形,一个是蹲着时候的多边形。

step方法是表示精灵随时间的变化,把dt看做一个不停自增的变量就可以了。

砸人的盒子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Q.Sprite.extend("Box",{
init: function() {
var levels = [ 565, 540, 500 ];
//获取player
var player = Q("Player").first();
this._super({
//必须相对player
x: player.p.x + Q.width + 50,
y: levels[Math.floor(Math.random() * 3)],
//选择哪一帧动画,2帧
frame: Math.random() < 0.5 ? 1 : 0,
//放大
scale: 2,
type: SPRITE_BOX,
sheet: "crates",
vx: -600 + 200 * Math.random(), //(-400~-600)
vy: 0,
ay: 0,
//变量
theta: (300 * Math.random() + 200) * (Math.random() < 0.5 ? 1 : -1),
});
this.on("hit");
},
step: function(dt) {
this.p.x += this.p.vx * dt;
//主要是撞击之后
this.p.vy += this.p.ay * dt;
this.p.y += this.p.vy * dt;
//不是贴地滑行
if(this.p.y != 565) {
this.p.angle += this.p.theta * dt;
}
//落到画布之外,销毁
if(this.p.y > 800) { this.destroy(); }
},
//hit事件
hit: function() {
this.p.type = 0;
//碰撞之后便不再能够碰撞
//this.p.collisionMask = Q.SPRITE_NONE;
this.p.type = Q.SPRITE_NONE;
this.p.vx = 200;
this.p.ay = 400;
this.p.vy = -300;
//透明度
this.p.opacity = 0.5;
}
});

盒子同样是由init和step方法,同时多了一个hit方法,hit是一种碰撞事件,通过this.add("hit")与box绑定了。

盒子投掷器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Q.GameObject.extend("BoxThrower",{
init: function() {
this.p = {
launchDelay: 0.75,
launchRandom: 1,
launch: 2
}
},
update: function(dt) {
this.p.launch -= dt;
//console.log(this.p.launch);
if(this.p.launch < 0) {
this.stage.insert(new Q.Box());
this.p.launch = this.p.launchDelay + this.p.launchRandom * Math.random();
}
}
});

盒子投掷器是控制盒子出现的一个类,继承的是GameObject,有init方法和update方法,update方法与step方法类似,是与时间变化有关的方法,不过update做的事情是随时间(dt)变化不停地重绘画面,这里this.stage.insert(new Q.Box()),随着dt的自增,向画布里面插入新的box对象。

游戏场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Q.scene("level1",function(stage) {
stage.insert(new Q.Repeater({ asset: "background-wall.png",
speedX: 0.5 }));
//x,y决定放置的位置
stage.insert(new Q.Repeater({ asset: "background-floor.png",
repeatY: false,
repeatX: true,
//视差,与player的相对速度
speedX: 1,
y: 300 }));
stage.insert(new Q.BoxThrower());
stage.insert(new Q.Player());
//前面使用了viewport固定player
stage.add("viewport");
});

游戏场景相当于一个关卡,一个舞台,就是游戏的主画面,这里向这个scene(level1)中插入了背景,地板,盒子投掷器,精灵。

游戏加载

1
2
3
4
5
6
7
8
9
10
11
12
13
Q.load("player.png, background-wall.png, background-floor.png, crates.png", function() {
//start the sprites at x=0 y=1 cols
Q.sheet("player","player.png",{sx:0,sy:1,cols:18,tilew:72,tileh:97,frames:18});
Q.sheet("crates","crates.png",{sx:0,sy:0,cols:2,tilew:32,tileh:32,frames:2});
Q.animations("player", {
walk_right: { frames: [0,1,2,3,4,5,6,7,8,9,10], rate: 1/15, flip: false, loop: true },
jump_right: { frames: [13], rate: 1/10, flip: false },
stand_right: { frames:[14], rate: 1/10, flip: false },
duck_right: { frames: [15], rate: 1/10, flip: false },
});
Q.stageScene("level1");
Q.debug = true;
});

最后一步就是游戏加载,加载图片,并通过Q.stageScene("level1")将第一关加入到游戏中。

定义两个sheet,一个是player,使用的图片是”player.png”,是一种元素集,总共有18个分解图形,依次取72*97大小作为一帧图形。

player.png

Q.animations("player",{})定义的前面提到的player对应的sprite,有四个动作,以walk_right为例,使用的是player(sheet)里面的0-10帧图形,帧切换速率是1/15,即1/15s切换一次,翻转为false,循环为true。

参考

  1. http://donmik.com/en/how-to-make-a-shootem-up-with-quintus-part-1/
  2. http://www.remcodraijer.nl/quintus/tutorial.html
  3. http://www.html5quintus.com/guide/intro.md