首頁 web前端 js教程 開發日誌 - 我正在創建一個遊戲引擎!

開發日誌 - 我正在創建一個遊戲引擎!

Sep 12, 2024 pm 06:15 PM

Devlog - Je créé un moteur de jeu !

我正在創建一個遊戲引擎!

這場偉大的冒險簡介

幾週以來,我一直在定期從事一個我認為可能很有趣的項目,即基於 canvas 使用 JavaScript 和 HTML5 創建我的視頻遊戲引擎。

您可能想知道為什麼選擇 HTML5 和 JavaScript 來創建視訊遊戲?答案沒有問題那麼酷,是我的學校(Zone01 Normandie)所需的項目的競爭以及這些語言擁有執行該項目所需的一切的事實,促使我選擇了這些技術

但實際上這些並不是我會選擇作為基礎的語言,在完成這個任務後我一定會用不同的語言開始其他類似的冒險。

建築學

所以我開始設計我的電玩引擎,它將由幾個類別組成,包括至少兩個主要類別:Game 類別將管理整個遊戲區域,而GameObject 類別允許您生成物件我們的遊戲並讓它們互相互動。

對於這些類,我將添加 CollideBox 類,它將允許我管理所有物件的碰撞框。

Game 類別有一個 GameLoop 方法,將在遊戲的每個畫面(圖片)執行,還有一個 Draw 方法,將在每個遊戲循環期間呼叫。

對於GameObject類,它有一個Step方法和一個Draw方法。
第一個執行每輪遊戲循環,第二個每次呼叫 GameLoop 類別的 Draw 方法時執行。

理論上,您可以透過將引擎模組匯入到專案中來創建遊戲。
為了顯示精靈,我選擇使用 HTML5 內建的canva API(內建意味著它預設附帶)
它將允許我顯示所有精靈並重新剪切圖像以創建對我來說非常有用的動畫!

幾天后,我能夠以給定的速度顯示動畫,並透過我的 CollideBox 偵測碰撞。
還有很多其他好東西,我會讓你在下面看到:

GameObject 類別

class GameObject{
    constructor(game) { // Initialize the GameObject
        this.x = 0
        this.y = 0 
        this.sprite_img = {file: undefined, col: 1, row: 1, fw: 1, fh: 1, step: 0, anim_speed: 0, scale: 1}
        this.loaded = false
        this.game = game
        this.kill = false
        this.collision = new CollideBox()

        game.gObjects.push(this)

    };
    setSprite(img_path, row=1, col=1, speed=12, scale=1) {
        var img = new Image();
        img.onload = () => {
            console.log("image loaded")
            this.sprite_img = {file: img, col: col, row: row, fw: img.width / col, fh: img.height / row, step: 0, anim_speed: speed, scale: scale}
            this.onSpriteLoaded()
        };
        img.src = img_path


    }
    onSpriteLoaded() {}
    draw(context, frame) { // Draw function of game object
        if (this.sprite_img.file != undefined) {


            let column = this.sprite_img.step % this.sprite_img.col;
            let row = Math.floor(this.sprite_img.step / this.sprite_img.col);

           // context.clearRect(this.x, this.y, this.sprite_img.fw, this.sprite_img.fh);
            context.drawImage(
                this.sprite_img.file,
                this.sprite_img.fw * column,
                this.sprite_img.fh * row,
                this.sprite_img.fw,
                this.sprite_img.fh,
                this.x,
                this.y,
                this.sprite_img.fw * this.sprite_img.scale,
                this.sprite_img.fh * this.sprite_img.scale
            );

            if (frame % Math.floor(60 / this.sprite_img.anim_speed) === 0) {
                // Mise à jour de step seulement à 12 fps
                if (this.sprite_img.step < this.sprite_img.row * this.sprite_img.col - 1) {
                    this.sprite_img.step += 1;
                } else {
                    this.sprite_img.step = 0;
                }
            }
        }
    }
    distance_to(pos) {
        return Math.sqrt((pos.x - this.x) ** 2 + (pos.y - this.y) ** 2)
    }

    collide_with(box) {
        if (box instanceof GameObject) {
            box = box.collision
        }
        return (
            this.collision.x < box.x + box.w &&
            this.collision.x + this.collision.w > box.x &&
            this.collision.y < box.y + box.h &&
            this.collision.y + this.collision.h > box.y
          )
    }
    onStep()   {};
}   

遊戲類

