成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

JavaScript 編程精解 中文第三版 十六、項(xiàng)目:平臺(tái)游戲

wayneli / 1235人閱讀

摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版所有現(xiàn)實(shí)都是游戲。黑色的方塊表示玩家,玩家任務(wù)是收集黃色的方塊硬幣,同時(shí)避免碰到紅色素材巖漿。網(wǎng)格中的元素可能是空氣固體或巖漿。

來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目

原文:Project: A Platform Game

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

部分參考了《JavaScript 編程精解(第 2 版)》

所有現(xiàn)實(shí)都是游戲。

Iain Banks,《The Player of Games》

我最初對(duì)電腦的癡迷,就像許多小孩一樣,與電腦游戲有關(guān)。我沉迷在那個(gè)計(jì)算機(jī)所模擬出的小小世界中,我可以操縱這個(gè)世界,我同時(shí)也沉迷在那些尚未展開的故事之中。但我沉迷其中并不是因?yàn)橛螒驅(qū)嶋H描述的故事,而是因?yàn)槲铱梢猿浞职l(fā)揮我的想象力,去構(gòu)思故事的發(fā)展。

我并不希望任何人把編寫游戲作為自己的事業(yè)。就像音樂產(chǎn)業(yè)中,那些希望加入這個(gè)行業(yè)的熱忱年輕人與實(shí)際的人才需求之間存在巨大的鴻溝,也因此產(chǎn)生了一個(gè)極不健康的就業(yè)環(huán)境。不過,把編寫游戲作為樂趣還是相當(dāng)不錯(cuò)的。

本章將會(huì)介紹如何實(shí)現(xiàn)一個(gè)小型平臺(tái)游戲。平臺(tái)游戲(或者叫作“跳爬”游戲)要求玩家操縱一個(gè)角色在世界中移動(dòng),這種游戲往往是二維的,而且采用單一側(cè)面作為觀察視角,玩家可以來回跳躍。

游戲

我們游戲大致基于由 Thomas Palef 開發(fā)的 Dark Blue。我之所以選擇了這個(gè)游戲,是因?yàn)檫@個(gè)游戲既有趣又簡單,而且不需要編寫大量代碼。該游戲看起來如下頁圖所示。

黑色的方塊表示玩家,玩家任務(wù)是收集黃色的方塊(硬幣),同時(shí)避免碰到紅色素材(“巖漿”)。當(dāng)玩家收集完所有硬幣后就可以過關(guān)。

玩家可以使用左右方向鍵移動(dòng),并使用上方向鍵跳躍。跳躍正是這個(gè)游戲角色的特長。玩家可以跳躍到數(shù)倍于自己身高的地方,也可以在半空中改變方向。雖然這樣不切實(shí)際,但這有助于玩家感覺自己在直接控制屏幕上那個(gè)自己的化身。

該游戲包含一個(gè)固定的背景,使用網(wǎng)格方式進(jìn)行布局,可可移動(dòng)元素則覆蓋在背景之上。網(wǎng)格中的元素可能是空氣、固體或巖漿??煽梢苿?dòng)元素是玩家、硬幣或者某一塊巖漿。這些元素的位置不限于網(wǎng)格,它們的坐標(biāo)可以是分?jǐn)?shù),允許平滑運(yùn)動(dòng)。

實(shí)現(xiàn)技術(shù)

我們會(huì)使用瀏覽器的 DOM 來展示游戲界面,我們會(huì)通過處理按鍵事件來讀取用戶輸入。

與屏幕和鍵盤相關(guān)的代碼只是實(shí)現(xiàn)游戲代碼中的很小一部分。由于所有元素都只是彩色方塊,因此繪制方法并不復(fù)雜。我們?yōu)槊總€(gè)元素創(chuàng)建對(duì)應(yīng)的 DOM 元素,并使用樣式來為其指定背景顏色、尺寸和位置。

由于背景是由不會(huì)改變的方塊組成的網(wǎng)格,因此我們可以使用表格來展示背景。自由可移動(dòng)元素可以使用絕對(duì)定位元素來覆蓋。

游戲和某些程序應(yīng)該在不產(chǎn)生明顯延遲的情況下繪制動(dòng)畫并響應(yīng)用戶輸入,性能是非常重要的。盡管 DOM 最初并非為高性能繪圖而設(shè)計(jì),但實(shí)際上 DOM 的性能表現(xiàn)得比我們想象中要好得多。讀者已經(jīng)在第 13 章中看過一些動(dòng)畫,在現(xiàn)代機(jī)器中,即使我們不怎么考慮性能優(yōu)化,像這種簡單的游戲也可以流暢運(yùn)行。

在下一章中,我們會(huì)研究另一種瀏覽器技術(shù) —— 標(biāo)簽。該標(biāo)簽提供了一種更為傳統(tǒng)的圖像繪制方式,直接處理形狀和像素而非 DOM 元素。

關(guān)卡

我們需要一種人類可讀的、可編輯的方法來指定關(guān)卡。因?yàn)橐磺凶铋_始都可以在網(wǎng)格,所以我們可以使用大型字符串,其中每個(gè)字符代表一個(gè)元素,要么是背景網(wǎng)格的一部分,要么是可移動(dòng)元素。

小型關(guān)卡的平面圖可能是這樣的:

var simpleLevelPlan = `
......................
..#................#..
..#..............=.#..
..#.........o.o....#..
..#.@......#####...#..
..#####............#..
......#++++++++++++#..
......##############..
......................`;

