[マイおもちゃ]動くボール2-JavaScript


ボールを弾く(ボールを捕まえることもできる!)

🔨 さぎょうコード


-ブロックを描画


布を点けてから積み木をもう一つ作るつもりです.
canvasで感じた、こいつは...本当に座標しかありません.ははは
ブロックを形成するために、位置決めします.
export class Block {
  // 블록의 x, y 좌표와 길이와 높이를 받는다.
  constructor(x, y, width, height) {
    this.width = width;
    this.height = height;
    this.x = x;
    this.y = y;
  }
  
  draw(ctx) {
    ctx.fillStyle = "tomato";
    ctx.beginPath();
    // 좌표를 받아 사각형을 만들어준다
    ctx.rect(this.x, this.y, this.width, this.height);
    ctx.fill();
  }
}
class App {
  constructor() {
    
    ...
    
    this.ball = new Ball(this.stageWidth, this.stageHeight, 60, 10);
    // 임의로 블록의 크기를 지정
    this.block = new Block(200, 300, 400, 30);

    window.requestAnimationFrame(this.animate.bind(this));
  }
  
  ...
  
  animate() {
    window.requestAnimationFrame(this.animate.bind(this));
    this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
	// 블록 그리기
    this.block.draw(this.ctx);
    this.ball.draw(this.ctx, this.stageWidth, this.stageHeight);
  }
}

では、これで積み木があります.このように置くと少し単調なので、ブロックにmoveTo、lineToで座標をつなぎ、立体的に見えます.
では.

ちゃんとした街が形成されています!ただし、ブロックはまだブロックにバウンドしていないので、ウィンドウでバウンドするのと同じように、ブロックにも関数が作成されます.

-ブロックイメージ化

class App {
  ...
  
  animate() {
    window.requestAnimationFrame(this.animate.bind(this));
    this.ctx.clearRect(0, 0, this.stageWidth, this.stageHeight);
    this.block.draw(this.ctx);
    // draw에 블록을 추가한 후
    this.ball.draw(this.ctx, this.stageWidth, this.stageHeight, this.block);
  }
}
export class Ball {
  ...

  draw(ctx, stageWidth, stageHeight, block) {
    this.x += this.vx;
    this.y += this.vy;

    this.bounceWindow(stageWidth, stageHeight);
    // 블록 함수 추가
    this.bounceBlock(block);

    ...
  }
  ...

  bounceBlock(block) {
    const minX = block.x - this.radius;
    const maxX = block.maxX + this.radius;
    const minY = block.y - this.radius;
    const maxY = block.maxY + this.radius;
	// 블록 영역 정의
    if (this.x > minX && this.x < maxX && this.y > minY && this.y < maxY) {
      // 블록의 x, y 중 어느 쪽에 닿았는지 판별
      const x1 = Math.abs(minX - this.x);
      const x2 = Math.abs(maxX - this.x);
      const y1 = Math.abs(minY - this.y);
      const y2 = Math.abs(maxY - this.y);
      const min1 = Math.min(x1, x2);
      const min2 = Math.min(y1, y2);
      const min = Math.min(min1, min2);

      if (min === min1) {
        this.vx *= -1;
        this.x += this.vx;
      } else if (min === min2) {
        this.vy *= -1;
        this.y += this.vy;
      }
    }
  }
}
では.

積み木に正常に跳ね上がったボールの様子が見えます.
しかし、ここで問題が発生した.

-エラーを検出🔍


ブロックが不可侵領域として指定されているため、最初に生成されたボールがブロック内に現れると.
ボールは積み木に閉じ込められている.

この問題を解決するために、条件文を指定します.

-エラー修復ライブラリ

class App {
  constructor() {
    ...
    this.block = new Block(200, 300, 400, 30);
    // 블록을 ball 안에 넣어준다.(블록의 좌표를 얻어야 하기 때문)
    this.ball = new Ball(this.stageWidth, this.stageHeight, 50, 5, this.block);

    window.requestAnimationFrame(this.animate.bind(this));
  }
export class Ball {
  constructor(stageWidth, stageHeight, radius, speed, block) {
    ...
    
    const diameter = this.radius * 2;
    this.checkX = this.radius + Math.random() * (stageWidth - diameter);
    this.checkY = this.radius + Math.random() * (stageHeight - diameter);
    
    // 생성된 위치가 블록 안일 경우를 체크
    this.checkXYWithBlock =
      block.x - this.radius < this.checkX &&
      this.checkX < block.maxX + this.radius &&
      block.y - this.radius < this.checkY &&
      this.checkY < block.maxY + this.radius;
    
    // checkXYWithBlock이 참이면 블록 안에 공이 만들어진 것이므로
    // 갇히는 것을 막기 위해 좌측 상단에 공이 나오게 지정
    this.x = this.checkXYWithBlock ? this.radius : this.checkX;
    this.y = this.checkXYWithBlock ? this.radius : this.checkY;
  }
  ...
}
これで、普通に仕事をしている様子が見えます!

-その他の機能の実装


ここで終わるのはちょっと残念ですが、ボールを勝手に動かしてもらえませんか?そこで、またマウスを使ったアクティビティを作りました.(drag & drop)
export class Ball {
  constructor(stageWidth, stageHeight, radius, speed, block) {
	...
    // 추가할 이벤트 함수를 변수로 선언 (그 이유는 뒤에서 말하겠다.)
    this.onMouseDown = this.mousedown.bind(this);
    this.onMouseUp = this.mouseup.bind(this);
    this.onMouseMove = this.mousemove.bind(this);
  }
  
