diff options
Diffstat (limited to 'files/zh-cn/games/techniques')
11 files changed, 1469 insertions, 0 deletions
diff --git a/files/zh-cn/games/techniques/2d_collision_detection/index.html b/files/zh-cn/games/techniques/2d_collision_detection/index.html new file mode 100644 index 0000000000..dcc688643f --- /dev/null +++ b/files/zh-cn/games/techniques/2d_collision_detection/index.html @@ -0,0 +1,82 @@ +--- +title: 2D collision detection +slug: Games/Techniques/2D_collision_detection +translation_of: Games/Techniques/2D_collision_detection +--- +<div>{{GamesSidebar}}</div><div>{{IncludeSubnav("/en-US/docs/Games")}}</div> + +<div class="summary"> +<p>检测2D游戏中的碰撞算法,依赖于可碰撞物体的形状(例如:矩形与矩形,矩形与圆形,圆形与圆形)。通常情况下,你使用的的简单通用形状,会被称为“hitbox”的实体所覆盖,尽管发生的碰撞并不是像素完美契合的,它看起来也足够好,而且可跨多个实体执行碰撞。本文提供了一系列较为通用的2D游戏中碰撞侦测技术。</p> +</div> + +<h2 id="Axis-Aligned_Bounding_Box">Axis-Aligned Bounding Box</h2> + +<p>碰撞侦测其中一种简单的形式是,在两个轴对齐的矩形之间碰撞 — 这意味着没有旋转。这个算法是确定两个矩形任意4边之间不再有间隔,存在间隔代表没有发生碰撞。</p> + +<pre class="brush: js">var rect1 = {x: 5, y: 5, width: 50, height: 50} +var rect2 = {x: 20, y: 10, width: 10, height: 10} + +if (rect1.x < rect2.x + rect2.width && + rect1.x + rect1.width > rect2.x && + rect1.y < rect2.y + rect2.height && + rect1.height + rect1.y > rect2.y) { + // collision detected! +} + +// filling in the values => + +if (5 < 30 && + 55 > 20 && + 5 < 20 && + 55 > 10) { + // collision detected! +}</pre> + +<div class="note"> +<p><strong>Note</strong>: You can see a <a href="http://jsfiddle.net/knam8/">live example of Axis-Aligned Bounding Box collision detection</a> on jsFiddle, to illustrate how this code would work in practice. <a href="https://jsfiddle.net/jlr7245/217jrozd/3/">Here is another example without Canvas or external libraries</a>.</p> +</div> + +<h2 id="圆形碰撞">圆形碰撞</h2> + +<p>碰撞测试的另一种形状是两个圆形间的碰撞,该算法是通过获取两个圆心点,并确定圆心距离小于两个圆形的半径和实现的。</p> + +<pre class="brush: js">var circle1 = {radius: 20, x: 5, y: 5}; +var circle2 = {radius: 12, x: 10, y: 5}; + +var dx = circle1.x - circle2.x; +var dy = circle1.y - circle2.y; +var distance = Math.sqrt(dx * dx + dy * dy); + +if (distance < circle1.radius + circle2.radius) { + // collision detected! +}</pre> + +<div class="note"> +<p><strong>Note</strong>: You can see a <a href="http://jsfiddle.net/gQ3hD/2/">live example of Circle collision detection</a> on jsFiddle, to illustrate how this code would work in practice.</p> +</div> + +<h2 id="Separating_Axis_Theorem">Separating Axis Theorem</h2> + +<p>This is a collision algorithm that can detect a collision between any two *convex* polygons. It's more complicated to implement than the above methods but is more powerful. The complexity of an algorithm like this means we will need to consider performance optimization, covered in the next section.</p> + +<p>Implementing SAT is out of scope for this page so see the recommended tutorials below:</p> + +<ol> + <li><a href="http://www.sevenson.com.au/actionscript/sat/">Separating Axis Theorem (SAT) explanation</a></li> + <li><a href="http://www.metanetsoftware.com/technique/tutorialA.html">Collision detection and response</a></li> + <li><a href="http://gamedevelopment.tutsplus.com/tutorials/collision-detection-using-the-separating-axis-theorem--gamedev-169">Collision detection Using the Separating Axis Theorem</a></li> + <li><a href="http://www.codezealot.org/archives/55">SAT (Separating Axis Theorem)</a></li> + <li><a href="http://rocketmandevelopment.com/blog/separation-of-axis-theorem-for-collision-detection/">Separation of Axis Theorem (SAT) for Collision Detection</a></li> +</ol> + +<h2 id="Collision_Performance">Collision Performance</h2> + +<p>While some of these algorithms for collision detection are simple enough to calculate, it can be a waste of cycles to test *every* entity with every other entity. Usually games will split collision into two phases, broad and narrow.</p> + +<h3 id="Broad_Phase">Broad Phase</h3> + +<p>Broad phase should give you a list of entities that *could* be colliding. This can be implemented with a spacial data structure that will give you a rough idea of where the entity exists and what exist around it. Some examples of spacial data structures are Quad Trees, R-Trees or a Spacial Hashmap.</p> + +<h3 id="Narrow_Phase">Narrow Phase</h3> + +<p>When you have a small list of entities to check you will want to use a narrow phase algorithm (like the ones listed above) to provide a certain answer as to whether there is a collision or not.</p> diff --git a/files/zh-cn/games/techniques/3d_collision_detection/index.html b/files/zh-cn/games/techniques/3d_collision_detection/index.html new file mode 100644 index 0000000000..4d4786f336 --- /dev/null +++ b/files/zh-cn/games/techniques/3d_collision_detection/index.html @@ -0,0 +1,153 @@ +--- +title: 3D 碰撞检测 +slug: Games/Techniques/3D_collision_detection +translation_of: Games/Techniques/3D_collision_detection +--- +<div>{{GamesSidebar}}</div><p class="summary">本文介绍了用于在3D环境中实现不同边界体积碰撞检测的技术。 后续文章将讨论特定3D库中的实现。</p> + +<h2 id="Axis-aligned_bounding_boxes(AABB包围盒)">Axis-aligned bounding boxes(<strong>AABB包围盒</strong>)</h2> + +<p> </p> + +<p>在游戏中,为了简化物体之间的碰撞检测运算,通常会对物体创建一个规则的几何外形将其包围。其中,AABB(axis-aligned bounding box)包围盒被称为轴对齐包围盒。</p> + +<p>与2D碰撞检测一样,轴对齐包围盒是判断两个物体是否重叠的最快算法,物体被包裹在一个非旋转的(因此轴对齐的)盒中,并检查这些盒在三维坐标空间中的位置,以确定它们是否重叠。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11797/Screen%20Shot%202015-10-16%20at%2015.11.21.png" style="display: block; height: 275px; margin: 0px auto; width: 432px;"></p> + +<p>由于性能原因,轴对齐是有一些约束的。两个非旋转的盒子之间是否重叠可以通过逻辑比较进行检查,而旋转的盒子则需要三角运算,这会导致性能下降。如果你有旋转的物体,可以通过修改边框的尺寸,这样盒子仍可以包裹物体,或者选择使用另一种边界几何类型,比如球体(球体旋转,形状不会变)。下图是一个AABB物体旋转,动态调节盒大小适应物体的例子。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11799/rotating_knot.gif" style="display: block; height: 192px; margin: 0px auto; width: 207px;"></p> + +<div class="note"> +<p><strong>Note:</strong> 参考<a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection/Bounding_volume_collision_detection_with_THREE.js">这里</a>,<font><font>使用</font></font><font><font>Three</font></font><font><font>.js进行</font><font>边界体积碰撞检测。</font></font></p> +</div> + +<h3 id="点与_AABB">点与 AABB</h3> + +<p>如果检测到一个点是否在AABB内部就非常简单了 — 我们只需要检查这个点的坐标是否在AABB内; 分别考虑到每种坐标轴. 如果假设 <em>P<sub>x</sub></em>, <em>P<sub>y</sub> 和</em> <em>P<sub>z</sub></em> 是点的坐标, <em>B<sub>minX</sub></em>–<em>B<sub>maxX</sub></em>, <em>B<sub>minY</sub></em>–<em>B<sub>maxY</sub></em>, 和 <em>B<sub>minZ</sub></em>–<em>B<sub>maxZ</sub></em> 是AABB的每一个坐标轴的范围, 我们可以使用以下公式计算两者之间的碰撞是否发生:</p> + +<p><math><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>P</mi><mo>,</mo><mi>B</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>x</mi></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>X</mi></mrow></msub><mo>∧</mo><msub><mi>P</mi><mi>x</mi></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>X</mi></mrow></msub><mo stretchy="false">)</mo><mo>∧</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>y</mi></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Y</mi></mrow></msub><mo>∧</mo><msub><mi>P</mi><mi>y</mi></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Y</mi></mrow></msub><mo stretchy="false">)</mo><mo>∧</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>z</mi></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Z</mi></mrow></msub><mo>∧</mo><msub><mi>P</mi><mi>z</mi></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Z</mi></mrow></msub><mo stretchy="false">)</mo></mrow><annotation encoding="TeX">f(P,B)= (P_x >= B_{minX} \wedge P_x <= B_{maxX}) \wedge (P_y >= B_{minY} \wedge P_y <= B_{maxY}) \wedge (P_z >= B_{minZ} \wedge P_z <= B_{maxZ})</annotation></semantics></math></p> + +<p>或者用JavaScript:</p> + +<pre class="brush: js">function isPointInsideAABB(point, box) { + return (point.x >= box.minX && point.x <= box.maxX) && + (point.y >= box.minY && point.y <= box.maxY) && + (point.z >= box.minY && point.z <= box.maxZ); +}</pre> + +<h3 id="AABB_与_AABB">AABB 与 AABB</h3> + +<p>检查一个AABB是否和另一个AABB相交类似于检测两个点一样. 我们只需要基于每一条坐标轴并利用盒子的边缘去检测. 下图显示了我们基于 X 轴的检测 — 当然, <em>A<sub>minX</sub></em>–<em>A<sub>maxX</sub></em> 和 <em>B<sub>minX</sub></em>–<em>B<sub>maxX</sub></em> 会不会重叠?</p> + +<p><img alt="updated version" src="https://mdn.mozillademos.org/files/11813/aabb_test.png" style="display: block; height: 346px; margin: 0px auto; width: 461px;"></p> + +<p>在数学上的表示就像这样:</p> + +<p><math><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>A</mi><mo>,</mo><mi>B</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>X</mi></mrow></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>X</mi></mrow></msub><mo>∧</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>X</mi></mrow></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>X</mi></mrow></msub><mo stretchy="false">)</mo><mo>∧</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Y</mi></mrow></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Y</mi></mrow></msub><mo>∧</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Y</mi></mrow></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Y</mi></mrow></msub><mo stretchy="false">)</mo><mo>∧</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Z</mi></mrow></msub><mo><=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Z</mi></mrow></msub><mo>∧</mo><msub><mi>A</mi><mrow><mi>m</mi><mi>a</mi><mi>x</mi><mi>Z</mi></mrow></msub><mo>>=</mo><msub><mi>B</mi><mrow><mi>m</mi><mi>i</mi><mi>n</mi><mi>Z</mi></mrow></msub><mo stretchy="false">)</mo></mrow><annotation encoding="TeX">f(A,B) =</annotation></semantics></math></p> + +<p>在JavaScript我们可以这样:</p> + +<pre class="brush: js">function intersect(a, b) { + return (a.minX <= b.maxX && a.maxX >= b.minX) && + (a.minY <= b.maxY && a.maxY >= b.minY) && + (a.minZ <= b.maxZ && a.maxZ >= b.minZ); +} +</pre> + +<h2 id="球体碰撞">球体碰撞</h2> + +<p>球体碰撞边缘检测比AABB盒子稍微复杂一点,但他的检测仍相当容易的。球体的主要优势是他们不变的旋转,如果包装实体旋转,边界领域仍将是相同的。他们的主要缺点是,除非他们包装的实体实际上是球形,包装的实体通常不是一个完美的球形(比如用这样的球形包装一个人将导致一些错误,而AABB盒子将更合适)。</p> + +<h3 id="点与球">点与球</h3> + +<p>检查是否一个球体包含一个点,我们需要计算点和球体的中心之间的距离。如果这个距离小于或等于球的半径,这个点就在里面。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11803/point_vs_sphere.png" style="display: block; height: 262px; margin: 0px auto; width: 385px;"></p> + +<p>两个点A和B之间的欧氏距离是<br> + <math><semantics><msqrt><mrow><mo stretchy="false">(</mo><msub><mi>A</mi><mi>x</mi></msub><mo>-</mo><msub><mi>B</mi><mi>x</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo stretchy="false">)</mo><mo>+</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mi>y</mi></msub><mo>-</mo><msub><mi>B</mi><mi>y</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mi>z</mi></msub><mo>-</mo><msub><mi>B</mi><mi>z</mi></msub><mo stretchy="false">)</mo></mrow></msqrt><annotation encoding="TeX">\sqrt{(A_x - B_x) ^ 2) + (A_y - B_y)^2 + (A_z - B_z)}</annotation></semantics></math> ,我们的公式指出,球体碰撞检测是:</p> + +<p><math><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>P</mi><mo>,</mo><mi>S</mi><mo stretchy="false">)</mo><mo>=</mo><msub><mi>S</mi><mrow><mi>r</mi><mi>a</mi><mi>d</mi><mi>i</mi><mi>u</mi><mi>s</mi></mrow></msub><mo>>=</mo><msqrt><mrow><mo stretchy="false">(</mo><msub><mi>P</mi><mi>x</mi></msub><mo>-</mo><msub><mi>S</mi><mi>x</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>y</mi></msub><mo>-</mo><msub><mi>S</mi><mi>y</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><msub><mi>P</mi><mi>z</mi></msub><mo>-</mo><msub><mi>S</mi><mi>z</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow></msqrt></mrow><annotation encoding="TeX">f(P,S) = S_{radius} >= \sqrt{(P_x - S_x)^2 + (P_y - S_y)^2 + (P_z - S_z)^2}</annotation></semantics></math></p> + +<p>或者用JavaScript:</p> + +<pre class="brush: js">function isPointInsideSphere(point, sphere) { + // we are using multiplications because is faster than calling Math.pow + var distance = Math.sqrt((point.x - sphere.x) * (point.x - sphere.x) + + (point.y - sphere.y) * (point.y - sphere.y) + + (point.z - sphere.z) * (point.z - sphere.z)); + return distance < sphere.radius; +} +</pre> + +<div class="note"> +<p>上面的代码有一个平方根,是一个开销昂贵的计算。一个简单的优化,以避免它由半径平方,所以优化方程不涉及<code>distance < sphere.radius * sphere.radius</code>.</p> +</div> + +<h3 id="球体与球体">球体与球体</h3> + +<p>球体与球体的距离类似于点和球体。我们需要测试是球体的中心之间的距离小于或等于半径的总和。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11805/sphere_vs_sphere.png" style="display: block; height: 262px; margin: 0px auto; width: 414px;"></p> + +<p>在数学上,像这样:</p> + +<p><math><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>A</mi><mo>,</mo><mi>B</mi><mo stretchy="false">)</mo><mo>=</mo><msqrt><mrow><mo stretchy="false">(</mo><msub><mi>A</mi><mi>x</mi></msub><mo>-</mo><msub><mi>B</mi><mi>x</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mi>y</mi></msub><mo>-</mo><msub><mi>B</mi><mi>y</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup><mo>+</mo><mo stretchy="false">(</mo><msub><mi>A</mi><mi>z</mi></msub><mo>-</mo><msub><mi>B</mi><mi>z</mi></msub><msup><mo stretchy="false">)</mo><mn>2</mn></msup></mrow></msqrt><mo><=</mo><msub><mi>A</mi><mrow><mi>r</mi><mi>a</mi><mi>d</mi><mi>i</mi><mi>u</mi><mi>s</mi></mrow></msub><mo>+</mo><msub><mi>B</mi><mrow><mi>r</mi><mi>a</mi><mi>d</mi><mi>i</mi><mi>u</mi><mi>s</mi></mrow></msub></mrow><annotation encoding="TeX">f(A,B) = \sqrt{(A_x - B_x)^2 + (A_y - B_y)^2 + (A_z - B_z)^2} <= A_{radius} + B_{radius}</annotation></semantics></math></p> + +<p>或者JavaScript:</p> + +<pre class="brush: js">function intersect(sphere, other) { + // we are using multiplications because it's faster than calling Math.pow + var distance = Math.sqrt((sphere.x - other.x) * (sphere.x - other.x) + + (sphere.y - other.y) * (sphere.y - other.y) + + (sphere.z - other.z) * (sphere.z - other.z)); + return distance < (sphere.radius + other.radius); } +}</pre> + +<h3 id="球体与AABB">球体与AABB</h3> + +<p>测试一个球和一个AABB的碰撞是稍微复杂,但过程仍然简单和快速。一个合乎逻辑的方法是,检查AABB每个顶点,计算每一个点与球的距离。然而这是大材小用了,测试所有的顶点都是不必要的,因为我们可以侥幸计算AABB最近的点(不一定是一个顶点)和球体的中心之间的距离,看看它是小于或等于球体的半径。我们可以通过逼近球体的中心和AABB的距离得到这个值。</p> + +<p><img alt="" src="https://mdn.mozillademos.org/files/11837/sphere_vs_aabb.png" style="display: block; height: 282px; margin: 0px auto; width: 377px;"></p> + +<p>在 JavaScript, 我们可以像这样子做:</p> + +<pre class="brush: js">function intersect(sphere, box) { + // get box closest point to sphere center by clamping + var x = Math.max(box.minX, Math.min(sphere.x, box.maxX)); + var y = Math.max(box.minY, Math.min(sphere.y, box.maxY)); + var z = Math.max(box.minZ, Math.min(sphere.z, box.maxZ)); + + // this is the same as isPointInsideSphere + var distance = Math.sqrt((x - sphere.x) * (x - sphere.x) + + (y - sphere.y) * (y - sphere.y) + + (z - sphere.z) * (z - sphere.z)); + + return distance < sphere.radius; +} + +</pre> + +<h2 id="使用一个物理引擎">使用一个物理引擎</h2> + +<p><strong>3D physics engines</strong> provide collision detection algorithms, most of them based on bounding volumes as well. The way a physics engine works is by creating a <strong>physical body</strong>, usually attached to a visual representation of it. This body has properties such as velocity, position, rotation, torque, etc., and also a <strong>physical shape</strong>. This shape is the one that is considered in the collision detection calculations.</p> + +<p>We have prepared a <a href="http://mozdevs.github.io/gamedev-js-3d-aabb/physics.html">live collision detection demo</a> (with <a href="https://github.com/mozdevs/gamedev-js-3d-aabb">source code</a>) that you can take a look at to see such techniques in action — this uses the open-source 3D physics engine <a href="https://github.com/schteppe/cannon.js">cannon.js</a>.</p> + +<h2 id="See_also">See also</h2> + +<p>Related articles on MDN:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_collision_detection/Bounding_volume_collision_detection_with_THREE.js">Bounding volumes collision detection with Three.js</a></li> + <li><a href="/en-US/docs/Games/Techniques/2D_collision_detection">2D collision detection</a></li> +</ul> + +<p>External resources:</p> + +<ul> + <li><a href="http://www.gamasutra.com/view/feature/3383/simple_intersection_tests_for_games.php">Simple intersection tests for games</a> on Gamasutra</li> + <li><a href="https://en.wikipedia.org/wiki/Bounding_volume">Bounding volume</a> on Wikipedia</li> +</ul> diff --git a/files/zh-cn/games/techniques/3d_on_the_web/basic_theory/index.html b/files/zh-cn/games/techniques/3d_on_the_web/basic_theory/index.html new file mode 100644 index 0000000000..4e4041a8da --- /dev/null +++ b/files/zh-cn/games/techniques/3d_on_the_web/basic_theory/index.html @@ -0,0 +1,127 @@ +--- +title: 解释基本的3D原理 +slug: Games/Techniques/3D_on_the_web/Basic_theory +tags: + - 3D + - 坐标 + - 片段 + - 顶点 +translation_of: Games/Techniques/3D_on_the_web/Basic_theory +--- +<div>{{GamesSidebar}}</div> + +<p>这篇文章解释了当你开始使用 3D 工作的时候需要的所有有用的基本理论</p> + +<h2 id="坐标系统">坐标系统</h2> + +<p>3D 指的是有关在 3D 空间中所有形状的表示,并且可以使用坐标系统来计算其位置。</p> + +<p><img alt="Coordinate system" src="https://mdn.mozillademos.org/files/13326/mdn-games-3d-coordinate-system.png" style="height: 338px; width: 600px;"></p> + +<p>WebGL使用右手坐标系统 — <code>x</code> 轴向右, <code>y</code> 轴向上 <code>z</code> 轴指向屏幕外, 在上图可以看到.</p> + +<h2 id="物体">物体</h2> + +<p>使用顶点建立不同类型的物体. <strong>一个顶点</strong>是在3D坐标系中拥有坐标位置的一个点以及一些额外可以定义它的信息。 每个点都包含这些属性:</p> + +<ul> + <li><strong>位置</strong>: 在3D空间用来辨认 (<code>x</code>, <code>y</code>, <code>z</code>).</li> + <li><strong>颜色</strong>: 包含RGBA (R, G 和 B 分别是红, 绿, 蓝和alpha通道, alpha通道控制透明度 — 所有通道值的范围都是 <code>0.0</code> 到 <code>1.0</code>).</li> + <li><strong>法线:</strong> 描述顶点朝向.</li> + <li><strong>纹理</strong>: 顶点用来装饰模型表面的一张2D图片, 它是代替简单颜色的选择之一.</li> +</ul> + +<p>你可以使用这些信息建立几何体 — 这是一个立方体的例子:</p> + +<p><img alt="Cube" src="https://mdn.mozillademos.org/files/13324/mdn-games-3d-cube.png" style="height: 265px; width: 600px;"></p> + +<p>给定形状的一个面是顶点之间的一个平面. 例如, 一个立方体有8个不同的顶点和6个不同的面 每个面由4个顶点构成. 一条法线定义面的朝向. 同时, 连接这些点可以创建立方体的边. 这个几何体通过点和面构成, 材质使用的是一张纹理贴图, 这里使用一个纯蓝色或一张图片. 该物体的几何形状(geometry)由顶点和面构成,而材质(material)则是由纹理构成。如果我们将几何体和材质连接起来会得到一个网格(mesh).</p> + +<h2 id="渲染流程">渲染流程</h2> + +<p>渲染流程是个将之前准备好的模型输出到屏幕的过程. 3D渲染流程会接受使用顶点描述3D物体的原始数据作为输入用于处理, 并计算其片段(fragment), 然后渲染为像素(pixels)输出到屏幕.</p> + +<p><img alt="Rendering pipeline" src="https://mdn.mozillademos.org/files/13334/mdn-games-3d-rendering-pipeline.png" style="height: 225px; width: 600px;"></p> + +<p>上图中用到了如下术语:</p> + +<ul> + <li><strong>原始数据</strong>: 渲染流程中的输入 — 用顶点生成, 它可能是三角形, 点或线.</li> + <li><strong>片段</strong>: 一个像素的3D投射, 有着和像素一样的属性.</li> + <li><strong>像素</strong>: 屏幕上的2D网格中的点布置的点, 包含RGBA.</li> +</ul> + +<p>顶点和片段处理是可编程的 — 你可以<a href="https://developer.mozilla.org/zh-CN/docs/Games/Techniques/3D_on_the_web/GLSL_Shaders">编写自己的着色器</a> 来控制输出.</p> + +<h2 id="顶点处理">顶点处理</h2> + +<p>顶点处理是将独立的顶点信息组合成原始数据并设置其在3D空间中的坐标, 方便显示器识别. 就像是对你准备的场景进行拍照 — 你必须先放置物品, 设置相机参数, 然后开拍.</p> + +<p><img alt="Vertex processing" src="https://mdn.mozillademos.org/files/13336/mdn-games-3d-vertex-processing.png" style="height: 338px; width: 600px;"></p> + +<p>这个过程分为四步: 第一步是筹备世界坐标中的物体, 也被称为<strong>模型转换(model transformation)</strong>. 然后是<strong>视图转换(view transformation)</strong> , 这一步只关心位置和3D空间中摄像机的朝向设置. 摄像机有三个参数(位置, 方向和朝向), 在新创建的场景中必须定义这三个参数.</p> + +<p><img alt="Camera" src="https://mdn.mozillademos.org/files/13322/mdn-games-3d-camera.png" style="height: 225px; width: 600px;"></p> + +<p><strong>投射转换</strong>(projection transformation), 也被称作透视转换(perspective transformation), 这一步定义摄像机设置, 在此过程会设置哪些在摄像机中可见, 配置包含视野(field of view), 宽高比例(aspect ratio) 和可选的近裁剪和远裁剪参数. 阅读Three.js文章<a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js#Camera">摄像机</a>了解更多.</p> + +<p><img alt="Camera settings" src="https://mdn.mozillademos.org/files/13320/mdn-games-3d-camera-settings.png" style="height: 338px; width: 600px;"></p> + +<p>最后一步是<strong>视图窗口转换</strong>(viewport transformation), 这一步会将一切都输出给渲染流程中的下一步.</p> + +<h2 id="栅格化">栅格化</h2> + +<p>栅格化将原始数据(从顶点信息转换过来的)转换为一系列的片段.</p> + +<p><img alt="Rasterization" src="https://mdn.mozillademos.org/files/13332/mdn-games-3d-rasterization.png" style="height: 338px; width: 600px;"></p> + +<p>那些片段(2D像素的3D投射)是对应到像素网格的, 所以在渲染合成阶段最后他们会被2D的屏幕直接打印到像素点上.</p> + +<h2 id="片段合成">片段合成</h2> + +<p>片段合成关注关注的是纹理和光照 — 它会基于给定参数计算最终的颜色.</p> + +<p><img alt="Fragment processing" src="https://mdn.mozillademos.org/files/13328/mdn-games-3d-fragment-processing.png" style="height: 338px; width: 600px;"></p> + +<h3 id="纹理">纹理</h3> + +<p>纹理是在3D空间中用了是模型看起来更加真实的2D图片. 纹理是由称为纹素的单个纹理元素组合, 和像素组合的原理一样. 如果必要的话, 在渲染流程中的片段处理阶段添加纹理到模型上允许我们使用包装(wrapping)和过滤(filtering)进行适配.</p> + +<p>纹理包装允许你在3D模型上重复使用2D图片. 纹理过滤是纹理贴图的原始分辨率和将要呈现的片段的分辨率不同的时候, 会根据情况对纹理进行缩放.</p> + +<h3 id="光照">光照</h3> + +<p>我们在屏幕上看到的颜色是光照和模型颜色, 材质进行交互之后的最终结果. 灯光会被吸收和反射, 在WebGL中实现的标准<strong>Phong光照模型</strong> 有一下四种光照参数:</p> + +<ul> + <li><strong>漫反射</strong>: 遥远的直接光照, 就像太阳.</li> + <li><strong>高光</strong>: 点光源, 就像房间的白炽灯或闪光灯.</li> + <li><strong>环境色</strong>: 常量灯光, 可影响场景中的所有模型.</li> + <li><strong>自发光</strong>: 模型自身发出的光.</li> +</ul> + +<h2 id="输出合成">输出合成</h2> + +<p>在输出操作阶段所有来自3D空间的原始数据的片段会被转换到2D像素网格中, 然后打印到屏幕像素上.</p> + +<p><img alt="Output merging" src="https://mdn.mozillademos.org/files/13330/mdn-games-3d-output-merging.png" style="height: 338px; width: 600px;"></p> + +<p>在输出合成阶段同样可以忽略不必要的信息, 例如在屏幕外的模型参数或者被其他模型遮挡的模型, 因为是不可见的所以不会被计算.</p> + +<ul> +</ul> + +<h2 id="总结">总结</h2> + +<p>现在你知道了3D操作背后的基本原理. 如果你想去练习或者看学习demo, 看看下面的教程:</p> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">用Three.js创建基本demo</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">用Babylon.js创建基本demo</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">用PlayCanvas创建基本demo</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_A-Frame">用A-Frame创建基本demo</a></li> +</ul> + +<p>继续, 去创建一些炫酷3D实验吧!</p> + +<div class="grammarly-disable-indicator"> </div> diff --git a/files/zh-cn/games/techniques/3d_on_the_web/building_up_a_basic_demo_with_three.js/index.html b/files/zh-cn/games/techniques/3d_on_the_web/building_up_a_basic_demo_with_three.js/index.html new file mode 100644 index 0000000000..5826579352 --- /dev/null +++ b/files/zh-cn/games/techniques/3d_on_the_web/building_up_a_basic_demo_with_three.js/index.html @@ -0,0 +1,258 @@ +--- +title: Building up a basic demo with Three.js +slug: Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js +translation_of: Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js +--- +<div>{{GamesSidebar}}</div><p class="summary">游戏中一个典型的3D场景(最简单的那种) 包含标准的物品比如在坐标轴中的形状, 一个实际可看到他们的摄像机, 灯光和材质让其看起来不错, 动画使其生动等等.<strong> Three.js</strong>, 和其他3D库一样, 提供内置的helper 函数来帮助你尽可能快地实现通用的3D功能 . 在这篇文章我们会带你了解使用Three的基本知识, 包含设置开发者环境, 必要的HTML结构, Three.js对象基础, 以及如何创建一个基本的demo.</p> + +<div class="note"> +<p><strong>注意</strong>: 我们选择Three.js因为它是最流行的<a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> 库之一, 并且很容易上手. 我们不会介绍任何其他更好的WebGL库, 你可以自由选择其他库做尝试, 比如 <a href="http://www.ambiera.com/copperlicht/index.html">CopperLicht</a>, <a href="http://www.glge.org/">GLGE</a>, <a href="http://osgjs.org/">OSG.js</a>, <a href="https://code.google.com/p/o3d/">O3D</a>, 或者其他你喜欢的库.</p> +</div> + +<h2 id="环境设置">环境设置</h2> + +<p>开始用 Three.js, 你不需要准备太多, 只需:</p> + +<ul> + <li>确保使用的支持 <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a> 的现代浏览器, 例如最新版的Firefox 或 Chrome.</li> + <li>创建一个目录保存例子.</li> + <li>复制最新的压缩版<a href="http://threejs.org/build/three.min.js"> Three.js</a> 到你的目录.</li> + <li>用单独的浏览器tab打开 <a href="http://threejs.org/docs/">Three.js </a>文档 — 对应参考很有用.</li> +</ul> + +<h2 id="HTML_结构">HTML 结构</h2> + +<p>这是将用到的HTML结构.</p> + +<pre class="brush: html"><!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>MDN Games: Three.js demo</title> + <style> + body { margin: 0; padding: 0; } + canvas { width: 100%; height: 100%; } + </style> +</head> +<body> +<script src="three.min.js"></script> +<script> + var WIDTH = window.innerWidth; + var HEIGHT = window.innerHeight; + /* all our JavaScript code goes here */ +</script> +</body> +</html> +</pre> + +<p>It contains some basic information like the document {{htmlelement("title")}}, and some CSS to set the <code>width</code> and <code>height</code> of the {{htmlelement("canvas")}} element that Three.js will insert on the page to 100% so that it will fill the entire available viewport space. The first {{htmlelement("script")}} element includes the Three.js library in the page, and we will write our example code into the second one. There are two helper variables already included, which store the window's <code>width</code> and <code>height</code>.</p> + +<p>Before reading on, copy this code to a new text file, and save it in your working directory as <code>index.html</code>.</p> + +<h2 id="渲染器">渲染器</h2> + +<p>A renderer is a tool that displays scenes right in your browser. There are a few different renderers: WebGL is the default one, and the others you can use are Canvas, SVG, CSS and DOM. They differ in a way everything is rendered, so the WebGL implementation will work differently than the CSS one, but the idea is to have it look exactly the same for the end user. Thanks to this approach, a fallback can be used if the primary technology is not supported by the browser.</p> + +<pre class="brush: js">var renderer = new THREE.WebGLRenderer({antialias:true}); +renderer.setSize(WIDTH, HEIGHT); +renderer.setClearColor(0xDDDDDD, 1); +document.body.appendChild(renderer.domElement); +</pre> + +<p>We are creating a new WebGL renderer, setting it's size to fit the whole available space on the screen and appending the DOM structure to the page. You probably noticed the <code>antialias</code> parameter in the first line — this enables the edges of the shapes to be rendered a little more smoothly. The <code>setClearColor()</code> method sets our background to a light gray colour instead of the default black one.</p> + +<p>Add this code into the second {{htmlelement("script")}} element, just below the JavaScript comment.</p> + +<h2 id="场景">场景</h2> + +<p>A scene is the place where everything happens. When creating new objects in the demo, we will be adding them all to the scene to make them visible on the screen. In three.js, the scene is reperesented by a <code>Scene</code> object. Let's create it, by adding the following line below our previous lines:</p> + +<pre class="brush: js">var scene = new THREE.Scene(); +</pre> + +<p>Later on we will be using the <code>.add()</code> method to add objects to the scene.</p> + +<h2 id="摄像机">摄像机</h2> + +<p>我们有渲染场景,但是我们仍然需要一个摄像机来观察场景-想象没有摄像机的电影场景。下面的代码将摄像机放在三维坐标系中,并将其指向我们的场景,这样人们就能看到一些东西:</p> + +<pre class="brush: js">var camera = new THREE.PerspectiveCamera(70, WIDTH/HEIGHT); +camera.position.z = 50; +scene.add(camera); +</pre> + +<p>Add these lines to your code, below the prevous ones.</p> + +<p>There are other types of camera available (Cube, Orthographic), but the simplest is the Perspective one. To initialize it we have to set its field of view and aspect ratio — the first one is used to set how much is seen, and a proper aspect ratio is important for the objects on the screen to have the right proportions when rendered and not look stretched. Let's explain the values we are setting in the code above:</p> + +<ul> + <li>The value we set for the field of view, 70, is something we can experiment with — the higher the value, the greater the amount of scene the camera will show. Imagine a normal camera view, versus a fish eye effect, which allows a lot more to be seen. The default value is 50.</li> + <li>The aspect ratio is set to the current width and height of the window so it will be dynamically adjusted. We could set a fixed ratio — for example 16 ⁄ 9, which is the aspect ratio of a widescreen TV. The default value is 1.</li> + <li>The <code>z</code> position with the value of 50 units is the distance between the camera and the center of the scene on the <code>z</code> axis — here we're moving the camera back so the objects on the scene can be viewed. 50 feels ok as it's not too near and not too far and the sizes of the objects allow them to stay on the scene within the given field of view. The <code>x</code> and <code>y</code> values, if not specified, will default to 0.</li> +</ul> + +<p>You should experiment with these values and see how they change what you see in the scene.</p> + +<div class="note"> +<p><strong>Note</strong>: The distance values (e.g. for the camera z position) are unitless, and can basically be anything you deem suitable for your scene — milimeters, meters, feet, or miles — it's up to you.</p> +</div> + +<h2 id="Rendering_the_scene">Rendering the scene</h2> + +<p>Everything is ready, but we still can't see anything. Although we set the renderer up, we still have to actually render everything. Our <code>render()</code> function will do this job, with a little help from <code><a href="/en-US/docs/Web/API/window/requestAnimationFrame">requestAnimationFrame()</a></code>, which causes the scene to be re-rendered constantly on every frame:</p> + +<pre class="brush: js">function render() { + requestAnimationFrame(render); + renderer.render(scene, camera); +} +render(); +</pre> + +<p>On every new frame the <code>render</code> function is invoked and the <code>renderer</code> renders the <code>scene</code> and the <code>camera</code>. Right after the function declaration we're invoking it for the first time to start the loop, after which it will be used indefinitely.</p> + +<p>Again add the new code below your previous additions, then try saving the file and loading it in your browser. You should now see a gray window. Congratulations!</p> + +<h2 id="Geometry">Geometry</h2> + +<p>Now the scene is properly rendering we can start adding 3D shapes to it. To speed up development Three.js provides a bunch of predefined primitives that you can to create shapes instantly in a single line of code. There's cubes, spheres, cylinders and more complicated shapes available. Drawing the needed vertices and faces for given shape is taken care of by the framework, so we can focus on the high level coding. Let's start by defining the geometry for a cube shape — add the following just above the <code>render()</code> function:</p> + +<pre class="brush: js">var boxGeometry = new THREE.BoxGeometry(10, 10, 10); +</pre> + +<p>In this case we define a simple cube that is 10 x 10 x 10 units. The geometry itself is not enough though — we also need a material that will be used for our shape.</p> + +<h2 id="Material">Material</h2> + +<p>Material is that thing covering the object — the colors or texture on its surface. In our case we will use a simple blue color to paint our box. There are predefined materials that can be used: Basic, Phong, Lambert. We will play with the last two later on, but for now the Basic one should be enough:</p> + +<pre class="brush: js">var basicMaterial = new THREE.MeshBasicMaterial({color: 0x0095DD}); +</pre> + +<p>Add this line below the previous one.</p> + +<p>Our material is ready, but what to do next?</p> + +<h2 id="Mesh">Mesh</h2> + +<p>To apply the material to a geometry a mesh is used. It takes a shape and adds the specified material to every face:</p> + +<pre class="brush: js">var cube = new THREE.Mesh(boxGeometry, basicMaterial); +</pre> + +<p>Again, add this line below the previous one.</p> + +<h2 id="Adding_the_cube_to_the_scene">Adding the cube to the scene</h2> + +<p>We've now created the actual cube using the geometry and material defined earlier. The last thing to do is to actually add the cube to our scene — add this line below the previous one:</p> + +<pre class="brush: js">scene.add(cube); +</pre> + +<p>If you save and refresh now, your object will look like a square, because it's facing the camera. The good thing about objects is that we can move them on the scene however we want, for example rotating and scaling as we like. Let's apply a little bit of rotation to the cube, so we can see more than one face — again, add below the previous one:</p> + +<pre class="brush: js">cube.rotation.set(0.4, 0.2, 0); +</pre> + +<p>Congratulations, you've created your first object in a 3D environment! It was easier than you thought, right? Here's how it should look:</p> + +<p><img alt="Blue cube on a gray background rendered with Three.js." src="https://mdn.mozillademos.org/files/11849/cube.png" style="display: block; height: 400px; margin: 0px auto; width: 600px;"></p> + +<p>And here's the code we have created so far:</p> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/end3r/bwup75fa/","","350")}}</p> + +<p>You can also <a href="https://github.com/end3r/MDN-Games-3D/blob/gh-pages/Three.js/cube.html">check it out on GitHub</a>.</p> + +<h2 id="More_shapes_and_materials">More shapes and materials</h2> + +<p>Now we will add more shapes to the scene and explore other shapes, materials, lighting, and more. Let's move the cube to the left to make space for some friends — add the following line just below the previous one:</p> + +<pre class="brush: js">cube.position.x = -25; +</pre> + +<p>Now onto the shapes and materials: what would you say for a torus using the Phong material? Try adding the following lines just below the lines that define the cube.</p> + +<pre class="brush: js">var torusGeometry = new THREE.TorusGeometry(7, 1, 6, 12); +var phongMaterial = new THREE.MeshPhongMaterial({color: 0xFF9500}); +var torus = new THREE.Mesh(torusGeometry, phongMaterial); +scene.add(torus); +</pre> + +<p>Thee lines will add a torus geometry; the <code>TorusGeometry()</code> method's parameters define and the parameters are <code>radius</code>, <code>tube diameter</code>, <code>radial segment count</code> and <code>tubular segment count</code>. The Phong material should look more glossy than the simple color of the box that was using the Basic material, although at the moment it will just look black.</p> + +<p>We can have even crazier predefined shapes; let's play some more — add the following lines below the ones that defined the torus:</p> + +<pre class="brush: js">var dodecahedronGeometry = new THREE.DodecahedronGeometry(7); +var lambertMaterial = new THREE.MeshLambertMaterial({color: 0xEAEFF2}); +var dodecahedron = new THREE.Mesh(dodecahedronGeometry, lambertMaterial); +dodecahedron.position.x = 25; +scene.add(dodecahedron); +</pre> + +<p>This time we are creating a dodecahedron, which is a shape containing twelve flat faces. The parameter <code>DodecahedronGeometry()</code> takes is the size of the object. We're using a Lambert material here, which is similar to Phong, but should be less glossy (again, black for now.) We're moving the object to the right, so it's not in the same place as the box or torus.</p> + +<p>As mentioned above, the new objects currently just look black. To have both the Phong and Lambert materials properly visible we need a source of light.</p> + +<h2 id="Lights">Lights</h2> + +<p>There are various types of light sources available in Three.js; the most basic one is the <code>PointLight</code>, which works like a flashlight — shinig a spotlight in a given direction. Add the following below your shapre definitions:</p> + +<pre class="brush: js">var light = new THREE.PointLight(0xFFFFFF); +light.position.set(-10, 15, 50); +scene.add(light); +</pre> + +<p>We define a white point of light, set it's position a bit away from the center of the scene so it can light up some parts of the shapes, and add it to the scene. Now everything works as it should — all three shapes are visible. You should check the documentation for other types of light like Ambient, Directional, Hemisphere or Spot, and experiment with placing them on the scene to see the effects.</p> + +<p><img alt="Shapes: blue cube, dark yellow torus and dark gray dodecahedron on a gray background rendered with Three.js." src="https://mdn.mozillademos.org/files/11851/shapes.png" style="height: 400px; width: 600px;"></p> + +<p>This looks a little bit boring though. In a game something is usually happening — we can see animations and such — so let's try to breathe a little life into those shapes by animating them.</p> + +<h2 id="Animation">Animation</h2> + +<p>We already used rotation to adjust the position of the cube; we could also scale the shapes, or change thier positions. To show actual animation, we need to make changes to these values inside the render loop so, they are updated on every frame.</p> + +<h3 id="Rotation">Rotation</h3> + +<p>Rotating is quite easy — all you need to do is to add a defined value to the given direction of the rotation on each frame. Add this line of code right after the <code>requestAnimationFrame()</code> invocation in the <code>render</code> function:</p> + +<pre class="brush: js">cube.rotation.y += 0.01; +</pre> + +<p>It will rotate the cube on every frame by a tiny bit, so it will look like a smooth animation.</p> + +<h3 id="Scaling">Scaling</h3> + +<p>We can also scale a given object. By applying a constant value we could make it grow or shrink once, but let's make it more interesting. First, we will need a helper variable called <code>t</code> for counting the elapsed time. Add it right before the <code>render()</code> function:</p> + +<pre class="brush: js">var t = 0; +</pre> + +<p>Now let's increase the value by a given constant value on each frame of the animation; add the following lines just below the <code>requestAnimationFrame()</code> invocation:</p> + +<pre class="brush: js">t += 0.01; +torus.scale.y = Math.abs(Math.sin(t)); +</pre> + +<p>This way we'll be able to use <code>Math.sin</code> and end up with quite an interesting result: this will scale the torus and repeat the whole process, as <code>sin</code> is a periodic function. We're wrapping the scale value in <code>Math.abs</code> to pass the absolute values (greater or equal to 0), because sin is between -1 and 0, and for negative values the torus might render unexpectedly (in this case it looks black half the time.)</p> + +<p>Now onto the movement part.</p> + +<h3 id="Moving">Moving</h3> + +<p>Beside rotation and scaling we can also move objects around the scene. Add the following, again just below the <code>requestAnimationFrame()</code> invocation:</p> + +<pre class="brush: js">dodecahedron.position.y = -7*Math.sin(t*2); +</pre> + +<p>This will move the dodecahedron up and down by applying the <code>sin()</code> value to the y axis on each frame, with a little bit of adjustment to make it look cooler. Try changing the values to see how it affects the animations.</p> + +<h2 id="Conclusion">Conclusion</h2> + +<p>Here's the final piece of the code:</p> + +<p>{{JSFiddleEmbed("https://jsfiddle.net/rybr720u/","","350")}}</p> + +<p>You can also <a href="https://github.com/end3r/MDN-Games-3D/blob/gh-pages/Three.js/shapes.html">see it on GitHub</a> and <a href="https://github.com/end3r/MDN-Games-3D/">fork the repository</a> if you want to play with it yourself locally. Now you know the basics of Three.js, you can get back to the parent page about <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web">3D on the Web</a>.</p> + +<p>You should also try learning raw WebGL, so you can get a better understanding of what's going on. See our <a href="/en-US/docs/Web/API/WebGL_API">WebGL documentation</a>.</p> diff --git a/files/zh-cn/games/techniques/3d_on_the_web/glsl_shaders/index.html b/files/zh-cn/games/techniques/3d_on_the_web/glsl_shaders/index.html new file mode 100644 index 0000000000..34e20c994d --- /dev/null +++ b/files/zh-cn/games/techniques/3d_on_the_web/glsl_shaders/index.html @@ -0,0 +1,180 @@ +--- +title: GLSL着色器 +slug: Games/Techniques/3D_on_the_web/GLSL_Shaders +tags: + - GLSL + - 片段着色器 + - 着色器 + - 顶点着色器 +translation_of: Games/Techniques/3D_on_the_web/GLSL_Shaders +--- +<div>{{GamesSidebar}}</div><p class="summary"><span class="seosummary">使用GLSL的着色器(shader), GLSL是一门特殊的有着类似于C语言的语法, 在图形管道(graphic pipeline)中直接可执行的OpenGL着色语言. 着色器有两种类型 -- 顶点着色器(Vertex Shader)和片段着色器(Fragment Shader). 前者是将形状转换到真实的3D绘制坐标中, 后者是计算最终渲染的颜色和其他属性用的.</span></p> + +<p>GLSL不同于JavaScript, 它是强类型语言, 并且内置很多数学公式用于计算向量和矩阵. 快速编写着色器非常复杂, 但创建一个简单的着色器并不难. 在这篇文章我们将介绍使用着色器的基础知识, 并且构建一个使用Three.js的例子来加速代码编写.</p> + +<p>你可能记得<a href="/en-US/docs/Games/Techniques/3D_on_the_web/Basic_theory">基本原理</a>那篇文章, 一个顶点(vertex)是在空间中有自己3D坐标的点, 并且通常包含些被定义的其他信息. 空间本身会被坐标系统定义. 在那个3D空间中一切都是关于形状的呈现.</p> + +<h2 id="着色器类型">着色器类型</h2> + +<p>一个着色器实际上就是一个绘制东西到屏幕上的函数. 着色器运行在GPU中, 它对这些操作进行了很多的优化, 这样你就可以卸载很多不必要的CPU, 然后集中处理能力去执行你自己的代码.</p> + +<h3 id="顶点着色器">顶点着色器</h3> + +<p>顶点着色器操作3D空间的坐标并且每个顶点都会调用一次这个函数. 其目的是设置 <code>gl_Position</code> 变量 -- 这是一个特殊的全局内置变量, 它是用来存储当前顶点的位置:</p> + +<pre class="brush: glsl">void main() { + gl_Position = makeCalculationsToHaveCoordinates; +} +</pre> + +<p>这个 <code>void main()</code> 函数是定义全局<code>gl_Position</code> 变量的标准方式. 所有在这个函数里面的代码都会被着色器执行. 如果将3D空间中的位置投射到2D屏幕上这些信息都会保存在计算结果的变量中.</p> + +<h3 id="片段着色器">片段着色器</h3> + +<p>片段 (或者纹理) 着色器 在计算时定义了每像素的 RGBA 颜色 — 每个像素只调用一次片段着色器. 这个着色器的作用是设置 <code>gl_FragColor</code> 变量, 也是一个GLSL内置变量:</p> + +<pre class="brush: glsl">void main() { + gl_FragColor = makeCalculationsToHaveColor; +} +</pre> + +<p>计算结果包含RGBA颜色信息.</p> + +<h2 id="例子">例子</h2> + +<p>让我们构建一个简单的例子来解释这些着色器的动作. 假设你已经看过<a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js 教程</a>并掌握了场景, 物体和材质的基本概念.</p> + +<div class="note"> +<p><strong>注意</strong>: 记住你没必要使用Three.js或者其他库来编写着色器 -- 纯<a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">WebGL</a> 完全够了. 我们这里使用Three.js来制作背景代码更简单和易理解. 所以你只需关注着色器代码. Three.js和其他3D库给你抽象了很多东西出来 -- 如果你想要用纯WebGL创建这个例子, 你得写很多其他的代码才能运行.</p> +</div> + +<h3 id="环境设置">环境设置</h3> + +<p>要开始编写WebGL着色器你不需要做太多, 只需:</p> + +<ul> + <li>确保你在使用对 <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API">WebGL</a> 有良好支持的现代浏览器, 比如最新版的Firefox或Chrome.</li> + <li>创建一个目录保存你的实验.</li> + <li>拷贝一份的 <a href="http://threejs.org/build/three.min.js">压缩版的Three.js 库</a> 到你的目录.</li> +</ul> + +<h3 id="HTML_结构">HTML 结构</h3> + +<p>这是将用到的HTML结构.</p> + +<pre class="brush: html"><!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>MDN Games: Shaders demo</title> + <style> + body { margin: 0; padding: 0; font-size: 0; } + canvas { width: 100%; height: 100%; } + </style> + <script src="three.min.js"></script> +</head> +<body> + <script id="vertexShader" type="x-shader/x-vertex"> + // 顶点着色器代码在这里 + </script> + <script id="fragmentShader" type="x-shader/x-fragment"> + // 片段着色器代码在这里 + </script> + <script> + // 场景设置在这里 + </script> +</body> +</html> +</pre> + +<p>他包含了一些基本信息比如 文档的 {{htmlelement("title")}}, 并且设置了{{htmlelement("canvas")}}元素css样式的宽高, Three.js会插入到页面中占满整个可视区域. {{htmlelement("script")}}元素在包含Three.js库的{{htmlelement("head")}}中. 我们的代码将卸载{{htmlelement("body")}}标签中的script标签中:</p> + +<ol> + <li>首先将包含顶点着色器.</li> + <li>然后包含片段着色器.</li> + <li>最后会包含一些生成实际场景的JavaScript代码.</li> +</ol> + +<p>阅读之前, 复制这些代码到一个新的文本文件中, 保存到你的工作目录作为 <code>index.html</code>. 我们将在这个文件中创建一个简单的立方体来解释着色器是如何工作的.</p> + +<h3 id="立方体源代码">立方体源代码</h3> + +<p>我们可以复用<a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Building up a basic demo with Three.js</a> 中立方体的源代码, 大多数元素例如渲染器, 摄像机和灯光都没有发生改变, 但是基本的材质会用到自己写的着色器.</p> + +<p>去<a href="https://github.com/end3r/MDN-Games-3D/blob/gh-pages/Three.js/cube.html">cube.html file on GitHub</a>中, 复制第二个{{htmlelement("script")}}元素中所有的JavaScript代码, 粘贴到当前例子中的第三个<code><script></code> 标签中. 保存并运行 <code>index.html</code> — 然后你会看到一个蓝色立方体</p> + +<h3 id="顶点着色器代码">顶点着色器代码</h3> + +<p>让我们继续编写顶点着色器 — 添加下面这段代码到你body的第一个 <code><script></code> 标签:</p> + +<pre class="brush: glsl">void main() { + gl_Position = projectionMatrix * modelViewMatrix * vec4(position.x+10.0, position.y, position.z+5.0, 1.0); +} +</pre> + +<p>每次的<code>gl_Position</code> 的结果是计算model-view矩阵和投射矩阵和投射矩阵相乘并得到最后的顶点位置.</p> + +<div class="note"> +<p><strong>注意</strong>: 你可以在 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Basic_theory#Vertex_processing">顶点处理</a>中学到更多关于模型, 视图和投射变换, 并且你可以在文末看到更多学习链接.</p> +</div> + +<p><code>projectionMatrix</code> 和 <code>modelViewMatrix</code> 两个函数都是Three.js提供的, 并且传入了一个新的3D位置向量, 转成着色器之后直接导致立方体向 <code>x</code> 轴移动10个单位, 向<code>z</code> 轴移动了5个单位. 我们可以忽略第四个参数并且保持为默认的<code>1.0</code> ; 这是用来控制3D空间中订单位置裁剪的, 这个例子中不需要.</p> + +<h3 id="纹理着色器代码">纹理着色器代码</h3> + +<p>现在我们将添加纹理着色器代码 — 将以下代码复制到第二个 <code><script></code> 标签中:</p> + +<pre class="brush: glsl">void main() { + gl_FragColor = vec4(0.0, 0.58, 0.86, 1.0); +} +</pre> + +<p>这将设置一个RGBA颜色来重建当前的蓝色灯光 — 前三个浮点数(范围是0.0到1.0) 代表红, 绿, 蓝, 第四个值代表alpha通道, 控制透明度(0.0完全透明, 1.0是完全不透明).</p> + +<h3 id="设置着色器">设置着色器</h3> + +<p>实际上是创建了一个新的着色器给立方体, 先用 <code>basicMaterial</code> 来定义:</p> + +<pre class="brush: js">// var basicMaterial = new THREE.MeshBasicMaterial({color: 0x0095DD}); +</pre> + +<p>然后创建 <a href="http://threejs.org/docs/#Reference/Materials/ShaderMaterial"><code>shaderMaterial</code></a>:</p> + +<pre class="brush: js">var shaderMaterial = new THREE.ShaderMaterial( { + vertexShader: document.getElementById( 'vertexShader' ).textContent, + fragmentShader: document.getElementById( 'fragmentShader' ).textContent +}); +</pre> + +<p>这个着色器材质使用脚本中的代码并将它们赋予给材质所赋予的物体</p> + +<p>然后, 在定义立方体材质那一行我们需要替换<code>basicMaterial</code> :</p> + +<pre class="brush: js">var cube = new THREE.Mesh(boxGeometry, basicMaterial); +</pre> + +<p>使用新创建的 <code>shaderMaterial</code>:</p> + +<pre class="brush: js">var cube = new THREE.Mesh(boxGeometry, shaderMaterial); +</pre> + +<p>Three.js编译和运行这两个这两个着色器到材质所在的网格(mesh)上. 在这个例子中, 为立方体添加了有顶点和纹理着色器. 好了, 你已经创建了最简单的着色器, 祝贺!</p> + +<p>下图是立方体最终效果:</p> + +<p><img alt="Three.js blue cube demo" src="http://end3r.github.io/MDN-Games-3D/Shaders/img/cube.png" style="display: block; margin: 0px auto;"></p> + +<p>它看起来好像和Three.js的立方体demo一样, 但不同的是, 位置有点轻微变化, 而且同样的蓝色使用的是着色器实现. 你可以看看实际操作, 这里有最终代码:{{JSFiddleEmbed("https://jsfiddle.net/end3r/LL55bhrz/","","350")}}</p> + +<p>你也可以在 <a href="https://github.com/end3r/MDN-Games-3D/blob/gh-pages/Shaders/shaders.html">GitHub</a> 看这个例子.</p> + +<h2 id="总结">总结</h2> + +<p>本文教了你最基本的着色器实现. 我们虽然只能做这么多, 但你可以用着色器做很更多炫酷的事情 — 在 <a href="http://shadertoy.com/">ShaderToy</a> 上去看真正炫酷的例子找找灵感吧</p> + +<h2 id="其他链接">其他链接</h2> + +<ul> + <li><a href="http://learningwebgl.com/blog/?page_id=1217">学习WebGL</a> — 基本WebGL知识</li> + <li><a href="http://webglfundamentals.org/webgl/lessons/webgl-shaders-and-glsl.html">WebGL着色器和WebGL中的GLSL基础</a> — GLSL特定信息</li> +</ul> diff --git a/files/zh-cn/games/techniques/3d_on_the_web/index.html b/files/zh-cn/games/techniques/3d_on_the_web/index.html new file mode 100644 index 0000000000..08e1fe27a7 --- /dev/null +++ b/files/zh-cn/games/techniques/3d_on_the_web/index.html @@ -0,0 +1,118 @@ +--- +title: 3D games on the Web +slug: Games/Techniques/3D_on_the_web +tags: + - Games + - Graphics + - NeedsContent + - NeedsExample + - NeedsTranslation + - TopicStub + - WebGL + - three.js +translation_of: Games/Techniques/3D_on_the_web +--- +<div>{{GamesSidebar}}</div> + +<p class="summary">为了web上丰富的游戏体验,一个好武器是webGL, 并呈现在HTML的 {{htmlelement("canvas")}}元素上。WebGL基本上是Web的OpenGL ES 2.0版本 — 作为一个JavaScript API,它提供了能构建丰富的交互式动画和游戏的工具。你可以使用硬件加速的JavaScript 生成和呈现动态3D图像。</p> + +<h2 id="文档和浏览器支持">文档和浏览器支持</h2> + +<p> <a href="/en-US/docs/Web/API/WebGL_API">WebGL</a>项目的文档和规范由<a href="https://www.khronos.org/">Khronos Group维护</a>,而非大部分Web APIs采用的W3C。它很好地支持了现代浏览器甚至是移动手机,由此你无需担心太多。主流浏览器均支持WebGL,你需要关注的仅仅是在你使用的设备上进行优化。</p> + +<p>目前进行的尝试是在不久的将来促成WebGL 2.0版本(基于OpenGL ES 3.0版本)的发布,WebGL 2.0版本将会带来许多改进,并且将会帮助开发者使用现在强大的硬件,为现代Web开发游戏。</p> + +<h2 id="对基本3D理论的解释">对基本3D理论的解释</h2> + +<p>基本3D理论的核心围绕在3D空间的形状呈现上,通过使用坐标系计算出它们的位置。寻找你所要的信息,请参考我们的文章 <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Basic_theory">Explaining basic 3D theory</a> 。</p> + +<h2 id="预先概念">预先概念</h2> + +<p>你可以使用 WebGL 做很多事情。你需要深入了解和学习一些预先的理念——像着色器、碰撞检测或最新的热门话题——Web上的虚拟现实。</p> + +<h3 id="着色器">着色器</h3> + +<p>值得提及的是着色器,着色器本身就是一个独立的故事。着色器使用GLSL,一个特殊的和C语言相似的 OpenGL 着色语言,但C语言直接通过图像管道执行。</p> + +<h3 id="碰撞检测">碰撞检测</h3> + +<p>很难想象没有碰撞检测的游戏——我们总是需要计算出一个物体什么时候会撞击到另一个物体。我们有一些可利用的信息供你学习:</p> + +<ul> + <li><a href="/en-US/docs/Games/Techniques/2D_collision_detection">2D 碰撞检测</a></li> + <li><a href="/en-US/docs/Games/Techniques/3D_collision_detection">3D 碰撞检测</a></li> +</ul> + +<h3 id="Web虚拟现实">Web虚拟现实</h3> + +<p>虚拟现实这一概念并不新鲜,但由于硬件的进步,它大有席卷网络之势,如<a href="https://www.oculus.com/en-us/rift/">Oculus Rift</a>和(目前实验性的) <a href="/en-US/docs/Web/API/WebVR_API">WebVR API</a>,它们从VR硬件中捕获信息并使其可在JavaScript中应用。有关的详细信息请阅读 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/WebVR">WebVR-Web 虚拟现实</a>。</p> + +<p>还有一篇<a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_A-Frame">用A-Frame构建基本demo</a>的文章,向您展示了使用<a href="http://aframe.io/">A-Frame</a>框架构建3D环境的虚拟现实是多么的简单。</p> + +<h2 id="库和框架的兴起">库和框架的兴起</h2> + +<p>编码原生WebGL是相当复杂的,但从长远来看,您需要了解它,如果您的项目变得更加先进(请从参阅我们的<a href="/en-US/docs/Web/API/WebGL_API">WebGL文档</a>开始)。对于现实世界中的项目,您可能还会使用框架来加快开发,并帮助您管理正在处理的项目。使用3D游戏框架还有助于优化性能,因为您使用的工具会处理很多问题,因此您可以专注于构建游戏本身。</p> + +<p>最流行的JavaScript 3D库是<a href="http://threejs.org/">Three.js</a>,这是一个多用途工具,它使常见的3D技术更易于实现。还有其他流行的游戏开发库和框架值得检查。<a href="https://aframe.io">A-Frame</a>、<a href="https://playcanvas.com/">PlayCanvas</a>和<a href="http://www.babylonjs.com/">Babylon.js</a>是最容易辨认的,拥有丰富的文档、在线编辑器和活跃的社区。</p> + +<h3 id="使用_A-Frame_搭建一个基础Demo">使用 A-Frame 搭建一个基础Demo</h3> + +<p>A-Frame是一个用于搭建3D和VR体验的Web框架。在内部,它是一个具有已声明的实体组件模式的three.js框架,也就是说我们只需借助HTML即可搭建场景。请参阅<a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_A-Frame">Building up a basic demo with A-Frame</a>子页面来了解创建Demo的步骤。</p> + +<h3 id="使用_Babylon.js_搭建一个基础Demo">使用 Babylon.js 搭建一个基础Demo</h3> + +<p><span class="seosummary">Babylon.js 是最受开发者欢迎的3D游戏引擎之一。与其他任何3D库一样,它提供了内置函数,帮助您更快地实现常见的3D功能。请参阅 <a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Building up a basic demo with Babylon.js</a> 子页,其中包括建立一个开发环境,构建必要的HTML,以及编写JavaScript代码。</span></p> + +<h3 id="使用_PlayCanvas_搭建一个基础Demo">使用 PlayCanvas 搭建一个基础Demo</h3> + +<p>PlayCanvas是一个流行的GitHub开源3D WebGL游戏引擎,有在线编辑器和良好的文档。更多详细信息 请参阅<a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">Building up a basic demo with PlayCanvas</a> ,文章将进一步介绍如何使用PlayCanvas库和联机编辑器搭建例子。</p> + +<h3 id="使用_Three.js_搭建一个基础Demo">使用 Three.js 搭建一个基础Demo</h3> + +<p>Three.js,与任何其他库一样,它给了您一个巨大的便利:不必编写数百行WebGL代码来构建任何有趣的东西,您可以使用内置的helper函数来轻松、快速地完成任务。请参阅 <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Building up a basic demo with Three.js</a> 子页 逐步创建Demo。</p> + +<h3 id="使用_Whitestorm.js_搭建一个基础Demo">使用 Whitestorm.js 搭建一个基础Demo</h3> + +<p>Whitestorm.js 是一个基于 <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Three.js</a> 技术上的框架。它的主要区别是内置的物理引擎和插件系统 基于NPM。请参阅 <a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Whitestorm.js">Building up a basic demo with Whitestorm.js</a> 了解更多信息、教程和例子制作基本的,甚至配合Three.js 制作更复杂的应用程序或游戏.</p> + +<h3 id="其它工具">其它工具</h3> + +<p><a href="http://unity3d.com/">Unity</a> 和 <a href="https://www.unrealengine.com/">Unreal</a> 可以将你的游戏通过 <a href="/en-US/docs/Games/Tools/asm.js">asm.js</a> 输出到WebGL,因此你可以自由地使用这些工具与技术来构建可被输出到Web上的游戏。</p> + +<p><img alt="" src="http://end3r.github.io/MDN-Games-3D/A-Frame/img/shapes.png" style="border-style: solid; border-width: 1px; display: block; margin: 0px auto;"></p> + +<h2 id="Where_to_go_next">Where to go next</h2> + +<p>With this article we just scratched the surface of what's possible with currently available technologies. You can build immersive, beautiful and fast 3D games on the Web using WebGL, and the libraries and frameworks build on top of it.</p> + +<h3 id="Source_code">Source code</h3> + +<p>You can find all the source code for this series <a href="http://end3r.github.io/MDN-Games-3D/">demos on GitHub</a>.</p> + +<h3 id="APIs">APIs</h3> + +<ul> + <li><a href="/en-US/docs/Web/API/Canvas_API">Canvas API</a></li> + <li><a href="/en-US/docs/Web/API/WebGL_API">WebGL API</a></li> + <li><a href="/en-US/docs/Web/API/WebVR_API">WebVR API</a></li> +</ul> + +<h3 id="Frameworks">Frameworks</h3> + +<ul> + <li><a href="http://threejs.org/">Three.js</a></li> + <li><a href="http://whitestormjs.xyz/">Whitestorm.js</a> (based on Three.js)</li> + <li><a href="https://playcanvas.com/">PlayCanvas</a></li> + <li><a href="http://www.babylonjs.com/">Babylon.js</a></li> + <li><a href="http://aframe.io/">A-Frame</a></li> +</ul> + +<h3 id="Tutorials">Tutorials</h3> + +<ul> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Three.js">Building up a basic demo with Three.js</a></li> + <li><a href="https://developer.mozilla.org/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Whitestorm.js">Building up a basic demo with Whitestorm.js</a></li> + <li><a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_PlayCanvas">Building up a basic demo with PlayCanvas</a></li> + <li><a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_Babylon.js">Building up a basic demo with Babylon.js</a></li> + <li><a href="/en-US/docs/Games/Techniques/3D_on_the_web/Building_up_a_basic_demo_with_A-Frame">Building up a basic demo with A-Frame</a></li> +</ul> diff --git a/files/zh-cn/games/techniques/async_scripts/index.html b/files/zh-cn/games/techniques/async_scripts/index.html new file mode 100644 index 0000000000..2f34e0d144 --- /dev/null +++ b/files/zh-cn/games/techniques/async_scripts/index.html @@ -0,0 +1,49 @@ +--- +title: asm.js的异步脚本 +slug: Games/Techniques/Async_scripts +translation_of: Games/Techniques/Async_scripts +--- +<div>{{GamesSidebar}}</div><div>{{IncludeSubnav("/zh-CN/docs/Games")}}</div> + +<div class="summary"> +<p><span class="seoSummary">每个中型或大型游戏都应编译<a href="https://developer.mozilla.org/en-US/docs/Games/Tools/asm.js">asm.js</a>代码作为异步脚本的一部分,以便浏览器能够最大限度地灵活地优化编译过程。 在Gecko中,异步编译允许JavaScript引擎在游戏加载时缓存主线程的asm.js,并缓存生成的机器代码,这样游戏就不需要在随后的加载中编译(从Firefox 28开始)。 要查看差异,请切换<code>javascript.options.parallel_parsing</code> in <code>about:config</code>.</span></p> +</div> + +<h2 id="异步执行">异步执行</h2> + +<p>获取异步编译非常简单:编写JavaScript时,只需使用<code>async</code>属性即可:</p> + +<pre class="brush: js"><script async src="file.js"></script></pre> + +<p>或者,通过脚本来做同样的事情:</p> + +<pre class="brush: js">var script = document.createElement('script'); +script.src = "file.js"; +document.body.appendChild(script);</pre> + +<p>(从脚本中创建的脚本默认为异步。) 默认的HTML shell Emscripten生成后者。</p> + +<h2 id="什么时候用async或者不用">什么时候用async或者不用?</h2> + +<p>两种常见的情况下是脚本是<strong>非</strong>异步的(由<a href="https://www.w3.org/TR/html5/scripting-1.html">HTML规范</a>定义)</p> + +<pre class="brush: js"><script async>code</script></pre> + +<p>以及</p> + +<pre class="brush: js">var script = document.createElement('script'); +script.innerHTML = "code"; +document.body.appendChild(script);</pre> + +<p>两者都被视为“内联”脚本,阻塞其余所有任务,进行编译,编译完成后立即执行。</p> + +<p>如果你的代码是一个JS字符串呢? 而不是使用eval或innerHTML,这两者都会触发同步编译,您应该使用Blob和URL对象:</p> + +<pre class="brush: js">var blob = new Blob([codeString]); +var script = document.createElement('script'); +var url = URL.createObjectURL(blob); +script.onload = script.onerror = function() { URL.revokeObjectURL(url); }; +script.src = url; +document.body.appendChild(script);</pre> + +<p>使用<code>src</code>而不是<code>innerHTML,则该</code>脚本是异步的。</p> diff --git a/files/zh-cn/games/techniques/control_mechanisms/index.html b/files/zh-cn/games/techniques/control_mechanisms/index.html new file mode 100644 index 0000000000..07b6e0b6e1 --- /dev/null +++ b/files/zh-cn/games/techniques/control_mechanisms/index.html @@ -0,0 +1,78 @@ +--- +title: Implementing game control mechanisms +slug: Games/Techniques/Control_mechanisms +tags: + - Controls + - Desktop + - Gamepad + - Games + - JavaScript + - Laptop + - Mobile + - NeedsTranslation + - TopicStub + - keyboard + - mouse + - touch +translation_of: Games/Techniques/Control_mechanisms +--- +<div>{{GamesSidebar}}</div><p class="summary">One of HTML5's main advantages as a game development platform is the ability to run on various platforms and devices. Streamlining cross device differences creates multiple challenges, not least when providing appropriate controls for different contexts. In this series of articles we will show you how you can approach building a game that can be played using touchscreen smartphones, mouse and keyboard, and also less common mechanisms such as gamepads.</p> + +<h2 id="Case_study">Case study</h2> + +<p>We'll be using the <a href="http://rogers2.enclavegames.com/demo/">Captain Rogers: Battle at Andromeda demo</a> as an example.</p> + +<p><img alt="Captain Rogers: Battle at Andromeda - cover of the game containing Enclave Games and Blackmoon Design logos, Roger's space ship and title of the game." src="https://mdn.mozillademos.org/files/13849/captainrogers2-cover.png" style="display: block; height: 325px; margin: 0px auto; width: 575px;"></p> + +<p>Captain Rogers was created using the <a href="http://phaser.io/">Phaser</a> framework, the most popular tool for simple 2D game development in JavaScript right now, but it should be fairly easy to reuse the knowledge contained within these articles when building games in pure JavaScript or any other framework. If you're looking for a good introduction to Phaser, then check the <a href="/en-US/docs/Games/Tutorials/2D_breakout_game_Phaser">2D breakout game using Phaser</a> tutorial.</p> + +<p>In the following articles we will show how to implement various different control mechanisms for Captain Rogers to support different platforms — from touch on mobile, through keyboard/mouse/gamepad on desktop, to more unconventional ones like TV remote, shouting to or waving your hand in front of the laptop, or squeezing bananas.</p> + +<h2 id="Setting_up_the_environment">Setting up the environment</h2> + +<p>Let's start with a quick overview of the game's folder structure, JavaScript files and in-game states, so we know what's happening where. The game's folders look like this:</p> + +<p><img alt="Captain Rogers: Battle at Andromeda - folder structure of the games' project containing JavaScript sources, images and fonts." src="https://mdn.mozillademos.org/files/13851/captainrogers2-folderstructure.png" style="border-style: solid; border-width: 1px; display: block; height: 479px; margin: 0px auto; width: 575px;"></p> + +<p>As you can see there are folders for images, JavaScript files, fonts and sound effects. The <code>src</code> folder contains the JavaScript files split as a separate states — <code>Boot.js</code>, <code>Preloader.js</code>, <code>MainMenu.js</code> and <code>Game.js</code> — these are loaded into the index file in this exact order. The first one initializes Phaser, the second preloads all the assets, the third one controls the main menu welcoming the player, and the fourth controls the actual gameplay.</p> + +<p>Every state has its own default methods: <code>preload()</code>, <code>create()</code>, and <code>update()</code>. The first one is needed for preloading required assets, <code>create()</code> is executed once the state had started, and <code>update()</code> is executed on every frame.</p> + +<p>For example, you can define a button in the <code>create()</code> function:</p> + +<pre class="brush: js">create: function() { + // ... + var buttonEnclave = this.add.button(10, 10, 'logo-enclave', this.clickEnclave, this); + // ... +} +</pre> + +<p>It will be created once at the start of the game, and will execute <code>this.clickEnclave()</code> action assigned to it when clicked, but you can also use the mouse's pointer value in the <code>update()</code> function to make an action:</p> + +<pre class="brush: js">update: function() { + // ... + if(this.game.input.mousePointer.isDown) { + // do something + } + // ... +} +</pre> + +<p>This will be executed whenever the mouse button is pressed, and it will be checked against the input's <code>isDown</code> boolean variable on every frame of the game.</p> + +<p>That should give you some understanding of the project structure. We'll be playing mostly with the <code>MainMenu.js</code> and <code>Game.js</code> files, and we'll explain the code inside the <code>create()</code> and <code>update()</code> methods in much more detail in later articles.</p> + +<h2 id="Pure_JavaScript_demo">Pure JavaScript demo</h2> + +<p>There's also a <a href="https://end3r.github.io/JavaScript-Game-Controls/">small online demo</a> with full source code <a href="https://github.com/end3r/JavaScript-Game-Controls/">available on GitHub</a> where the basic support for the control mechanisms described in the articles is implemented in pure JavaScript. It will be explained in the given articles themselves below, but you can play with it already, and use the code however you want for learning purposes.</p> + +<h2 id="The_articles">The articles</h2> + +<p>JavaScript is the perfect choice for mobile gaming because of HTML5 being truly multiplatform; all of the following articles focus on the APIs provided for interfacing with different control mechanisms:</p> + +<ol> + <li><a href="/en-US/docs/Games/Techniques/Control_mechanisms/Mobile_touch">Mobile touch controls</a> — The first article will kick off with touch, as the mobile first approach is very popular.</li> + <li><a href="/en-US/docs/Games/Techniques/Control_mechanisms/Desktop_with_mouse_and_keyboard">Desktop mouse and keyboard controls</a> — When playing on a desktop/laptop computer, providing keyboard and mouse controls is essential to provide an acceptable level of accessibility for the game.</li> + <li><a href="/en-US/docs/Games/Techniques/Control_mechanisms/Desktop_with_gamepad">Desktop gamepad controls</a> — The Gamepad API rather usefully allows gamepads to be used for controlling web apps on desktop/laptop, for that console feel.</li> + <li><a href="/en-US/docs/Games/Techniques/Control_mechanisms/Other">Unconventional controls</a> — The final article showcases some unconventional control mechanisms, from the experimental to the slightly crazy, which you might not believe could be used to play the game.</li> +</ol> diff --git a/files/zh-cn/games/techniques/control_mechanisms/移动端触摸控制/index.html b/files/zh-cn/games/techniques/control_mechanisms/移动端触摸控制/index.html new file mode 100644 index 0000000000..e9a9abaf15 --- /dev/null +++ b/files/zh-cn/games/techniques/control_mechanisms/移动端触摸控制/index.html @@ -0,0 +1,152 @@ +--- +title: 移动端触摸控制 +slug: Games/Techniques/Control_mechanisms/移动端触摸控制 +translation_of: Games/Techniques/Control_mechanisms/Mobile_touch +--- +<div>{{GamesSidebar}}</div> + +<p>{{NextMenu("Games/Techniques/Control_mechanisms/Desktop_with_mouse_and_keyboard", "Games/Techniques/Control_mechanisms")}}</p> + +<p class="summary">未来手游一定是Web的天下,许多开发在游戏开发过程中首先选择手游 — 既然如此,触摸控制是不可少的。我们将在本教程中了解怎样简单地在移动端H5游戏中实现触摸控制 ,只要移动端支持触摸,你就可以尽情的玩。</p> + +<p class="note"><strong>说明</strong>:游戏 <a class="external external-icon" href="http://rogers2.enclavegames.com/demo/">Captain Rogers: Battle at Andromeda</a> 是基于<a href="http://phaser.io/">Phaser</a> 和Phaser-based管理控制,但它也可以用纯JavaScript实现。使用Phaser的好处它提供了辅助变量和方法可以直接调用,有助于快速的开发游戏,这需要根据项目实际情况选择。</p> + +<h2 id="纯_JavaScript_方式实现">纯 JavaScript 方式实现</h2> + +<p>我们可以实现自己的触摸事件 — 给document添加事件监听,并传入自定义功能的方法,非常简单:</p> + +<pre class="brush: js">var el = document.getElementsByTagName("canvas")[0]; +el.addEventListener("touchstart", handleStart); +el.addEventListener("touchmove", handleMove); +el.addEventListener("touchend", handleEnd); +el.addEventListener("touchcancel", handleCancel);</pre> + +<p>这样, 在移动设备上屏幕上触摸游戏的 {{htmlelement("canvas")}} 将触发这些事件,因为我们就可以随意操控游戏(如:移动太空船)。 事件如下所示:</p> + +<ul> + <li><a href="/en-US/docs/Web/API/GlobalEventHandlers/ontouchstart">touchstart</a> 当用户手指放在屏幕上触发。</li> + <li><a href="/en-US/docs/Web/API/GlobalEventHandlers/ontouchmove">touchmove</a> 当他们在屏幕上移动手指时触发。</li> + <li><a href="/en-US/docs/Web/API/GlobalEventHandlers/ontouchend">touchend</a> 当用户在屏幕上停止移动时触发。</li> + <li><a href="/en-US/docs/Web/API/GlobalEventHandlers/ontouchcancel">touchcancel</a> 触摸被取消是触发,例如当用户将他们的手指移动到屏幕之外时。</li> +</ul> + +<div class="note"> +<p><strong>说明</strong>: 这篇 <a href="/en-US/docs/Web/API/Touch_events">touch events</a> 参考文章提供了更多的实例和介绍。</p> +</div> + +<h3 id="纯JavaScript示例">纯JavaScript示例</h3> + +<p>这个实现了移动端触摸的<a href="https://github.com/end3r/JavaScript-Game-Controls/">little demo</a>代码已经放到了GibHub上,我们下载这个示例就可以实现在移动端屏幕上移动飞船。</p> + +<p>我们将两种事件:<code>touchstart </code>和<code>touchmove</code> 放到一个方法里处理. 为什么呢? <code>touchHandler</code> 方法定义的飞船位置变量适合下面两种情况下: 当玩家触摸时,但不移动它(<code>touchstart</code>)和当手指在屏幕上开始移动 (<code>touchmove</code>):</p> + +<pre class="brush: js">document.addEventListener("touchstart", touchHandler); +document.addEventListener("touchmove", touchHandler);</pre> + +<p><code>touchHandler</code> 方法的代码如下:</p> + +<pre class="brush: js">function touchHandler(e) { + if(e.touches) { + playerX = e.touches[0].pageX - canvas.offsetLeft - playerWidth / 2; + playerY = e.touches[0].pageY - canvas.offsetTop - playerHeight / 2; + output.innerHTML = "Touch: "+ " x: " + playerX + ", y: " + playerY; + e.preventDefault(); + } +}</pre> + +<p>If the touch occurs (<code>touches</code> object is not empty), then we will have all the info we need in that object. We can get the first touch (<code>e.touches[0]</code>, our example is not multitouch-enabled), extract the <code>pageX</code> and <code>pageY</code> variables and set the player's ship position on the screen by subtracting the Canvas offset (distance from the Canvas and the edge of the screen) and half the player's width and height.</p> + +<p><img alt="Touch controls for the player's ship, with visible output of the x and y position." src="https://mdn.mozillademos.org/files/14201/controls-touch.png" style="border-style: solid; border-width: 1px; display: block; height: 436px; margin: 0px auto; width: 575px;"></p> + +<p>To see if it's working correctly we can output the <code>x</code> and <code>y</code> positions using the <code>output</code> element. The <code>preventDefault()</code> function is needed to prevent the browser from moving — without it you'd have the default behaviour, and the Canvas would be dragged around the page, which would show the browser scroll bars and look messy.</p> + +<h2 id="Touch_events_in_Phaser">Touch events in Phaser</h2> + +<p>We don't have to do this on our own; frameworks like Phaser offer systems for managing touch events for us — see <a href="http://phaser.io/docs/2.6.1/Phaser.Touch.html">managing the touch events</a>.</p> + +<h3 id="Pointer_theory">Pointer theory</h3> + +<p>A <a href="http://phaser.io/docs/2.6.1/Phaser.Pointer.html">pointer</a> represents a single finger on the touch screen. Phaser starts two pointers by default, so two fingers can perform an action at once. Captain Rogers is a simple game — it can be controlled by two fingers, the left one moving the ship and the right one controlling the ship's gun. There's no multitouch or gestures — everything is handled by single pointer inputs.</p> + +<p>You can add more pointers to the game by using; <code>this.game.input.addPointer</code> up to ten pointers can be managed simultaneously. The most recently used pointer is available in the <code>this.game.input.activePointer</code> object — the most recent finger active on the screen.</p> + +<p>If you need to access a specific pointer, they are all available at, <code>this.game.input.pointer1</code><code>this.game.input.pointer2</code>, etc. They are assigned dynamically, so if you put three fingers on the screen, then, <code>pointer1</code><code>pointer2</code>, and <code>pointer3</code> will be active. Removing the second finger, for example, won't affect the other two, and setting it back again will use the first available property, so <code>pointer2</code> will be used again.</p> + +<p>You can quickly get the coordinates of the most recently active pointer via the <code>this.game.input.x</code> and <code>this.game.input.y</code> variables.</p> + +<h3 id="Input_events">Input events</h3> + +<p>Instead of using the pointers directly it is also possible to listen for <code>this.game.input</code> events, like <code>onDown</code>, <code>onUp</code>, <code>onTap</code> and <code>onHold</code>:</p> + +<pre class="brush: js">this.game.input.onDown.add(itemTouched, this); + +function itemTouched(pointer) { + // do something +}</pre> + +<p>The <code>itemTouched()</code> function will be executed when the <code>onDown</code> event is dispatched by touching the screen. The <code>pointer</code> variable will contain the information about the pointer that activated the event.</p> + +<p>This approach uses the generally available <code>this.game.input</code> object, but you can also detect the actions on any game objects like sprites or buttons by using <code>onInputOver</code>, <code>onInputOut</code>, <code>onInputDown</code>, <code>onInputUp</code>, <code>onDragStart</code>, or <code>onDragStop</code>:</p> + +<pre class="brush: js">this.button.events.onInputOver.add(itemTouched, this); + +function itemTouched(button, pointer) { + // do something +}</pre> + +<p>That way you'll be able to attach an event to any object in the game, like the player's ship, and react to the actions performed by the user.</p> + +<p>An additional advantage of using Phaser is that the buttons you create will take any type of input, whether it's a touch on mobile or a click on desktop — the framework sorts this out in the background for you.</p> + +<h3 id="Implementation">Implementation</h3> + +<p>The easiest way to add an interactive object that will listen for user input is to create a button:</p> + +<pre class="brush: js">var buttonEnclave = this.add.button(10, 10, 'logo-enclave', this.clickEnclave, this);</pre> + +<p>This one is formed in the <code>MainMenu</code> state — it will be placed ten pixels from the top left corner of the screen, use the <code>logo-enclave</code> image, and execute the <code>clickEnclave()</code> function when it is touched. This will work on mobile and desktop out of the box. There are a few buttons in the main menu, including the one that will start the game.</p> + +<p>For the actual gameplay, instead of creating more buttons and covering the small mobile screen with them, we can use something a little bit different: we'll create invisible areas which respond to the given action. From a design point of view, it is better to make the field of activity bigger without covering half of the screen with button images. For example, tapping on the right side of the screen will fire the weapon:</p> + +<pre class="brush: js">this.buttonShoot = this.add.button(this.world.width*0.5, 0, 'button-alpha', null, this); +this.buttonShoot.onInputDown.add(this.goShootPressed, this); +this.buttonShoot.onInputUp.add(this.goShootReleased, this);</pre> + +<p>The code above will create a new button using a transparent image that covers the right half of the screen. You can assign functions on input down and input up separately if you'd like to perform more complicated actions, but in this game touching the right side of the screen will simply fire the bullets to the right — this is all we need in this case.</p> + +<p>Moving the player could be managed by creating the four directional buttons, but we can take the advantage of touch screens and drag the player's ship around:</p> + +<pre class="brush: js">var player = this.game.add.sprite(30, 30, 'ship'); +player.inputEnabled = true; +player.input.enableDrag(); +player.events.onDragStart.add(onDragStart, this); +player.events.onDragStop.add(onDragStop, this); + +function onDragStart(sprite, pointer) { + // do something when dragging +}</pre> + +<p>We can pull the ship around and do something in the meantime, and react when the drag is stopped. Hauling in Phaser, if enabled, will work out of the box — you don't have to set the position of the sprite yourself manually, so you could leave the <code>onDragStart()</code> function empty, or place some debug output to see if it's working correctly. The <code>pointer</code> element contains the <code>x</code> and <code>y</code> variables storing the current position of the dragged element.</p> + +<h3 id="Dedicated_plugins">Dedicated plugins</h3> + +<p>You could go even further and use dedicated plugins like <a href="http://phaser.io/shop/plugins/virtualjoystick">Virtual Joystick</a> — this is a paid, official Phaser plugin, but you can find free and <a href="https://github.com/Gamegur-us/phaser-touch-control-plugin">open source alternatives</a>. The initialization of Virtual Joystick looks like this:</p> + +<pre class="brush: js">this.pad = this.game.plugins.add(Phaser.VirtualJoystick); +this.stick = this.pad.addStick(30, 30, 80, 'generic');</pre> + +<p>In the <code>create()</code> function of the <code>Game</code> state we're creating a virtual pad and a generic stick that has four directional virtual buttons by default. This is placed 30 pixels from the top and left edges of the screen and is 80 pixels wide.</p> + +<p>The stick being pressed can be handled during the gameplay in the <code>update</code> function like so:</p> + +<pre class="brush: js">if(this.stick.isDown) { + // move the player +}</pre> + +<p>We can adjust the player's velocity based on the current angle of the stick and move him appropriately.</p> + +<h2 id="摘要">摘要</h2> + +<p>这篇文章主要讲解如何在移动端实现触摸控制; 下一篇文章我们将看到怎样添加键盘和鼠标支持。</p> + +<p>{{NextMenu("Games/Techniques/Control_mechanisms/Desktop_with_mouse_and_keyboard", "Games/Techniques/Control_mechanisms")}}</p> diff --git a/files/zh-cn/games/techniques/controls_gamepad_api/index.html b/files/zh-cn/games/techniques/controls_gamepad_api/index.html new file mode 100644 index 0000000000..34b033d854 --- /dev/null +++ b/files/zh-cn/games/techniques/controls_gamepad_api/index.html @@ -0,0 +1,238 @@ +--- +title: 使用 Gamepad API 实现控制 +slug: Games/Techniques/Controls_Gamepad_API +translation_of: Games/Techniques/Controls_Gamepad_API +--- +<div>{{GamesSidebar}}</div><p class="summary">这篇文章着眼于使用 Gamepad API 为网页游戏实现一个有效的跨浏览器控制系统,可以让你使用端游控制器来控制你的网页游戏。Hungry Fridge,就是 <a href="http://enclavegames.com/">Enclave Games</a> 以此制作的游戏。</p> + +<h2 id="网页游戏的控制">网页游戏的控制</h2> + +<p>在历史上,在连接主机(console)的电视上玩游戏和在电脑(PC)上玩游戏是两种完全不一样的体验,最大的区别就是它们的控制方式。后来,额外的驱动程序和插件让我们能够使用主机控制器来游玩电脑端的游戏--不论是本地游戏,还是运行在浏览器中的游戏。 到现在的 HTML5 时代,我们终于有了 <a href="/en-US/docs/Web/API/Gamepad_API">Gamepad API</a> ,让我们能够在不安装任何插件的情况下,可以使用控制器来游玩基于浏览器的游戏。Gamepad API 通过提供一个接口公开按钮的按下和坐标的变化来实现这一点,在 JavaScript 中我们可以用这些变化来处理输入。这对于网页游戏来说是非常棒的特性。</p> + +<p><img alt="gamepad-controls" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img01.png" style="display: block; height: 400px; margin: 0px auto; width: 600px;"></p> + +<h2 id="API_状态与浏览器支持">API 状态与浏览器支持</h2> + +<p><a href="http://www.w3.org/TR/gamepad/">Gamepad API</a> 在 W3C 的进程中仍然还是工作草案的状态,这意味着它的实现方法可能还会出现变动,但是就目前来说<a href="http://caniuse.com/gamepad">浏览器的支持性</a>相当不错。Firefox 29+ 和 Chrome 35+ 对其支持得非常好。Opera 在版本 22+ 对 API 进行了支持 (一点也不奇怪,因为他们现在使用 Chrome 的引擎了。) 并且微软最近在 Edge 中对 API 实现了支持,也就是说四大主流浏览器现在都支持 Gamepad API。</p> + +<h2 id="哪种控制器最好?">哪种控制器最好?</h2> + +<p><img alt="gamepad devices" src="http://end3r.com/tmp/gamepad/devices.jpg" style="display: block; height: 450px; margin: 0px auto; width: 600px;"></p> + +<p>目前最受欢迎的控制器是来自 XBox 360、XBox One、PS3 和 PS4 的 — 它们经受过时间的检验,并且在浏览器跨 Windows 与 Mac OS 平台中对 Gamepad API 的实现中工作良好。</p> + +<p>还有一些其他各种各样不同布局的控制器或多或少的支持跨浏览器的实现。本文中讨论的代码使用了一些控制器进行测试,但是笔者比较喜欢的配置是:无线 XBox 360 控制器和 Mac OS X 平台的 Firefox 浏览器。</p> + +<h2 id="实例分析:Hungry_Fridge">实例分析:Hungry Fridge</h2> + +<p><a href="https://github.com/blog/1674-github-game-off-ii">GitHub Game Off II</a> 比赛举行于2013年11月,<a href="http://enclavegames.com/">Enclave Games</a> 决定参加比赛。比赛的主题为“改变”(change),所以他们提交了这样一个游戏——你需要通过点击来喂食饥饿的冰箱健康的食物 (苹果、萝卜、莴苣) 并避开“坏”的食物 (啤酒、汉堡、披萨) 。其中会有倒计时会改变接下来几秒内冰箱想吃的东西,所以你又要小心动作又要块。你可以<a href="http://enclavegames.com/games/hungry-fridge/">在这里尝试 Hungry Fridge</a>。</p> + +<p><img alt="hungryfridge-mainmenu" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img02.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p> + +<p>第二个隐藏的“改变”的实现是可以从单纯静态的冰箱改变成涡轮驱动、射击和吞食的机器能力。当你连接控制器后,游戏会有很明显的改变 (饥饿冰箱会变成超级涡轮的饥饿冰箱) 并且你可以使用 Gamepad API 来控制装甲冰箱。你需要击落食物但是你仍然需要找到冰箱目前想吃的食物,否则你会失去能量。</p> + +<p><img alt="hungryfridge-howtoplay-gamepad" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img03.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p> + +<p>游戏封装了两种截然不同的“变化”(change) ——好食物对坏食物,与移动端对桌面端。</p> + +<p><img alt="hungryfridge-gameplay-gamepad" src="http://end3r.com/tmp/gamepad/gamepadapi-hungryfridge-img04.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p> + +<h2 id="示例">示例</h2> + +<p>Game API 的动作展示与JavaScript的源代码公布是在完整版的 Hungry Fridge 构建之后才开始的,然后据此创建了一个 <a href="https://end3r.github.io/Gamepad-API-Content-Kit/demo/demo.html">简单的示例</a>。部分 <a href="https://end3r.github.io/Gamepad-API-Content-Kit/">Gamepad API Content Kit</a> 在Github 上供你分析代码并研究其如何工作。</p> + +<p><img alt="Hungry Fridge demo using Gamepad API" src="http://end3r.com/tmp/gamepad/super-turbo.png" style="display: block; height: 333px; margin: 0px auto; width: 500px;"></p> + +<p>以下讨论的代码来自于完整的 Hungry Fridge 游戏中,除了原代码需要 <code>turbo</code> 变量来决定是否启动“超级涡轮模式”以外几乎一模一样。此代码可以独立运行,就算不连接控制器也可以。</p> + +<div class="note"> +<p><strong>注:</strong>一个彩蛋:点击界面右上角的控制器图标有个隐藏选项——不连接控制器也能启动“超级涡轮模式” 。你可以使用键盘上的 A 和 D 控制左右旋转,W 射击,方向键移动。</p> +</div> + +<h2 id="实现方法">实现方法</h2> + +<p>使用Gamepad API时有两个重要的事件—— <code>gamepadconnected</code> 和 <code>gamepaddisconnected</code>。 前者将于浏览器侦测到新控制器连接时触发;而后者则是断开连接 (不管是物理断开还是无响应了) 的时候触发。在示例中 <code>gamepadAPI</code> 对象通常存储着所有关于 API 的东西:</p> + +<pre class="brush: js"><code>var gamepadAPI = { + controller: {}, + turbo: false, + connect: function() {}, + disconnect: function() {}, + update: function() {}, + buttonPressed: function() {}, + buttons: [], + buttonsCache: [], + buttonsStatus: [], + axesStatus: [] +};</code></pre> + +<p>数组 <code>buttons</code> 存储着 XBox 360 控制器的按键布局button layout:</p> + +<pre class="brush: js"><code>buttons: [ + 'DPad-Up','DPad-Down','DPad-Left','DPad-Right', + 'Start','Back','Axis-Left','Axis-Right', + 'LB','RB','Power','A','B','X','Y', +],</code></pre> + +<p>这可能和例如 PS3 控制器 (或者其他没名字的通用控制器) 等其他控制器有所不同,所以你需要注意并不要假设你期望的布局和你真正使用的控制器布局是一样。接下来我们设置两个事件侦听器来获取数据:</p> + +<pre class="brush: js"><code>window.addEventListener("gamepadconnected", gamepadAPI.connect); +window.addEventListener("gamepaddisconnected", gamepadAPI.disconnect);</code></pre> + +<p>由于安全策略,你必须先与控制器产生交互才能触发当前显示页面的事件。如果 API 在没有接收到用户交互的时候工作,那它可能会在不知情的情况下被用来识别指纹。</p> + +<p>两个函数都十分简单:</p> + +<pre class="brush: js"><code>connect: function(evt) { + gamepadAPI.controller = evt.gamepad; + gamepadAPI.turbo = true; + console.log('控制器已连接。'); +},</code></pre> + +<p>函数 <code>connect()</code> 接受一个事件作为参数,并将其中的 <code>gamepad</code> 对象分配给 <code>gamepadAPI.controller</code> 变量。我们在这个游戏中只使用一个控制器,所以这个变量是一个单独的对象而不是控制器的数组。然后我们设置 <code>turbo</code> 属性为 <code>true</code>。 (这个可以直接用 <code>gamepad.connected</code> 实现,但我们想单独设置一个变量来控制“涡轮模式”而不需要连接控制器,原因已在前面说明过了。)</p> + +<pre class="brush: js"><code>disconnect: function(evt) { + gamepadAPI.turbo = false; + delete gamepadAPI.controller; + console.log('控制器已断开。'); +},</code></pre> + +<p>函数 <code>disconnect</code> 设置 <code>gamepad.turbo</code> 属性为 <code>false</code> 并移除存储着 <code>gamepad</code> 对象的变量。</p> + +<h3 id="Gamepad_对象">Gamepad 对象</h3> + +<p>对象 <code>gamepad</code> 中有包含了许多有用的信息,其中就包括按钮和坐标轴的状态等重要信息:</p> + +<ul> + <li><code>id</code>: 一个包含关于控制器信息的字符串。</li> + <li><code>index</code>: 一个为已连接的设备分配的唯一标识。</li> + <li><code>connected</code>: 一个布尔变量,<code>true</code> 表示设备已连接。</li> + <li><code>mapping</code>: 键位的布局类型;现在只有 <code>standard</code> 是唯一可用的值。</li> + <li><code>axes</code>: 每一个坐标轴的状态。表示为存储浮点值的数组。</li> + <li><code>buttons</code> : 每个按钮的状态,表示为一个 <code>GamepadButton</code> 对象,其包含 <code>pressed</code> 和 <code>value</code> 属性。</li> +</ul> + +<p>变量 <code>index</code> 在我们连接了多个控制器时非常有用,我们可以用此来区分它们的操作——例如我们有一个需要连接两个控制器的双人游戏。</p> + +<h3 id="查询控制器对象">查询控制器对象</h3> + +<p>除了 <code>connect()</code> 和 <code>disconnect()</code> ,<code>gamepadAPI</code> 对象中还有另外两个方法:<code>update()</code> 和 <code>buttonPressed()</code>。<code>update()</code> 会在游戏循环的每一帧中执行,来更新 gamepad 对象的实时状态:</p> + +<pre class="brush: js"><code>update: function() { + // 清除按钮缓存 + gamepadAPI.buttonsCache = []; + // 从上一帧中移动按钮状态到缓存中 + for(var k=0; k<gamepadAPI.buttonsStatus.length; k++) { + gamepadAPI.buttonsCache[k] = gamepadAPI.buttonsStatus[k]; + } + // 清除按钮状态 + gamepadAPI.buttonsStatus = []; + // 获取 gamepad 对象 + var c = gamepadAPI.controller || {}; + + // 遍历按键,并将按下的按钮加到数组中 + var pressed = []; + if(c.buttons) { + for(var b=0,t=c.buttons.length; b<t; b++) { + if(c.buttons[b].pressed) { + pressed.push(gamepadAPI.buttons[b]); + } + } + } + // 遍历坐标值并加到数组中 + var axes = []; + if(c.axes) { + for(var a=0,x=c.axes.length; a<x; a++) { + axes.push(c.axes[a].toFixed(2)); + } + } + // 分配接收到的值 + gamepadAPI.axesStatus = axes; + gamepadAPI.buttonsStatus = pressed; + // 返回按钮以便调试 + return pressed; +},</code></pre> + +<p>在每一帧上,<code>update()</code> 都会将上一帧的按钮状态保存至数组 <code>buttonsCache</code> 中,并在 <code>gamepadAPI.controller</code> 对象提取出新的状态信息。然后它就能轮询按钮和坐标值并获得它们的实时状态和值。</p> + +<h3 id="监测按钮按下">监测按钮按下</h3> + +<p>方法 <code>buttonPressed()</code> 也位于主游戏循环中来监听按钮的按下。它有两个参数——我们想要监听的按钮和 (可选) 用来告诉游戏接收的按键是(从之前就)被按住了的。没了它你需要松开并再按一次按钮才能得到想要的结果。</p> + +<pre class="brush: js"><code>buttonPressed: function(button, hold) { + var newPress = false; + // 轮询按下的按钮 + for(var i=0,s=gamepadAPI.buttonsStatus.length; i<s; i++) { + // 如果我们找到我们想要的按钮 + if(gamepadAPI.buttonsStatus[i] == button) { + // 设置布尔变量(newPress)为 true + newPress = true; + // 如果我们想检查按住还是单次按下 + if(!hold) { + // 从上一帧轮询缓存状态 + for(var j=0,p=gamepadAPI.buttonsCache.length; j<p; j++) { + // 如果按钮(之前)已经被按下了则忽略新的按下状态 + if(gamepadAPI.buttonsCache[j] == button) { + newPress = false; + } + } + } + } + } + return newPress; +},</code></pre> + +<p>在一个按钮中有两种动作:单次按下和按住。变量 <code>newPress</code> 布尔变量将会指出这个是不是一个按钮新的按下操作。下次我们再轮询已按下按钮的数组——如果有按钮是我们正在找的,那么设 <code>newPress</code> 变量为 <code>true</code> 。通过检查本次按下是不是新按下的,就能得出玩家是不是按住按钮了。我们从游戏循环中的上一帧轮询按钮的缓存状态,如果我们找到了,就说明按钮被按住了,所以就不是新的按下。最后 <code>newPress</code> 变量被返回。函数 <code>buttonPressed</code> 通常这样来更新游戏循环:</p> + +<pre class="brush: js"><code>if(gamepadAPI.turbo) { + if(gamepadAPI.buttonPressed('A','hold')) { + this.turbo_fire(); + } + if(gamepadAPI.buttonPressed('B')) { + this.managePause(); + } +}</code></pre> + +<p>如果 <code>gamepadAPI.turbo</code> 为 <code>true</code> 并有按钮被按下(或被按住),我们就会为其分配恰当的操作。在本例中,按下或按住 <code>A</code> 开火,按下 <code>B</code> 暂停游戏。</p> + +<h3 id="坐标阈值">坐标阈值</h3> + +<p>按钮只有两种状态:<code>0</code> 或 <code>1</code>,但是摇杆可以有许多不同的值——他们在 <code>X</code> 和 <code>Y</code> 轴上都有一个范围为 <code>-1</code> 到 <code>1</code> 的浮点值。</p> + +<p><img alt="axis threshold" src="http://end3r.com/tmp/gamepad/axis-threshold.png" style="display: block; height: 300px; margin: 0px auto; width: 400px;"></p> + +<p>控制器放在一边不活动时轴值也可能有一定波动 (get dusty) ,这也就是说通过判断等于绝对的 -1 或 1 来可能是会有问题的。因此对此最好是给轴值设定一个阈值来触发生效。比如说,“冰箱坦克”仅会在 <code>X</code> 值大于 <code>0.5</code> 的时候向右转:</p> + +<pre><code>if(gamepadAPI.axesStatus[0].x > 0.5) { + this.player.angle += 3; + this.turret.angle += 3; +}</code></pre> + +<p>即使我们稍微误推摇杆或者摇杆没有弹回原始位置,“冰箱坦克”也不会意外转向。</p> + +<h2 id="规范更新">规范更新</h2> + +<p>经过长达一年多的规范化,W3C Gamepaf API 于2015年4月更新了规范 (<a href="https://w3c.github.io/gamepad/">查看最新信息</a>)。更新的改动并不是很大,但是我们最好了解一下到底更新了些什么—— 以下为更新。</p> + +<h3 id="获取控制器">获取控制器</h3> + +<p>{{domxref("Naviagator.getGamepads()")}} 方法已用<a href="https://w3c.github.io/gamepad/#navigator-interface-extension">更长的说明和示例代码</a>更新。现在控制器数组的长度必须为 <code>n+1</code> ( <code>n</code> 是已连接设备的数量) —— 当设备连接且其有索引 1,数组长度为 2,那么它将会是这样: <code>[null, [object Gamepad]]</code>。如果设备被断开或不可用的话,值将被设为 <code>null</code>。</p> + +<h3 id="映射标准">映射标准</h3> + +<p>布局类型现在是一个可枚举的对象而不是字符串:</p> + +<pre>enum GamepadMappingType { + "", + "standard" +};</pre> + +<p>此枚举中定义了已知的控制器映射集。目前只有 <code>standard</code> 布局可用,但是未来可能会有新的布局。如果布局未知,那么将会是空字符串。</p> + +<h3 id="事件">事件</h3> + +<p>除了当前可用的 <code>gamepadconnected</code> 和 <code>gamepaddisconnected</code> 事件,其实还有其它事件也曾在规范中,但它们因为不是非常的有用所以被移出了规范。相关讨论仍在进行中,关于它们是否应该恢复规范,以及以什么形式恢复。</p> + +<h2 id="总结">总结</h2> + +<p>Gamepad API 非常易于开发。现在它比以往更容易向浏览器提供游戏主机的体验而不需要任何插件。你可以直接在你的浏览器中游玩完整的 <a href="http://enclavegames.com/games/hungry-fridge/">Hungry Fridge</a> 游戏。你可以从 <a href="https://marketplace.firefox.com/app/hungry-fridge">Firefox Marketplace</a> 中安装,或者可以在 <a href="https://github.com/EnclaveGames/Hungry-Fridge">Gamepad API Content Kit</a> 中查看示例源代码。</p> diff --git a/files/zh-cn/games/techniques/index.html b/files/zh-cn/games/techniques/index.html new file mode 100644 index 0000000000..f80eff7adc --- /dev/null +++ b/files/zh-cn/games/techniques/index.html @@ -0,0 +1,34 @@ +--- +title: Techniques for game development +slug: Games/Techniques +tags: + - Games + - Guide + - NeedsTranslation + - TopicStub +translation_of: Games/Techniques +--- +<div>{{GamesSidebar}}</div> + +<div>{{IncludeSubnav("/en-US/docs/Games")}}</div> + +<div class="summary"> +<p><span class="seoSummary">这个页面为想要使用开放的网页技术来开发游戏的人列举出了必要的核心技术。</span></p> +</div> + +<dl> + <dt><a href="/en-US/docs/Games/Techniques/Async_scripts">使用asm.js中的异步脚本</a></dt> + <dd>尤其在制作中大型游戏时,异步脚本是一项必备技术,你游戏中的JavaScript因此可以在主进程之外被编译,并被缓存以之后游戏的运行,这会带来显著的性能提升。这篇文章解释了如何做到。</dd> + <dt><a href="/en-US/docs/Apps/Developing/Optimizing_startup_performance" title="/en-US/docs/Apps/Developing/Optimizing_startup_performance">Optimizing startup performance</a></dt> + <dd>How to make sure your game starts up quickly, smoothly, and without appearing to lock up the user's browser or device.</dd> + <dt><a href="/en-US/docs/Games/WebRTC_data_channels" title="/en-US/docs/Games/WebRTC_data_channels">Using WebRTC peer-to-peer data channels</a></dt> + <dd>In addition to providing support for audio and video communication, WebRTC lets you set up peer-to-peer data channels to exchange text or binary data actively between your players. This article explains what this can do for you, and shows how to use libraries that make this easy.</dd> + <dt><a href="/en-US/docs/Games/Techniques/Efficient_animation_for_web_games">Efficient animation for web games</a></dt> + <dd>This article covers techniques and advice for creating efficient animation for web games, with a slant towards supporting lower end devices such as mobile phones. We touch on CSS transitions and CSS animations, and JavaScript loops involving {{ domxref("window.requestAnimationFrame") }}.</dd> + <dt><a href="/en-US/docs/Games/Techniques/Audio_for_Web_Games">Audio for Web Games</a></dt> + <dd>Audio is an important part of any game — it adds feedback and atmosphere. Web-based audio is maturing fast, but there are still many browser differences to negotiate. This article provides a detailed guide to implementing audio for web games, looking at what works currently across as wide a range of platforms as possible.</dd> + <dt><a href="/en-US/docs/Games/Techniques/2D_collision_detection">2D collision detection</a></dt> + <dd>A concise introduction to collision detection in 2D games.</dd> + <dt><a href="/en-US/docs/Games/Techniques/Tilemaps">Tilemaps</a></dt> + <dd>Tiles are a very popular technique in 2D games for building the game world. These articles provide an introduction to tilemaps and how to implement them with the Canvas API.</dd> +</dl> |