句號(hào)是空的位置,井號(hào)(#)字符是墻,加號(hào)是巖漿。玩家的起始位置是 AT 符號(hào)(@)。每個(gè)O字符都是一枚硬幣,等號(hào)(=)是一塊來回水平移動(dòng)的巖漿塊。

我們支持兩種額外的可移動(dòng)巖漿:管道符號(hào)(|)表示垂直移動(dòng)的巖漿塊,而v表示下落的巖漿塊 —— 這種巖漿塊也是垂直移動(dòng),但不會(huì)來回彈跳,只會(huì)向下移動(dòng),直到遇到地面才會(huì)直接回到其起始位置。

整個(gè)游戲包含了許多關(guān)卡,玩家必須完成所有關(guān)卡。每關(guān)的過關(guān)條件是玩家需要收集所有硬幣。如果玩家碰到巖漿,當(dāng)前關(guān)卡會(huì)恢復(fù)初始狀態(tài),而玩家可以再次嘗試過關(guān)。

讀取關(guān)卡

下面的類存儲(chǔ)了關(guān)卡對(duì)象。它的參數(shù)應(yīng)該是定義關(guān)卡的字符串。

class Level {
  constructor(plan) {
    let rows = plan.trim().split("
").map(l => [...l]);
    this.height = rows.length;
    this.width = rows[0].length;
    this.startActors = [];
    this.rows = rows.map((row, y) => {
      return row.map((ch, x) => {
        let type = levelChars[ch];
        if (typeof type == "string") return type;
        this.startActors.push(
          type.create(new Vec(x, y), ch));
        return "empty";
      });
    });
  }
}

trim方法用于移除平面圖字符串起始和終止處的空白。這允許我們的示例平面圖以換行開始,以便所有行都在彼此的正下方。其余的字符串由換行符拆分,每一行擴(kuò)展到一個(gè)數(shù)組中,生成了字符數(shù)組。

因此,rows包含字符數(shù)組、平面圖的行。我們可以從中得出水平寬度和高度。但是我們?nèi)匀槐仨殞⒖梢苿?dòng)元素與背景網(wǎng)格分開。我們將其稱為角色(Actor)。它們將存儲(chǔ)在一個(gè)對(duì)象數(shù)組中。背景將是字符串的數(shù)組的數(shù)組,持有字段類型,如"empty","wall",或"lava"。

為了創(chuàng)建這些數(shù)組,我們在行上映射,然后在它們的內(nèi)容上進(jìn)行映射。請記住,map將數(shù)組索引作為第二個(gè)參數(shù)傳遞給映射函數(shù),它告訴我們給定字符的xy坐標(biāo)。游戲中的位置將存儲(chǔ)為一對(duì)坐標(biāo),左上角為0, 0,并且每個(gè)背景方塊為 1 單位高和寬。

為了解釋平面圖中的字符,Level構(gòu)造器使用levelChars對(duì)象,它將背景元素映射為字符串,角色字符映射為類。當(dāng)type是一個(gè)角色類時(shí),它的create靜態(tài)方法用于創(chuàng)建一個(gè)對(duì)象,該對(duì)象被添加到startActors,映射函數(shù)為這個(gè)背景方塊返回"empty"。

角色的位置存儲(chǔ)為一個(gè)Vec對(duì)象,它是二維向量,一個(gè)具有xy屬性的對(duì)象,像第六章一樣。

當(dāng)游戲運(yùn)行時(shí),角色將停在不同的地方,甚至完全消失(就像硬幣被收集時(shí))。我們將使用一個(gè)State類來跟蹤正在運(yùn)行的游戲的狀態(tài)。

class State {
  constructor(level, actors, status) {
    this.level = level;
    this.actors = actors;
    this.status = status;
  }

  static start(level) {
    return new State(level, level.startActors, "playing");
  }

  get player() {
    return this.actors.find(a => a.type == "player");
  }
}

當(dāng)游戲結(jié)束時(shí),status屬性將切換為"lost""won"。

這又是一個(gè)持久性數(shù)據(jù)結(jié)構(gòu),更新游戲狀態(tài)會(huì)創(chuàng)建新狀態(tài),并使舊狀態(tài)保持完整。

角色

角色對(duì)象表示,游戲中給定可移動(dòng)元素的當(dāng)前位置和狀態(tài)。所有的角色對(duì)象都遵循相同的接口。它們的pos屬性保存元素的左上角坐標(biāo),它們的size屬性保存其大小。

然后,他們有update方法,用于計(jì)算給定時(shí)間步長之后,他們的新狀態(tài)和位置。它模擬了角色所做的事情:響應(yīng)箭頭鍵并且移動(dòng),因巖漿而來回彈跳,并返回新的更新后的角色對(duì)象。

type屬性包含一個(gè)字符串,該字符串指定了角色類型:"player""coin"或者"lava"。這在繪制游戲時(shí)是有用的,為角色繪制的矩形的外觀基于其類型。

角色類有一個(gè)靜態(tài)的create方法,它由Level構(gòu)造器使用,用于從關(guān)卡平面圖中的字符中,創(chuàng)建一個(gè)角色。它接受字符本身及其坐標(biāo),這是必需的,因?yàn)?b>Lava類處理幾個(gè)不同的字符。

這是我們將用于二維值的Vec類,例如角色的位置和大小。

class Vec {
  constructor(x, y) {
    this.x = x; this.y = y;
  }
  plus(other) {
    return new Vec(this.x + other.x, this.y + other.y);
  }
  times(factor) {
    return new Vec(this.x * factor, this.y * factor);
  }
}

times方法用給定的數(shù)字來縮放向量。當(dāng)我們需要將速度向量乘時(shí)間間隔,來獲得那個(gè)時(shí)間的行走距離時(shí),這就有用了。

不同類型的角色擁有他們自己的類,因?yàn)樗麄兊男袨榉浅2煌?。讓我們定義這些類。稍后我們將看看他們的update方法。

玩家類擁有speed屬性,存儲(chǔ)了當(dāng)前速度,來模擬動(dòng)量和重力。

class Player {
  constructor(pos, speed) {
    this.pos = pos;
    this.speed = speed;
  }

  get type() { return "player"; }

  static create(pos) {
    return new Player(pos.plus(new Vec(0, -0.5)),
                      new Vec(0, 0));
  }
}

Player.prototype.size = new Vec(0.8, 1.5);

因?yàn)橥婕腋叨仁且粋€(gè)半格子,因此其初始位置相比于@字符出現(xiàn)的位置要高出半個(gè)格子。這樣一來,玩家角色的底部就可以和其出現(xiàn)的方格底部對(duì)齊。

size屬性對(duì)于Player的所有實(shí)例都是相同的,因此我們將其存儲(chǔ)在原型上,而不是實(shí)例本身。我們可以使用一個(gè)類似type的讀取器,但是每次讀取屬性時(shí),都會(huì)創(chuàng)建并返回一個(gè)新的Vec對(duì)象,這將是浪費(fèi)的。(字符串是不可變的,不必在每次求值時(shí)重新創(chuàng)建。)

構(gòu)造Lava角色時(shí),我們需要根據(jù)它所基于的字符來初始化對(duì)象。動(dòng)態(tài)巖漿以其當(dāng)前速度移動(dòng),直到它碰到障礙物。這個(gè)時(shí)候,如果它擁有reset屬性,它會(huì)跳回到它的起始位置(滴落)。如果沒有,它會(huì)反轉(zhuǎn)它的速度并以另一個(gè)方向繼續(xù)(彈跳)。

