版權申明:
- 本文原創首發於以下網站:
- 博客園『優夢創客』的空間:https://www.cnblogs.com/raymondking123
- 優夢創客的官方博客:https://91make.top
- 優夢創客的游戲講堂:https://91make.ke.qq.com
- 『優夢創客』的微信公眾號:umaketop
- 您可以自由轉載,但必須加入完整的版權聲明!
游戲原圖
游戲介紹
- 《信使(The Messenger)》是一款橫版過關冒險游戲,此游戲在IGN上獲得了8.0分的不錯成績,IGN編輯MITCHELL SALTZMAN認為《信使》擁有很多想帶給玩家的內容。它是一款2D動作平台游戲,擁有華麗的美學,從8位風格無縫過渡到16位,游戲擁有一些搞笑的幽默場景,巧妙的對白,以及一個最佳的復古"芯片風”音樂。
- 《信使(The Messenger)》無論從哪一方面來看都是受到了FC時代《忍者龍劍傳》的影響,這款游戲的發行商Devolver Digital還邀請到了FC《忍者龍劍傳》初代的制作人吉沢秀雄進行體驗。
游戲規則介紹
- 游戲的操作與FC上的忍龍基本一致,左右移動、跳、落(從平台上落下)、攻擊
游戲開發過程
搭建游戲UI界面
- 在assets上創建一個文件夾名為Scence,這個文件夾用來存放場景,新建一個場景Scence改名為Game
- 在Canvas上建一個Background節點,在這個節點上插入Sprite(圖片精靈)組件,把背景圖片拖進去,調整大小,這個就是游戲的背景
- 在Canvas上新建節點Ground、Platform、Wall、Pitfall、Save、Lame,分別用來放置地面、平台、牆、陷阱、保存點、燈
創建主角
- 將一張忍者的圖片拉至Canvas節點下,並重命名為Player
制作動畫特效
- 在資源管理器的assets文件夾下創建一個子文件夾Animation,用來存放各種動畫
- 選擇Player,在動畫編輯器中點擊按鈕《添加Animation組件》為Player節點添加動畫組件
- 在點擊按鈕《新建Clip文件》為Player創建動畫
- 單擊動畫編輯器左上角的書寫圖標按鈕開始編輯動畫
- 將主角的往右跑的4張圖片組合成主角右跑動畫(序列幀動畫)
- 再創建主角左跑動畫、攻擊動畫、爬牆動畫······
寫腳本
Player腳本
創建Player腳本
- 在資源管理器的assets文件夾下創建一個子文件夾Script,用來存放各種腳本
- 在資源管理器的Script文件夾下創建一個JavaScript腳本,重命名為Player
- 將腳本Player掛在Player對象上
編輯Player腳本
定義玩家屬性
properties: {
camera: { //攝像機
type: cc.Node, //屬性類型
default: null, //默認值
},
jumpHeight: 200, //跳躍高度
playerState: 2, //玩家狀態 0站立 1跳 2落 3爬牆
isRun: false, //是否跑動
playerDirection: 1, //玩家方向 0左 1右
mayJump: true, //能否跳躍
speed: 0, //速度
hp: 5, //生命值
time: 0, //時間
shinState: 0, //攀爬狀態 0為不動 1為上爬 2為下滑
isAttack: false, //是攻擊狀態
whetherSpringback: true, //是否回彈
},
給玩家創建各種能力(函數)
- 攻擊
attack() {
this.isAttack = true; //是攻擊狀態
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左攻動畫");
} else { //否則
this.getComponent(cc.Animation).play("主角右攻動畫");
}
this.node.getComponent(cc.BoxCollider).size = cc.size(140, 87); //獲取主角的碰撞器,並設置包圍盒大小(擴大包圍盒,因為攻擊時人物的包圍盒要把刀包圍進去)
},
- 左移
leftMove() {
this.node.x -= 10
},
- 右移
rightMove() {
this.node.x += 10;
},
- 攀爬,觸碰牆壁進入爬牆狀態后可使用爬牆方法
shin() {
if (this.shinState == 1) { //如果人物攀爬狀態為上爬
this.node.y += 3;
} else if (this.shinState == 2) { //否則,就是人物狀態為下滑
this.node.y -= 9;
}
},
- 跳躍,在平台、地面、空中時可執行
jumpAction: null,
jump() {
this.jumpAction = cc.moveBy(0.5, cc.v2(0, this.jumpHeight)).easing(cc.easeCubicActionOut());
this.node.runAction(this.jumpAction);
},
- 爬牆跳,此方法只有攀爬在牆壁上時可使用
jumpDistance: null,
wallJump: null,
WallJump() {
if (this.playerDirection == 0) {
this.jumpDistance = 200;
} else {
this.jumpDistance = -200;
}
this.wallJump = cc.moveBy(0.5, cc.v2(this.jumpDistance, this.jumpHeight)).easing(cc.easeCubicActionOut());
this.node.runAction(this.wallJump);
},
- 翻跳上地面,當與牆壁結束碰撞時執行,用於結束攀爬進入站立狀態
WallGroundJump() {
if (this.playerDirection == 0) {
this.jumpDistance = -100;
this.getComponent(cc.Animation).play("主角左跳動畫");
} else {
this.jumpDistance = 100;
this.getComponent(cc.Animation).play("主角右跳動畫");
}
var WallJump = cc.moveBy(0.25, cc.v2(this.jumpDistance, this.jumpHeight / 2));
this.node.runAction(WallJump);
},
- 落下,這是一個被動方法,只要玩家在空中且沒有上升力,此方法就一直執行
drop(speed) {
this.node.y += speed;
},
主角的碰撞處理
- 碰撞開始
onCollisionEnter: function (other, self) { //處理碰撞的方法
if (other.node.group == "Ground" || other.node.group == "Platform") { //如果玩家碰到的是節點分組中的地面,或平台
this.otherObject = other; //獲取另一個對象(玩家碰撞的對象)
if (this.speed < -30) { //如果速度過快
this.hp = 0; //摔死
}
if (self.node.getComponent("Player").playerState == 2 && other.node.y < self.node.y) { //如果玩家狀態為下落
self.node.getComponent("Player").playerState = 0; //玩家狀態變為站立
self.node.y = other.node.y + other.node.height / 2 + 42; //回彈,防止人物腳陷入地面
if (this.isRun) { //如果是跑動狀態
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左跑動畫");
} else { //否則,就是玩家方向為右
this.getComponent(cc.Animation).play("主角右跑動畫");
}
} else { //否則就是玩家不是跑動狀態
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左立動畫");
} else { //否則,就是玩家方向為右
this.getComponent(cc.Animation).play("主角右立動畫");
}
}
} else if (self.node.getComponent("Player").shinState == 2) {
self.node.getComponent("Player").playerState = 0; //玩家狀態變為站立
if (self.node.getComponent("Player").playerDirection == 0) {
self.node.x += 20;
this.getComponent(cc.Animation).play("主角左立動畫");
} else {
self.node.x -= 20;
this.getComponent(cc.Animation).play("主角右立動畫");
}
}
} else if (other.node.group == "Wall") { //如果玩家碰到的是節點分組中的牆壁
if (this.isAttack) { //如果是攻擊狀態
this.isRun = false; //停止跑動
if (this.playerDirection == 0) { //如果人物方向為左
this.node.x += 50; //攻擊到牆壁往右回彈
} else { //否則(人物方向為右)
this.node.x -= 50; //攻擊到牆壁往左回彈
}
} else {
if (self.node.getComponent("Player").playerState == 0) {
self.node.y += 20;
}
this.node.stopAction(this.wallJump);
self.node.getComponent("Player").playerState = 3; //玩家狀態變為爬牆
this.isRun = false;
this.shinState = 0; //攀爬狀態為不動
if (this.playerDirection == 0) { //如果人物方向為左
self.node.x = other.node.x + other.node.width / 2 + 25; //回彈,防止人物陷入牆壁
this.getComponent(cc.Animation).play("主角左貼動畫");
} else { //否則,就是人物方向為右
self.node.x = other.node.x - other.node.width / 2 - 25; //回彈,防止人物陷入牆壁
this.getComponent(cc.Animation).play("主角右貼動畫");
}
}
} else if (other.node.group == "Pitfall") { //如果玩家碰到的是節點分組中的陷阱
this.hp = 0;
this.time = 0;
} else if (other.node.group == "Save") { //如果玩家碰到的是節點分組中的保存點
this.playerX = this.node.x;
this.playerY = this.node.y;
}
},
- 碰撞過程中
onCollisionStay: function (other, self) {
if (other.node.group == "Lamp") {
if (this.isAttack) {
this.mayJump = true;
this.node.getComponent(cc.BoxCollider).size = cc.size(55, 87); //獲取主角的碰撞器,並設置包圍盒大小(縮小包圍盒,因為攻擊到燈后結束攻擊人物收刀后碰撞范圍變小)
this.isAttack = false;
this.time = 0;
}
}
},
- 碰撞結束時
onCollisionExit: function (other, self) {
if (this.hp > 0) {
if (other.node.group == "Ground" || other.node.group == "Platform") { //如果玩家與地面或平台碰撞結束
if (self.node.getComponent("Player").playerState == 0) { //如果玩家狀態為站立
self.node.getComponent("Player").playerState = 2; //玩家狀態變為下落
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左落動畫");
} else { //否則,就是玩家方向為右
this.getComponent(cc.Animation).play("主角右落動畫");
}
}
} else if (other.node.group == "Wall" && self.node.getComponent("Player").playerState == 3) {
this.playerState = 1; //玩家狀態設為跳
this.WallGroundJump(); //玩家執行翻跳上地面
this.speed = 100; //剛起跳時速度快
this.mayJump = false; //能跳設為false
}
}
},
在update中刷新玩家
update(dt) {
if (this.hp == 0) { //如果玩家生命值為0,就是死了
if (this.time == 0) {
this.getComponent(cc.Animation).play("主角爆炸動畫");
}
if (this.time < 0.4) {
this.time += dt;
} else if (this.time >= 0.4) {
this.node.x = this.playerX;
this.node.y = this.playerY + 30;
this.speed = 0;
this.playerState = 2;
this.time = 0;
this.isRun = false;
this.hp = 5;
}
} else { //否則,就是活着
//this.camera.x = this.node.x;
//if (this.camera.x > this.node.x + 100) {
this.camera.x = this.node.x //+ 100;
//} else if (this.camera.x < this.node.x - 100) {
this.camera.x = this.node.x //- 100;
//}
if (this.camera.y > this.node.y + 100) {
this.camera.y = this.node.y + 100;
} else if (this.camera.y < this.node.y - 100) {
this.camera.y = this.node.y - 100;
if (this.isAttack) { //是否是攻擊狀態
this.time += dt; //計時
if (this.time > 0.3) { //當時間大於0.3秒
this.node.getComponent(cc.BoxCollider).size = cc.size(55, 87); //獲取主角的碰撞器,並設置包圍盒大小(縮小包圍盒,因為攻擊結束人物收刀后碰撞范圍變小)
this.isAttack = false; //將攻擊狀態變為false
this.time = 0; //時間歸零
if (this.playerState == 0) { //如果玩家是站立狀態
if (this.isRun) { //如果玩家是跑動的的
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左跑動畫");
} else { //否則(就是玩家方向為右)
this.getComponent(cc.Animation).play("主角右跑動畫");
}
} else { //否則(就是站在不動)
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左立動畫");
} else { //否則(就是玩家方向為右)
this.getComponent(cc.Animation).play("主角右立動畫");
}
}
} else { //否則(就是在空中)
if (this.playerDirection == 0) { //如果玩家方向為左
this.getComponent(cc.Animation).play("主角左落動畫");
} else { //否則(就是玩家方向為右)
this.getComponent(cc.Animation).play("主角右落動畫");
}
}
}
if (this.isRun) { //如果能跑動
if (this.playerDirection == 0) { //如果玩家方向是左
this.leftMove(); //向左跑
} else { //否則
this.rightMove(); //向右跑
}
if (this.playerState == 0) { //如果玩家狀態為站立
this.speed = 0; //速度歸零
this.mayJump = true; //能跳躍
} else if (this.playerState == 1) { //如果玩家狀態為跳
this.speed -= dt * 400; //速度越來越慢
if (this.speed <= 0) { //如果速度減少到小於等於0
this.speed = 0; //速度歸零
this.node.stopAction(this.jumpAction); //停止跳
this.playerState = 2; //玩家狀態變為下落
}
} else if (this.playerState == 2) { //如果玩家狀態為下落
this.speed -= dt * 30; //下落狀態下速度隨時間變得越來越快
this.drop(this.speed); //執行下落
} else if (this.playerState == 3) { //如果玩家狀態為爬牆
this.speed = 0; //速度歸零
this.mayJump = true; //能跳躍
this.shin(); //攀爬
}
}
},
Game腳本
創建Game腳本
- 在資源管理器的Script文件夾下創建一個JavaScript腳本,重命名為Game
- 將腳本Game掛在Canvas上
編輯Game腳本
在onLoad中開啟碰撞監聽、監聽系統事件
onLoad() {
var manager = cc.director.getCollisionManager(); //獲取碰撞組件
manager.enabled = true; //開啟碰撞監聽
//manager.enabledDebugDraw = true; //開啟碰撞組件Debug
this.playerJS = this.Player.getComponent("Player"), //獲取玩家腳本
//系統事件,當鍵被按下時調用keyDown回調函數處理
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.keyDown, this);
//系統事件,當鍵彈起時調用keyUp回調函數處理
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.keyUp, this);
},
創建keyDown於keyUp方法用來控制玩家
- keyDown方法
playerJS: null, //玩家腳本
keyDown(event) {
if (this.playerJS.hp > 0) { //如果玩家hp大於0,就是玩家活着
switch (event.keyCode) {
case cc.macro.KEY.a:
if (this.playerJS.isRun == false) { //如果能跑動是錯誤的
if (this.playerJS.playerState != 3) { //如果玩家不為爬牆
this.playerJS.isRun = true; //能跑動設為true
this.playerJS.playerDirection = 0; //玩家方向設為左
if (this.playerJS.playerState == 0) { //如果玩家狀態為站立
this.Player.getComponent(cc.Animation).play("主角左跑動畫");
}
} else if (this.playerJS.playerDirection == 1) {
this.playerJS.playerState = 1; //玩家狀態設為跳
this.playerJS.WallJump(); //玩家執行爬牆跳
this.playerJS.playerDirection = 0; //玩家方向設為左
this.playerJS.speed = 200; //剛起跳時速度快
this.playerJS.mayJump = false; //能跳設為false
this.Player.getComponent(cc.Animation).play("主角左跳動畫");
}
}
break;
case cc.macro.KEY.d:
if (this.playerJS.isRun == false) { //如果能跑動是錯誤的
if (this.playerJS.playerState != 3) { //如果玩家不為爬牆
this.playerJS.isRun = true; //能跑動設為true
this.playerJS.playerDirection = 1; //玩家方向設為右
if (this.playerJS.playerState == 0) { //如果玩家狀態為站立
this.Player.getComponent(cc.Animation).play("主角右跑動畫");
}
} else if (this.playerJS.playerDirection == 0) {
this.playerJS.playerState = 1; //玩家狀態設為跳
this.playerJS.WallJump(); //玩家執行爬牆跳
this.playerJS.playerDirection = 1; //玩家方向設為右
this.playerJS.speed = 200; //剛起跳時速度快
this.playerJS.mayJump = false; //能跳設為false
this.Player.getComponent(cc.Animation).play("主角右跳動畫");
}
}
break;
case cc.macro.KEY.w:
if (this.playerJS.playerState != 3) { //如果玩家狀態不為爬牆
if (this.playerJS.mayJump) { //如果能跳
this.playerJS.playerState = 1; //玩家狀態設為跳
this.playerJS.jump(); //玩家執行跳
this.playerJS.speed = 200; //剛起跳時速度快
this.playerJS.mayJump = false; //能跳設為false
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左跳動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右跳動畫");
}
}
} else { //否則,就是玩家為爬牆狀態
this.playerJS.shinState = 1; //攀爬狀態設為上爬
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左爬動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右爬動畫");
}
}
break;
case cc.macro.KEY.s:
if (this.playerJS.playerState == 0) { //如果玩家狀態為站立
if (this.playerJS.otherObject.node.group == "Ground") { //如果玩家依附在地面上
//蹲下
} else if (this.playerJS.otherObject.node.group == "Platform") { //如果玩家依附在平台上
//this.Player.y -= 30; //玩家y坐標減,為了快速從平台上落下(此語句可有可無)
this.playerJS.playerState = 2; //玩家狀態變為下落狀態
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左落動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右落動畫");
}
}
} else if (this.playerJS.playerState == 3) { //如果玩家狀態為爬牆
this.playerJS.shinState = 2 //攀爬狀態設為下滑
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左爬動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右爬動畫");
}
}
break;
case cc.macro.KEY.j:
if (this.playerJS.isAttack == false) { //如果玩家攻擊狀態為false(已經是攻擊狀態不能再攻擊,必須等攻擊結束才能再次攻擊)
if (this.playerJS.playerState != 3) //如果玩家不為爬牆狀態(不能在爬牆時攻擊)
{
this.playerJS.attack(); //玩家執行攻擊
}
}
break;
}
}
},
- keyUp方法
keyUp(event) {
if (this.playerJS.hp > 0) { //如果玩家hp大於0,就是玩家活着
switch (event.keyCode) {
case cc.macro.KEY.a:
if (this.playerJS.isRun && this.playerJS.playerDirection == 0) { //如果在跑動,並且玩家方向朝左
this.playerJS.isRun = false; //能跑動設為false
if (this.playerJS.playerState == 0) { //如果玩家狀態為站立
this.Player.getComponent(cc.Animation).play("主角左立動畫");
}
}
break;
case cc.macro.KEY.d:
if (this.playerJS.isRun && this.playerJS.playerDirection == 1) { //如果在跑動,並且玩家方向朝右
this.playerJS.isRun = false; //能跑動設為false
if (this.playerJS.playerState == 0) { //如果玩家狀態為站立
this.Player.getComponent(cc.Animation).play("主角右立動畫");
}
}
break;
case cc.macro.KEY.w:
if (this.playerJS.playerState != 3) { //如果玩家狀態不為爬牆
if (this.playerJS.playerState == 1) { //如果玩家狀態為跳,並且速度大於100
if (this.playerJS.speed > 100) {
this.playerJS.speed = 50; //玩家速度變慢
}
}
} else { //否則,就是玩家為爬牆狀態
this.playerJS.shinState = 0; //攀爬狀態設為不動
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左貼動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右貼動畫");
}
}
break;
case cc.macro.KEY.s:
if (this.playerJS.playerState == 3) {
this.playerJS.shinState = 0; //攀爬狀態設為不動
if (this.playerJS.playerDirection == 0) { //如果玩家方向為左
this.Player.getComponent(cc.Animation).play("主角左貼動畫");
} else { //否則,就是玩家方向為右
this.Player.getComponent(cc.Animation).play("主角右貼動畫");
}
}
break;
}
}
},