使用PixiJS做一个小游戏
PixiJS
PixiJS使用WebGL,是一个超快的HTML5 2D渲染引擎。作为一个Javascript的2D渲染器,Pixi.js的目标是提供一个快速的、轻量级而且是兼任所有设备的2D库。
官方网址: http://www.pixijs.com/
知识点
做一个小游戏,我们使用到PixiJS的功能不多,只需要了解以下几个点即可快速上手。
PIXI.Application创建一个游戏时第一个要初始化的对象。stage舞台,我们可以看做是所有对象的根节点,类似于document。PIXI.loader资源加载和管理器。PIXI.Texture材质,通常是指我们加载的图片。PIXI.Sprite精灵,就是游戏中的一个对象,结合PIXI.Texture材质使用。PIXI.extras.AnimatedSprite动画精灵,可以设置多个图片,按序播放。PIXI.Container精灵容器,我们可以把多个精灵结合在一起组成一个更复杂的对象。
了解以上内容我们就可以直接做小游戏了,其它知识可以去官网查看。
游戏制作
此为一个躲避下落物体的小游戏,体验地址 (移动端):https://jiamao.github.io/pixigame/game.html
初始化PixiJS
var opt = {
width: window.innerWidth,
height: window.innerHeight,
antialias: true, // default: false
transparent: false, // default: false
resolution: 1 // default: 1
};
//生成app对象,指定宽高,这里直接全屏
var app = new PIXI.Application(opt);
app.renderer.backgroundColor = 0xffffff;
app.renderer.autoResize = true;
//这里使用app生成的app.view(canvas)
document.body.appendChild(app.view);
//这里是APP的ticker,会不断调用此回调
//我们在这里去调用游戏的状态更新函数
app.ticker.add(function(delta) {
//理论上要用delta去做时间状态处理,我们这里比较简单就不去处理时间问题了
//每次执行都当做一个有效的更新
game.update(delta);
});
资源加载
加载资源使用PIXI.loader,支持单个图片,或雪碧图的配置json文件。
PIXI.loader
.add(name1, 'img/bg_1-min.jpg')
.add(name2, 'img/love.json').load(function(){
//加载完
});
雪碧图和其json配置文件可以用工具TexturePackerGUI来生成, 格式如下:
{"frames": {
"bomb.png":
{
"frame": {"x":0,"y":240,"w":192,"h":192},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":192,"h":192},
"sourceSize": {"w":192,"h":192}
},
...//省略多个
"x.png":
{
"frame": {"x":576,"y":240,"w":192,"h":192},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":192,"h":192},
"sourceSize": {"w":192,"h":192}
}},
"animations": {
"m": ["m1.png","m2.png"]
},
"meta": {
"app": "https://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "love.png?201902132001",
"format": "RGBA8888",
"size": {"w":768,"h":432},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:5bb8625ec2f5c0ee2a84ed4f5a6ad212:f3955dc7846d47f763b8c969f5e7bed3:7f84f9b657b57037d77ff46252171049$"
}
}
精灵
加载完资源后,我们就可以用PIXI.loader.resources读取资源,制作一个普通精灵。
var textures = PIXI.loader.resources['qq'].textures;
var sprite = new PIXI.Sprite(textures['qq_head.png']);
动画
跟上面普通精灵类似,只是使用多个图片做为侦。然后用PIXI.extras.AnimatedSprite来播放。 例如下面我们取雪碧图中f开头的图片组成一个动画。
资源图:
var textures = PIXI.loader.resources['bling'];
var expTextures = [];//当前动画所有材质集合
var keys = textures.data.animations['f'];
//按索引排个序,以免侦次序乱了
keys.sort(function(k1,k2){
return k1.replace(/[^\d]/g,'') - k2.replace(/[^\d]/g,'');
});
for(var i=0;i<keys.length;i++) {
var t = textures[keys[i]];
expTextures.push(t);
}
var side = new PIXI.extras.AnimatedSprite(expTextures);
side.animationSpeed = 0.15;//指定其播放速度
app.stage.addChild(side);
//其它接口请查看官方文档
效果:
状态更新
每个对象都有一个update函数,都在这里自已更新自已的位置和状态(update由app.ticker定时调用)。所有对外开放的状态设置都提供接口,比如die、move等。 如下:
this.die = function() {
this.state = 'dead';
this.sprite.visible = false;
map.removeBob(this);
}
//发生碰撞,炸弹会导致气球破裂
this.hitEnd = function() {
//气球破裂
heart.break(function(){
console.log('我跟气球撞了');
});
}
//更新炸弹状态
this.update = function(delta) {
//计算当前在屏幕中的坐标
var p = map.toLocalPosition(this.position.x, this.position.y);
//运行中,障碍物到屏幕时才需要显示
if(game.state == 'play' && p.y >= -this.sprite.height) {
this.start();
}
if(!this.sprite.visible) return;
//移动精灵
this.sprite.x = p.x;
this.sprite.y = p.y;
//出了屏外,则不需要再显示
if(p.y > game.app.screen.height) {
this.die();
return;
}
//如果碰到当前精灵,则精灵死
if(heart.hitTest(this)) {
this.hitEnd();
}
this.position.y += this.vy; //保持自身的速度
}
游戏设计
地图
背景
游戏的背景是一张超长的图:
- 第一要考虑的就是分辨率问题,因为高度相对于屏来说是够长的,这里我们以宽度跟屏宽的比来做为缩放比例,而且所有游戏元素都是相对于背景设计的,因此所有元素都采用此缩放比即可。 此处代码都是在游戏map对象中的。
this.background = new pixiContainer(); //地图元素的container
this.scale = (this.width / this.bg_width).toFixed(4) * 1;//地图宽缩放比例,为整个地图缩放比例
this.height = this.bg_height * this.scale;
this.background.scale.x = this.scale;
this.background.scale.y = this.scale;
计算对象在屏幕中的坐标
//转为画布坐标
toLocalPosition: function(x, y) {
if(typeof x == 'object') {
y = x.y;
x = x.x;
}
x = x||0;
y = y||0;
//x坐标为地图偏移量在对象在地图的坐标
x = x + this.offsetPosition.x;
//y为屏高+当前地图相对屏的偏移量,加上对象在地图的Y坐标再减去屏幕高度。
y = y + game.app.screen.height + this.offsetPosition.y - this.height; return {
x: x,
y: y
};
},
- 图片加载问题,如果直接加载长图效率太低。我们把图切成等高的五份。首次加载最底下的图,其它位置只用一个空精灵占位,再异步加载其它四张后替换其材质即可。
//初始化背景图
var bgspOffsetY = 0;
var bgHeights = [1646,1640,1652,1652,1637];
//默认只加载了第一张图,其它的全用第一张图占位先,加载完后再覆盖
for(var i=bgHeights.length-1; i>=0; i--) {
var bgsp = new pixiSprite(pixiResources['map_background1'].texture);
bgsp.position.set(0, bgspOffsetY);
this.background.addChild(bgsp);
bgspOffsetY += bgHeights[i];
}
//load其它背景图
loadBackground: function(hs) {
var bg2=loadSource('map_background2', cdnDomain+'img/bg_2-min.jpg');
var bg3=loadSource('map_background3', cdnDomain+'img/bg_3-min.jpg');
var bg4=loadSource('map_background4', cdnDomain+'img/bg_4-min.jpg');
var bg5=loadSource('map_background5', cdnDomain+'img/bg_5-min.jpg');
if(!bg2.isComplete||!bg3.isComplete||!bg4.isComplete||!bg5.isComplete){
pixiLoader.load(function(){
//children中,第一张是第五张,依次
for(var i=5;i>1;i--) {
map.background.children[5-i].texture = pixiResources['map_background' + i].texture;
}
});
}
else {
for(var i=5;i>1;i--) {
this.background.children[5-i].texture = pixiResources['map_background' + i].texture;
}
}
}
- 背景、障碍物和气球滑动问题。解决这个问题,我们把所有地图上的物体都初始化在背景上,它们的位置都是相对于背景的。当执行update时,实时根据地图相对于屏幕的位置来更新对象在屏幕上的坐标。
气球
气球跟所有物体一样,有多个状态,当吃糖时还会有相应的动画。 比如,气球在复活时有一定时间的无敌状态,这时我们就要一闪一闪来表示。
updateGoldAni: function() {
//无敌显示状态 ,只隐显几下即可
if(this.state == 'gold') {
if(this.container.alpha >= 1) {
this.__appha_dir = 0;
}
else if(this.container.alpha <= 0.4) {
this.__appha_dir = 1;
}
if(this.__appha_dir) {
this.container.alpha += 0.02;
}
else {
this.container.alpha -= 0.02;
}
}
else if(this.container.alpha != 1) {
this.container.alpha = 1;
}
},
滑动事件
由于无论滑到屏幕任何位置都需要有效,则把事件绑到stage上。PixiJS对象如果要响应事件,则必须把interactive设置为true。
//绑定滑动事件
bindEvent: function() {
var isTouching = false; //是否在移动中
var lastPosition = {x:0, y:0};//最近一次移到的地方
this.app.stage.interactive = true;
this.app.stage.on('touchstart', function(e){
if(game.state == 'play') {
isTouching = true;
lastPosition.x = e.data.global.x;
lastPosition.y = e.data.global.y;
e.data.originalEvent && e.data.originalEvent.preventDefault && e.data.originalEvent.preventDefault();
//console.log(e.data.global)
}
}).on('touchmove', function(e){
if(isTouching && game.state == 'play') {
//console.log(e.data.global, lastPosition);
var offx = e.data.global.x - lastPosition.x;
heart.move(offx); //移动气球,只让横向移动
lastPosition.x = e.data.global.x;
lastPosition.y = e.data.global.y;
}
e.data.originalEvent && e.data.originalEvent.preventDefault && e.data.originalEvent.preventDefault();
}).on('touchend', touchEnd).on('touchcancel', touchEnd).on('touchendoutside', touchEnd);
function touchEnd(e) {
heart.m_state = 'normal';
console.log('normal')
isTouching = false;
heart.line.gotoAndStop(0);
e.data.originalEvent && e.data.originalEvent.preventDefault && e.data.originalEvent.preventDefault();
}
},
在移动时,需要播放气球线条的左右移动画。line是一个animation精灵。
var newx = this.container.x + offsetX;
var directX = newx - this.container.x;
//往右移动
if(directX > 0) {
if(this.m_state != 'right') {
//开始右移动画
this.line.gotoAndPlay(1);
}
this.m_state = 'right'; //往右移动
}
//往左移动
else if(directX < 0) {
if(this.m_state != 'left') {
//开始右移动画
this.line.gotoAndPlay(5);
}
this.m_state = 'left'; //往左移动
}
//超过一定时间没移动,则回到正常位置
this.__moveTimeHandler = setTimeout(function(){
heart.m_state = 'normal';
heart.line.gotoAndStop(0);
}, 500);
this.container.x = newx;
障碍物
障碍物和糖果只需要相对于地图移动即可,为了保证路不被卡死,我们一排最多放置3个障碍物。 且难易分为三个阶段
- 一阶段比较简单,每排放置1和2个,并且行距比较大,掉落速度最慢。
- 二阶段每排放1和3个,增加一定速度。
- 三阶段每排2和3个,速度最快,且行距最小。
为了游戏效果,上一行的的空档和当前行空档之前的物体上下浮动增加一些错乱感。
碰撞检测
这块比较简单,都是规则的矩形。
//二个矩形是否有碰撞
function hitTestRectangle(r1, r2) {
var hitFlag, combinedHalfWidths, combinedHalfHeights, vx, vy, x1, y1, x2, y2, width1, height1, width2, height2;
hitFlag = false; x1 = r1.x;
x2 = r2.x;
y1 = r1.y;
y2 = r2.y;
width1 = r1.width;
width2 = r2.width;
height1 = r1.height;
height2 = r2.height;
//如果对象有指定碰撞区域,则我们采用指定的坐标计算
if(r1.hitArea) {
x1 += r1.hitArea.x * map.scale;
y1 += r1.hitArea.y * map.scale;
width1 = r1.hitArea.width * map.scale;
height1 = r1.hitArea.height * map.scale;
}
if(r2.hitArea) {
x2 += r2.hitArea.x * map.scale;
y2 += r2.hitArea.y * map.scale;
width2 = r2.hitArea.width * map.scale;
height2 = r2.hitArea.height * map.scale;
} //中心坐标点
r1.centerX = x1 + width1 / 2;
r1.centerY = y1 + height1 / 2;
r2.centerX = x2 + width2 / 2;
r2.centerY = y2 + height2 / 2; //半宽高
r1.halfWidth = width1 / 2;
r1.halfHeight = height1 / 2;
r2.halfWidth = width2 / 2;
r2.halfHeight = height2 / 2; //中心点的X和Y偏移值
vx = r1.centerX - r2.centerX;
vy = r1.centerY - r2.centerY; //计算宽高一半的和
combinedHalfWidths = r1.halfWidth + r2.halfWidth;
combinedHalfHeights = r1.halfHeight + r2.halfHeight; //如果中心X距离小于二者的一半宽和
if (Math.abs(vx) < combinedHalfWidths) {
//如果中心V偏移量也小于半高的和,则二者碰撞
if (Math.abs(vy) < combinedHalfHeights) {
hitFlag = true;
} else {
hitFlag = false;
}
} else {
hitFlag = false;
}
return hitFlag;
};
此小游戏主要内容就这么多,具体的可以细看代码:
使用PixiJS做一个小游戏的更多相关文章
- Pygame:编写一个小游戏 标签: pythonpygame游戏 2017-06-20 15:06 103人阅读 评论(0)
大学最后的考试终于结束了,迎来了暑假和大四的漫长的"自由"假期.当然要自己好好"玩玩"了. 我最近在学习Python,本意是在机器学习深度学习上使用Python ...
- 用struts2标签如何从数据库获取数据并在查询页面显示。最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变量。
最近做一个小项目,需要用到struts2标签从数据库查询数据,并且用迭代器iterator标签在查询页面显示,可是一开始,怎么也获取不到数据,想了许久,最后发现,是自己少定义了一个变量,也就是var变 ...
- DirectX游戏开发——从一个小游戏開始
本系列文章由birdlove1987编写,转载请注明出处. 文章链接: http://blog.csdn.net/zhurui_idea/article/details/26364129 写在前面:自 ...
- 用 JS 做一个数独游戏(二)
用 JS 做一个数独游戏(二) 在 上一篇博客 中,我们通过 Node 运行了我们的 JavaScript 代码,在控制台中打印出来生成好的数独终盘.为了让我们的数独游戏能有良好的体验,这篇博客将会为 ...
- 用 JS 做一个数独游戏(一)
用 JS 做一个数独游戏(一) 数独的棋盘由 9x9 的方格组成,每一行的数字包含 1 ~ 9 九个数字,并且每一列包含 1 ~ 9 这 9 个不重复的数字,另外,整个棋盘分为 9 个 3x3 的块, ...
- 用RecyclerView做一个小清新的Gallery效果 - Ryan Lee的博客
一.简介 RecyclerView现在已经是越来越强大,且不说已经被大家用到滚瓜烂熟的代替ListView的基础功能,现在RecyclerView还可以取代ViewPager实现Banner效果,当然 ...
- js实现一个小游戏(飞翔的jj)
js实现一个小游戏(飞翔的jj) 源代码+素材图片在我的仓库 <!DOCTYPE html> <html lang="en"> <head> & ...
- 【h5-egret】如何快速开发一个小游戏
1.环境搭建 安装教程传送门:http://edn.egret.com/cn/index.php?g=&m=article&a=index&id=207&terms1_ ...
- 菜鸟做HTML5小游戏 - 翻翻乐
记录下开放过程.做小游戏开发,又要跨平台,flash又不支持iPhone,html5是最好的选择. 先看看最后效果: 好了,开始demo. 1.准备工作: 图片素材(省略...最后代码一起打包) 了解 ...
随机推荐
- eclipse 安装svn和gradle
公司项目用的eclispe svn和gradle 所以需要配置 SVN教程:https://blog.csdn.net/jieshaowang1229/article/details/5159499 ...
- Python 基础【一】
python运行流程 一.变量及注释 命名: 合法-变量名由字母.数字和下划线组成,并且不能以数字开头.以下保留字不可以当变量名: ['False', 'None', 'True', 'and', ' ...
- TensorFlow-谷歌深度学习库 文件I/O Wrapper
这篇文章主要介绍一下TensorFlow中相关的文件I/O操作,我们主要使tf.gfile来完成. Exists tf.gfile.Exists(filename) 用来判断一个路径是否存在,如果存在 ...
- BootStrap 常用控件总结
下拉选择Select2:http://ivaynberg.github.io/select2/index.html 文件上传bootstrap-fileinput:https://github.com ...
- Pat1067:Sort with Swap(0,*)
1067. Sort with Swap(0,*) (25) 时间限制 150 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue G ...
- 向Oracle数据库插入中文乱码解决方法
解决方法: 第一步:sqlplus下执行:select userenv('language') from dual;//查看oracle字符集 注:如果oracle字符集与后台代码设置的 ...
- Ajax跨域之ContentType为application/json请求失败的问题
项目里的接口都是用springmvc写的,其中在@requestmapping接口中定义了consumes="application/json",也就是该接口只接受ContentT ...
- Activity的状态保存
这两个图其实说的是一个意思,具体onSaveInstanceState()这个函数什么时候会调用,在网络上搜了一下 这个第一种情况,我可以解释一下,说的是这个方法只在onResume和onPause之 ...
- 一种转换Ipv6地址的方法
原CSDN博客不再更新维护. 本文介绍了一种将char* 类型的Ipv6地址转换成BYTE(unsigned char)或者in6_addr类型的方法. 说明:使用时需要下载Ipv6++.lib和Ip ...
- What is the best way to handle Invalid CSRF token found in the request when session times out in Spring security
18.5.1 Timeouts One issue is that the expected CSRF token is stored in the HttpSession, so as soon a ...