# 小朋友下樓梯
<style>
.slide-number {
font-size: 75% !important;
}
</style>
---
# 簡報
## [https://ntust.me/887yu](https://ntust.me/887yu)
---
# 講師 - Pikachu
![](https://i.imgur.com/6bOrGl4.png)
---
# [:smiling_imp:小朋友下樓梯:smiling_imp: ](http://codepen.io/pikapika/full/Lbdojm/)
---
# Phaser
![](https://i.imgur.com/5mXdMEX.png)
---
# Fork 程式碼
1. 登入/註冊 [codepen](http://codepen.io/)
2. [打開連結](http://codepen.io/pikapika/pen/bBLMOX?editors=1010)
2. 按右上方的 Fork 按鈕複製到自己的專案下
---
# 創造 Phaser
創造長 ==400== 寬 ==400== 的遊戲畫面,Phaser.AUTO 表示使用預設的繪圖方式,"game" 是告訴 Phaser 遊戲要放置在網頁的那個部分
```javascript=
var game = new Phaser.Game(400, 400, Phaser.AUTO, "game",
{ preload: preload, create: create, update: update });
```
---
# 遊戲架構
**preload**
載入遊戲素材,例如圖片、聲音
**create**
遊戲一開始前初始化動作,只會執行一次
**update**
在遊戲進行中,會不斷的被執行
---
# 想想看
1. 創造場景和主角的程式要寫在那個函式?
1. 玩家死掉後會顯示 GameOver 的圖片,這張圖片創造的動作應該寫在?
2. 當我按下左鍵時主角要往左移動,偵測按鍵是否被按下的動作應該寫在?
4. 主角從左移動到右,改變位置的動作應該寫在?
3. 判斷主角是否撞到牆壁,偵測是否碰撞的動作應該放在?
6. 每隔一段時間就創造新的階梯,創造階梯的這個動作應該寫在?
---
# preload
baseURL 是載入資源的來源
http://s.ntustcoding.club/ns-shaft-workshop/player.png
載入這張圖片並命名為 player
```javascript=
game.load.crossOrigin = 'anonymous';
game.load.baseURL = 'http://s.ntustcoding.club/ns-shaft-workshop/';
game.load.spritesheet('player', 'player.png', 32, 32);
```
---
# SpriteSheet
spritesheet 和 image 的差異,就是一個 spritesheet 包含很多個分別的圖片,這樣的做法有助於減少儲存空間
[spritesheet](http://s.ntustcoding.club/ns-shaft-workshop/player.png) vs [image](http://s.ntustcoding.club/ns-shaft-workshop/normal.png)
```javascript=
game.load.spritesheet('player', '/assets/player.png', 32, 48);
game.load.image('normal', '/assets/block.png');
```
---
# 創造 Sprite
在遊戲中任何的東西都可以稱為 Sprite (遊戲精靈)
試著用以下語法創造出主角
game.add.sprite( x座標, y座標, 圖片 );
```javascript=
player = game.add.sprite(200, 200, 'player');
```
---
# 掛載物理引擎
啟用 player 的物理特性,例如移動、碰撞等,但如果只是想顯示一張圖片例如背景圖,在遊戲中不需要互動就不用掛載物理引擎
```javascript=
player = game.add.sprite(200, 200, 'player');
game.physics.arcade.enable(player);
```
---
![](https://i.imgur.com/RxP01sw.jpg)
---
# 座標點
sprite座標點在圖片的左上角
![](https://i.imgur.com/lAfBwyq.jpg)
---
# Sprite 座標屬性
取得 sprite 的當前位置
```javascript=
var x = player.body.x;
var y = player.body.y;
// 印出 x,y
console.log(x, y);
```
設定 sprite 的座標
```javascript=
player.body.x = 100;
player.body.y = 100;
```
---
# 移動
每執行一次 update,就讓x座標加上 1
```javascript=
player.body.x += 1;
```
---
# 設定速度
設定角色的速度每秒移動X距離60
```javascript=
player.body.velocity.x = 60;
```
---
# 鍵盤事件
創造鍵盤
```javascript=
keyboard = game.input.keyboard.addKeys({
'up': Phaser.Keyboard.UP,
'down': Phaser.Keyboard.DOWN,
'left': Phaser.Keyboard.LEFT,
'right': Phaser.Keyboard.RIGHT
});
```
判斷某個鍵是否被按下
```javascript=
if(keyboard.left.isDown) {
// 當左方向鍵按下時執行的動作...
}
if(keyboard.right.isDown) {
// 當右方向鍵按下時執行的動作...
}
```
---
# 任務1
* 按上下左右鍵時角色會移動
* 放開按鍵時角色停止
:point_right: [任務目標](http://codepen.io/pikapika/full/GNQdOB/)
---
# spritesheet 用法
32, 32 就是裁切的長和寬,編號是從 0 開始
```javascript=
game.load.spritesheet('player', 'player.png', 32, 32);
```
![](https://i.imgur.com/yQJVLnP.jpg)
---
# 影格動畫
幫角色增加動畫
Sprite.animations.add(動畫名字, 影格, 每秒幀數)
```javascript=
player.animations.add('left', [0, 1, 2, 3], 6);
player.animations.add('right', [9, 10, 11, 12], 6);
```
播放動畫 Sprite.play(動畫名字)
```javascript=
player.animations.play('right');
```
---
# 任務2
* 向左或向右移動時,播放相對應的動畫
:point_right:[任務目標](http://codepen.io/pikapika/full/pNaVOJ)
---
# 設定外觀
設定外觀為圖片編號 1 的部分
```javascript=
player.frame = 1;
```
---
# 任務3
* 不移動時角色要呈現正面站立的樣子
[任務示範](http://codepen.io/pikapika/full/LbQmJe)
---
# 作用域
作用域是什麼?
```javascript=
var a = 10;
function test1 () {
var a = 100;
console.log(a);
}
function test2 () {
console.log(a);
}
test1(); // 印出 100
text2(); // 印出 10
```
---
# 全域變數
```javascript=
var player;
var keyboard;
create() {
player = game.add.sprite(200, 200, 'player');
keyboard = game.input.keyboard.addKeys({
'left': Phaser.Keyboard.LEFT,
'right': Phaser.Keyboard.RIGHT
});
}
update() {
if(keyboard.left.isDown) {
player.body.velocity.x = -250;
}
if(keyboard.right.isDown) {
player.body.velocity.x = 250;
}
}
```
---
# 任務4
* 增加左右兩邊的牆壁和上排的釘子
:point_right:[任務目標](http://codepen.io/pikapika/full/GNQdXw)
---
# 設定牆壁
啟用物理引擎,immovable=true 表示設定牆壁為固定,可以嘗試設為 false 並在碰撞會發生?
```javascript=
leftWall = game.add.sprite(0, 0, 'wall');
game.physics.arcade.enable(leftWall);
// 設定固定屬性
leftWall.body.immovable = true;
```
---
# 碰撞
==game.physics.arcade.collide(A, B)== 會判斷 A,B 是否碰撞
接受陣列做為參數,以下程式會檢查玩家是否與左牆或右牆碰撞
```javascript=
game.physics.arcade.collide(player, [leftWall, rightWall]);
```
---
# 任務5
* 讓牆壁有阻擋功能
:point_right:[任務目標](http://codepen.io/pikapika/full/BQYxOE)
---
# 掉落
改變角色的速率,實現加速度的方法
```javascript=
player.body.velocity.y += 30;
```
改用設定玩家的引力,原理和上面的做法相似
==sprite.body.gravity== 可以設定施加在角色身上的引力
```javascript=
player.body.gravity.y = 500;
```
---
# 任務 6
* 設定角色的引力,會向下墜落
* 創要一個平台,角色踩在平台上會停住
:point_right:[任務目標](http://codepen.io/pikapika/full/ZBroqO)
---
# 移動 大!平!台!
平台被設定為固定,因此不能使用 body.velocity 來移動,但可以在 update 裡修改座標
---
# 任務7
* 平台向上移動
:point_right:[任務目標](http://codepen.io/pikapika/full/rWJvqW)
---
# 溫習陣列
```javascript=
// 存有 5 個成績的陣列
var scores = [100, 95, 90, 85, 80];
console.log(scores[2]); // 取出第0個成績,印出 90
// 把第0個的成績改成 60
scores[0] = 60;
console.log(scores[0]); // 取出第0個成績,印出 60
// 新增新的成績
scores.push(90);
console.log(scores[5]); // 取得第5個成績,印出 90
// 取得成績的個數
console.log(scores.length); // 印出 6
```
---
# 多個平台
遊戲中平台是不斷生成和消失,因此不能用各自的變數去存放平台,必須改用==陣列==來儲存
```javascript=
var platforms = [];
function create() {
var platform = game.add.sprite(0, 0, 'normal');
platforms.push(platform);
}
```
---
# 任務8
* 創造三個平台
* 改用陣列存放平台
* 使用迴圈去更新陣列裡每個平台位置
:point_right:[任務目標](http://codepen.io/pikapika/full/dOdegm)
---
# 時間
Phaser 自帶時間計時方便我們取得,
使用 ==game.time.now== 可以取得遊戲開始到現在的時間
(單位毫秒ms)
```javascript=
var now = Phaser.time.now;
console.log(now); // 印出遊戲開始到現在的時間
```
---
# 任務9
* 每隔一段時間(600ms)創造新的平台
:point_right:[任務目標](http://codepen.io/pikapika/full/GNQdYw)
---
# 移除平台
銷毀 platform 物件
```javascript=
platform.destroy();
```
從陣列移除第 i 個平台
```javascript=
platforms.splice(i, 1);
```
---
# 隨機數
執行 ==Math.random()== 會產生0~1的隨機數字,
乘上數字 100 就會取得 0~100 的隨機數字
```javascript=
// rand 是0~100的隨機數字
var rand = Math.random()*100;
```
---
# 任務10
* 隨機產生平台的位置
[任務示範](http://codepen.io/pikapika/full/QGQrJW)
---
# 平台種類
1. [normal](http://s.ntustcoding.club/ns-shaft-workshop/normal.png) 藍色平台
2. [nails](http://s.ntustcoding.club/ns-shaft-workshop/nails.png) 釘子
3. [conveyorRight](http://s.ntustcoding.club/ns-shaft-workshop/conveyor_right.png) 往右的傳送帶
4. [conveyorLeft](http://s.ntustcoding.club/ns-shaft-workshop/conveyor_left.png) 往左的傳送帶
5. [trampoline](http://s.ntustcoding.club/ns-shaft-workshop/trampoline.png) 彈簧墊
6. [fake](http://s.ntustcoding.club/ns-shaft-workshop/fake.png) 翻轉的平台
---
# 任務11
1. 隨機產生不同的平台
:point_right:[任務目標](http://codepen.io/pikapika/full/yVKNbg)
---
# 碰撞時執行函式
增加第三個參數 effect 為碰撞時執行的函式
```javascript=
game.physics.arcade.collide(player, platforms, effect);
```
effect 函式傳入的參數是碰撞的兩個 Sprite
```javascript=
function effect(player, platform) {
console.log(platform.key);
}
```
==platform.key== 會是 sprite 的圖片名字
---
# 任務12
當角色碰撞上平台時印出碰撞到的平台種類
console在左下角打開
:point_right:[任務目標](http://codepen.io/pikapika/pen/dOmoWg)
---
# 持續播放動畫
第四個參數為 true 時,動畫會不停播放
```javascript=
platform.animations.add('scroll', [0, 1, 2, 3], 16, true);
```
---
# 任務13
* 完成傳送帶的動畫 [圖片1](http://s.ntustcoding.club/ns-shaft-workshop/conveyor_left.png) [圖片2](http://s.ntustcoding.club/ns-shaft-workshop/conveyor_right.png)
:point_right:[任務目標](http://codepen.io/pikapika/full/mOxJmg)
---
# 任務14
* 實作傳送帶的功能
:point_right:[任務目標](http://codepen.io/pikapika/full/NbYqgG)
---
# 任務15
* 完成彈簧墊的彈跳功能
:point_right:[任務目標](http://codepen.io/pikapika/full/mOoLPB/)
---
# 任務16
* 完成彈簧墊的動畫 [圖片](http://s.ntustcoding.club/ns-shaft-workshop/trampoline.png)
:point_right:[任務目標](http://codepen.io/pikapika/full/xRWGrj)
---
# 物件
```javascript=
var pikachu = {
color: "yellow",
weight: 13.2,
height: 40,
gender: true
}
```
==pikachu.life== 皮卡丘的生命值
```javascript=
// 印出 13.2公分
console.log(pikachu.height + "公分");
// 皮卡丘增胖 5 公斤
pikachu.weight += 5;
```
---
# 物件
動態增加屬性和賦予值
```javascript=
var pikachu = {}
pikachu.color = "yellow";
pikachu.weight = 13.2;
pikachu.height = 40;
pikachu.gender = true;
```
---
# player 增加屬性
==life== 存放玩家的生命
==onTouch== 紀錄碰撞的物體,防止重複觸法事件
==unbeatableTime== 角色無敵狀態的時間
```javascript=
player.life = 10;
player.touchOn = undefined;
player.unbeatableTime = 0;
```
---
# 顯示文字
創造文字物件
game.add.text(x座標, y座標, 文字內容);
```javascript=
text = game.add.text(10, 10, "");
```
動態修改文字的內容
```javascript=
text.setText("這裡放新的文字");
```
---
# 任務17
* 增加 player 三個屬性
* 顯示玩家血量
:point_right:[任務目標](http://codepen.io/pikapika/full/YpaXQB)
---
# 任務18
* 實作釘子功能,玩家踩上去只會一次扣血
* 提示:player.onTouch 紀錄碰撞的平台
:point_right:[任務目標](http://codepen.io/pikapika/full/QGmbME)
---
# 平台碰撞邊界
修改圖片碰撞的邊界
![](https://i.imgur.com/WYfCslk.jpg)
sprite.body.setSize(長, 寬, x座標, y座標)
```javascript=
game.physics.arcade.enable(platform);
platform.body.setSize(96, 15, 0, 15);
```
---
# 任務19
* 修改釘子平台的碰撞邊界
:point_right:[任務目標](http://codepen.io/pikapika/full/XNGqEX)
---
# 任務20
* 實作普通平台的功能,玩家踩上去會加血
* 限制血量上限為 10
:point_right:[任務目標](http://codepen.io/pikapika/full/LbdVjJ)
---
# 關閉側邊跟下面碰撞
取消左邊、右邊、下邊的碰撞
增加以下語法接到創造 platform 程式後面,角色彈跳時就不會撞到上面的平台
```javascript=
platform.body.checkCollision.down = false;
platform.body.checkCollision.left = false;
platform.body.checkCollision.right = false;
```
---
# setTimeout
延遲執行函式,setTimeout(執行的函式, 延遲時間ms)
```javascript=
setTimeout(function() {
// do something...
}, 100);
```
---
# 任務21
* 實作翻轉平台的功能
:point_right:[任務目標](http://codepen.io/pikapika/full/aBMGMJ/)
---
# 任務22
* 實作翻轉的動畫 [圖片](http://s.ntustcoding.club/ns-shaft-workshop/fake.png)
:point_right:[任務目標](http://codepen.io/pikapika/full/bBvdoV)
---
# 任務23
天花板扣血
提示:==game.time.now==, ==player.unbeatableTime==
:point_right:[任務目標](http://codepen.io/pikapika/full/zoWGEp)
---
# 背景閃爍
game.camera.flash(顏色色碼, 時間)
```javascript=
game.camera.flash(0xff0000, 100);
```
---
# 任務24
* 判斷玩家是否死亡
* 玩家死亡時停止遊戲,並顯示 gameOver
:point_right:[任務目標](http://codepen.io/pikapika/full/oYqXGa)
---
# [完成版程式](http://codepen.io/pikapika/pen/MbxPBo)
---
# 挑戰1 - 角色動畫
* 完成角色的所有動畫
* 左走、右走、往左掉、往右掉、往下掉
---
# 挑戰2 - 完成
* 設計重新開始
---
# [對戰模式](http://codepen.io/pikapika/pen/ZBPoNW)