#javascript #math #canvas #physics
#javascript #математика #холст #физика
Вопрос:
Я пытаюсь создать небольшую игру на JavaScript (без движка) и хочу избавиться от анимации на основе кадров.
Я успешно добавил дельта-время для горизонтальных перемещений (отлично работает при 60 или 144 кадрах в секунду).
Но я не могу заставить это работать с прыжком, высота (или сила) не всегда одинакова, и я не знаю почему.
Я уже пробовал (и все еще сталкивался с точно такой же проблемой):
- Прохождение дельта-времени в конце
update()
:x = Math.round(dx * dt)
- Изменение
Date.now()
наperformance.now()
- Без округления
DeltaY
- Фиксирующая высота прыжка
Я создал упрощенный пример с двумя типами прыжков, прыжком с фиксированной высотой и обычным прыжком (не знаю, как это назвать). У обоих одна и та же проблема.
const canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
canvas2 = document.getElementById('canvas2'),
ctx2 = canvas2.getContext('2d');
// CLASS PLAYER ------------------------
class Actor {
constructor(color, ctx, j) {
this.c = ctx
this.w = 20
this.h = 40
this.x = canvas.width /2 - this.w/2
this.y = canvas.height/2 - this.h/2
this.color = color
// Delta
this.dy = 0
// Movement
this.gravity = 25/1000
this.maxSpeed = 600/1000
// Jump Height lock
this.jumpType = (j) ? 'capedJump' : 'normalJump'
this.jumpHeight = -50
// Booleans
this.isOnFloor = false
}
// Normal jump
normalJump(max) {
if(!this.isOnFloor) return
this.dy = -max
this.isOnFloor = false
}
// Jump lock (locked max height)
capedJump(max) {
const jh = this.jumpHeight;
if(jh >= 0) return
this.dy = -max/15
if(jh - this.dy >= 0) {
this.dy = (jh - this.dy) jh
this.jumpHeight = 0
} else {
this.jumpHeight = -this.dy
}
}
update(dt) {
const max = this.maxSpeed*dt,
gravity = this.gravity*dt;
// JUMP
this[this.jumpType](max)
// GRAVITY
this.dy = gravity
// TOP AND DOWN COLLISION (CANVAS BORDERS)
const y = this.y this.dy,
h = y this.h;
if (y <= 0) this.y = this.dy = 0
else if (h >= canvas.height) {
this.y = canvas.height - this.h
this.dy = 0
this.isOnFloor = true
this.jumpHeight = -50
}
// Update Y
this.y = Math.round(this.dy)
}
draw() {
const ctx = this.c
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.w, this.h)
}
}
const Player = new Actor('brown', ctx, false)
const Player2 = new Actor('blue', ctx2, true)
// ANIMATE -----------------------------
let lastRender = 0
let currRender = Date.now()
function animate() {
// Set Delta Time
lastRender = currRender
currRender = Date.now()
let dt = currRender - lastRender
// CANVAS #1 (LEFT)
ctx.clearRect(0, 0, canvas.width, canvas.height)
background(ctx)
Player.update(dt)
Player.draw()
// CANVAS #2 (RIGHT)
ctx2.clearRect(0, 0, canvas2.width, canvas2.height)
background(ctx2)
Player2.update(dt)
Player2.draw()
window.requestAnimationFrame(animate)
}
animate()
// EVENT LISTENERS -----------------------
window.addEventListener('keydown', (e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code)) Player.keys[e.code] = true
})
window.addEventListener('keyup', (e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code)) Player.keys[e.code] = false
})
// Just a function to draw Background nothing to see here
function background(c) {
const lineNumber = Math.floor(canvas.height/10)
c.fillStyle = 'gray'
for(let i = 0; i < lineNumber; i ) {
c.fillRect(0, lineNumber*i, canvas.width, 1)
}
}
div {
display: inline-block;
font-family: Arial;
}
canvas {
border: 1px solid black;
}
span {
display: block;
color: gray;
text-align: center;
}
<div>
<canvas width="100" height="160" id="canvas"></canvas>
<span>Normal</span>
</div>
<div>
<canvas width="100" height="160" id="canvas2"></canvas>
<span>Locked</span>
</div>
Комментарии:
1. gamedev.stackexchange.com/questions/29617/… Вы получаете странные результаты, потому что ваша математика неверна в двух словах.
2. Извините, возможно, я что-то пропустил, но я не увидел никакой разницы между моим кодом и кодом в сообщении, которое вы только что отправили.
3. Вам следует взглянуть на эту книгу barnesandnoble.com/p / … всего 3 доллара, используется для 50 . Это то, что мы использовали в колледже. Это хорошо, но весь пример кода написан на c . Код достаточно прост и на самом деле не имеет значения. Какое значение имеет объяснение математики. Вы действительно должны использовать векторы.
4. Наконец, не создавайте свой собственный игровой движок. Похоже, вы довольно новичок в программировании. Создавать игры СЛОЖНО. Создание игрового движка делает это простым. Используйте Phase или что-то подобное.
5. Спасибо за книгу, я посмотрю! Я не хочу использовать игровой движок, я хочу научиться и посмотреть, как далеко я могу продвинуться (я не создаю свою игру для реальных игровых целей)
Ответ №1:
Вот как я бы реорганизовал код:
-
Не используйте
dy
как для скорости, так и для положения (что вы, похоже, делаете). Переименуйте егоvy
и используйте исключительно как вертикальную скорость. -
Перейдите
isOnFloor
к функции, чтобы мы всегда могли проверять наличие столкновений с полом. -
Отделите функции перехода от фактических обновлений движения. Просто заставьте их установить вертикальную скорость, если игрок находится на полу.
-
Выполняйте проверку столкновения сверху / снизу отдельно в зависимости от направления движения.
-
Не округляйте
DeltaY
— это испортит небольшие движения.
С учетом этих изменений поведение движения является правильным и стабильным:
const canvas1 = document.getElementById('canvas1'),
ctx1 = canvas1.getContext('2d'),
canvas2 = document.getElementById('canvas2'),
ctx2 = canvas2.getContext('2d');
// Global physics variables
const GRAVITY = 0.0015;
const MAXSPEED = 0.6;
const MAXHEIGHT = 95;
// CLASS PLAYER ------------------------
class Actor {
constructor(C, W, H, J) {
// World size
this.worldW = W;
this.worldH = H;
// Size amp; color
this.w = 20;
this.h = 40;
this.color = C;
// Speed
this.vy = 0;
// Position
this.x = W/2 - this.w/2;
this.y = H/2 - this.h/2;
// Jump Height lock
this.jumpCapped = J;
this.jumpHeight = 0;
}
// move isOnFloor() to a function
isOnFloor() {
return this.y >= this.worldH - this.h;
}
// Normal jump
normalJump() {
if(!this.isOnFloor()) return;
this.vy = -MAXSPEED;
}
// Jump lock (locked max height)
cappedJump(max) {
if(!this.isOnFloor()) return;
this.vy = -MAXSPEED;
this.jumpHeight = max;
}
update(dt) {
// JUMP
if (this.jumpCapped)
this.cappedJump(MAXHEIGHT);
else
this.normalJump();
// GRAVITY
this.vy = GRAVITY * dt;
this.y = this.vy * dt;
// Bottom collision
if (this.vy > 0) {
if (this.isOnFloor()) {
this.y = this.worldH - this.h;
this.vy = 0;
}
}
else
// Top collision
if (this.vy < 0) {
const maxh = (this.jumpCapped) ? (this.worldH - this.jumpHeight) : 0;
if (this.y < maxh) {
this.y = maxh;
this.vy = 0;
}
}
}
draw(ctx) {
ctx.fillStyle = this.color;
ctx.fillRect(this.x, this.y, this.w, this.h);
}
}
const Player1 = new Actor('brown', canvas1.width, canvas1.height, false);
const Player2 = new Actor('blue', canvas2.width, canvas2.height, true);
// ANIMATE -----------------------------
let lastUpdate = 0;
let randomDT = 0;
function animate() {
// Compute delta time
let currUpdate = Date.now();
let dt = currUpdate - lastUpdate;
// Randomize the update time interval
// to test the physics' stability
if (dt > randomDT) {
randomDT = 35 * Math.random() 5;
Player1.update(dt);
Player2.update(dt);
lastUpdate = currUpdate;
}
// CANVAS #1 (LEFT)
ctx1.clearRect(0, 0, canvas1.width, canvas1.height);
background(ctx1);
Player1.draw(ctx1);
// CANVAS #2 (RIGHT)
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
background(ctx2);
Player2.draw(ctx2);
window.requestAnimationFrame(animate);
}
animate();
// EVENT LISTENERS -----------------------
window.addEventListener('keydown',
(e) => {
e.preventDefault();
if (Player.keys.hasOwnProperty(e.code))
Player.keys[e.code] = true;
}
)
window.addEventListener('keyup',
(e) => {
e.preventDefault()
if (Player.keys.hasOwnProperty(e.code))
Player.keys[e.code] = false;
}
)
// Just a function to draw Background nothing to see here
function background(c) {
const lineNumber = Math.floor(canvas1.height/10)
c.fillStyle = 'gray'
for(let i = 0; i < lineNumber; i ) {
c.fillRect(0, lineNumber*i, canvas1.width, 1)
}
}
div {
display: inline-block;
font-family: Arial;
}
canvas {
border: 1px solid black;
}
span {
display: block;
color: gray;
text-align: center;
}
<div>
<canvas width="100" height="160" id="canvas1"></canvas>
<span>Normal</span>
</div>
<div>
<canvas width="100" height="160" id="canvas2"></canvas>
<span>Locked</span>
</div>