跟 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
<<: [Day 18] 制作更多的Debug工具 (1) - 连接期错误
>>: 程序精炼唯熟练尔:高阶函式 预设参数 high-order function, default parameter
先前我们利用广度优先搜寻,找到图中两节点之间的最短路径,其中所谓「最短」是指「经过最少的边」。可是这...
如果画面太小或看不清楚,可移驾至 https://www.youtube.com/watch?v=...
系统上下文图 System Context Diagram (SCD) 是一种概念图的呈现,用於表达...
因私人因素欧吉桑有一段时间没发文了,不知道有没有人期待我的新文章呢? 今天,我想跟大家分享的主题是【...
<if> 条件控制 <if> 元素根据 test 属性中的评估值决定其下的元...