跟 FP 一樣,OOP 到目前已經第三天了,我們來點實戰吧!
今天的實戰很特別啊,基本上是工作派不上用場的程式,但因為我不知為何靈光一閃,覺得寫這種東西很好玩,所以就剛好用今天試試看吧!
當然,多少還是會用到一些 OOP 的概念進去,可以邊寫邊體會哦!
印象中在我小時候那個年代,沒什麼遊戲可以玩,最經典的就能玩很久了。而像是貪食蛇、小精靈(Pac-Man) 都是玩不膩的經典。
像這兩個遊戲都有個共通點,就是會有個場景,裡面會有個角色可以操縱,最單純的操作就是上下左右嘛!
但只有一個角色太無聊了,所以會有一些附加的遊玩價值:要嘛有個獎品放在場景中,吃到就可以加分;要嘛反過來,放一隻會移動的鬼在場景裡面跑,被它碰到就死掉。
今天要實作一個地圖遊戲的半成品,也就是只做到遊玩價值以前的部分XD,直接來看圖:
這張 GIF 我自己錄起來都覺得很好笑XD
但真的相信我,寫的過程中,即便畫面上沒有任何獎勵,只要你寫的場景如預期畫出來,按方向鍵可以操作角色隨便走來走去,光這樣就會很嗨了!((好啦只有我嗨
OOP 以物件(object)為思考主體,所以最重要的就是先定義出會有哪些物件:
Scene
)Actor
)嗯... actor 是演員,不過我們暫且用這個詞,來稱呼被我們擺在場景中的物體
constructor
,接收兩個參數(width
、height
),用來定義出這個場景的長寬。
map
:透過 width
、height
計算出一個二維陣列,用來存放目前場景的長相actors
:存放這個場景中的所有 Actor
實體物件class Scene {
constructor(width, height) {
const map = new Array(width).fill('-');
for (let i=0; i < map.length; i++) {
map[i] = new Array(height).fill('-');
}
this.map = map;
this.width = width;
this.height = height;
this.actors = [];
}
}
register
:將 Actor
實體物件放到 actors
裡面unregister
:將指定的 Actor
實體物件移除class Scene {
// ...
register(actor) {
this.actors.push(actor);
this.map[actor.x][actor.y] = actor.sign;
this.draw();
}
unregister(actor) {
const index = this.actors.indexOf(actor);
this.actors.splice(index, 1);
this.map[actor.x][actor.y] = '-';
this.draw();
}
}
changeActorPosition
:將 Actor
放到新的位置上class Scene {
// ...
changeActorPosition(actor, x, y) {
// 舊的移除
this.map[actor.x][actor.y] = '-';
// 新的補上
this.map[x][y] = actor.sign;
this.draw();
}
}
draw
:將目前的二維場景(map
)畫在 console 上class Scene {
// ...
draw() {
let display = '';
for (let i = 0; i < this.map.length; i++) {
for (let j = 0; j < this.map[i].length; j++) {
display += this.map[j][i];
}
display += '\n';
}
console.clear();
console.log(display);
}
}
constructor
,接收四個參數(scene
、x
、y
、sign
),分別代表放置的場景、x位置、y位置與代表符號。
初始化就會把自己註冊在指定的場景(Scene
)中。
class Actor {
constructor(scene, x, y, sign) {
this.scene = scene;
this.x = x;
this.y = y;
this.sign = sign.substr(0, 1);
scene.register(this);
}
}
exit
:離開這個場景(Scene
)class Actor {
// ...
exit() {
this.scene.unregister(this);
}
}
moveTo
:移動到指定的 x,y 位置class Actor {
// ...
moveTo(x, y) {
const { width, height } = this.scene
if (x < 0 || y < 0 || x >= width || y >= height) {
return
}
this.scene.changeActorPosition(this, x, y);
this.x = x;
this.y = y;
}
}
把以下程式碼複製貼到 console 按下 Enter 試試看:
class Scene {
constructor(width, height) {
const map = new Array(width).fill('-');
for (let i=0; i < map.length; i++) {
map[i] = new Array(height).fill('-');
}
this.map = map;
this.width = width;
this.height = height;
this.actors = [];
}
register(actor) {
this.actors.push(actor);
this.map[actor.x][actor.y] = actor.sign;
this.draw();
}
unregister(actor) {
const index = this.actors.indexOf(actor);
this.actors.splice(index, 1);
this.map[actor.x][actor.y] = '-';
this.draw();
}
changeActorPosition(actor, x, y) {
// 舊的移除
this.map[actor.x][actor.y] = '-';
// 新的補上
this.map[x][y] = actor.sign;
this.draw();
}
draw() {
let display = '';
for (let i = 0; i < this.map.length; i++) {
for (let j = 0; j < this.map[i].length; j++) {
display += this.map[j][i];
}
display += '\n';
}
console.clear();
console.log(display);
}
}
class Actor {
constructor(scene, x, y, sign) {
this.scene = scene;
this.x = x;
this.y = y;
this.sign = sign.substr(0, 1);
scene.register(this);
}
exit() {
this.scene.unregister(this);
}
moveTo(x, y) {
const { width, height } = this.scene
if (x < 0 || y < 0 || x >= width || y >= height) {
return
}
this.scene.changeActorPosition(this, x, y);
this.x = x;
this.y = y;
}
}
const s = new Scene(10, 10);
const a = new Actor(s, 5, 5, 'A');
const b = new Actor(s, 2, 6, 'B');
這邊我們除了將 class 定義好,也在最下面使用 new
將類別實體化,變成三個物件。
s
:場景物件,寬高都是 10a
:演員物件,以 s
為場景,位置落在 5,5,在地圖上用 A 來代表b
:演員物件,以 s
為場景,位置落在 2,6,在地圖上用 B 來代表執行結果
我想不太到什麼好的方式,所以暫時先把 keyup
事件綁在 window
物件上,因為沒辦法直接在 console 按上下左右,所以務必要先點一下網頁的畫面,才可以開始按方向鍵哦!
另外,就像第二行註解寫的,因為 Actor
可能有很多個,我暫時先抓場景內第一個 Actor
來進行上下左右的移動操作,這個完全可以自行修改哦!
window.addEventListener('keyup', e => {
// 預設操控第一隻角色
const actor = s.actors[0];
switch (e.code) {
case 'ArrowUp':
actor.moveTo(actor.x, actor.y-1);
break;
case 'ArrowDown':
actor.moveTo(actor.x, actor.y+1);
break;
case 'ArrowLeft':
actor.moveTo(actor.x-1, actor.y);
break;
case 'ArrowRight':
actor.moveTo(actor.x+1, actor.y);
break;
default:
break;
}
});
可以直接整串複製貼到 console 上 Enter,接著點一下網頁本身,就可以操作上下左右囉!(但目前走到牆壁會當掉哦XD)
class Scene {
constructor(width, height) {
const map = new Array(width).fill('-');
for (let i=0; i < map.length; i++) {
map[i] = new Array(height).fill('-');
}
this.map = map;
this.width = width;
this.height = height;
this.actors = [];
}
register(actor) {
this.actors.push(actor);
this.map[actor.x][actor.y] = actor.sign;
this.draw();
}
unregister(actor) {
const index = this.actors.indexOf(actor);
this.actors.splice(index, 1);
this.map[actor.x][actor.y] = '-';
this.draw();
}
changeActorPosition(actor, x, y) {
// 舊的移除
this.map[actor.x][actor.y] = '-';
// 新的補上
this.map[x][y] = actor.sign;
this.draw();
}
draw() {
let display = '';
for (let i = 0; i < this.map.length; i++) {
for (let j = 0; j < this.map[i].length; j++) {
display += this.map[j][i];
}
display += '\n';
}
console.clear();
console.log(display);
}
}
class Actor {
constructor(scene, x, y, sign) {
this.scene = scene;
this.x = x;
this.y = y;
this.sign = sign.substr(0, 1);
scene.register(this);
}
exit() {
this.scene.unregister(this);
}
moveTo(x, y) {
const { width, height } = this.scene
if (x < 0 || y < 0 || x >= width || y >= height) {
return
}
this.scene.changeActorPosition(this, x, y);
this.x = x;
this.y = y;
}
}
const s = new Scene(10, 10);
const a = new Actor(s, 5, 5, 'A');
const b = new Actor(s, 2, 6, 'B');
window.addEventListener('keyup', e => {
// 預設操控第一隻角色
const actor = s.actors[0];
switch (e.code) {
case 'ArrowUp':
actor.moveTo(actor.x, actor.y-1);
break;
case 'ArrowDown':
actor.moveTo(actor.x, actor.y+1);
break;
case 'ArrowLeft':
actor.moveTo(actor.x-1, actor.y);
break;
case 'ArrowRight':
actor.moveTo(actor.x+1, actor.y);
break;
default:
break;
}
});
雖然我自己覺得開發過程滿好玩的,不過當然這還是個半成品,所以 bug 如下:
如果要繼續完善它的遊戲性,可以往這幾個方向思考:
extends
,也就是子類別的用法,去擴充 Actor
,創造不同類型的角色(比如說每隔兩秒會瞬間移動的角色)其實今天這個這麼跳 tone 的範例,是大概在晚上八點多才想到的XD,所以很多思考跟實作都沒有最佳化。(歡迎留言建議拜託!!)
但如果要做為一個 OOP 的小練習,我想這個範例還是可以體會到如何以物件的角度來思考,並且設計物件之間的互動,如何影響屬性等。
決定寫這個單純就是一個好玩,畢竟自己會喜歡玩前端,就是因為有很多畫面可以看,我的每一次小改動,都會讓程式創造出不同有趣的畫面,這點讓我覺得很有動力繼續寫下去!
真實世界的每一步
都是電腦眼中的
0 與 1
這讓我想起我第一次學程式的時候,就是用 C 寫類似的小遊戲,好令人懷念啊~~
先快速來個撞牆不會報錯的 update XD
class Scene {
constructor(width, height) {
...
this.width = width
this.height = height
}
...
}
class Actor {
...
moveTo(x, y) {
const { width, height } = this.scene
if (x < 0 || y < 0 || x >= width || y >= height) {
return
}
this.scene.changeActorPosition(this, x, y);
this.x = x;
this.y = y;
}
}
TD 你真的是大家的小精靈欸~超貼心的!
我晚點再來更新上去!