- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我试图让下面的球继续出现并以设定的间隔在 y 轴上发射,并且总是从桨(鼠标)的 x 位置所在的位置开始,我需要在每次发射球之间有一个延迟。我正在尝试制作太空入侵者,但球会以设定的间隔不断发射。
我是否需要为每个球创建多个 requestAnimationFrame 循环?有人可以帮忙提供一个非常基本的例子来说明如何做到这一点或链接一篇好文章吗?我一直在为每个球创建一个数组,但不确定如何构建循环来实现这种效果。我能找到的所有例子都太复杂了
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
* {
padding: 0;
margin: 0;
}
canvas {
background: #eee;
display: block;
margin: 0 auto;
width: 30%;
}
</style>
</head>
<body>
<canvas id="myCanvas" height="400"></canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
//start the requestAnimationFrame loop
var myRequestAnimation;
var myRequestAnimationBall;
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
var cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame;
drawLoop();
setInterval(drawBallLoop, 400);
var x = canvas.width / 2;
var y = canvas.height - 30;
var defaultSpeedX = 0;
var defaultSpeedY = 4;
var dx = defaultSpeedX;
var dy = -defaultSpeedY;
var ballRadius = 10;
var paddleX = (canvas.width - paddleWidth) / 2;
var paddleHeight = 10;
var paddleWidth = 70;
//control stuff
var rightPressed = false;
var leftPressed = false;
var brickRowCount = 1;
var brickColumnCount = 1;
var brickWidth = 40;
var brickHeight = 20;
var brickPadding = 10;
var brickOffsetTop = 30;
var brickOffsetLeft = 30;
var score = 0;
var lives = 3;
//paddle
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, canvas.height - paddleHeight, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
//bricks
function drawBricks() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
if (bricks[c][r].status == 1) {
var brickX = (c * (brickWidth + brickPadding)) + brickOffsetLeft;
var brickY = (r * (brickHeight + brickPadding)) + brickOffsetTop;
bricks[c][r].x = brickX;
bricks[c][r].y = brickY;
ctx.beginPath();
ctx.rect(brickX, brickY, brickWidth, brickHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
}
}
//collision detection
function collisionDetection() {
for (var c = 0; c < brickColumnCount; c++) {
for (var r = 0; r < brickRowCount; r++) {
var b = bricks[c][r];
if (b.status == 1) {
if (x > b.x && x < b.x + brickWidth && y > b.y && y < b.y + brickHeight) {
dy = -dy;
b.status = 0;
score++;
console.log(score);
if (score == brickRowCount * brickColumnCount) {
console.log("YOU WIN, CONGRATS!");
window.cancelAnimationFrame(myRequestAnimation);
}
}
}
}
}
}
//default bricks
var bricks = [];
for (var c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (var r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 0, y: 0, status: 1 };
}
}
//lives
function drawLives() {
ctx.font = "16px Arial";
ctx.fillStyle = "#0095DD";
ctx.fillText("Lives: " + lives, canvas.width - 65, 20);
}
// ball1
var ball1 = {
x,
y,
directionX: 0,
directionY: -5
}
// ball1
var ball2 = {
x,
y,
directionX: 0,
directionY: -2
}
// put each ball in a balls[] array
var balls = [ball1, ball2];
function drawBall() {
// clearCanvas();
for (var i = 0; i < balls.length; i++) {
var ball = balls[i]
ctx.beginPath();
ctx.arc(ball.x, ball.y, ballRadius, 0, Math.PI * 2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
}
///////DRAW BALL LOOP////////
function drawBallLoop() {
myRequestAnimationBall = requestAnimationFrame(drawBallLoop);
// clear frame
//ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw ball
drawBall();
//move balls
for (var i = 0; i < balls.length; i++) {
balls[i].y += balls[i].directionY;
}
}
//Clear Canvas
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
///////DRAW MAIN LOOP////////
function drawLoop() {
myRequestAnimation = requestAnimationFrame(drawLoop);
// clear frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
//draw ball
drawPaddle();
drawBricks();
collisionDetection();
drawLives();
//bounce off walls
if (x + dx > canvas.width - ballRadius || x + dx < ballRadius) {
dx = -dx;
}
if (rightPressed) {
paddleX += 7;
if (paddleX + paddleWidth > canvas.width) {
paddleX = canvas.width - paddleWidth;
}
}
else if (leftPressed) {
paddleX -= 7;
if (paddleX < 0) {
paddleX = 0;
}
}
}
//keyboard left/right logic
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = true;
}
else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = true;
}
}
function keyUpHandler(e) {
if (e.key == "Right" || e.key == "ArrowRight") {
rightPressed = false;
}
else if (e.key == "Left" || e.key == "ArrowLeft") {
leftPressed = false;
}
}
//relative mouse pos
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(), // abs. size of element
scaleX = canvas.width / rect.width, // relationship bitmap vs. element for X
scaleY = canvas.height / rect.height; // relationship bitmap vs. element for Y
return {
x: (evt.clientX - rect.left) * scaleX, // scale mouse coordinates after they have
y: (evt.clientY - rect.top) * scaleY // been adjusted to be relative to element
}
}
//mouse movemment
document.addEventListener("mousemove", mouseMoveHandler, false);
function mouseMoveHandler(e) {
var mouseX = getMousePos(canvas, e).x;
//e.clientX = the horizontal mouse position in the viewport
//canvas.offsetLeft = the distance between the left edge of the canvas and left edge of the viewport
var relativeX = mouseX;
// console.log('mouse= ',relativeX, canvas.offsetLeft)
// console.log('paddle= ', paddleX);
// console.log(getMousePos(canvas, e).x);
if (relativeX - (paddleWidth / 2) > 0 && relativeX < canvas.width - (paddleWidth / 2)) {
paddleX = relativeX - (paddleWidth / 2);
}
}
</script>
</body>
</html>
最佳答案
基本原则
这是您可以做到的一种方法:
Game
将处理更新逻辑的对象,存储所有当前实体,处理游戏循环...... IMO,这是您应该跟踪最后一次是什么时候 Ball
解雇以及是否解雇一个新的。Game.update(deltaTime)
在每一帧上。Entity
类,因为我想确保所有游戏实体都具有运行所需的最低限度(即更新、绘制、x、y...)。Ball
类(class)extends Entity
并负责了解自己的参数(速度、大小、...)、如何更新和绘制自身、...Paddle
我留给你完成的类(class)。Do I need to create multiple requestAnimationFrame loops for each ball?
lastUpdate
,
deltaTime
,
lastBallCreated
使事情变得简单得多。在实践中,开发人员倾向于为此尝试使用单个动画帧循环。
class Entity {
constructor(x, y) {
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.speed = 100 // px per second
this.size = 10 // radius in px
}
update(deltaTime) {
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.size, 0, 2 * Math.PI)
context.fill()
}
isDead() {
return this.y < 0 - this.size
}
}
class Paddle extends Entity {
constructor() {
super(0, 0)
}
update() { /**/ }
draw() { /**/ }
isDead() { return false }
}
class Game {
/** @param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 1000 // ms between each ball
this.lastBallCreated = 0 // timestamp of last time a ball was launched
}
start() {
this.lastUpdate = performance.now()
const paddle = new Paddle()
this.entities.push(paddle)
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
// update every entity
this.entities.forEach(entity => entity.update(deltaTime))
// other update logic (here, create new entities)
if(this.lastBallCreated + this.newBallInterval < newTime) {
const ball = new Ball(100, 300) // this is quick and dirty, you should put some more thought into `x` and `y` here
this.entities.push(ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
draw() {
this.entities.forEach(entity => entity.draw(this.context))
}
cleanup() {
// to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if(entity.isDead()) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
loop() {
requestAnimationFrame(() => {
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
<canvas height="300" width="300"></canvas>
class Paddle extends Entity {
constructor() {
// we just add a default initial x,y and height,width
super(150, 20)
this.width = 50
this.height = 10
}
update() { /**/ }
/** @param {CanvasRenderingContext2D} context */
draw(context) {
// we just draw a simple rectangle centered on x,y
context.beginPath()
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height)
context.fill()
}
isDead() { return false }
}
现在我们添加一个基本的
InputsManager
您可以根据需要制作复杂的类(class)。仅用于两个键,处理
keydown
和
keyup
并且可以同时按下两个键这一事实已经是几行代码,因此最好将事情分开以免弄乱我们的
Game
目的。
class InputsManager {
constructor() {
this.direction = 0 // this is the value we actually need in out Game object
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if(this.direction === -1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if(this.direction === 1) // make sure the direction was set by this key before resetting it
this.direction = 0
break
}
}
}
现在,我们可以更新我们的
Game
类来利用这个新
InputsManager
class Game {
// ...
start() {
// ...
this.inputsManager = new InputsManager()
this.loop()
}
update() {
// update every entity
const frameData = {
deltaTime,
inputs: this.inputsManager,
} // we now pass more data to the update method so that entities that need to can also read from our InputsManager
this.entities.forEach(entity => entity.update(frameData))
}
// ...
}
并在更新
update
的代码后我们实体实际使用新
InputsManager
的方法,这是结果:
class Entity {
constructor(x, y) {
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.speed = 300 // px per second
this.radius = 10 // radius in px
}
update({deltaTime}) {
// Ball still only needs deltaTime to calculate its update
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fill()
}
isDead() {
return this.y < 0 - this.radius
}
}
class Paddle extends Entity {
constructor() {
super(150, 50)
this.speed = 200
this.width = 50
this.height = 10
}
update({deltaTime, inputs}) {
// Paddle needs to read both deltaTime and inputs
this.x += this.speed * deltaTime / 1000 * inputs.direction
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height)
context.fill()
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if(this.direction === -1)
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if(this.direction === 1)
this.direction = 0
break
}
}
}
class Game {
/** @param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 500 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
}
start() {
this.lastUpdate = performance.now()
// we store the new Paddle in this.player so we can read from it later
this.player = new Paddle()
// but we still add it to the entities list so it gets updated like every other Entity
this.entities.push(this.player)
this.inputsManager = new InputsManager()
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
// update every entity
const frameData = {
deltaTime,
inputs: this.inputsManager,
}
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if(this.lastBallCreated + this.newBallInterval < newTime) {
// we can now read from this.player to the the position of where to fire a Ball
const ball = new Ball(this.player.x, 300)
this.entities.push(ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
draw() {
this.entities.forEach(entity => entity.draw(this.context))
}
cleanup() {
// to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if(entity.isDead()) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
loop() {
requestAnimationFrame(() => {
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
<canvas height="300" width="300"></canvas>
<script src="script.js"></script>
x
坐标作为桨。您可以阅读我在上面的代码片段中留下的注释,以快速解释其工作原理。
Entity
的子类。所以对我来说,把代码放在那里是有意义的:
class Entity {
constructor(x, y) {
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if(a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if(b.collision === 'none') {
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if(a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2) < a.radius + b.radius
}
if(a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height / 2
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height / 2
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 2
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
}
现在有很多种 2D 碰撞,所以代码有点冗长,但重点是:这是我在这里做出的设计决定。
我可以成为通才和 future 的证明 但后来看起来像上面那样......我必须添加一个
.collision
我所有游戏对象的属性,以便他们知道是否应该将它们视为
'circle'
或 '
rect'
在上述算法中。
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'circle'
}
// ...
}
class Paddle extends Entity {
constructor() {
super(150, 50)
this.collision = 'rect'
}
// ...
}
或者我可以是极简主义者,只添加我需要的东西,在这种情况下,将代码实际放在
Paddle
中可能更有意义。实体:
class Paddle extends Entity {
testBallCollision(ball) {
const topOfBallIsAboveBottomOfRect = ball.y - ball.radius <= this.y + this.height / 2
const bottomOfBallIsBelowTopOfRect = ball.y + ball.radius >= this.y - this.height / 2
const ballIsRightOfRectLeftSide = ball.x + ball.radius >= this.x - this.width / 2
const ballIsLeftOfRectRightSide = ball.x - ball.radius <= this.x + this.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
}
无论哪种方式,我现在都可以访问来自
cleanup
的碰撞信息。函数
Game
循环(我选择放置删除死实体的逻辑)。
class Game {
cleanup() {
this.entities.forEach(entity => {
// I'm passing this.player so all entities can test for collision with the player
if(entity.isDead(this.player)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
}
class Ball extends Entity {
isDead(player) {
// this is the "out of bounds" test we already had
const outOfBounds = this.y < 0 - this.radius
// this is the new "collision with player paddle"
const collidesWithPlayer = Entity.testCollision(player, this)
return outOfBounds || collidesWithPlayer
}
}
使用第二种极简主义方法,我仍然需要通过播放器进行测试:
class Game {
cleanup() {
this.entities.forEach(entity => {
// I'm passing this.player so all entities can test for collision with the player
if(entity.isDead(this.player)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
}
class Ball extends Entity {
isDead(player) {
// this is the "out of bounds" test we already had
const outOfBounds = this.y < 0 - this.radius
// this is the new "collision with player paddle"
const collidesWithPlayer = player.testBallCollision(this)
return outOfBounds || collidesWithPlayer
}
}
class Entity {
constructor(x, y) {
this.collision = 'none'
this.x = x
this.y = y
}
update() { console.warn(`${this.constructor.name} needs an update() function`) }
draw() { console.warn(`${this.constructor.name} needs a draw() function`) }
isDead() { console.warn(`${this.constructor.name} needs an isDead() function`) }
static testCollision(a, b) {
if(a.collision === 'none') {
console.warn(`${a.constructor.name} needs a collision type`)
return undefined
}
if(b.collision === 'none') {
console.warn(`${b.constructor.name} needs a collision type`)
return undefined
}
if(a.collision === 'circle' && b.collision === 'circle') {
return Math.sqrt((a.x - b.x)**2 + (a.y - b.y)**2) < a.radius + b.radius
}
if(a.collision === 'circle' && b.collision === 'rect' || a.collision === 'rect' && b.collision === 'circle') {
let circle = a.collision === 'circle' ? a : b
let rect = a.collision === 'rect' ? a : b
// this is a waaaaaay simplified collision that just works in this case (circle always comes from the bottom)
const topOfBallIsAboveBottomOfRect = circle.y - circle.radius <= rect.y + rect.height / 2
const bottomOfBallIsBelowTopOfRect = circle.y + circle.radius >= rect.y - rect.height / 2
const ballIsRightOfRectLeftSide = circle.x + circle.radius >= rect.x - rect.width / 2
const ballIsLeftOfRectRightSide = circle.x - circle.radius <= rect.x + rect.width / 2
return topOfBallIsAboveBottomOfRect && bottomOfBallIsBelowTopOfRect && ballIsRightOfRectLeftSide && ballIsLeftOfRectRightSide
}
console.warn(`there is no collision function defined for a ${a.collision} and a ${b.collision}`)
return undefined
}
}
class Ball extends Entity {
constructor(x, y) {
super(x, y)
this.collision = 'circle'
this.speed = 300 // px per second
this.radius = 10 // radius in px
}
update({deltaTime}) {
this.y -= this.speed * deltaTime / 1000 // deltaTime is ms so we divide by 1000
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.arc(this.x, this.y, this.radius, 0, 2 * Math.PI)
context.fill()
}
isDead(player) {
const outOfBounds = this.y < 0 - this.radius
const collidesWithPlayer = Entity.testCollision(player, this)
return outOfBounds || collidesWithPlayer
}
}
class Paddle extends Entity {
constructor() {
super(150, 50)
this.collision = 'rect'
this.speed = 200
this.width = 50
this.height = 10
}
update({deltaTime, inputs}) {
this.x += this.speed * deltaTime / 1000 * inputs.direction
}
/** @param {CanvasRenderingContext2D} context */
draw(context) {
context.beginPath()
context.rect(this.x - this.width / 2, this.y - this.height / 2, this.width, this.height)
context.fill()
}
isDead() { return false }
}
class InputsManager {
constructor() {
this.direction = 0
window.addEventListener('keydown', this.onKeydown.bind(this))
window.addEventListener('keyup', this.onKeyup.bind(this))
}
onKeydown(event) {
switch (event.key) {
case 'ArrowLeft':
this.direction = -1
break
case 'ArrowRight':
this.direction = 1
break
}
}
onKeyup(event) {
switch (event.key) {
case 'ArrowLeft':
if(this.direction === -1)
this.direction = 0
break
case 'ArrowRight':
this.direction = 1
if(this.direction === 1)
this.direction = 0
break
}
}
}
class Game {
/** @param {HTMLCanvasElement} canvas */
constructor(canvas) {
this.entities = [] // contains all game entities (Balls, Paddles, ...)
this.context = canvas.getContext('2d')
this.newBallInterval = 500 // ms between each ball
this.lastBallCreated = -Infinity // timestamp of last time a ball was launched
}
start() {
this.lastUpdate = performance.now()
this.player = new Paddle()
this.entities.push(this.player)
this.inputsManager = new InputsManager()
this.loop()
}
update() {
// calculate time elapsed
const newTime = performance.now()
const deltaTime = newTime - this.lastUpdate
// update every entity
const frameData = {
deltaTime,
inputs: this.inputsManager,
}
this.entities.forEach(entity => entity.update(frameData))
// other update logic (here, create new entities)
if(this.lastBallCreated + this.newBallInterval < newTime) {
const ball = new Ball(this.player.x, 300)
this.entities.push(ball)
this.lastBallCreated = newTime
}
// remember current time for next update
this.lastUpdate = newTime
}
draw() {
this.entities.forEach(entity => entity.draw(this.context))
}
cleanup() {
// to prevent memory leak, don't forget to cleanup dead entities
this.entities.forEach(entity => {
if(entity.isDead(this.player)) {
const index = this.entities.indexOf(entity)
this.entities.splice(index, 1)
}
})
}
loop() {
requestAnimationFrame(() => {
this.context.clearRect(0, 0, this.context.canvas.width, this.context.canvas.height)
this.update()
this.draw()
this.cleanup()
this.loop()
})
}
}
const canvas = document.querySelector('canvas')
const game = new Game(canvas)
game.start()
<canvas height="300" width="300"></canvas>
<script src="script.js"></script>
关于javascript - 运行多个 requestAnimation 循环来发射多个球?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64371736/
我试图让球弹起,或者在它们碰撞时反转方向。我让它检查移动方法中的碰撞。它检查两个球之间是否发生碰撞,如果发生碰撞,它将反转速度。问题是,有时球会互相穿过,主要是较小的球。球要么弹跳得早,要么弹得晚,要
我一直在努力根据 2 个弹跳圆/球获得正确的矢量值。我知道他们什么时候反弹;使用毕达哥拉斯,但后来我迷路了。我知道我可能不得不使用三 Angular 函数 cos/sin/tan2。 情况,以我的形象
using UnityEngine; using System.Collections; public class audio : MonoBehaviour { public AudioClip
我正在尝试创建 3d 球体的动画,该动画是由该球体表面上的随机点制作的。这是我的代码,我在其中创建 500 个随机极点,然后将这些极坐标转换为笛卡尔坐标,然后将 X 和 Y 坐标映射到屏幕。这就是我得
我创建的这个程序应该基本上使用公式 V=Pi*h^2(3r-h)/3 但我的最终答案并没有相加。 例如:如果我用 1 代替半径,用 2 代替高度,我应该得到 4.18,但通过程序我得到 -1。 #in
我正在尝试制作一个简单的游戏,但如果我需要弹跳球的图像,我该怎么做呢?我正在做这个- function draw() { ctx.clearRect(0, 0, 300, 300);
我只是提出一个有可能结束的想法。我需要画一个 Crystal 球,红色和蓝色粒子随机分布在其中。我想我必须使用 photoshop,甚至尝试在图像中制作球,但由于这是用于研究论文并且不必很花哨,我想知
我有圆与圆相交的代码。但我需要将其扩展到 3-D。你能帮我写函数吗? static class Point{ double x, y, z; int dimension; Po
目标:我有一个三 Angular 形的球。球具有初始位置和速度。我试图弄清楚球会击中三 Angular 形的哪一边。 我试过的: I derived a formula通过参数化球的路径和三 Angu
由于我是 cocos2d 的新手,而且我很挣扎。任何人都可以建议我如何解决这个问题。 我有 3 个盒子(它们是运动体) 还有多个球(它们是动态物体),每个球都有一个标签值(盒子编号)。 我在射球位置和
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 7 年前。 Improve this qu
我正在为 rpm 编写一个 .spec 文件,它只是将一个 tar 球解压到文件系统上的某个目录中 那么我把原来的 tar 球放在哪里呢?我看到的所有示例都是从互联网上下载原始 tar 球的。但就我而
我是 THREE.js 的新手,对物理知识知之甚少 - 但我正在尝试构建一个足球游戏引擎(从顶部看),现在我正在为球的运动而苦苦挣扎。 当尝试将球从一侧移动到另一侧时,旋转始终朝向一个方向,我不明白如
我必须在 Android 中开发一个在屏幕上 move 球的应用程序。我需要用加速度计 move 球。我有这段代码,但球绕过边界并且没有反弹。 package com.example.test
我正在尝试创建一个 Roomba 程序,其中有一个球在屏幕上弹跳,以清洁它经过的瓷砖。该程序应该从所有灰色瓷砖开始,当球经过它们时,瓷砖就会变成白色。目前我有一个可以到处弹跳的球和一个创建 5x5 网
我正在尝试为我当前的作业创建一个加载屏幕效果。 它需要我们创建一个与 position: fixed .以此资金为背景。使用这个 div,有 4 个 与 position: absolute . 我们
我想将一个球(带有图像)扔到一个二维场景中,并在它到达一定距离时检查它是否发生碰撞。但我无法让它正确地“飞”。似乎这个问题已经被问过一百万次了,但随着我发现的越多,我就越困惑..现在我关注了this
这是我的代码 import java.util.*; import javafx.application.Application; import javafx.scene.Scene; import
我是一名优秀的程序员,十分优秀!