Heute werde ich mit Ihnen ein kleines Spiel teilen, das mit H5 erstellt wurde, damit jeder darüber diskutieren und lernen kann. Es ist ein sehr klassisches Spiel, Snake. Es gibt zwei klassische Arten, Snake zu spielen: Die eine besteht darin, Punkte zu sammeln, um Level zu meistern, und die andere darin, bis zum Ende zu essen.
Das spezifische Gameplay der ersten Methode besteht darin, dass die Schlange das Level abschließt, nachdem sie eine bestimmte Menge an Futter gegessen hat, und dass sich die Geschwindigkeit erhöht, nachdem sie das Level bestanden hat es gibt kein Essen mehr. Was wir heute umsetzen werden, ist die zweite Spielweise.
MVCDesignmuster
Basierend auf dem klassischen Snake verwende ich bei der Implementierung auch ein klassisches Designmodell: MVC (d. h.: Modell – Ansicht – Kontrolle). Die verschiedenen Zustände und Datenstrukturen des Spiels werden von Model verwaltet. Die Ansicht wird zur Anzeige von Änderungen im Model verwendet. Die Interaktion zwischen dem Benutzer und dem Spiel wird von Control vervollständigt (Control stellt verschiedene Spiel-API-Schnittstellen bereit).
Das Modell ist der Kern des Spiels und der Hauptinhalt dieses Artikels beinhaltet einige Leistungsprobleme. Die Steuerung ist für die Geschäftslogik verantwortlich. Der Vorteil dieses Designs besteht darin, dass das Modell völlig unabhängig ist, die Ansicht die Zustandsmaschine des Modells ist und sowohl das Modell als auch die Ansicht von der Steuerung gesteuert werden.
Model
Sehen Sie sich ein klassisches Bild einer gierigen Schlange an.
Schlange hat vier Hauptteilnehmer:
Schlange
Nahrung
Wände (Grenzen)
Bühne (Zone)
Die Bühne ist eine m * n-Matrix (zweidimensionales Array), und die Indexgrenze der Matrix ist An den Wänden der Bühne, Elemente auf der Matrix werden verwendet, um die Standorte von Nahrung und Schlangen zu markieren.
Die leere Bühne ist wie folgt:
[ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ]
Futter (F) und Schlange (S) erscheinen auf der Bühne:
[ [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,F,0,0,0,0,0,0,0], [0,0,0,S,S,S,S,0,0,0], [0,0,0,0,0,0,S,0,0,0], [0,0,0,0,S,S,S,0,0,0], [0,0,0,0,S,0,0,0,0,0], [0,0,0,0,S,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], [0,0,0,0,0,0,0,0,0,0], ]
Da sie zweidimensional funktioniert Arrays sind nicht so gut wie Eindimensionales Array ist praktisch, daher verwende ich ein eindimensionales Array wie folgt:
[ 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,F,0,0,0,0,0,0,0, 0,0,0,S,S,S,S,0,0,0, 0,0,0,0,0,0,S,0,0,0, 0,0,0,0,S,S,S,0,0,0, 0,0,0,0,S,0,0,0,0,0, 0,0,0,0,S,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0, ]
Schlange und Essen auf der Bühnenmatrix sind genau das Zuordnung der Bühne zu den beiden, und sie haben unabhängige Daten füreinander:
Snake ist eine Liste von Koordinatenindizes;
Food ist ein Indexwert, der auf die Bühnenkoordinaten zeigt.
Aktivitäten von Schlangen
Es gibt drei Aktivitäten von Schlangen, wie folgt:
Bewegen (bewegen)
Essen (essen)
Kollision
Bewegung
Was passiert im Inneren der Schlange, wenn sie sich bewegt?
Die Schlangenverknüpfte Liste führt während einer Bewegung zwei Dinge aus: das Einfügen eines neuen Knotens am Kopf der Tabelle und das Löschen eines alten Knotens am Ende der Tabelle . Verwenden Sie ein Array, um die Schlangenverknüpfungsliste darzustellen. Dann ist die Bewegung der Schlange der folgende Pseudocode:
function move(next) { snake.pop() & snake.unshift(next); }
Ist das Array als Schlangenverknüpfungsliste geeignet?
Das ist die erste Frage, über die ich nachgedacht habe. Schließlich kann das Unshift & Pop des Arrays nahtlos die Bewegung der Schlange darstellen. Bequemlichkeit bedeutet jedoch nicht eine gute Leistung. Die Zeitkomplexität von unshift zum Einfügen von Elementen in das Array beträgt O(n) und die Zeitkomplexität von pop zum Entfernen des Endelements des Arrays beträgt O(1).
Die Bewegung einer Schlange ist eine Hochfrequenzaktion. Wenn die Algorithmuskomplexität einer Aktion O(n) ist und die Länge der Schlange relativ groß ist, ist die Leistung des Spiels problematisch. Die gierige Schlange, die ich erkennen möchte, ist theoretisch eine lange Schlange, daher lautet meine Antwort in diesem Artikel: Arrays eignen sich nicht als mit Schlangen verknüpfte Listen.
Die Schlangen-verknüpfte Liste muss eine echte verknüpfte Listenstruktur sein.
Der Zeitaufwand für das Löschen oder Einfügen eines Knotens in einer verknüpften Liste beträgt O(1). Die Verwendung einer verknüpften Liste als Datenstruktur einer verknüpften Schlangenliste kann die Leistung des Spiels verbessern. Javascript Es gibt keine vorgefertigte verknüpfte Listenstruktur. Ich habe eine verknüpfte Listenklasse namens Chain geschrieben, die unshfit & pop bietet. Der folgende Pseudocode erstellt eine Schlangenverknüpfungsliste:
let snake = new Chain();
Aus Platzgründen werden wir hier nicht vorstellen, wie Chain implementiert wird: https://github.com/leeenx/ es6-utils#chain
Essen & Kollision
Der Unterschied zwischen „Essen“ und „Kollision“ besteht darin, dass Essen auf „Essen“ und Kollision auf „Wand“ trifft. Ich denke, „Fressen“ und „Zusammenstoßen“ sind zwei Zweige der drei möglichen Ergebnisse der „Bewegung“ einer Schlange. Die drei möglichen Ergebnisse der Bewegung einer Schlange sind: „Vorrücken“, „Fressen“ und „Zusammenstoß“.
Schauen Sie sich noch einmal den Pseudocode für die Schlangenbewegung an:
function move(next) { snake.pop() & snake.unshift(next); }
Der nächste im Code stellt den Indexwert des Gitters dar, in das der Schlangenkopf eintreten wird 0 kann die Schlange „vorwärts gehen“, wenn dieses Gitter S ist, bedeutet es „Kollision“ mit sich selbst, und wenn dieses Gitter F ist, bedeutet es Essen.
Es scheint, als gäbe es weniger Unebenheiten in der Wand?
Während des Designprozesses habe ich die Wand nicht in der Matrix der Bühne entworfen, sondern das Auftreffen auf die Wand durch Indizieren außerhalb der Grenzen dargestellt. Vereinfacht ausgedrückt bedeutet „next === -1“, dass man außerhalb des Spielfelds ist und gegen die Wand stößt.
Der folgende Pseudocode stellt den gesamten Aktivitätsprozess der Schlange dar:
// B 表示撞墙 let cell = -1 === next ? B : zone[next]; switch(cell) { // 吃食 case F: eat(); break; // 撞到自己 case S: collision(S); break; // 撞墙 case B: collision(B): break; // 前进 default: move; }
Zufällige Fütterung
Zufällige Fütterung bedeutet die zufällige Auswahl eines Indexwerts der Stufe zur Kartierung des Nahrungsorts . Das scheint sehr einfach zu sein und kann direkt so geschrieben werden:
// 伪代码 food = Math.random(zone.length) >> 0;
如果考虑到投食的前提 —— 不与蛇身重叠,你会发现上面的随机代码并不能保证投食位置不与蛇身重叠。由于这个算法的安全性带有赌博性质,且把它称作「赌博算法」。为了保证投食的安全性,我把算法扩展了一下:
// 伪代码 function feed() { let index = Math.random(zone.length) >> 0; // 当前位置是否被占用 return zone[index] === S ? feed() : index; } food = feed();
上面的代码虽然在理论上可以保证投食的绝对安全,不过我把这个算法称作「不要命的赌徒算法」,因为上面的算法有致命的BUG —— 超长递归 or 死循环。
为了解决上面的致命问题,我设计了下面的算法来做随机投食:
// 伪代码 function feed() { // 未被占用的空格数 let len = zone.length - snake.length; // 无法投食 if(len === 0) return ; // zone的索引 let index = 0, // 空格计数器 count = 0, // 第 rnd 个空格子是最终要投食的位置 rnd = Math.random() * count >> 0 + 1; // 累计空格数 while(count !== rnd) { // 当前格子为空,count总数增一 zone[index++] === 0 && ++count; } return index - 1; } food = feed();
这个算法的平均复杂度为 O(n/2)。由于投食是一个低频操作,所以 O(n/2)的复杂度并不会带来任何性能问题。不过,我觉得这个算法的复杂度还是有点高了。回头看一下最开始的「赌博算法」,虽然「赌博算法」很不靠谱,但是它有一个优势 —— 时间复杂度为 O(1)。
「赌博算法」的靠谱概率 = (zone.length – snake.length) / zone.length。snake.length 是一个动态值,它的变化范围是:0 ~ zone.length。推导出「赌博算法」的平均靠谱概率是:
「赌博算法」平均靠谱概率 = 50%
看来「赌博算法」还是可以利用一下的。于是我重新设计了一个算法:
新算法的平均复杂度可以有效地降低到 O(n/4),人生有时候需要点运气 : )。
View
在 View 可以根据喜好选择一款游戏渲染引擎,我在 View 层选择了 PIXI 作为游戏游戏渲染引擎。
View 的任务主要有两个:
绘制游戏的界面;
渲染 Model 里的各种数据结构
也就是说 View 是使用渲染引擎还原设计稿的过程。本文的目的是介绍「贪吃蛇」的实现思路,如何使用一个渲染引擎不是本文讨论的范畴,我想介绍的是:「如何提高渲染的效率」。
在 View 中显示 Model 的蛇可以简单地如以下伪代码:
上面代码的时间复杂度是 O(n)。上面介绍过蛇的移动是一个高频的活动,我们要尽量避免高频率地运行 O(n) 的代码。来分析蛇的三种活动:「移动」,「吃食」,「碰撞」。
首先,Model 发生了「碰撞」,View 应该是直接暂停渲染 Model 里的状态,游戏处在死亡状态,接下来的事由 Control 处理。
Model 中的蛇(链表)在一次「移动」过程中做了两件事:向表头插入一个新节点,同时剔除表尾一个旧节点;蛇(链表)在一次「吃食」过程中只做一件事:向表头插入一个新节点。
如果在 View 中对 Model 的蛇链表做差异化检查,View 只增量更新差异部分的话,算法的时间复杂度即可降低至 O(1) ~ O(2) 。以下是优化后的伪代码:
Control
Control 主要做 3 件事:
游戏与用户的互动
驱动 Model
同步 View 与 Model
「游戏与用户的互动」是指向外提供游戏过程需要使用到的 APIs 与 各类事件。我规划的 APIs 如下:
name
type
deltail
init method 初始化游戏
start method 开始游戏
restart method 重新开始游戏
pause method 暂停
resume method 恢复
turn method 控制蛇的转向。如:turn(“left”)
destroy method 销毁游戏
speed property 蛇的移动速度
事件如下:
name
detail
countdown 倒时计
eat 吃到食物
before-eat 吃到食物前触发
gameover 游戏结束
事件统一挂载在游戏实例下的 event 对象下。
「驱动 Model 」只做一件事 —— 将 Model 的蛇的方向更新为用户指定的方向。
「同步 View 与 Model 」也比较简单,检查 Model 是否有更新,如果有更新通知 View 更新游戏界面。
以上就是用H5做贪吃蛇小游戏的全部步奏,有兴趣的朋友可以深度研究一下。
推荐阅读:
Das obige ist der detaillierte Inhalt von„Snake' – Entwicklung eines H5-Minispiels. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!