天天看点

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

本节书摘来异步社区《java 2d游戏编程入门》一书中的第8章,第8.1节,作者:【美】timothy wright(莱特),更多章节内容可以访问云栖社区“异步社区”公众号查看。

制作一款2d太空飞船游戏时,首先要解决的大问题是,当飞船到达屏幕边缘时,会发生什么事情。一个解决方案是,将飞船保持在屏幕的中央,而移动其周围的环境。没有对加载文件的任何支持,也没有关卡编辑器的话,要做到这点似乎有点难。另一个选择是,当飞船到达屏幕边界的时候,将其弹回。这样做似乎显得很奇怪。第三种选择是,让飞船从一端到另一端折返。让我们采用第三种方法。

还有很多方法可以解决这一问题。一种方法是,让飞船完全离开屏幕,然后让其在另一端返回,如图8.1所示。

尝试这一想法似乎很简单。我们使用围绕飞船的一个边界框来测试飞船何时离开了屏幕,并且将其移动到另外一端。这种方法有两个问题:一个问题是,可能会使得飞船在飞行中变成只有很小的一块可见,从而很难碰到它;第二个问题是,飞船会持续地处在屏幕之外。这并不经常发生,但是,当多边形的大部分都在屏幕之外的时候,可能很难撞击到该物体。由于存在这些问题,因此与一直等待直到整个物体离开屏幕相比,折返物体似乎是更好的选择。

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

折返物体以使得离开屏幕一端的任何部分从屏幕的另一端返回,也需要担心风险。当你将一个物体从一端折返到另一端的时候,同一物体可能会有4个副本,如图8.2所示。

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

这使得不仅需要绘制4个副本,而且所有4个副本都需要进行碰撞检查。如果只对第一个物体进行碰撞检查,位于其对角的两个物体可能就不会记录碰撞,即便它们的绘制物体有重叠,如图8.3所示。

创建一个渲染列表,其中包含了每个物体的副本,这可以解决该问题。物体可以绘制,并且整个列表可用来检查碰撞。物体折返的时候不会遇到问题,因为一旦物体的中心到了游戏世界的边界之外,就会通过游戏世界的宽度和高度来调整其位置以将其折返。应该总是将折返位置放置在边界之中,这样做物体就不会跑到屏幕之外,如图8.4所示。

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

// polygonwrapper.java

private vector2f getmin( vector2f[] poly ) {

  vector2f min = new vector2f( float.max_value, float.max_value );

  for( vector2f v : poly ) {

    min.x = math.min( v.x, min.x );

    min.y = math.min( v.y, min.y );

  }

  return min;

}

