介绍
最近用 Html 制作了一款洛克人游戏,直接上效果图:
预览地址
效果链接(只看看不如亲自体验一下):
http://h5demo.yyfuncdn.com/res/gameDemo/man/
手机扫描运行:
最近用 pixi 和 js 制作了一款洛克人游戏,致敬一下经典,简单实现了人物的跑动、跳跃、以及镜头跟随的效果,在手机及网页做了相关适配,下面是手机横屏的一些操作效果图:
接下来我们来看一看源码,是如何实现这些功能的
先给大家分享到这里~更多的可以下载源码看一看,以下是源码下载链接:
点击下载
(http://h5demo.yyfuncdn.com/res/gameDemo/man.zip)
这是所有的项目文件
index 页面为这个项目的运行文件,主要用于创建舞台以及适配,利用 Game 对象来创建整个游戏场景
完整代码
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><title>Demo</title><meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><script src="js/pixi.min.js"></script><script src="js/game.js"></script><script src="js/people.js"></script><script src="js/ground.js"></script><script src="js/button.js"></script><script src="js/Animation.js"></script><script src="js/AnimationData.js"></script><style>body{margin: 0;}</style>
</head>
<body><script>//判断横竖屏及刷新function orientationChange() {switch(window.orientation) {case 0: //竖屏alert("建议在横屏状态下浏览");window.localStorage.setItem('name','a');break;case -90: //横屏var shu=window.localStorage.getItem('name');console.log(shu)if(shu=='a'){window.location.reload();window.localStorage.setItem('name','b');}//alert('竖屏')orientation = 'portrait';break;case 90: //横屏var shu=window.localStorage.getItem('name')if(shu=='a'){window.location.reload();window.localStorage.setItem('name','b');}//alert('竖屏')orientation = 'portrait';break;case 180: //竖屏alert("建议在横屏状态下浏览");window.localStorage.setItem('name','a');break;};};window.addEventListener("load", orientationChange, false);window.addEventListener("onorientationchange" in window ? "orientationchange" : "resize", orientationChange, false);//获取当前屏幕宽高var html = document.getElementsByTagName('html')[0];var phoneHeight = html.clientHeight;var phoneWidth = html.clientWidth;//创建当前屏幕大小舞台var app = new PIXI.Application(phoneWidth,phoneHeight);document.body.appendChild(app.view);//创建游戏界面var game = new Game();app.stage.addChild(game.gameCeng);//操作提示var text = new PIXI.Text("键盘操作:A 左移动 D 右移动 K 跳跃");text.x = 50;text.y = 50;text.style.fill = 0xffffff; app.stage.addChild(text);//创建帧频动画app.ticker.add(animate);var frame = 1;function animate() {if(frame >= Math.round(app.ticker.FPS / 60)) {game.update();frame = 0;}frame ++;}</script>
</body>
</html>
接下来再创建整个游戏场景,即 Game 对象,里面创建图层用于码放所有的背景、角色、按钮,并且在最后还添加了键盘事件用于操控角色
function Game(){//当前屏幕宽高比var screenScale = phoneHeight / phoneWidth;//自适应缩放比例var scale = (phoneHeight / 561);//游戏元素图层this.gameCeng = new PIXI.Container();//游戏元素图层场景图层this.gameCjCeng = new PIXI.Container();this.gameCeng.addChild(this.gameCjCeng);//游戏元素人物场景图层this.gameManCeng = new PIXI.Container();this.gameCeng.addChild(this.gameManCeng);//背景var background = new PIXI.Sprite.fromImage("img/bg.png");this.gameCjCeng.addChild(background);background.width = phoneWidth;background.height = phoneHeight;//背景图宽高比var bgScale = 640 / 960;if(bgScale > screenScale) {background.width = phoneWidth;background.height = 640 * (phoneWidth / 960);} else {background.width = 960 * (phoneHeight / 640);background.height = phoneHeight;}//路面var ground = new Ground();this.gameCjCeng.addChild(ground.groundView);//人物var people = new People();ground.groundView.addChild(people.peopleView);people.player.init(action_data);people.player.play("stand")//向左按钮var leftBtn = new Button("img/left.png",0,phoneHeight*0.2,phoneHeight - 150*scale,people);this.gameManCeng.addChild(leftBtn.buttonView);//向右按钮var rightBtn = new Button("img/right.png",1,phoneHeight*0.2+150*scale,phoneHeight - 150*scale,people);this.gameManCeng.addChild(rightBtn.buttonView);//跳跃按钮var jumpBtn = new Button("img/jump.png",2,phoneWidth - 200*scale,phoneHeight - 150*scale,people);this.gameManCeng.addChild(jumpBtn.buttonView);this.update = function() {//人物移动people.update();//地面跟随ground.groundFollow(people.peopleView);}//键盘控制document.onkeydown = function (e) {var e = e || window.event; //标准化事件对象var keyCode = e.keyCode;if(keyCode == 75) { //k 跳跃people.textureType = 2;people.isJump = true;} else if (keyCode == 65) { //a 左if(people.textureType != 2) {people.textureType = 1;}people.peopleView.scale.x = -1;people.isMove = true;people.speedX = -(phoneWidth / 180);} else if (keyCode == 68) { //d 右if(people.textureType != 2) {people.textureType = 1;}people.peopleView.scale.x = 1;people.isMove = true;people.speedX = phoneWidth / 180;}}document.onkeyup = function (e) {var e = e || window.event; //标准化事件对象var keyCode = e.keyCode;if(keyCode == 65 || keyCode == 68) { //a 左 d 右people.isMove = false;people.speedX = 0;if(people.textuerType != 2 && people.peopleView.y == -phoneHeight*0.3/scale) {//判断人物是否在地面people.player.play("stand");people.textureType = 3;}}}
}
整个人物对象我封装到了 People 对象中,并写了他的移动、跳跃的方法,人物动画的纹理切换我封装成了动画工具 Animation 对象,即存储好所有的纹理图及播放间隔时间即可
function People(){var self = this;var scale = phoneHeight / 561;//自适应缩放比例this.textureType = 3;//人物运动类型:1 为跑动 2 为跳跃 3 为静止this.player = new Animation();//人物动画this.peopleView = this.player.sprite;//人物图片this.peopleView.x = phoneWidth*0.3/scale;//人物 x 坐标 & 在所有屏幕位置自适应this.peopleView.y = -phoneHeight*0.3/scale;//人物 y 坐标 & 在所有屏幕位置自适应this.peopleView.scale.x = 1;//人物图片翻转this.peopleView.anchor.set(0.6,1);//人物图片锚点设置this.jumpSpeed = -7;//跳跃距离&跳跃初始速度this.speedX = 0;//水平速度this.speedY = this.jumpSpeed;//垂直速度this.isMove = false;//人物移动开关量this.isJump = false;//人物跳跃开关量this.distance = 0;//移动距离this.peopleMove = function() {//人物移动if(this.isMove) {//人物跑动动画if(this.textureType == 1) {self.player.play("run");}//控制人物移动if(this.peopleView.x <= 0 && this.speedX < 0) { // 判断左极限this.peopleView.x = 0;} else if (this.peopleView.x >= 2300 && this.speedX > 0) { // 判断右极限this.peopleView.x = 2300;} else {this.peopleView.x += this.speedX;}}}this.peopleJump = function() {//人物跳跃if(this.isJump) {//人物跳跃动画this.speedY += 0.2;this.peopleView.y += this.speedY;if(this.speedY < 0){this.player.play("jump");}else{this.player.play("fall");}if(this.peopleView.y >= -phoneHeight*0.3/scale) {//结束跳跃this.peopleView.y = -phoneHeight*0.3/scale;this.isJump = false;this.speedY = this.jumpSpeed;if(self.isMove) {this.textureType = 1;this.player.play("run");} else {this.textureType = 3;this.player.play("fallover");this.player.play("stand");}}}}this.update = function(){this.peopleMove();this.peopleJump();this.player.action();}
}
所有的按钮都是共用一个 Button 对象创建,每次传入对应数据即可,其方法根据数据的不同绑定不同的方法
function Button(url,type,x,y,object) {//路径 类型 x坐标 y坐标 控制对象var self = this;var scale = phoneHeight / 561;this.url = url;this.type = type;// 0 :为向左按钮 1 :为向右按钮 2:为跳跃按钮this.buttonView = new PIXI.Sprite.fromImage(this.url);this.buttonView.x = x;this.buttonView.y = y;this.buttonView.width = 100*scale;this.buttonView.height = 100*scale;this.buttonView.interactive = true;this.speedX = phoneWidth / 180;this.buttonView.on("pointerdown",function() {//按下if(self.type == 0) {//左按钮if(object.textureType != 2) {object.textureType = 1;}object.peopleView.scale.x = -1;object.isMove = true;object.speedX = -self.speedX;} else if (self.type == 1) {//右按钮if(object.textureType != 2) {object.textureType = 1;}object.peopleView.scale.x = 1;object.isMove = true;object.speedX = self.speedX;} else if (self.type == 2) {//跳跃按钮object.textureType = 2;object.isJump = true;}})this.buttonView.on("pointerup",function() {//抬起if(self.type == 0 || self.type == 1) {//左按钮 & 右按钮object.isMove = false;object.speedX = 0;if(object.textuerType != 2 && object.peopleView.y == -phoneHeight*0.3/scale) {//判断人物是否在地面object.player.play("stand");object.textureType = 3;}}})
}
角色跟随这里是我最满意的地方,为了体验的舒适并没有做成同步移动,而是跟随的效果,这里是利用的角色绑定在地面图片上,保证角色始终保持在中间位置,当脱离中间位置,地面跟随移动,保证其位置始终在中间
function Ground() {var self = this;var scale = phoneHeight / 561;//自适应缩放比例this.groundView = new PIXI.Sprite.fromImage("img/ground.png");this.groundView.anchor.set(0,1);this.groundView.y = phoneHeight+phoneHeight*0.2;this.groundView.height = phoneHeight;this.groundView.width = 2300 * scale;this.speedX = -5;this.distance = 0;this.groundFollow = function(obj) {var obj = obj.getGlobalPosition();//获取角色当前窗口位置if(obj.x != phoneWidth/2) {//判断角色位置偏移窗口中心点var deviation_x = obj.x-phoneWidth/2;//计算角色X值与窗口中心点偏移距离var correct_speed = deviation_x/10;//计算角色矫正速度self.groundView.x -= correct_speed;if(self.groundView.x >= 0) {//判断场景为最左侧时矫正场景位置self.groundView.x = 0;correct_speed = 0;} else if (self.groundView.x <= phoneWidth-self.groundView.width) {//判断场景为最右侧时矫正场景位置self.groundView.x = phoneWidth-self.groundView.width;correct_speed = 0;}}if(obj.y != phoneHeight*0.9) {//判断角色位置偏移窗口中心点var deviation_y = obj.y-phoneHeight*0.9;//计算角色Y值与窗口中心点偏移距离var correct_speed = deviation_y/10;//计算角色矫正速度self.groundView.y -= correct_speed;if(self.groundView.y >= phoneHeight+phoneHeight*0.4) {//判断场景为最上侧时矫正场景位置self.groundView.y = phoneHeight+phoneHeight*0.4;correct_speed = 0;} else if (self.groundView.y <= phoneHeight+phoneHeight*0.2) {//判断场景为最下侧时矫正场景位置self.groundView.y = phoneHeight+phoneHeight*0.2;correct_speed = 0;}}}
}
下面的即是我动画方法的封装,类似相关的动画都可以用其实现,非常的通用
function Animation() {var self = this;this.sprite = new PIXI.Sprite();this.frameData = {};this.frameIndex = 0;this.actionName = "";this.waitTime = 0;this.isPlay = true; //是否播放状态this.targetFrame = -1; //目标帧this.init = function(frameData) {this.frameData = frameData;}this.play = function(actionName) {if(self.actionName != actionName) {self.actionName = actionName;self.frameIndex = 0;self.waitTime = 0;self.action(true);} }this.update = function() {this.action();}this.action = function(isUpdate = false) {var frameArr = self.frameData[self.actionName];if(self.frameIndex == frameArr.length) {self.frameIndex = 0;}//切换纹理var frameInfo = frameArr[self.frameIndex];if(frameInfo.wait == self.waitTime || isUpdate == true) {this.sprite.texture = new PIXI.Texture.fromImage(frameInfo.url);this.frameIndex ++;self.waitTime = 0;} else {self.waitTime ++;}}}
人物动画数据
var action_data = {"stand": [{"url":"img/Z0001.png", "wait": 20, "frame": 1},{"url":"img/Z0002.png", "wait": 15, "frame": 2},{"url":"img/Z0003.png", "wait": 15, "frame": 3},{"url":"img/Z0004.png", "wait": 15, "frame": 4},{"url":"img/Z0005.png", "wait": 15, "frame": 5},{"url":"img/Z0006.png", "wait": 15, "frame": 6},],"run": [{"url":"img/P0001.png", "wait": 5, "frame": 1},{"url":"img/P0002.png", "wait": 5, "frame": 2},{"url":"img/P0003.png", "wait": 5, "frame": 3},{"url":"img/P0004.png", "wait": 5, "frame": 4},{"url":"img/P0005.png", "wait": 5, "frame": 5},{"url":"img/P0006.png", "wait": 5, "frame": 6},{"url":"img/P0007.png", "wait": 5, "frame": 7},{"url":"img/P0008.png", "wait": 5, "frame": 8},{"url":"img/P0009.png", "wait": 5, "frame": 9},{"url":"img/P0010.png", "wait": 5, "frame": 10},],"jump": [{"url":"img/T0001.png", "wait": 5, "frame": 1},{"url":"img/T0002.png", "wait": 5, "frame": 2},{"url":"img/T0003.png", "wait": 5, "frame": 3},{"url":"img/T0004.png", "wait": 5, "frame": 4},{"url":"img/T0005.png", "wait": 5, "frame": 5},{"url":"img/T0006.png", "wait": 5, "frame": 6},{"url":"img/T0007.png", "wait": 5, "frame": 7},{"url":"img/T0008.png", "wait": 5, "frame": 8},{"url":"img/T0009.png", "wait": 5, "frame": 9},{"url":"img/T0010.png", "wait": 120, "frame": 10},],"fall": [{"url":"img/T0007.png", "wait": 5, "frame": 7},{"url":"img/T0008.png", "wait": 5, "frame": 8},{"url":"img/T0009.png", "wait": 5, "frame": 9},{"url":"img/T0010.png", "wait": 5, "frame": 10},],"fallover": [{"url":"img/T0011.png", "wait": 5, "frame": 9},{"url":"img/T0012.png", "wait": 5, "frame": 10},]}