create方法查看Level構(gòu)造器傳遞的字符,并創(chuàng)建適當(dāng)?shù)膸r漿角色。

class Lava {
  constructor(pos, speed, reset) {
    this.pos = pos;
    this.speed = speed;
    this.reset = reset;
  }

  get type() { return "lava"; }

  static create(pos, ch) {
    if (ch == "=") {
      return new Lava(pos, new Vec(2, 0));
    } else if (ch == "|") {
      return new Lava(pos, new Vec(0, 2));
    } else if (ch == "v") {
      return new Lava(pos, new Vec(0, 3), pos);
    }
  }
}

Lava.prototype.size = new Vec(1, 1);

Coin對(duì)象相對(duì)簡單,大多時(shí)候只需要待在原地即可。但為了使游戲更加有趣,我們讓硬幣輕微搖晃,也就是會(huì)在垂直方向上小幅度來回移動(dòng)。每個(gè)硬幣對(duì)象都存儲(chǔ)了其基本位置,同時(shí)使用wobble屬性跟蹤圖像跳動(dòng)幅度。這兩個(gè)屬性同時(shí)決定了硬幣的實(shí)際位置(存儲(chǔ)在pos屬性中)。

class Coin {
  constructor(pos, basePos, wobble) {
    this.pos = pos;
    this.basePos = basePos;
    this.wobble = wobble;
  }

  get type() { return "coin"; }

  static create(pos) {
    let basePos = pos.plus(new Vec(0.2, 0.1));
    return new Coin(basePos, basePos,
                    Math.random() * Math.PI * 2);
  }
}

Coin.prototype.size = new Vec(0.6, 0.6);

第十四章中,我們知道了Math.sin可以計(jì)算出圓的y坐標(biāo)。因?yàn)槲覀冄刂鴪A移動(dòng),因此y坐標(biāo)會(huì)以平滑的波浪形式來回移動(dòng),正弦函數(shù)在實(shí)現(xiàn)波浪形移動(dòng)中非常實(shí)用。

為了避免出現(xiàn)所有硬幣同時(shí)上下移動(dòng),每個(gè)硬幣的初始階段都是隨機(jī)的。由Math.sin產(chǎn)生的波長是。我們可以將Math.random的返回值乘以,計(jì)算出硬幣波形軌跡的初始位置。

現(xiàn)在我們可以定義levelChars對(duì)象,它將平面圖字符映射為背景網(wǎng)格類型,或角色類。

const levelChars = {
  ".": "empty", "#": "wall", "+": "lava",
  "@": Player, "o": Coin,
  "=": Lava, "|": Lava, "v": Lava
};

這給了我們創(chuàng)建Level實(shí)例所需的所有部件。

let simpleLevel = new Level(simpleLevelPlan);
console.log(`${simpleLevel.width} by ${simpleLevel.height}`);
// → 22 by 9

上面一段代碼的任務(wù)是將特定關(guān)卡顯示在屏幕上,并構(gòu)建關(guān)卡中的時(shí)間與動(dòng)作。

成為負(fù)擔(dān)的封裝

本章中大多數(shù)代碼并沒有過多考慮封裝。首先,封裝需要耗費(fèi)額外精力。封裝使得程序變得更加龐大,而且會(huì)引入額外的概念和接口。我盡量將程序的體積控制在較小的范圍之內(nèi),避免讀者因?yàn)榇a過于龐大而走神。

其次,游戲中的大量元素是緊密耦合在一起的,如果其中一個(gè)元素行為改變,其他的元素很有可能也會(huì)發(fā)生變化。我們需要根據(jù)游戲的工作細(xì)節(jié)來為元素之間設(shè)計(jì)大量接口。這使得接口的效果不是很好。每當(dāng)你改變系統(tǒng)中的某一部分時(shí),由于其他部分的接口可能沒有考慮到新的情況,因此你需要關(guān)心這一修改是否會(huì)影響到其他部分的代碼。

系統(tǒng)中的某些分割點(diǎn)可以通過嚴(yán)格的接口對(duì)系統(tǒng)進(jìn)行合理的劃分,但某些分割點(diǎn)則不是如此。嘗試去封裝某些本沒有合理邊界的代碼必然會(huì)導(dǎo)致浪費(fèi)大量精力。當(dāng)你犯下這種大錯(cuò)之際,你就會(huì)注意到你的接口變得龐大臃腫,而且隨著程序不斷演化,你需要頻繁修改這些接口。

我們會(huì)封裝的一部分代碼是繪圖子系統(tǒng)。其原因是我們會(huì)在下一章中使用另一種方式來展示相同的游戲。通過將繪圖代碼隱藏在接口之后,我們可以在下一章中使用相同的游戲程序,只需要插入新的顯示模塊即可。

繪圖

我們通過定義一個(gè)“顯示器”對(duì)象來封裝繪圖代碼,該對(duì)象顯示指定關(guān)卡,以及狀態(tài)。本章定義的顯示器類型名為DOMDisplay,因?yàn)樵擃愋褪褂煤唵蔚?DOM 元素來顯示關(guān)卡。

我們會(huì)使用樣式表來設(shè)定實(shí)際的顏色以及其他構(gòu)建游戲中所需的固定的屬性。創(chuàng)建這些屬性時(shí),我們可以直接對(duì)元素的style屬性進(jìn)行賦值,但這會(huì)使得游戲代碼變得冗長。

下面的幫助函數(shù)提供了一種簡潔的方法,來創(chuàng)建元素并賦予它一些屬性和子節(jié)點(diǎn):

function elt(name, attrs, ...children) {
  let dom = document.createElement(name);
  for (let attr of Object.keys(attrs)) {
    dom.setAttribute(attr, attrs[attr]);
  }
  for (let child of children) {
    dom.appendChild(child);
  }
  return dom;
}

我們創(chuàng)建顯示器對(duì)象時(shí)需要指定其父元素,顯示器將會(huì)創(chuàng)建在該父元素上,同時(shí)還需指定一個(gè)關(guān)卡對(duì)象。

class DOMDisplay {
  constructor(parent, level) {
    this.dom = elt("div", {class: "game"}, drawGrid(level));
    this.actorLayer = null;
    parent.appendChild(this.dom);
  }

  clear() { this.dom.remove(); }
}

