使用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.准备工作: 图片素材(省略...最后代码一起打包) 了解 ...
随机推荐
- Linux 下 Redis 安装与配置
1.Redis 的安装 在 Ubuntu 系统安装 redis 可以使用以下命令: $ sudo apt-get update $ sudo apt-get install redis-server ...
- vue中keep-alive的用法
1.keep-alive的作用以及好处 在做电商有关的项目中,当我们第一次进入列表页需要请求一下数据,当我从列表页进入详情页,详情页不缓存也需要请求下数据,然后返回列表页,这时候我们使用keep-al ...
- Linux 内核模块编译 Makefile
驱动编译分为静态编译和动态编译:静态编译即为将驱动直接编译进内核,动态编译即为将驱动编译成模块. 而动态编译又分为两种: a -- 内部编译 在内核源码目录内编译 b -- 外部编译 在内核源码的目录 ...
- sniffer pro 使用方法
一.捕获数据包前的准备工作 在默认情况下,sniffer将捕获其接入碰撞域中流经的所有数据包,但在某些场景下,有些数据包可能不是我们所需要的,为了快速定位网络问题所在,有必要对所要捕获的数据包作过滤. ...
- 【Quartz】持久化到数据库【五】
前言 我们做到这里已经对Quartz定时器组件已经是学会了基本的使用了.但是我们有没有想过任务开启之后万一断掉了,当机了我们怎么办,你是否还想继续执行原先的任务.我们普通的创建是把任务放在内存中存 ...
- SSM-MyBatis-13:Mybatis中多条件查询
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 实体类 public class Book { private Integer bookID; private ...
- backbone的一些认识
body,td { font-family: 微软雅黑; font-size: 10pt } 官网:http://backbonejs.org/ 作者:Jeremy Ashkenas 杰里米·阿什肯纳 ...
- 解决WordPress用户名密码都正确但点击登陆就清空密码的问题
作者:荒原之梦 问题产生的环境与问题描述: 今天我在浏览器的书签里将我保存的用于登陆WordPress的书签名字改了一下,之后再登陆WordPress就出现提示说Cookies被阻止,要启用Cooki ...
- js中闭包来实现bind函数的一段代码的分析
今天研究了一下bind函数,发现apply和call还可以有这样的妙用,顺便巩固复习了闭包. var first_object = { num: 42 }; var second_object = { ...
- 安装scrapy出错Failed building wheel for Twisted
用64位windows10的CMD命令安装pip install scrapy出错: Running setup.py bdist_wheel for Twisted ... error Failed ...