Как создать препятствия в canvas
Я пытаюсь сделать простой платформер игра.Код, который я использую, показан ниже
window.onload = function(){
var canvas = document.getElementById('game');
var ctx = canvas.getContext("2d");
var rightKeyPress = false;
var leftKeyPress = false;
var upKeyPress = false;
var downKeyPress = false;
var playerX = canvas.width / 2;
var playerY = -50;
var dx = 3;
var dy = 3;
var dxp = 3;
var dyp = 3;
var dxn = 3;
var dyn = 3;
var prevDxp = dxp;
var prevDyp = dyp;
var prevDxn = dxn;
var prevDyn = dyn;
var playerWidth = 50;
var playerHeight = 50;
var obstacleWidth = 150;
var obstacleHeight = 50;
var obstaclePadding = 10;
var G = .98;
var currentVelocity = 0;
var obstacles = [];
var imageLoaded = false;
document.addEventListener("keyup",keyUp,false);
document.addEventListener("keydown",keyDown,false);
function keyDown(e){
if(e.keyCode == 37){
leftKeyPress = true;
if(currentVelocity > 2){
currentVelocity -= .1;
}
}
if(e.keyCode == 38){
upKeyPress = true;
}
if(e.keyCode == 39){
rightKeyPress = true;
if(currentVelocity < 2){
currentVelocity += .1;
}
}
if(e.keyCode == 40){
downKeyPress = true;
}
}
function keyUp(e){
if(e.keyCode == 37){
leftKeyPress = false;
}
if(e.keyCode == 38){
upKeyPress = false;
}
if(e.keyCode == 39){
rightKeyPress = false;
}
if(e.keyCode == 40){
downKeyPress = false;
}
}
function createObstacles(){
for(x=0;x < 4;x++){
var obX = (200 * x) + Math.round(Math.random() * 150);
var obY = 50 + Math.round(Math.random() * 400);
obstacles.push({"x":obX,"y":obY});
}
}
createObstacles();
function drawObstacles(){
ctx.beginPath();
for(x=0;x < 4;x++){
var obX = obstacles[x].x;
var obY = obstacles[x].y;
ctx.rect(obX,obY,obstacleWidth,obstacleHeight)
}
ctx.fillStyle = "grey";
ctx.fill();
ctx.closePath();
}
function initPlayer(){
ctx.beginPath();
ctx.rect(playerX,playerY,50,50);
ctx.fillStyle="orange";
ctx.fill();
ctx.closePath();
}
function KeyPressAndGravity(){
checkObstacleCollision();
playerX += currentVelocity;
if(rightKeyPress && playerX + 50 < canvas.width){
playerX += dxp;
}
if(leftKeyPress && playerX > 0){
playerX -= dxn;
}
if(upKeyPress && playerY > 0){
playerY -= dyn;
}
if(downKeyPress && playerY + 50 < canvas.height){
playerY += dyp;
}
if(playerY+50 < canvas.height){
playerY += G;
}
if(playerX <= 0){
currentVelocity = 0;
}else if(playerX + 50 >= canvas.width){
currentVelocity = 0;
}
dxp = prevDxp;
dyp = prevDyp;
dxn = prevDxn;
dyn = prevDyn;
G = .98;
if(currentVelocity != 0){
if(currentVelocity > 0){
currentVelocity -= .01;
}else{
currentVelocity += .01;
}
}
}
/*-----------------------------------------------------------
-------------------------------------------------------------
-------------------------------------------------------------
---------------------------Check this part-------------------
-------------------------------------------------------------
-------------------------------------------------------------
-------------------------------------------------------------
------------------------------------------------------------*/
function checkObstacleCollision(){
var obLen = obstacles.length;
for(var x=0;x<obLen;x++){
var obX = obstacles[x].x;
var obY = obstacles[x].y;
if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY + playerHeight > obY - obstaclePadding && playerY + playerHeight < obY){
dyp = 0;
G = 0;
}else if((playerX + playerWidth > obX && playerX + playerWidth < obX + obstacleWidth || playerX > obX && playerX < obX + obstacleWidth) && playerY > obY + obstacleHeight && playerY < obY + obstacleHeight + obstaclePadding){
dyn = 0;
}else if(playerX + playerWidth > obX - obstaclePadding && playerX + playerWidth < obX && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY + obstacleHeight))){
dxp = 0;
}else if(playerX > obX + obstacleWidth && playerX < obX + obstacleWidth + obstaclePadding && ((playerY + playerHeight > obY && playerY + playerHeight < obY + obstacleHeight) || (playerY > obY && playerY < obY + obstacleHeight))){
dxn = 0;
}
}
}
function draw(){
ctx.clearRect(0,0,canvas.width,canvas.height);
initPlayer();
KeyPressAndGravity();
drawObstacles();
}
setInterval(draw,15);
}
<canvas id="game" width="1000" height="600" style="border:1px solid #000;"></canvas>
проблема в том, что иногда, когда скорость "игрока" высока, он может проходить через препятствия, такие как изображение ниже. Как я могу это предотвратить ?
Так что я хочу, чтобы игрок должен остановиться прямо, когда он достигает препятствия, а не проходить через него
3 ответов
существует осложнение при столкновении тестовых объектов, которые быстро движутся
вы должны определить, если ваш игрок и препятствие пересеклись at в любое время во время движения -- даже если игрок вышел за пределы препятствием к концу хода. Поэтому вы должны учитывать полный путь, который игрок переместил от начала до конца хода.
затем вы можете проверить, если игрок когда-либо пересекал препятствие во время движения, проверив, если трек игрока пересекает препятствие.
относительно эффективный метод тестирования столкновений с участием быстро движущихся объектов
- определите 3 отрезка линии, которые соединяют 3 вершины начального прямоугольника игрока, ближайшие к прямоугольник окончания игрока.
- для любой из 3 линий, пересекающих препятствие, вычислите расстояние отрезка линии до препятствия. Выберите линию, которая имеет кратчайшее расстояние между начальной вершиной и препятствием.
-
вычислить расстояния" x "и" y " выбранной строки сегмент.
var dx = obstacleIntersection.x - start.x; var dy = obstacleIntersection.y - start.y;
-
переместите игрока из начальной позиции на расстояние, рассчитанное в #3. Это приводит к тому, что игрок перемещается на место, где он впервые столкнулся с препятствием.
player.x += dx; player.y += dy;
код и демо:
полезные функции в коде:
setPlayerVertices
определяет 3 отрезка линии, которые соединяют 3 вершины начального прямоугольника игрока, ближайшие к конечному прямоугольнику игрока.hasCollided
находит кратчайший отрезок, соединяющий вершину из начальной позиции игрока с точкой столкновения на препятствии.line2lineIntersection
находит точку пересечения (если есть) между 2 линиями. Это используется для проверки пересечения между начальным сегментом (от #1) и любым из 4 линейных сегментов, составляющих прямоугольник препятствия. атрибуция: эта функция адаптирована из полезного поля Бурка treatice на перекрестках.
вот пример кода и демо, показывающий, как остановить игрока в точке столкновения на препятствии:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var isDown=false;
var startX,startY,dragging;
ctx.translate(0.50,0.50);
ctx.textAlign='center';
ctx.textBaseline='middle';
var pts;
var p1={x:50,y:50,w:25,h:25,fill:''};
var p2={x:250,y:250,w:25,h:25,fill:''};
var ob={x:100,y:150,w:125,h:25,fill:''};
var obVertices=[
{x:ob.x,y:ob.y},
{x:ob.x+ob.w,y:ob.y},
{x:ob.x+ob.w,y:ob.y+ob.h},
{x:ob.x,y:ob.y+ob.h}
];
var s1,s2,s3,e1,e2,e3,o1,o2,o3,o4;
draw();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
$("#canvas").mouseup(function(e){handleMouseUpOut(e);});
$("#canvas").mouseout(function(e){handleMouseUpOut(e);});
function draw(){
ctx.clearRect(0,0,cw,ch);
//
ctx.lineWidth=4;
ctx.globalAlpha=0.250;
ctx.strokeStyle='blue';
ctx.strokeRect(ob.x,ob.y,ob.w,ob.h);
ctx.globalAlpha=1.00;
ctx.fillStyle='black';
ctx.fillText('obstacle',ob.x+ob.w/2,ob.y+ob.h/2);
//
ctx.globalAlpha=0.250;
ctx.strokeStyle='gold';
ctx.strokeRect(p1.x,p1.y,p1.w,p1.h);
ctx.strokeStyle='purple';
ctx.strokeRect(p2.x,p2.y,p2.w,p2.h);
ctx.fillStyle='black';
ctx.globalAlpha=1.00;
ctx.fillText('start',p1.x+p1.w/2,p1.y+p1.h/2);
ctx.fillText('end',p2.x+p2.w/2,p2.y+p2.h/2);
}
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
startX=parseInt(e.clientX-offsetX);
startY=parseInt(e.clientY-offsetY);
// Put your mousedown stuff here
var mx=startX;
var my=startY;
if(mx>p1.x && mx<p1.x+p1.w && my>p1.y && my<p1.y+p1.h){
isDown=true;
dragging=p1;
}else if(mx>p2.x && mx<p2.x+p2.w && my>p2.y && my<p2.y+p2.h){
isDown=true;
dragging=p2;
}
}
function handleMouseUpOut(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// Put your mouseup stuff here
isDown=false;
dragging=null;
}
function handleMouseMove(e){
if(!isDown){return;}
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// Put your mousemove stuff here
var dx=mouseX-startX;
var dy=mouseY-startY;
startX=mouseX;
startY=mouseY;
//
dragging.x+=dx;
dragging.y+=dy;
//
draw();
//
setPlayerVertices(p1,p2);
var c=hasCollided(obVertices);
if(c.dx){
ctx.strokeStyle='gold';
ctx.strokeRect(p1.x+c.dx,p1.y+c.dy,p1.w,p1.h);
ctx.fillStyle='black';
ctx.fillText('hit',p1.x+c.dx+p1.w/2,p1.y+c.dy+p1.h/2);
line(c.s,c.i,'red');
}
}
function setPlayerVertices(p1,p2){
var tl1={x:p1.x, y:p1.y};
var tl2={x:p2.x, y:p2.y};
var tr1={x:p1.x+p1.w, y:p1.y};
var tr2={x:p2.x+p2.w, y:p2.y};
var br1={x:p1.x+p1.w, y:p1.y+p1.h};
var br2={x:p2.x+p2.w, y:p2.y+p2.h};
var bl1={x:p1.x, y:p1.y+p1.h};
var bl2={x:p2.x, y:p2.y+p2.h};
//
if(p1.x<=p2.x && p1.y<=p2.y){
s1=tr1; s2=br1; s3=bl1;
e1=tr2; e2=br2; e3=bl2;
o1=0; o2=1; o3=3; o4=0;
}else if(p1.x<=p2.x && p1.y>=p2.y){
s1=tl1; s2=tr1; s3=br1;
e1=tl2; e2=tr2; e3=br2;
o1=2; o2=3; o3=3; o4=0;
}else if(p1.x>=p2.x && p1.y<=p2.y){
s1=tl1; s2=br1; s3=bl1;
e1=tl2; e2=br2; e3=bl2;
o1=0; o2=1; o3=1; o4=2;
}else if(p1.x>=p2.x && p1.y>=p2.y){
s1=tl1; s2=tr1; s3=bl1;
e1=tl2; e2=tr2; e3=bl2;
o1=1; o2=2; o3=2; o4=3;
}
}
function hasCollided(o){
//
var i1=line2lineIntersection(s1,e1,o[o1],o[o2]);
var i2=line2lineIntersection(s2,e2,o[o1],o[o2]);
var i3=line2lineIntersection(s3,e3,o[o1],o[o2]);
var i4=line2lineIntersection(s1,e1,o[o3],o[o4]);
var i5=line2lineIntersection(s2,e2,o[o3],o[o4]);
var i6=line2lineIntersection(s3,e3,o[o3],o[o4]);
//
var tracks=[];
if(i1){tracks.push(track(s1,e1,i1));}
if(i2){tracks.push(track(s2,e2,i2));}
if(i3){tracks.push(track(s3,e3,i3));}
if(i4){tracks.push(track(s1,e1,i4));}
if(i5){tracks.push(track(s2,e2,i5));}
if(i6){tracks.push(track(s3,e3,i6));}
//
var nohitDist=10000000;
var minDistSq=nohitDist;
var halt={dx:null,dy:null,};
for(var i=0;i<tracks.length;i++){
var t=tracks[i];
var testdist=t.dx*t.dx+t.dy*t.dy;
if(testdist<minDistSq){
minDistSq=testdist;
halt.dx=t.dx;
halt.dy=t.dy;
halt.s=t.s;
halt.i=t.i;
}
}
return(halt);
}
//
function track(s,e,i){
dot(s);dot(i);line(s,i);line(i,e);
return({ dx:i.x-s.x, dy:i.y-s.y, s:s, i:i });
}
function line2lineIntersection(p0,p1,p2,p3) {
var unknownA = (p3.x-p2.x) * (p0.y-p2.y) - (p3.y-p2.y) * (p0.x-p2.x);
var unknownB = (p1.x-p0.x) * (p0.y-p2.y) - (p1.y-p0.y) * (p0.x-p2.x);
var denominator = (p3.y-p2.y) * (p1.x-p0.x) - (p3.x-p2.x) * (p1.y-p0.y);
// Test if Coincident
// If the denominator and numerator for the ua and ub are 0
// then the two lines are coincident.
if(unknownA==0 && unknownB==0 && denominator==0){return(null);}
// Test if Parallel
// If the denominator for the equations for ua and ub is 0
// then the two lines are parallel.
if (denominator == 0) return null;
// If the intersection of line segments is required
// then it is only necessary to test if ua and ub lie between 0 and 1.
// Whichever one lies within that range then the corresponding
// line segment contains the intersection point.
// If both lie within the range of 0 to 1 then
// the intersection point is within both line segments.
unknownA /= denominator;
unknownB /= denominator;
var isIntersecting=(unknownA>=0 && unknownA<=1 && unknownB>=0 && unknownB<=1)
if(!isIntersecting){return(null);}
return({
x: p0.x + unknownA * (p1.x-p0.x),
y: p0.y + unknownA * (p1.y-p0.y)
});
}
function dot(pt){
ctx.beginPath();
ctx.arc(pt.x,pt.y,3,0,Math.PI*2);
ctx.closePath();
ctx.fill();
}
function line(p0,p1,stroke,lw){
ctx.beginPath();
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
ctx.lineWidth=lw || 1;
ctx.strokeStyle=stroke || 'gray';
ctx.stroke();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Drag start & end player position rects<br>The shortest segment intersecting the obstacle is red.<br>The repositioned player is shown on the obstacle.</h4>
<canvas id="canvas" width=400 height=400></canvas>
то, что вы испытываете, обычно называется туннелированием.
здесь много различных способов его решения, но самый простой, как правило, сохранить последнюю позицию и сделать одно из следующих.
A
вычислить новую большую коллизионную коробку для каждого элемента, содержащую последнюю позицию элемента, и она новая. Считай, что это коробка, содержащая два элемента. Один раз для последней позиции (LP) и один для новой позиция (NP),
------------
|| LP | |
||____| |
| ____ |
| | NP ||
|______|____||
теперь, если вы используете этот новый флажок для проверки столкновений, он будет учитывать пройденный путь, чтобы избежать туннелирования. Это может создать неожиданные столкновения в верхнем правом и нижнем левом углу, но это простая реализация и компромисс может быть стоит.
B
Проверьте столкновения для каждого шага по пути, пройденному от последней позиции до новой позиции. Если ваш элемент прошел 5 пикселей начиная с последнего кадра, вы проверяете столкновение один раз для каждого пикселя (или минимальное допустимое расстояние столкновения).
____
| LP |
|____||
---- |___
|___|NP |
|____|
Это, конечно, увеличит количество обнаружений столкновений и повлияет на производительность. Здесь вы можете посмотреть на quadtrees, чтобы компенсировать потерю производительности.
двигаясь вперед, есть гораздо более элегантные и передовые решения, но тема является широкой для полного ответа здесь.
надеюсь, что это помогает!
Ну, я сделал "калькулятор столкновений" около нескольких месяцев назад, поэтому вы можете изменить и использовать код ниже, как вам нравится :) для лучшего объяснения:
- p_x-последняя позиция игрока x плюс его ширина
- p_y-последняя позиция игрока y плюс его высота
- p_x_m-последняя позиция игрока x
- p_y_m-последняя позиция игрока y
- y_m-новая позиция игрока (его y - somevalue)
- x_m-новая позиция игрока (его x - некоторое_значение)
- y_p-новая позиция игрока (его y + somevalue + его высота)
- y_p_m-новая позиция игрока (его y + somevalue)
- x_p-новая позиция игрока (его x + somevalue + его ширина)
- x_p_m-новая позиция игрока (его x + somevalue)
- w_x-положение стены x
- w_y-положение стены y
- w_w-это ширина стены
w_h-высота стены
pressedKeys является строка, указывающая, какие клавиши игрок нажал (например, " был "или" wd "или" ad " и т. д.)
-
this.walls
является переменной со стенами (например, если у меня есть 4 стены, массив будет выглядеть как[false,'s',false,false]
потому что я коснулся второй стены с помощью клавиши" s").
код:
if(
pressedKeys.indexOf("s")>-1 &&
(
( // P
p_y>w_y&&p_y<(w_y+w_h)&&x_p_m>w_x && x_p-5>w_x && x_m<w_x // +----
) || // |
( // P
y_p>w_y&&p_y<(w_y+w_h) && x_p-5>w_x && x_p<=(w_x+w_w) // +--------+
) || // | |
( // P
y_p>w_y&&p_y<(w_y+w_h) && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w) // ----+
) // |
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "s";
}
if(
pressedKeys.indexOf("d")>-1 &&
(
( // P+----
p_x>w_x&&p_x<(w_x+w_w)&&y_p_m>w_y && y_p-5>w_y && y_m<w_y // |
) || // |
( // |
x_p>w_x&&p_x<(w_x+w_w) && y_p-5>w_y && y_p<=(w_y+w_h) // P|
) || // |
( // |
x_p>w_x&&p_x<(w_x+w_w) && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h) // |
) // P+----
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "d";
}
if(
pressedKeys.indexOf("w")>-1 &&
(
( // |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_m<w_x &&x_p_m>w_x // +----
) || // P
( // | |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p-5>w_x && x_p<=(w_x+w_w) // +--------+
) || // P
( // |
y_m<(w_y+w_h)&&y_p-5>w_y && x_p>(w_x+w_w)&&p_x_m<(w_x+w_w) && x_m+5<(w_x+w_w) // ----+
) // P
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "w";
}
if(
pressedKeys.indexOf("a")>-1 &&
(
( // ----+P
x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_m<w_y &&y_p_m>w_y // |
) || // |
( // |
x_m<(w_x+w_w)&&x_p-5>w_x && y_p-5>w_y && y_p<=(w_y+w_h) // |P
) || // |
( // |
x_m<(w_x+w_w)&&x_p-5>w_x && y_p>(w_y+w_h)&&p_y_m<(w_y+w_h) && y_m+5<(w_y+w_h) // |P
) // ----+P
)
)
{
if(this.walls[i] == false)
this.walls[i] = "";
this.walls[i] += "a";
}
комментарии в правой части кода показывают, как игрок сталкивается.
этот код 100% работает, я использую его каждый раз, когда я хочу проверить столкновение.
Надежда это немного помогло:)