九、发射物
我们的游戏全是键盘控制,但是鼠标呢?这一章都是关于射弹的;如何解雇他们,他们如何移动。
在这一章,我们将添加一些新的,流行的键盘控制(WASD),鼠标瞄准和射击。在这个过程中,我们将添加一个自定义的十字准线。这将会很有趣....
自定义十字线
在显示自定义十字光标之前,我们需要一种方法来禁用默认的鼠标光标。幸运的是,CSS 已经包含了这种机制:
body {
background: url("path/to/sprites/background.png");
color: grey;
font-family: helvetica, arial;
font-size: 20px;
cursor: none;
}
这是出自 http://codepen.io/assertchris/pen/YGaYvy
。
只需将cursor: none
加到body
上,我们就可以在游戏屏幕上隐藏默认光标。然后,让我们添加十字准线作为新的贴花:
const crosshair = new Decal(
new PIXI.Sprite.fromImage(
"path/to/sprites/crosshair.png",
),
new PIXI.Rectangle(
0, 0, 18, 18,
),
)
game.addObject(crosshair)
game.crosshair = crosshair
这是出自 http://codepen.io/assertchris/pen/YGaYvy
。
十字准线现在可以在游戏屏幕的左上角看到(缩小你的浏览器窗口)。不过,这不是我们想要的。我们真正想要的是它靠近玩家,但是在玩家中心和鼠标光标之间有一个角度。
让我们捕捉光标位置并计算鼠标光标和玩家之间的角度:
element.addEventListener("mousemove", (event) => {
this.state.mouse.clientX = event.clientX
this.state.mouse.clientY = event.clientY
const rect = this.player.rectangle
const centerX = (window.innerWidth / 2) + (rect.width / 2)
const centerY = (window.innerHeight / 2) + (rect.height / 2)
const deltaX = event.clientX - centerX
const deltaY = centerY - event.clientY
this.state.angle = Math.atan2(deltaY, deltaX)
})
这段代码出自 http://codepen.io/assertchris/pen/YGaYvy
。
这段代码读起来有点棘手,但它所做的只是获取鼠标光标和播放器之间的三角形的宽度和高度。我们从三角形中获取角度,并将其存储在游戏状态中。光标将使用这个(一旦我们用cursor = new Cursor
替换cursor = new Decal
):
class Ladder extends Box {
get collides() {
return false
}
}
class LeftSlope extends Box {
get collides() {
return false
}
}
class RightSlope extends Box {
get collides() {
return false
}
}
class Decal extends Box {
get collides() {
return false
}
}
class Crosshair extends Decal {
animate(state) {
const rect = state.player.rectangle
const centerX = rect.x + (rect.width / 2)
const centerY = rect.y + (rect.height / 2)
const radius = 70
const targetX = centerX + Math.cos(state.angle) * radius
const targetY = centerY - Math.sin(state.angle) * radius
this.sprite.x = targetX
this.sprite.y = targetY
}
}
这段代码出自 http://codepen.io/assertchris/pen/YGaYvy
。
在我们深入研究Crosshair.animate
方法之前,请注意我是如何缩短Ladder
、LeftSlope
、RightSlope
和Decal
的定义的?我们可以使用extends
关键字来继承Box
的行为。
Note
继承并不总是带来好的代码架构,但是在这个简单的例子中,它对我们来说很好。
使用我们计算的角度(在mousemove
事件监听器中),我们计算十字准线需要渲染的点(见图 9-1 )。
图 9-1。
Fixed crosshair
自定义键
到目前为止,我们一直用箭头键移动播放器。如果我们只需要使用键盘,那没问题,但我们刚刚添加了鼠标目标。让我们把流行的 WASD 机芯钥匙添加到Player.animate
:
animate(state) {
if (state.keys[37] || state.keys[65]) { // left
this.velocityX = Math.max(
this.velocityX - this.accelerationX,
this.maximumVelocityX * -1,
)
}
if (state.keys[39] || state.keys[68]) { // right
this.velocityX = Math.min(
this.velocityX + this.accelerationX,
this.maximumVelocityX,
)
}
// ...velocity calculations
state.objects.forEach((object) => {
if (object === this) {
return
}
const me = this.rectangle
const you = object.rectangle
const collides = object.collides
if (me.x < you.x + you.width &&
me.x + me.width > you.x &&
me.y < you.y + you.height &&
me.y + me.height > you.y) {
if (object.constructor.name === "Ladder") {
if (state.keys[38] || state.keys[40] ||
state.keys[87] || state.keys[83]) {
this.isOnLadder = true
this.isOnGround = false
this.velocityY = 0
this.velocityX = 0
}
if (state.keys[38] || state.keys[87]) {
this.rectangle.y -= this.climbingSpeed
}
if (state.keys[40] || state.keys[83] &&
me.y + me.height < you.y + you.height) {
this.rectangle.y += this.climbingSpeed
}
if (me.y <= you.x - me.height) {
this.isOnLadder = false
}
return
}
// ...remaining collision detection
}
})
// ...remaining calculations
}
This is from http://codepen.io/assertchris/pen/YGaYvy.
我们添加了state.keys[65]
和state.keys[68]
作为可选的左右键。然后,为了上下移动梯子,我们还增加了state.keys[87]
和state.keys[83]
。
射击
最后,让我们添加向十字准线方向射击的功能。为此,我们需要创建另一种类型的对象:
class Bullet extends Decal {
animate(state) {
const rect = state.player.rectangle
this.x = this.x || rect.x + rect.width
this.y = this.y || rect.y + (rect.height / 2)
this.angle = this.angle || state.angle
this.rotation = this.rotation || state.rotation
this.radius = this.radius || 0
this.radius += 15
const targetX = this.x + Math.cos(this.angle) * this.radius
const targetY = this.y - Math.sin(this.angle) * this.radius
this.sprite.x = targetX
this.sprite.y = targetY
this.sprite.rotation = this.rotation
}
}
这是出自 http://codepen.io/assertchris/pen/YGaYvy
。
与大多数其他对象类型的动画不同,它会随着每一次滴答修改其状态。this.x = this.x || something
的模式对于初始化一次值很有用。重要的是,我们只在子弹发射时存储像x
、y
、angle
和rotation
这样的东西,否则它们会随着我们移动玩家或十字准线而不断变化。
radius
从玩家的前方开始,随着子弹射出时十字准线的方向增加。我们也旋转子弹精灵来匹配相同的角度。这要求我们将rotation
和angle
一起存储在鼠标事件监听器中。我们还必须在单击鼠标时创建新的项目符号:
element.addEventListener("mousedown", (event) => {
this.state.clicks[event.which] = {
"clientX": event.clientX,
"clientY": event.clientY,
}
if (event.button === 0) { // left click
const rect = this.player.rectangle
const bullet = new Bullet(
new PIXI.Sprite.fromImage(
"path/to/sprites/bullet.png",
),
new PIXI.Rectangle(
rect.x + rect.width, rect.y, 8, 8,
),
)
this.addObject(bullet)
setTimeout(() => {
this.removeObject(bullet)
}, 250)
}
})
element.addEventListener("mousemove", (event) => {
this.state.mouse.clientX = event.clientX
this.state.mouse.clientY = event.clientY
const rect = this.player.rectangle
const centerX = (window.innerWidth / 2) + (rect.width / 2)
const centerY = (window.innerHeight / 2) + (rect.height / 2)
const deltaX = event.clientX - centerX
const deltaY = centerY - event.clientY
const rotationX = event.clientX - centerX
const rotationY = event.clientY - centerY
this.state.angle = Math.atan2(deltaY, deltaX)
this.state.rotation = Math.atan2(rotationY, rotationX)
})
// ...remaining event listeners
这是出自 http://codepen.io/assertchris/pen/YGaYvy
。
我们计算rotation
的方式与计算angle
的方式类似,但是我们翻转了垂直轴。当玩家点击时,我们创建一个新的bullet
(位于玩家前面)并将其添加到游戏中。
在250
毫秒后,我们要取出子弹。这是为了当不再需要项目符号时,它们不会减慢动画周期。我们需要将removeObject
方法添加到Game
类中:
removeObject(object) {
this.state.objects = this.state.objects.filter(
function(next) {
return next !== object
}
)
this.stage.removeChild(object.sprite)
}
这是出自 http://codepen.io/assertchris/pen/YGaYvy
。
我们将在下一章创建怪物射击时探索更多的投射行为。
摘要
在这一章中,我们看了创建自定义光标和发射物体的方法。我们发现了一点有用的三角学,将十字准线限制在玩家周围的某个半径范围内,然后将子弹推过这个范围。
试验你的投射物的速度和外观。也许你正在建造一个中世纪的游戏,你的投射物是魔法箭。考虑一下增加垂直加速度,这样你的子弹会在一段距离后落到地上。
版权属于:月萌API www.moonapi.com,转载请注明出处