八、相机锁定

到目前为止,我们已经将级别限制到了浏览器窗口的大小。这对于概念验证来说没问题,但是真实的关卡需要比我们目前所能提供的更多的空间。

我已经计划了一些事情来克服这个限制。首先,我们要将摄像机锁定到玩家,这样它会随着玩家的移动而移动。然后,我们将增加级别的大小,以便它可以是我们需要的任何大小。

用照相机包装

PixiJS 不支持任何自由移动的摄像机来渲染场景,但是我们不需要。我们可以将渲染器或场景包装在 HTML 元素中并移动,而不是移动渲染器或场景。我们需要改变嵌入游戏的方式:

<div class="camera"></div>
<div class="focus-target">click to focus</div>
------

.camera, .focus-target {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

.focus-target {
  padding: 25px;
  text-align: center;
}
------

game.addEventListenerTo(window)
game.addRendererTo(document.querySelector(".camera"))
game.animate()

这些都出自 http://codepen.io/assertchris/pen/WGzkym

我们首先创建一个占据整个屏幕空间的摄像机。然后,不是将场景附加到document.body,而是将其附加到.camera。这让我们有能力以有趣的方式改造.camera:

animate() {
  requestAnimationFrame(this.animate)

  this.state.renderer = this.renderer
  this.state.stage = this.stage

  this.state.objects.forEach((object) => {
    object.animate(this.state)
  })

  if (this.player) {
    const offsetLeft = Math.round(
      this.player.rectangle.x - (window.innerWidth / 2)
    ) * -1

    const offsetTop = Math.round(
      this.player.rectangle.y - (window.innerHeight / 2)
    ) * -1

    this.element.style = `
      transform:
      scale(1.2)
      translate(${offsetLeft}px)
      translateY(${offsetTop}px)
    `
  }

  this.renderer.render(this.stage)
}

// ...later

const player = new Player(
  new PIXI.Sprite.fromImage(
    "path/to/sprites/player-idle.png",
  ),
  new PIXI.Rectangle(
    Math.round(window.innerWidth / 2),
    Math.round(window.innerHeight / 2),
    44,
    56,
  ),
)

game.addObject(player)
game.player = player

这是出自 http://codepen.io/assertchris/pen/WGzkym

我们需要稍微不同地创建player对象。我们没有直接把它加到game上,而是声明它是一个常量。我们仍然把它添加到game,但是我们也把它作为一个属性分配。

这意味着我们可以在Game.animate中引用它。我们得到玩家的xy坐标,然后减去半个屏幕的innerWidthinnerHeight,这样玩家就会大致在屏幕的中央。

然后我们使用 CSS 转换将.camera向左和向上移动我们刚刚计算的量。我们也可以把相机放大一点,这样东西看起来会稍微放大一点(见图 8-1 )。由于这段代码在Game.animate中,所以每次渲染玩家时都会更新,也就是说它会随着玩家的移动而移动。

A435434_1_En_8_Fig1_HTML.jpg

图 8-1。

Zoomed and locked

增长水平

这导致了一个有趣的发现。当玩家站在我的平台上然后跳跃时,他们穿过渲染场景的顶部(然后消失)。这是因为场景的高度是固定的(与窗口高度相同)。

为了解决这个问题,我们需要改变我们定义游戏尺寸的方式,以及我们在游戏中放置物体的方式:

constructor(w, h) {
  this.w = w
  this.h = h

  this.state = {
    "keys": {},
    "clicks": {},
    "mouse": {},
    "objects": [],
  }

  this.animate = this.animate.bind(this)
}

newRenderer() {
  return new PIXI.autoDetectRenderer(
    this.w, this.h, this.newRendererOptions(),
  )
}

// ...later

const width = window.innerWidth
const height = window.innerHeight + 200

const game = new Game(
  width,
  height,
)

game.addObject(
  new Box(
    new PIXI.extras.TilingSprite.fromImage(
      "path/to/sprites/floor-tile.png",
      width,
      64,
    ),
    new PIXI.Rectangle(
      0,
      height - 64,
      width,
      64,
    ),
  ),
)

// ...remaining object definitions

这是出自 http://codepen.io/assertchris/pen/WGzkym

我们更改Game来接受widthheight构造函数参数。然后,我们使用与提供给game相同的宽度和高度,而不是到处使用window.innerWidthwindow.innerHeight

到目前为止,我们已经相对于屏幕边缘添加了游戏对象。这使得我们可以很容易地改变宽度和高度,而不必重新定位所有的游戏对象。现在我们需要用widthheight常量替换先前(全局)宽度和高度的所有实例。

我们唯一没有删除全局引用的地方是我们刚刚添加的摄像机锁定代码。这些需要相对于窗口(而不是整个级别的大小),所以它们可以保持原样。

摘要

在这一章中,我们学习了如何将我们的层次扩展到窗口之外。既然我们可以将相机锁定在玩家身上,我们就可以创建庞大的迷宫和令人敬畏的城堡。

花些时间考虑如何改善每个关卡的背景和边框,这样它们就不会像我的一样空了。也许你想创建动画背景来表达每一层的基调和位置。