class Game {
    constructor(width = 1400, height = 700) {
        this.gObjects = [];
        this.toLoad = [];
        this.timers = [];
        this.layers = [];
        this.canvas = document.getElementsByTagName("canvas")[0]

        this.canvas.width = width
        this.canvas.height = height
        this.context =  this.canvas.getContext("2d")
        this.context.globalCompositeOperation = 'source-over';
        this.inputs = {};
        this.mouse = {x: 0, y: 0}
        document.addEventListener('keydown', (e) => {
            this.inputs[e.key] = true;
        }, false);
        document.addEventListener('keyup', (e) => {
            this.inputs[e.key] = false;
        }, false);
        document.addEventListener('mousemove', (e) => {
            this.mouse.x = e.x;
            this.mouse.y = e.y;
        })
        document.addEventListener('mouseevent', (e) => {
            switch (e.button) {

            }
        })

    }
    draw(frame) {
        this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
        console.log( this.canvas.width, this.canvas.heigh)
        for(let i = 0; i < this.gObjects.length; i ++) {
            this.gObjects[i].draw(this.context, frame)
        }

    }
    gLoop() {
        let fps = Math.floor(1000 / 60)
        console.log(fps)
        let clock = 0
        setInterval(() => {
            clock += 1
            for(let i = 0; i < this.gObjects.length; i ++) {

                if (this.gObjects[i] != undefined) {
                    if (this.gObjects[i].kill) {
                        this.gObjects.splice(i, 1);
                        continue;
                    }
                    this.gObjects[i].onStep();
                }


            }
            this.draw(Math.floor(clock))
               // context.l();

           //     console.log(clock)

           if (fps <= clock) {
            clock = 0
           } 

        }, fps)
    }
    keyboard_check(key) {
        return this.inputs[key] == true
    }
}

當然有很多最佳化或其他錯誤,但一切都正常,
「完美的!」你能告訴我嗎?
那就太簡單了。

憂慮

完成此任務並開始嘗試使用此引擎創建遊戲後,我在與同事的談話中得知了一些可怕的消息。

我想您還記得所做的技術選擇是為了符合我的 Zone01 學校的要求......
確實,選擇的語言很好,但我不知道有一條指令會嚴重阻礙該項目......
我們被禁止使用 Canva 函式庫!

提醒一下,這是我們用來顯示圖像的函式庫。

接下來是什麼?

當我寫這篇文章時,我也開始完全重新設計這個遊戲引擎,而不使用canva。

本開發日誌已經完成,您很快就會看到這個故事的其餘部分,別擔心。
對於下一個開發日誌,我一定會嘗試新的格式。

希望這些內容對您有所幫助,給您帶來樂趣,或至少在一些主題上對您進行了教育。祝您一天愉快,程式設計愉快。

DevLogs 1.1:引擎完成了,它是如何運作的?

之前

幾個月前,我開始創建我的電玩引擎,我完成了它......不久前,在Zone01 的幾位同事的幫助下,我們甚至成功地創建了一款受《超級瑪利歐兄弟》啟發的遊戲,可以在我的網站上找到Itch.io 頁面。

決定申請這個開發日誌的格式花了很多時間,我承認我稍微推遲了甚至完全推遲了寫這篇文章的截止日期。
透過耐心地以我猶豫不決為藉口不從事這個主題,我現在發現自己在計劃發布日期兩個月後在魯昂汽車站的休息區寫作,而取消的火車迫使我多等一個小時。

因此,讓我們忽略架構的所有細節,自我的開發日誌的第一部分以來,這個架構的變化很小(除了透過避免使用畫布進行的調整之外)。
因此,我們將討論所進行的專案、我們作為團隊的工作方式以及我們遇到的問題。
將此視為對該專案的反饋,我希望您能夠從本文中吸取一些教訓,這將對您的專案之一有所幫助。

專案

這個專案是用 JavaScript 重新創建一個超級瑪利歐兄弟,並從頭開始,至少在程式碼方面是如此。

規格很簡單,我們必須有一個具有多個等級的馬裡奧遊戲,這是一種簡單地創建新等級的方法。
此外,我們還必須建立一個記分板和一個選單來調整選項。

這個項目的困難是:

  • 螢幕上元素的水平捲動
  • 優化螢幕上不存在的元素

捲動,因為它要求所有元素在背景中相對於玩家的位置滾動。
並且優化螢幕上未顯示的元素可以減少運行遊戲所需的資源,而不會損失效能。

解決了這些困難後,我們在我的 itch.io 頁面上發布了這個遊戲,您甚至可以去測試它。

本開發日誌就這樣結束了,現在完成了,我將能夠撰寫有關其他項目和/或其他主題的內容。

如果你對我告訴你的內容有一點興趣,你可以在 github 上查看我的不同項目(包括本開發日誌中的項目)。

祝你今天休息愉快!

以上是開發日誌 - 我正在創建一個遊戲引擎!的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何在JS中與日期和時間合作? 如何在JS中與日期和時間合作? Jul 01, 2025 am 01:27 AM