由于關(guān)卡的背景網(wǎng)格不會(huì)改變,因此只需要繪制一次即可。角色則需要在每次刷新顯示時(shí)進(jìn)行重繪。drawFame需要使用actorLayer屬性來跟蹤已保存角色的動(dòng)作,因此我們可以輕松移除或替換這些角色。

我們的坐標(biāo)和尺寸以網(wǎng)格單元為單位跟蹤,也就是說尺寸或距離中的 1 單元表示一個(gè)單元格。在設(shè)置像素級(jí)尺寸時(shí),我們需要將坐標(biāo)按比例放大,如果游戲中的所有元素只占據(jù)一個(gè)方格中的一個(gè)像素,那將是多么可笑。而scale綁定會(huì)給出一個(gè)單元格在屏幕上實(shí)際占據(jù)的像素?cái)?shù)目。

const scale = 20;

function drawGrid(level) {
  return elt("table", {
    class: "background",
    style: `width: ${level.width * scale}px`
  }, ...level.rows.map(row =>
    elt("tr", {style: `height: ${scale}px`},
        ...row.map(type => elt("td", {class: type})))
  ));
}

前文提及過,我們使用

元素來繪制背景。這非常符合關(guān)卡中grid屬性的結(jié)構(gòu)。網(wǎng)格中的每一行對(duì)應(yīng)表格中的一行(元素)。網(wǎng)格中的每個(gè)字符串對(duì)應(yīng)表格單元格(
  • )元素的類型名。擴(kuò)展(三點(diǎn))運(yùn)算符用于將子節(jié)點(diǎn)數(shù)組作為多帶帶的參數(shù)傳給elt。

    下面的 CSS 使表格看起來像我們想要的背景:

    .background    { background: rgb(52, 166, 251);
                     table-layout: fixed;
                     border-spacing: 0;              }
    .background td { padding: 0;                     }
    .lava          { background: rgb(255, 100, 100); }
    .wall          { background: white;              }

    其中某些屬性(border-spacing和padding)用于取消一些我們不想保留的表格默認(rèn)行為。我們不希望在單元格之間或單元格內(nèi)部填充多余的空白。

    其中background規(guī)則用于設(shè)置背景顏色。CSS中可以使用兩種方式來指定顏色,一種方法是使用單詞(white),另一種方法是使用形如rgb(R,G,B)的格式,其中R表示顏色中的紅色成分,G表示綠色成分,B表示藍(lán)色成分,每個(gè)數(shù)字范圍均為 0 到 255。因此在rgb(52,166,251)中,紅色成分為 52,綠色為 166,而藍(lán)色是 251。由于藍(lán)色成分?jǐn)?shù)值最大,因此最后的顏色會(huì)偏向藍(lán)色。而你可以看到.lava規(guī)則中,第一個(gè)數(shù)字(紅色)是最大的。

    我們繪制每個(gè)角色時(shí)需要?jiǎng)?chuàng)建其對(duì)應(yīng)的 DOM 元素,并根據(jù)角色屬性來設(shè)置元素坐標(biāo)與尺寸。這些值都需要與scale相乘,以將游戲中的尺寸單位轉(zhuǎn)換為像素。

    function drawActors(actors) {
      return elt("div", {}, ...actors.map(actor => {
        let rect = elt("div", {class: `actor ${actor.type}`});
        rect.style.width = `${actor.size.x * scale}px`;
        rect.style.height = `${actor.size.y * scale}px`;
        rect.style.left = `${actor.pos.x * scale}px`;
        rect.style.top = `${actor.pos.y * scale}px`;
        return rect;
      }));
    }

    為了賦予一個(gè)元素多個(gè)類別,我們使用空格來分隔類名。在下面展示的 CSS 代碼中,actor類會(huì)賦予角色一個(gè)絕對(duì)坐標(biāo)。我們將角色的類型名稱作為額外的 CSS 類來設(shè)置這些元素的顏色。我們并沒有再次定義lava類,因?yàn)槲覀兛梢灾苯訌?fù)用前文為巖漿單元格定義的規(guī)則。

    .actor  { position: absolute;            }
    .coin   { background: rgb(241, 229, 89); }
    .player { background: rgb(64, 64, 64);   }

    setState方法用于使顯示器顯示給定的狀態(tài)。它首先刪除舊角色的圖形,如果有的話,然后在他們的新位置上重新繪制角色。試圖將 DOM 元素重用于角色,可能很吸引人,但是為了使它有效,我們需要大量的附加記錄,來關(guān)聯(lián)角色和 DOM 元素,并確保在角色消失時(shí)刪除元素。因?yàn)橛螒蛑型ǔV挥猩贁?shù)角色,重新繪制它們開銷并不大。

    DOMDisplay.prototype.setState = function(state) {
      if (this.actorLayer) this.actorLayer.remove();
      this.actorLayer = drawActors(state.actors);
      this.dom.appendChild(this.actorLayer);
      this.dom.className = `game ${state.status}`;
      this.scrollPlayerIntoView(state);
    };

    我們可以將關(guān)卡的當(dāng)前狀態(tài)作為類名添加到包裝器中,這樣可以根據(jù)游戲勝負(fù)與否來改變玩家角色的樣式。我們只需要添加 CSS 規(guī)則,指定祖先節(jié)點(diǎn)包含特定類的player元素的樣式即可。

    .lost .player {
      background: rgb(160, 64, 64);
    }
    .won .player {
      box-shadow: -4px -7px 8px white, 4px -7px 8px white;
    }

    在遇到巖漿之后,玩家的顏色應(yīng)該變成深紅色,暗示著角色被燒焦了。當(dāng)玩家收集完最后一枚硬幣時(shí),我們添加兩個(gè)模糊的白色陰影來創(chuàng)建白色的光環(huán)效果,其中一個(gè)在左上角,一個(gè)在右上角。

    我們無法假定關(guān)卡總是符合視口尺寸,它是我們在其中繪制游戲的元素。所以我們需要調(diào)用scrollPlayerIntoView來確保如果關(guān)卡在視口范圍之外,我們可以滾動(dòng)視口,確保玩家靠近視口的中央位置。下面的 CSS 樣式為包裝器的DOM元素設(shè)置了一個(gè)最大尺寸,以確保任何超出視口的元素都是不可見的。我們可以將外部元素的position設(shè)置為relative,因此該元素中的角色總是相對(duì)于關(guān)卡的左上角進(jìn)行定位。

    .game {
      overflow: hidden;
      max-width: 600px;
      max-height: 450px;
      position: relative;
    }

    scrollPlayerIntoView方法中,我們找出玩家的位置并更新其包裝器元素的滾動(dòng)坐標(biāo)。我們可以通過操作元素的scrollLeftscrollTop屬性,當(dāng)玩家接近視口邊界時(shí)修改滾動(dòng)坐標(biāo)。

    DOMDisplay.prototype.scrollPlayerIntoView = function(state) {
      let width = this.dom.clientWidth;
      let height = this.dom.clientHeight;
      let margin = width / 3;
    
      // The viewport
      let left = this.dom.scrollLeft, right = left + width;
      let top = this.dom.scrollTop, bottom = top + height;
    
      let player = state.player;
      let center = player.pos.plus(player.size.times(0.5))
                             .times(scale);
    
      if (center.x < left + margin) {
        this.dom.scrollLeft = center.x - margin;
      } else if (center.x > right - margin) {
        this.dom.scrollLeft = center.x + margin - width;
      }
      if (center.y < top + margin) {
        this.dom.scrollTop = center.y - margin;
      } else if (center.y > bottom - margin) {
        this.dom.scrollTop = center.y + margin - height;
      }
    };

    找出玩家中心位置的代碼展示了,我們?nèi)绾问褂?b>Vec類型來寫出相對(duì)可讀的計(jì)算代碼。為了找出玩家的中心位置,我們需要將左上角位置坐標(biāo)加上其尺寸的一半。計(jì)算結(jié)果就是關(guān)卡坐標(biāo)的中心位置。但是我們需要將結(jié)果向量乘以顯示比例,以將坐標(biāo)轉(zhuǎn)換成像素級(jí)坐標(biāo)。

    接下來,我們對(duì)玩家的坐標(biāo)進(jìn)行一系列檢測,確保其位置不會(huì)超出合法范圍。這里需要注意的是這段代碼有時(shí)候依然會(huì)設(shè)置無意義的滾動(dòng)坐標(biāo),比如小于 0 的值或超出元素滾動(dòng)區(qū)域的值。這是沒問題的。DOM 會(huì)將其修改為可接受的值。如果我們將scrollLeft設(shè)置為–10,DOM 會(huì)將其修改為 0。

    最簡單的做法是每次重繪時(shí)都滾動(dòng)視口,確保玩家總是在視口中央。但這種做法會(huì)導(dǎo)致畫面劇烈晃動(dòng),當(dāng)你跳躍時(shí),視圖會(huì)不斷上下移動(dòng)。比較合理的做法是在屏幕中央設(shè)置一個(gè)“中央?yún)^(qū)域”,玩家在這個(gè)區(qū)域內(nèi)部移動(dòng)時(shí)我們不會(huì)滾動(dòng)視口。

    我們現(xiàn)在能夠顯示小型關(guān)卡。

    
    
    

    我們可以在link標(biāo)簽中使用rel="stylesheet",將一個(gè) CSS 文件加載到頁面中。文件game.css包含了我們的游戲所需的樣式。

    動(dòng)作與沖突

    現(xiàn)在我們是時(shí)候來添加一些動(dòng)作了。這是游戲中最令人著迷的一部分。實(shí)現(xiàn)動(dòng)作的最基本的方案(也是大多數(shù)游戲采用的)是將時(shí)間劃分為一個(gè)個(gè)時(shí)間段,根據(jù)角色的每一步速度和時(shí)間長度,將元素移動(dòng)一段距離。我們將以秒為單位測量時(shí)間,所以速度以單元每秒來表示。

    移動(dòng)?xùn)|西非常簡單。比較困難的一部分是處理元素之間的相互作用。當(dāng)玩家撞到墻壁或者地板時(shí),不可能簡單地直接穿越過去。游戲必須注意特定的動(dòng)作會(huì)導(dǎo)致兩個(gè)對(duì)象產(chǎn)生碰撞,并需要采取相應(yīng)措施。如果玩家遇到墻壁,則必須停下來,如果遇到硬幣則必須將其收集起來。

    想要解決通常情況下的碰撞問題是件艱巨任務(wù)。你可以找到一些我們稱之為物理引擎的庫,這些庫會(huì)在二維或三維空間中模擬物理對(duì)象的相互作用。我們在本章中采用更合適的方案:只處理矩形物體之間的碰撞,并采用最簡單的方案進(jìn)行處理。

    在移動(dòng)角色或巖漿塊時(shí),我們需要測試元素是否會(huì)移動(dòng)到墻里面。如果會(huì)的話,我們只要取消整個(gè)動(dòng)作即可。而對(duì)動(dòng)作的反應(yīng)則取決于移動(dòng)元素類型。如果是玩家則停下來,如果是巖漿塊則反彈回去。

    這種方法需要保證每一步之間的時(shí)間間隔足夠短,確保能夠在對(duì)象實(shí)際碰撞之前取消動(dòng)作。如果時(shí)間間隔太大,玩家最后會(huì)懸浮在離地面很高的地方。另一種方法明顯更好但更加復(fù)雜,即尋找到精確的碰撞點(diǎn)并將元素移動(dòng)到那個(gè)位置。我們會(huì)采取最簡單的方案,并確保減少動(dòng)畫之間的時(shí)間間隔,以掩蓋其問題。

    該方法用于判斷某個(gè)矩形(通過位置與尺寸限定)是否會(huì)碰到給定類型的網(wǎng)格。

    Level.prototype.touches = function(pos, size, type) {
      var xStart = Math.floor(pos.x);
      var xEnd = Math.ceil(pos.x + size.x);
      var yStart = Math.floor(pos.y);
      var yEnd = Math.ceil(pos.y + size.y);
    
      for (var y = yStart; y < yEnd; y++) {
        for (var x = xStart; x < xEnd; x++) {
          let isOutside = x < 0 || x >= this.width ||
                          y < 0 || y >= this.height;
          let here = isOutside ? "wall" : this.rows[y][x];
          if (here == type) return true;
        }
      }
      return false;
    };

    該方法通過對(duì)坐標(biāo)使用Math.floorMath.ceil,來計(jì)算與身體重疊的網(wǎng)格方塊集合。記住網(wǎng)格方塊的大小是1x1個(gè)單位。通過將盒子的邊上下顛倒,我們得到盒子接觸的背景方塊的范圍。

    我們通過查找坐標(biāo)遍歷網(wǎng)格方塊,并在找到匹配的方塊時(shí)返回true。關(guān)卡之外的方塊總是被當(dāng)作"wall",來確保玩家不能離開這個(gè)世界,并且我們不會(huì)意外地嘗試,在我們的“rows數(shù)組的邊界之外讀取。

    狀態(tài)的update方法使用touches來判斷玩家是否接觸巖漿。

    State.prototype.update = function(time, keys) {
      let actors = this.actors
        .map(actor => actor.update(time, this, keys));
      let newState = new State(this.level, actors, this.status);
      if (newState.status != "playing") return newState;
      let player = newState.player;
      if (this.level.touches(player.pos, player.size, "lava")) {
        return new State(this.level, actors, "lost");
      }
      for (let actor of actors) {
        if (actor != player && overlap(actor, player)) {
          newState = actor.collide(newState);
        }
      }
      return newState;
    };

    它接受時(shí)間步長和一個(gè)數(shù)據(jù)結(jié)構(gòu),告訴它按下了哪些鍵。它所做的第一件事是調(diào)用所有角色的update方法,生成一組更新后的角色。角色也得到時(shí)間步長,按鍵,和狀態(tài),以便他們可以根據(jù)這些來更新。只有玩家才會(huì)讀取按鍵,因?yàn)檫@是唯一由鍵盤控制的角色。

    如果游戲已經(jīng)結(jié)束,就不需要再做任何處理(游戲不能在輸之后贏,反之亦然)。否則,該方法測試玩家是否接觸背景巖漿。如果是這樣的話,游戲就輸了,我們就完了。最后,如果游戲?qū)嶋H上還在繼續(xù),它會(huì)查看其他玩家是否與玩家重疊。

    overlap函數(shù)檢測角色之間的重疊。它需要兩個(gè)角色對(duì)象,當(dāng)它們觸碰時(shí)返回true,當(dāng)它們沿X軸和Y軸重疊時(shí),就是這種情況。

    function overlap(actor1, actor2) {
      return actor1.pos.x + actor1.size.x > actor2.pos.x &&
             actor1.pos.x < actor2.pos.x + actor2.size.x &&
             actor1.pos.y + actor1.size.y > actor2.pos.y &&
             actor1.pos.y < actor2.pos.y + actor2.size.y;
    }

    如果任何角色重疊了,它的collide方法有機(jī)會(huì)更新狀態(tài)。觸碰巖漿角色將游戲狀態(tài)設(shè)置為"lost",當(dāng)你碰到硬幣時(shí),硬幣就會(huì)消失,當(dāng)這是最后一枚硬幣時(shí),狀態(tài)就變成了"won"。

    Lava.prototype.collide = function(state) {
      return new State(state.level, state.actors, "lost");
    };
    
    Coin.prototype.collide = function(state) {
      let filtered = state.actors.filter(a => a != this);
      let status = state.status;
      if (!filtered.some(a => a.type == "coin")) status = "won";
      return new State(state.level, filtered, status);
    };
    角色的更新

    角色對(duì)象的update方法接受時(shí)間步長、狀態(tài)對(duì)象和keys對(duì)象作為參數(shù)。Lava角色類型忽略keys對(duì)象。

    Lava.prototype.update = function(time, state) {
      let newPos = this.pos.plus(this.speed.times(time));
      if (!state.level.touches(newPos, this.size, "wall")) {
        return new Lava(newPos, this.speed, this.reset);
      } else if (this.reset) {
        return new Lava(this.reset, this.speed, this.reset);
      } else {
        return new Lava(this.pos, this.speed.times(-1));
      }
    };

    它通過將時(shí)間步長乘上當(dāng)前速度,并將其加到其舊位置,來計(jì)算新的位置。如果新的位置上沒有障礙,它移動(dòng)到那里。如果有障礙物,其行為取決于巖漿塊的類型:滴落巖漿具有reset位置,當(dāng)它碰到某物時(shí),它會(huì)跳回去。跳躍巖漿將其速度乘以-1,從而開始向相反的方向移動(dòng)。

    硬幣使用它們的act方法來晃動(dòng)。他們忽略了網(wǎng)格的碰撞,因?yàn)樗鼈冎皇窃谒鼈冏约旱姆綁K內(nèi)部晃動(dòng)。

    const wobbleSpeed = 8, wobbleDist = 0.07;
    
    Coin.prototype.update = function(time) {
      let wobble = this.wobble + time * wobbleSpeed;
      let wobblePos = Math.sin(wobble) * wobbleDist;
      return new Coin(this.basePos.plus(new Vec(0, wobblePos)),
                      this.basePos, wobble);
    };

    遞增wobble屬性來跟蹤時(shí)間,然后用作Math.sin的參數(shù),來找到波上的新位置。然后,根據(jù)其基本位置和基于波的偏移,計(jì)算硬幣的當(dāng)前位置。

    還剩下玩家本身。玩家的運(yùn)動(dòng)對(duì)于每和軸多帶帶處理,因?yàn)榕龅降匕宀粦?yīng)阻止水平運(yùn)動(dòng),碰到墻壁不應(yīng)停止下降或跳躍運(yùn)動(dòng)。

    const playerXSpeed = 7;
    const gravity = 30;
    const jumpSpeed = 17;
    
    Player.prototype.update = function(time, state, keys) {
      let xSpeed = 0;
      if (keys.ArrowLeft) xSpeed -= playerXSpeed;
      if (keys.ArrowRight) xSpeed += playerXSpeed;
      let pos = this.pos;
      let movedX = pos.plus(new Vec(xSpeed * time, 0));
      if (!state.level.touches(movedX, this.size, "wall")) {
        pos = movedX;
      }
    
      let ySpeed = this.speed.y + time * gravity;
      let movedY = pos.plus(new Vec(0, ySpeed * time));
      if (!state.level.touches(movedY, this.size, "wall")) {
        pos = movedY;
      } else if (keys.ArrowUp && ySpeed > 0) {
        ySpeed = -jumpSpeed;
      } else {
        ySpeed = 0;
       }
      return new Player(pos, new Vec(xSpeed, ySpeed));
    };

    水平運(yùn)動(dòng)根據(jù)左右箭頭鍵的狀態(tài)計(jì)算。當(dāng)沒有墻壁阻擋由這個(gè)運(yùn)動(dòng)產(chǎn)生的新位置時(shí),就使用它。否則,保留舊位置。

    垂直運(yùn)動(dòng)的原理類似,但必須模擬跳躍和重力。玩家的垂直速度(ySpeed)首先考慮重力而加速。

    我們再次檢查墻壁。如果我們不碰到任何一個(gè),使用新的位置。如果存在一面墻,就有兩種可能的結(jié)果。當(dāng)按下向上的箭頭,并且我們向下移動(dòng)時(shí)(意味著我們碰到的東西在我們下面),將速度設(shè)置成一個(gè)相對(duì)大的負(fù)值。這導(dǎo)致玩家跳躍。否則,玩家只是撞到某物上,速度就被設(shè)定為零。

    重力、跳躍速度和幾乎所有其他常數(shù),在游戲中都是通過反復(fù)試驗(yàn)來設(shè)定的。我測試了值,直到我找到了我喜歡的組合。

    跟蹤按鍵

    對(duì)于這樣的游戲,我們不希望按鍵在每次按下時(shí)生效。相反,我們希望只要按下了它們,他們的效果(移動(dòng)球員的數(shù)字)就一直有效。

    我們需要設(shè)置一個(gè)鍵盤處理器來存儲(chǔ)左、右、上鍵的當(dāng)前狀態(tài)。我們調(diào)用preventDefault,防止按鍵產(chǎn)生頁面滾動(dòng)。

    下面的函數(shù)接受一個(gè)按鍵名稱數(shù)組,返回跟蹤這些按鍵的當(dāng)前位置的對(duì)象。并注冊"keydown""keyup"事件,當(dāng)事件對(duì)應(yīng)的按鍵代碼存在于其存儲(chǔ)的按鍵代碼集合中時(shí),就更新對(duì)象。

    function trackKeys(keys) {
      let down = Object.create(null);
      function track(event) {
        if (keys.includes(event.key)) {
          down[event.key] = event.type == "keydown";
          event.preventDefault();
        }
      }
      window.addEventListener("keydown", track);
      window.addEventListener("keyup", track);
      return down;
    }
    
    const arrowKeys =
      trackKeys(["ArrowLeft", "ArrowRight", "ArrowUp"]);

    兩種事件類型都使用相同的處理程序函數(shù)。該處理函數(shù)根據(jù)事件對(duì)象的type屬性來確定是將按鍵狀態(tài)修改為true(“keydown”)還是false(“keyup”)。

    運(yùn)行游戲

    我們在第十四章中看到的requestAnimationFrames函數(shù)是一種產(chǎn)生游戲動(dòng)畫的好方法。但該函數(shù)的接口有點(diǎn)過于原始。該函數(shù)要求我們跟蹤上次調(diào)用函數(shù)的時(shí)間,并在每一幀后再次調(diào)用requestAnimationFrame方法。

    我們這里定義一個(gè)輔助函數(shù)來將這部分煩人的代碼包裝到一個(gè)名為runAnimation的簡單接口中,我們只需向其傳遞一個(gè)函數(shù)即可,該函數(shù)的參數(shù)是一個(gè)時(shí)間間隔,并用于繪制一幀圖像。當(dāng)幀函數(shù)返回false時(shí),整個(gè)動(dòng)畫停止。

    function runAnimation(frameFunc) {
      let lastTime = null;
      function frame(time) {
        let stop = false;
        if (lastTime != null) {
          let timeStep = Math.min(time - lastTime, 100) / 1000;
          if (frameFunc(timeStep) === false) return;
        }
        lastTime = time;
        requestAnimationFrame(frame);
      }
      requestAnimationFrame(frame);
    }

    我們將每幀之間的最大時(shí)間間隔設(shè)置為 100 毫秒(十分之一秒)。當(dāng)瀏覽器標(biāo)簽頁或窗口隱藏時(shí),requestAnimationFrame調(diào)用會(huì)自動(dòng)暫停,并在標(biāo)簽頁或窗口再次顯示時(shí)重新開始繪制動(dòng)畫。在本例中,lastTimetime之差是隱藏頁面的整個(gè)時(shí)間。一步一步地推進(jìn)游戲看起來很傻,可能會(huì)造成奇怪的副作用,比如玩家從地板上掉下去。

    該函數(shù)也會(huì)將時(shí)間單位轉(zhuǎn)換成秒,相比于毫秒大家會(huì)更熟悉秒。

    runLevel函數(shù)的接受Level對(duì)象和顯示對(duì)象的構(gòu)造器,并返回一個(gè)Promise。runLevel函數(shù)(在document.body中)顯示關(guān)卡,并使得用戶通過該節(jié)點(diǎn)操作游戲。當(dāng)關(guān)卡結(jié)束時(shí)(或勝或負(fù)),runLevel會(huì)多等一秒(讓用戶看看發(fā)生了什么),清除關(guān)卡,并停止動(dòng)畫,如果我們指定了andThen函數(shù),則runLevel會(huì)以關(guān)卡狀態(tài)為參數(shù)調(diào)用該函數(shù)。

    function runLevel(level, Display) {
      let display = new Display(document.body, level);
      let state = State.start(level);
      let ending = 1;
      return new Promise(resolve => {
        runAnimation(time => {
          state = state.update(time, arrowKeys);
          display.setState(state);
          if (state.status == "playing") {
            return true;
          } else if (ending > 0) {
            ending -= time;
            return true;
          } else {
            display.clear();
            resolve(state.status);
            return false;
          }
        });
      });
    }

    一個(gè)游戲是一個(gè)關(guān)卡序列。每當(dāng)玩家死亡時(shí)就重新開始當(dāng)前關(guān)卡。當(dāng)完成關(guān)卡后,我們切換到下一關(guān)。我們可以使用下面的函數(shù)來完成該任務(wù),該函數(shù)的參數(shù)為一個(gè)關(guān)卡平面圖(字符串)數(shù)組和顯示對(duì)象的構(gòu)造器。

    async function runGame(plans, Display) {
      for (let level = 0; level < plans.length;) {
        let status = await runLevel(new Level(plans[level]),
                                    Display);
        if (status == "won") level++;
      }
      console.log("You"ve won!");
    }

    因?yàn)槲覀兪?b>runLevel返回Promise,runGame可以使用async函數(shù)編寫,如第十一章中所見。它返回另一個(gè)Promise,當(dāng)玩家完成游戲時(shí)得到解析。

    在本章的沙盒的GAME_LEVELS綁定中,有一組可用的關(guān)卡平面圖。這個(gè)頁面將它們提供給runGame,啟動(dòng)實(shí)際的游戲:

    
    
    
      
    
    習(xí)題 游戲結(jié)束

    按照慣例,平臺(tái)游戲中玩家一開始會(huì)有有限數(shù)量的生命,每死亡一次就扣去一條生命。當(dāng)玩家生命耗盡時(shí),游戲就從頭開始了。

    調(diào)整runGame來實(shí)現(xiàn)生命機(jī)制。玩家一開始會(huì)有 3 條生命。每次啟動(dòng)時(shí)輸出當(dāng)前生命數(shù)量(使用console.log)。

    
    
    
    
    
    暫停游戲

    現(xiàn)在實(shí)現(xiàn)一個(gè)功能 —— 當(dāng)用戶按下 ESC 鍵時(shí)可以暫?;蚶^續(xù)游戲。

    我們可以修改runLevel函數(shù),使用另一個(gè)鍵盤事件處理器來實(shí)現(xiàn)在玩家按下 ESC 鍵的時(shí)候中斷或恢復(fù)動(dòng)畫。

    乍看起來,runAnimation無法完成該任務(wù),但如果我們使用runLevel來重新安排調(diào)度策略,也是可以實(shí)現(xiàn)的。

    當(dāng)你完成該功能后,可以嘗試加入另一個(gè)功能。我們現(xiàn)在注冊鍵盤事件處理器的方法多少有點(diǎn)問題?,F(xiàn)在arrows對(duì)象是一個(gè)全局綁定,即使游戲沒有運(yùn)行時(shí),事件處理器也是有效的。我們稱之為系統(tǒng)泄露。請擴(kuò)展tracKeys,提供一種方法來注銷事件處理器,接著修改runLevel在啟動(dòng)游戲時(shí)注冊事件處理器,并在游戲結(jié)束后注銷事件處理器。

    
    
    
    
    
    怪物

    它是傳統(tǒng)的平臺(tái)游戲,里面有敵人,你可以跳到它頂上來打敗它。這個(gè)練習(xí)要求你把這種角色類型添加到游戲中。

    我們稱之為怪物。怪物只能水平移動(dòng)。你可以讓它們朝著玩家的方向移動(dòng),或者像水平巖漿一樣來回跳動(dòng),或者擁有你想要的任何運(yùn)動(dòng)模式。這個(gè)類不必處理掉落,但是它應(yīng)該確保怪物不會(huì)穿過墻壁。

    當(dāng)怪物接觸玩家時(shí),效果取決于玩家是否跳到它們頂上。你可以通過檢查玩家的底部是否接近怪物的頂部來近似它。如果是這樣的話,怪物就消失了。如果沒有,游戲就輸了。

    
    
    
    
      
    

    文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

    轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/105047.html

    相關(guān)文章

    • JavaScript 編程精解 中文三版 十九、項(xiàng)目:像素藝術(shù)編輯器

      摘要:相反,當(dāng)響應(yīng)指針事件時(shí),它會(huì)調(diào)用創(chuàng)建它的代碼提供的回調(diào)函數(shù),該函數(shù)將處理應(yīng)用的特定部分?;卣{(diào)函數(shù)可能會(huì)返回另一個(gè)回調(diào)函數(shù),以便在按下按鈕并且將指針移動(dòng)到另一個(gè)像素時(shí)得到通知。它們?yōu)榻M件構(gòu)造器的數(shù)組而提供。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Project: A Pixel Art Editor 譯者:飛龍 協(xié)議:CC BY-NC-SA 4...

      Meils 評(píng)論0 收藏0
    • JavaScript 編程精解 中文三版 零、前言

      摘要:來源編程精解中文第三版翻譯項(xiàng)目原文譯者飛龍協(xié)議自豪地采用谷歌翻譯部分參考了編程精解第版,這是一本關(guān)于指導(dǎo)電腦的書。在可控的范圍內(nèi)編寫程序是編程過程中首要解決的問題。我們可以用中文來描述這些指令將數(shù)字存儲(chǔ)在內(nèi)存地址中的位置。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Introduction 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地...

      sanyang 評(píng)論0 收藏0
    • JavaScript 編程精解 中文三版 十三、瀏覽器中的 JavaScript

      摘要:在本例中,使用屬性指定鏈接的目標(biāo),其中表示超文本鏈接。您應(yīng)該認(rèn)為和元數(shù)據(jù)隱式出現(xiàn)在示例中,即使它們沒有實(shí)際顯示在文本中。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:JavaScript and the Browser 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《JavaScript 編程精解(第 2 版)》 ...

      zhiwei 評(píng)論0 收藏0
    • JavaScript 編程精解 中文三版 二、程序結(jié)構(gòu)

      摘要:為了運(yùn)行包裹的程序,可以將這些值應(yīng)用于它們。在瀏覽器中,輸出出現(xiàn)在控制臺(tái)中。在英文版頁面上運(yùn)行示例或自己的代碼時(shí),會(huì)在示例之后顯示輸出,而不是在瀏覽器的控制臺(tái)中顯示。這被稱為條件執(zhí)行。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Program Structure 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《J...

      ThinkSNS 評(píng)論0 收藏0
    • JavaScript 編程精解 中文三版 九、正則表達(dá)式

      摘要:使用構(gòu)造器時(shí),需要將模式書寫成普通的字符串,因此反斜杠的使用規(guī)則與往常相同。構(gòu)造器的后四個(gè)參數(shù)小時(shí)分鐘秒毫秒是可選的,如果用戶沒有指定這些參數(shù),則參數(shù)的值默認(rèn)為。 來源:ApacheCN『JavaScript 編程精解 中文第三版』翻譯項(xiàng)目原文:Regular Expressions 譯者:飛龍 協(xié)議:CC BY-NC-SA 4.0 自豪地采用谷歌翻譯 部分參考了《JavaScript...

      Pluser 評(píng)論0 收藏0

    發(fā)表評(píng)論

    0條評(píng)論

    最新活動(dòng)
    閱讀需要支付1元查看
    <kbd id="uee0c"></kbd>
    <strike id="uee0c"><s id="uee0c"></s></strike>
    <ul id="uee0c"></ul>
    <ul id="uee0c"></ul>
    <th id="uee0c"></th>
  • <strike id="uee0c"></strike>
    • <ul id="uee0c"><pre id="uee0c"></pre></ul>
      • <