private vector2f getmax( vector2f[] poly ) {

  vector2f max = new vector2f( -float.max_value, -float.max_value );

    max.x = math.max( v.x, max.x );

    max.y = math.max( v.y, max.y );

  return max;

}<code>`</code>

wrappolygon()方法计算最小和最大值,并且使用这些值来判断物体是否需要向北、南、东或西折返。如果物体向两个主要方向折返,例如,向南和向东,那么,它也需要向北和向西折返。四个主要方向的每一个都要进行测试。使用变换方法,复制每个折返多边形,并且将其添加到渲染列表:

package javagames.prototype;

import java.util.list;

import javagames.util.matrix3x3f;

import javagames.util.vector2f;

public class polygonwrapper {

  private float worldwidth;

  private float worldheight;

  private vector2f worldmin;

  private vector2f worldmax;

  public polygonwrapper( float worldwidth, float worldheight ) {

    this.worldwidth = worldwidth;

    this.worldheight = worldheight;

    worldmax = new vector2f( worldwidth / 2.0f, worldheight / 2.0f );

    worldmin = worldmax.inv();

  public boolean hasleftworld( vector2f position ) {

    return position.x &lt; worldmin.x || position.x &gt; worldmax.x ||

      position.y &lt; worldmin.y || position.y &gt; worldmax.y;

  public vector2f wrapposition( vector2f position ) {

    vector2f wrapped = new vector2f( position );

    if( position.x &lt; worldmin.x ) {

      wrapped.x = position.x + worldwidth;

    } else if( position.x &gt; worldmax.x ) {

      wrapped.x = position.x - worldwidth;

    }

    if( position.y &lt; worldmin.y ) {

      wrapped.y = position.y + worldheight;

    } else if( position.y &gt; worldmax.y ) {

      wrapped.y = position.y - worldheight;

    return wrapped;

  public void wrappolygon( vector2f[] poly, list renderlist ) {

    vector2f min = getmin( poly );

    vector2f max = getmax( poly );

    boolean north = max.y &gt; worldmax.y;

    boolean south = min.y &lt; worldmin.y;

    boolean west = min.x &lt; worldmin.x;

    boolean east = max.x &gt; worldmax.x;

    if( west ) renderlist.add( wrapeast( poly ) );

    if( east ) renderlist.add( wrapwest( poly ) );

    if( north ) renderlist.add( wrapsouth( poly ) );

    if( south ) renderlist.add( wrapnorth( poly ) );

    if( north &amp;&amp; west ) renderlist.add( wrapsoutheast( poly ) );

    if( north &amp;&amp; east ) renderlist.add( wrapsouthwest( poly ) );

    if( south &amp;&amp; west ) renderlist.add( wrapnortheast( poly ) );

    if( south &amp;&amp; east ) renderlist.add( wrapnorthwest( poly ) );

  private vector2f getmin( vector2f[] poly ) {

    vector2f min = new vector2f( float.max_value, float.max_value );

    for( vector2f v : poly ) {

      min.x = math.min( v.x, min.x );

      min.y = math.min( v.y, min.y );

    return min;

  private vector2f getmax( vector2f[] poly ) {

    vector2f max = new vector2f( -float.max_value, -float.max_value );

      max.x = math.max( v.x, max.x );

      max.y = math.max( v.y, max.y );

    return max;

  private vector2f[] wrapnorth( vector2f[] poly ) {

    return transform( poly, matrix3x3f.translate( 0.0f, worldheight ) );

  private vector2f[] wrapsouth( vector2f[] poly ) {

    return transform( poly, matrix3x3f.translate( 0.0f, -worldheight ) );

  private vector2f[] wrapeast( vector2f[] poly ) {

    return transform( poly, matrix3x3f.translate( worldwidth, 0.0f ) );

  private vector2f[] wrapwest( vector2f[] poly ) {

    return transform( poly, matrix3x3f.translate( -worldwidth, 0.0f ) );

  private vector2f[] wrapnorthwest( vector2f[] poly ) {

    return transform(

      poly, matrix3x3f.translate( -worldwidth, worldheight )

    );

  private vector2f[] wrapnortheast( vector2f[] poly ) {

      poly, matrix3x3f.translate( worldwidth, worldheight )

  private vector2f[] wrapsoutheast( vector2f[] poly ) {

      poly, matrix3x3f.translate( worldwidth, -worldheight )

  private vector2f[] wrapsouthwest( vector2f[] poly ) {

      poly, matrix3x3f.translate( -worldwidth, -worldheight )

  private vector2f[] transform( vector2f[] poly, matrix3x3f mat ) {

    vector2f[] copy = new vector2f[ poly.length ];

    for( int i = 0; i &lt; poly.length; ++i ) {

      copy[i] = mat.mul( poly[i] );

    return copy;

位于javagames.prototype包中的screenwrapexample,如图8.7所示,使用polygonwrapper类来折返一个屏幕上来回移动的方块。pos向量保存了方块的位置。poly数组保存了方块多边形。renderlist保存了要折返的方块的副本,以及最初的变换多边形。折返变量是前面所介绍的polygonwrapper。

《Java 2D游戏编程入门》—— 8.1 创建一个多边形包装类

initialize()方法创建了所有的物体并且将鼠标设置为相对移动,从而不需要鼠标离开屏幕物体就可以在屏幕上折返。如果使用绝对位置的话,processinput()方法使用鼠标位置来移动方块;如果使用相对移动的话,它通过将当前位置和相对移动相加来移动方块。该方法还使用空格键来切换相对鼠标移动。

transform()方法创建了对象的一个副本,并且通过给定的矩形来变换它。updateobjects()方法清理了渲染列表,将方块折返并变换,然后使用polygonwrapper将物体的任何副本添加到渲染列表中。render()方法将渲染列表中的所有物体绘制到屏幕上。