JavaScript中的日期和時間處理需注意以下幾點:1.創建Date對像有多種方式,推薦使用ISO格式字符串以保證兼容性;2.獲取和設置時間信息可用get和set方法,注意月份從0開始;3.手動格式化日期需拼接字符串,也可使用第三方庫;4.處理時區問題建議使用支持時區的庫,如Luxon。掌握這些要點能有效避免常見錯誤。

為什麼要將標籤放在的底部? 為什麼要將標籤放在的底部? Jul 02, 2025 am 01:22 AM

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

什麼是在DOM中冒泡和捕獲的事件? 什麼是在DOM中冒泡和捕獲的事件? Jul 02, 2025 am 01:19 AM

事件捕獲和冒泡是DOM中事件傳播的兩個階段,捕獲是從頂層向下到目標元素,冒泡是從目標元素向上傳播到頂層。 1.事件捕獲通過addEventListener的useCapture參數設為true實現;2.事件冒泡是默認行為,useCapture設為false或省略;3.可使用event.stopPropagation()阻止事件傳播;4.冒泡支持事件委託,提高動態內容處理效率;5.捕獲可用於提前攔截事件,如日誌記錄或錯誤處理。了解這兩個階段有助於精確控制JavaScript響應用戶操作的時機和方式。

如何減少JavaScript應用程序的有效載荷大小? 如何減少JavaScript應用程序的有效載荷大小? Jun 26, 2025 am 12:54 AM

如果JavaScript應用加載慢、性能差,問題往往出在payload太大,解決方法包括:1.使用代碼拆分(CodeSplitting),通過React.lazy()或構建工具將大bundle拆分為多個小文件,按需加載以減少首次下載量;2.移除未使用的代碼(TreeShaking),利用ES6模塊機制清除“死代碼”,確保引入的庫支持該特性;3.壓縮和合併資源文件,啟用Gzip/Brotli和Terser壓縮JS,合理合併文件並優化靜態資源;4.替換重型依賴,選用輕量級庫如day.js、fetch

JavaScript模塊上的確定JS綜述:ES模塊與COMPORJS JavaScript模塊上的確定JS綜述:ES模塊與COMPORJS Jul 02, 2025 am 01:28 AM

ES模塊和CommonJS的主要區別在於加載方式和使用場景。 1.CommonJS是同步加載,適用於Node.js服務器端環境;2.ES模塊是異步加載,適用於瀏覽器等網絡環境;3.語法上,ES模塊使用import/export,且必須位於頂層作用域,而CommonJS使用require/module.exports,可在運行時動態調用;4.CommonJS廣泛用於舊版Node.js及依賴它的庫如Express,ES模塊則適用於現代前端框架和Node.jsv14 ;5.雖然可混合使用,但容易引發問題

如何在node.js中提出HTTP請求? 如何在node.js中提出HTTP請求? Jul 13, 2025 am 02:18 AM

在Node.js中發起HTTP請求有三種常用方式:使用內置模塊、axios和node-fetch。 1.使用內置的http/https模塊無需依賴,適合基礎場景,但需手動處理數據拼接和錯誤監聽,例如用https.get()獲取數據或通過.write()發送POST請求;2.axios是基於Promise的第三方庫,語法簡潔且功能強大,支持async/await、自動JSON轉換、攔截器等,推薦用於簡化異步請求操作;3.node-fetch提供類似瀏覽器fetch的風格,基於Promise且語法簡單

垃圾收集如何在JavaScript中起作用? 垃圾收集如何在JavaScript中起作用? Jul 04, 2025 am 12:42 AM

JavaScript的垃圾回收機制通過標記-清除算法自動管理內存,以減少內存洩漏風險。引擎從根對像出發遍歷並標記活躍對象,未被標記的則被視為垃圾並被清除。例如,當對像不再被引用(如將變量設為null),它將在下一輪迴收中被釋放。常見的內存洩漏原因包括:①未清除的定時器或事件監聽器;②閉包中對外部變量的引用;③全局變量持續持有大量數據。 V8引擎通過分代回收、增量標記、並行/並發回收等策略優化回收效率,降低主線程阻塞時間。開發時應避免不必要的全局引用、及時解除對象關聯,以提升性能與穩定性。

var vs Let vs const:快速JS綜述解釋器 var vs Let vs const:快速JS綜述解釋器 Jul 02, 2025 am 01:18 AM

var、let和const的區別在於作用域、提升和重複聲明。 1.var是函數作用域,存在變量提升,允許重複聲明;2.let是塊級作用域,存在暫時性死區,不允許重複聲明;3.const也是塊級作用域,必須立即賦值,不可重新賦值,但可修改引用類型的內部值。優先使用const,需改變變量時用let,避免使用var。

See all articles