jsdoでmatter その2

#概要

jsdoでmatterやってみた。
ピンボール見つけた。

#写真

![image.png](https://qiita-image-store.s3.amazonaws.com/0/18104/07beabc1-d358-756a-e759-e00a4bf9a4b7.png)

#サンプルコード

```
(() => {
	Matter.use(MatterAttractors);
	const PATHS = {
		DOME: '0 0 0 250 19 250 20 231.9 25.7 196.1 36.9 161.7 53.3 129.5 74.6 100.2 100.2 74.6 129.5 53.3 161.7 36.9 196.1 25.7 231.9 20 268.1 20 303.9 25.7 338.3 36.9 370.5 53.3 399.8 74.6 425.4 100.2 446.7 129.5 463.1 161.7 474.3 196.1 480 231.9 480 250 500 250 500 0 0 0',
		DROP_LEFT: '0 0 20 0 70 100 20 150 0 150 0 0',
		DROP_RIGHT: '50 0 68 0 68 150 50 150 0 100 50 0',
		APRON_LEFT: '0 0 180 120 0 120 0 0',
		APRON_RIGHT: '180 0 180 120 0 120 180 0'
	};
	const COLOR = {
		BACKGROUND: '#212529',
		OUTER: '#495057',
		INNER: '#15aabf',
		BUMPER: '#fab005',
		BUMPER_LIT: '#fff3bf',
		PADDLE: '#e64980',
		PINBALL: '#dee2e6'
	};
	const GRAVITY = 0.75;
	const WIREFRAMES = false;
	const BUMPER_BOUNCE = 1.5;
	const PADDLE_PULL = 0.002;
	const MAX_VELOCITY = 40;
	let $currentScore = $('.current-score span');
	let $highScore = $('.high-score span');
	let currentScore, 
        highScore;
	let engine, 
        world,
        render,
        pinball, 
        stopperGroup;
	let leftPaddle,
        leftUpStopper,
        leftDownStopper,
        isLeftPaddleUp;
	let rightPaddle, 
        rightUpStopper,
        rightDownStopper,
        isRightPaddleUp;

	function load() {
		init();
		createStaticBodies();
		createPaddles();
		createPinball();
		createEvents();
	}
	function init() {
		engine = Matter.Engine.create();
		world = engine.world;
		world.bounds = {
			min: {
                x: 0,
                y: 0
            },
			max: {
                x: 500,
                y: 800
            }
		};
		world.gravity.y = GRAVITY;
     	render = Matter.Render.create({
			element: $('.container')[0],
			engine: engine,
			options: {
				width: world.bounds.max.x,
				height: world.bounds.max.y,
				wireframes: WIREFRAMES,
				background: COLOR.BACKGROUND
			}
		});
		Matter.Render.run(render);
		let runner = Matter.Runner.create();
		Matter.Runner.run(runner, engine);
		stopperGroup = Matter.Body.nextGroup(true);
		currentScore = 0;
		highScore = 0;
		isLeftPaddleUp = false;
		isRightPaddleUp = false;
	}
	function createStaticBodies() {
		Matter.World.add(world, [
			boundary(250, -30, 500, 100),
			boundary(250, 830, 500, 100),
			boundary(-30, 400, 100, 800),
			boundary(530, 400, 100, 800),
			path(239, 86, PATHS.DOME),
			wall(140, 140, 20, 40, COLOR.INNER),
			wall(225, 140, 20, 40, COLOR.INNER),
			wall(310, 140, 20, 40, COLOR.INNER),
			bumper(105, 250),
			bumper(225, 250),
			bumper(345, 250),
			bumper(165, 340),
			bumper(285, 340),
			wall(440, 520, 20, 560, COLOR.OUTER),
			path(25, 360, PATHS.DROP_LEFT),
			path(425, 360, PATHS.DROP_RIGHT),
			wall(120, 510, 20, 120, COLOR.INNER),
			wall(330, 510, 20, 120, COLOR.INNER),
			wall(60, 529, 20, 160, COLOR.INNER),
			wall(390, 529, 20, 160, COLOR.INNER),
			wall(93, 624, 20, 98, COLOR.INNER, -0.96),
			wall(357, 624, 20, 98, COLOR.INNER, 0.96),
			path(79, 740, PATHS.APRON_LEFT),
			path(371, 740, PATHS.APRON_RIGHT),
			reset(225, 50),
			reset(465, 30)
		]);
	}
	function createPaddles() {
		leftUpStopper = stopper(160, 591, 'left', 'up');
		leftDownStopper = stopper(140, 743, 'left', 'down');
		rightUpStopper = stopper(290, 591, 'right', 'up');
		rightDownStopper = stopper(310, 743, 'right', 'down');
		Matter.World.add(world, [leftUpStopper, leftDownStopper, rightUpStopper, rightDownStopper]);
		let paddleGroup = Matter.Body.nextGroup(true);
		let paddleLeft = {};
		paddleLeft.paddle = Matter.Bodies.trapezoid(170, 660, 20, 80, 0.33, {
			label: 'paddleLeft',
			angle: 1.57,
			chamfer: {},
			render: {
				fillStyle: COLOR.PADDLE
			}
		});
		paddleLeft.brick = Matter.Bodies.rectangle(172, 672, 40, 80, {
			angle: 1.62,
			chamfer: {},
			render: {
				visible: false
			}
		});
		paddleLeft.comp = Matter.Body.create({
			label: 'paddleLeftComp',
			parts: [paddleLeft.paddle, paddleLeft.brick]
		});
		paddleLeft.hinge = Matter.Bodies.circle(142, 660, 5, {
			isStatic: true,
			render: {
				visible: false
			}
		});
		Object.values(paddleLeft).forEach((piece) => {
			piece.collisionFilter.group = paddleGroup
		});
		paddleLeft.con = Matter.Constraint.create({
			bodyA: paddleLeft.comp,
			pointA: { 
                x: -29.5,
                y: -8.5
            },
			bodyB: paddleLeft.hinge,
			length: 0,
			stiffness: 0
		});
		Matter.World.add(world, [paddleLeft.comp, paddleLeft.hinge, paddleLeft.con]);
		Matter.Body.rotate(paddleLeft.comp, 0.57, { 
            x: 142,
            y: 660
        });
		let paddleRight = {};
		paddleRight.paddle = Matter.Bodies.trapezoid(280, 660, 20, 80, 0.33, {
			label: 'paddleRight',
			angle: -1.57,
			chamfer: {},
			render: {
				fillStyle: COLOR.PADDLE
			}
		});
		paddleRight.brick = Matter.Bodies.rectangle(278, 672, 40, 80, {
			angle: -1.62,
			chamfer: {},
			render: {
				visible: false
			}
		});
		paddleRight.comp = Matter.Body.create({
			label: 'paddleRightComp',
			parts: [paddleRight.paddle, paddleRight.brick]
		});
		paddleRight.hinge = Matter.Bodies.circle(308, 660, 5, {
			isStatic: true,
			render: {
				visible: false
			}
		});
		Object.values(paddleRight).forEach((piece) => {
			piece.collisionFilter.group = paddleGroup
		});
		paddleRight.con = Matter.Constraint.create({
			bodyA: paddleRight.comp,
			pointA: { 
                x: 29.5,
                y: -8.5
            },
			bodyB: paddleRight.hinge,
			length: 0,
			stiffness: 0
		});
		Matter.World.add(world, [paddleRight.comp, paddleRight.hinge, paddleRight.con]);
		Matter.Body.rotate(paddleRight.comp, -0.57, { 
            x: 308, 
            y: 660
        });
	}
	function createPinball() {
		pinball = Matter.Bodies.circle(0, 0, 14, {
			label: 'pinball',
			collisionFilter: {
				group: stopperGroup
			},
			render: {
				fillStyle: COLOR.PINBALL
			}
		});
		Matter.World.add(world, pinball);
		launchPinball();
	}
	function createEvents() {
		Matter.Events.on(engine, 'collisionStart', function(event) {
			let pairs = event.pairs;
			pairs.forEach(function(pair) {
				if (pair.bodyB.label === 'pinball')
                {
					switch (pair.bodyA.label) 
                    {
					case 'reset':
						launchPinball();
					break;
					case 'bumper':
						pingBumper(pair.bodyA);
					break;
					}
				}
			});
		});
		Matter.Events.on(engine, 'beforeUpdate', function(event) {
			Matter.Body.setVelocity(pinball, {
				x: Math.max(Math.min(pinball.velocity.x, MAX_VELOCITY), -MAX_VELOCITY),
				y: Math.max(Math.min(pinball.velocity.y, MAX_VELOCITY), -MAX_VELOCITY),
			});
			if (pinball.position.x > 450 && pinball.velocity.y > 0) 
            {
				Matter.Body.setVelocity(pinball, {
                    x: 0, 
                    y: -5
                });
			}
		});
		Matter.World.add(world, Matter.MouseConstraint.create(engine, {
			mouse: Matter.Mouse.create(render.canvas),
			constraint: {
				stiffness: 0.2,
				render: {
					visible: false
				}
			}
		}));
		$('body').on('keydown', function(e) {
			if (e.which === 37) 
            {
            	isLeftPaddleUp = true;
			} 
            else if (e.which === 39)
            {
            	isRightPaddleUp = true;
			}
		});
		$('body').on('keyup', function(e) {
			if (e.which === 37)
            {
            	isLeftPaddleUp = false;
			} 
            else if (e.which === 39)
            {
            	isRightPaddleUp = false;
			}
		});
		$('.left-trigger').on('mousedown touchstart', function(e) {
			isLeftPaddleUp = true;
        }).on('mouseup touchend', function(e) {
			isLeftPaddleUp = false;
		});
		$('.right-trigger').on('mousedown touchstart', function(e) {
			isRightPaddleUp = true;
		}).on('mouseup touchend', function(e) {
			isRightPaddleUp = false;
		});
	}
	function launchPinball() {
		updateScore(0);
		Matter.Body.setPosition(pinball, { 
            x: 465,
            y: 765
        });
		Matter.Body.setVelocity(pinball, {
            x: 0,
            y: -25 + rand(-2, 2) 
        });
		Matter.Body.setAngularVelocity(pinball, 0);
	}
	function pingBumper(bumper) {
		updateScore(currentScore + 10);
		bumper.render.fillStyle = COLOR.BUMPER_LIT;
		setTimeout(function() {
			bumper.render.fillStyle = COLOR.BUMPER;
		}, 100);
	}
	function updateScore(newCurrentScore) {
		currentScore = newCurrentScore;
		$currentScore.text(currentScore);
		highScore = Math.max(currentScore, highScore);
		$highScore.text(highScore);
	}
	function rand(min, max) {
		return Math.random() * (max - min) + min;
	}
	function boundary(x, y, width, height) {
		return Matter.Bodies.rectangle(x, y, width, height, {
			isStatic: true,
			render: {
				fillStyle: COLOR.OUTER
			}
		});
	}
	function wall(x, y, width, height, color, angle = 0) {
		return Matter.Bodies.rectangle(x, y, width, height, {
			angle: angle,
			isStatic: true,
			chamfer: { 
                radius: 10 
            },
			render: {
				fillStyle: color
			}
		});
	}
	function path(x, y, path) {
		let vertices = Matter.Vertices.fromPath(path);
		return Matter.Bodies.fromVertices(x, y, vertices, {
			isStatic: true,
			render: {
				fillStyle: COLOR.OUTER,
				strokeStyle: COLOR.OUTER,
				lineWidth: 1
			}
		});
	}
	function bumper(x, y) {
		let bumper = Matter.Bodies.circle(x, y, 25, {
			label: 'bumper',
			isStatic: true,
			render: {
				fillStyle: COLOR.BUMPER
			}
		});
		bumper.restitution = BUMPER_BOUNCE;
		return bumper;
	}
	function stopper(x, y, side, position) {
		let attracteeLabel = (side === 'left') ? 'paddleLeftComp' : 'paddleRightComp';
		return Matter.Bodies.circle(x, y, 40, {
			isStatic: true,
			render: {
				visible: false,
			},
			collisionFilter: {
				group: stopperGroup
			},
			plugin: {
				attractors: [function(a, b) {
					if (b.label === attracteeLabel) 
                    {
						let isPaddleUp = (side === 'left') ? isLeftPaddleUp : isRightPaddleUp;
						let isPullingUp = (position === 'up' && isPaddleUp);
						let isPullingDown = (position === 'down' && !isPaddleUp);
						if (isPullingUp || isPullingDown) 
                        {
							return {
								x: (a.position.x - b.position.x) * PADDLE_PULL,
								y: (a.position.y - b.position.y) * PADDLE_PULL,
							};
						}
					}
				}]
			}
		});
	}
	function reset(x, width) {
		return Matter.Bodies.rectangle(x, 781, width, 2, {
			label: 'reset',
			isStatic: true,
			render: {
				fillStyle: '#fff'
			}
		});
	}
	window.addEventListener('load', load, false);
})();

```

#成果物

http://jsdo.it/ohisama1/CW2u

以上。