  draw(ctx, stageWidth, stageHeight, block) {
    // mousedown 이벤트를 추가
    window.addEventListener("mousedown", this.onMouseDown);
    ...
  }
  
  setDirection() {
    // 현재 공의 방향을 변수에 저장
    this.vxDirection = this.vx < 0 ? -1 : 1;
    this.vyDirection = this.vy < 0 ? -1 : 1;
  }

  mousedown(event) {
    // 7. 다시 mousedown이 발생하면 맨 뒤에 발생했던 mouseup제거 (그 이유는 뒤에서 말하겠다.)
    window.removeEventListener("mouseup", this.onMouseUp);
    this.offsetX = event.clientX - this.x;
    this.offsetY = event.clientY - this.y;
    
	// 1. mousedown이 발생될 때 현재 공의 방향을 불러온다.
    this.setDirection();
    
    // 2. 만약 공 안에서 mousedown이 발생하면 mousemove를 이벤트를 실행
    if (
      Math.abs(this.offsetX) <= this.radius &&
      Math.abs(this.offsetY) <= this.radius
    ) {
      window.addEventListener("mousemove", this.onMouseMove);
    }
  }

  mousemove(event) {
    // 3. mousemove 이벤트 실행시 공이 멈추고 공이 마우스를 따라 이동
    this.x = event.clientX - this.offsetX;
    this.y = event.clientY - this.offsetY;

    this.vx = 0;
    this.vy = 0;
    
    // 4. 마우스를 떼면 mouseup 이벤트 실행
    window.addEventListener("mouseup", this.onMouseUp);
  }

  mouseup() {
    // 5. 공을 mousedown으로 집었을 때의 방향을 불러와 공이 다시 움직이도록 구현
    this.vx = this.speed;
    this.vy = this.speed;

    if (this.vxDirection < 0) {
      this.vx *= -1;
    }
    if (this.vyDirection < 0) {
      this.vy *= -1;
    }
    
	// 6. mousemove, mousedown 이벤트 제거
    window.removeEventListener("mousemove", this.onMouseMove);
    window.removeEventListener("mousedown", this.onMouseDown);
  }
}

🎉 結果



私が自由に操ることができるボールが完成しました!(パチパチ)

🩹 辛いところ


classでeventを追加および削除すると、望ましくない動作がしばしば発生します.
特に、addEventListener(「mouseup」,this.function)という関数の部分がthisです.function.bind(this)のようにbindで包まないと動作しません.
だからbindで包んで運転してみると今回removeEventListenerは動作していません
(今考えても鬱陶しいハハ)
原理を理解するために、いろいろ探しましたが、あまり理解していません.
そしてbindに関する良い文章を見つけました->bind了解
const object = {
	name: 'object',
	getName() {
		return this.name;
	}
}

const getName = object.getName;
const bindGetName = object.getName.bind(object);

console.log(getName === bindGetName); // false
リンクの文章を見ると、上のコードが違うことがわかります.
 class TryEvent {
    ...
	add() {
		this.element.addEventListener('click', this.click.bind(this)); 
	}
	
	remove() {
		this.element.removeEventListener('click', this.click);
		this.element.removeEventListener('click', this.click.bind(this));
	}
}
したがって,この3つの関数にも異なる値がある.
だからRemoveEventListenerは動いていませんない事件を取り除きたいからだ.
この問題を解決するために、関数をthisに設定します.function.bind(this)として宣言し、変数を作成して値を割り当てます.
  • this.onMouseUp = this.mouseup.bind(this)
  • これにより、eventはどこで作成されても、直接関数呼び出しではなく変数呼び出しによって同じeventを追加および削除できます.(今の私の理解の道の上でこれは最も良いです呜呜)
    このように仮定を確立して実行して、幸いなことにeventは正常に実行します!!
    でも.また一つ問題が発生した.
    mouseupアクティビティでボールの方向を覚えているせいか、
    マウスは空のスクリーンを往復移動したが、ボールの方向が急に変わった.ははは
    それを解消するために、私は多くの試みをしました.私は少しも好きではありません.
    mousedownイベントが再び発生すると、元のmouseupイベントがキャンセルされます.
    期待した機能の実現はうまくいかなかったが、収穫があり、うれしかった.😄