介绍
最近用 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},
]
}
转载:https://blog.csdn.net/dayouyu001/article/details/115489498