Skip to content

Commit

Permalink
🚩 Add jump to earn coin feature (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
romantech authored Feb 4, 2024
1 parent 4a5fa87 commit 05d2b38
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 139 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class Game {
}

checkCollision() {
for (let obstacle of this.obstacles.list) {
for (let obstacle of this.entityList.list) {
const marioRect = this.mario.element.getBoundingClientRect();
const obstacleRect = obstacle.element.getBoundingClientRect();

Expand Down
Binary file added assets/audio/coin-sound.mp3
Binary file not shown.
Binary file added assets/coin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/goomba.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed assets/mario.png
Binary file not shown.
Binary file added assets/piranha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 13 additions & 1 deletion index.css
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ main {
}

.obstacle {
background-image: url('./assets/obstacle.png');
background-size: 100%;
background-repeat: no-repeat;
position: absolute;
Expand All @@ -42,6 +41,19 @@ main {
height: 49px;
}

.spin {
animation: spin 2s linear infinite;
}

@keyframes spin {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(360deg);
}
}

header {
display: flex;
align-items: center;
Expand Down
22 changes: 19 additions & 3 deletions src/audio-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ class AudioManager {
static SOUND_ON_PATH = './assets/sound-on.png';
static SOUND_OFF_PATH = './assets/sound-off.png';
static JUMP_SOUND_PATH = './assets/audio/jump-sound.mp3';
static COIN_SOUND_PATH = './assets/audio/coin-sound.mp3';
static BGM_PATH = './assets/audio/bgm.mp3';

soundOnImage = new Image();
soundOffImage = new Image();

audio = new Audio(AudioManager.BGM_PATH);
jumpSound = new Audio(AudioManager.JUMP_SOUND_PATH);
coinSound = new Audio(AudioManager.COIN_SOUND_PATH);

constructor({ autoplay = true, loop = true, muted = true } = {}) {
this.audio.autoplay = autoplay;
this.audio.loop = loop;
this.audio.muted = muted;
this.jumpSound.volume = 0.5;
this.coinSound.volume = 0.5;

this.preloadImages();
}
Expand All @@ -41,10 +44,23 @@ class AudioManager {
}
}

playJumpSound() {
playEffect(type) {
if (this.isMuted) return;
this.jumpSound.currentTime = 0; // 재생 위치를 처음으로 설정
this.jumpSound.play();

let sound;
switch (type) {
case 'jump': {
sound = this.jumpSound;
break;
}
case 'coin': {
sound = this.coinSound;
break;
}
}

sound.currentTime = 0; // 재생 위치를 처음으로 설정
sound.play();
}

#toggleIcon() {
Expand Down
168 changes: 168 additions & 0 deletions src/entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import DomManager from './dom-manager.js';
import { generateRandomNumber } from './utils.js';

class EntityManager {
static ENTITY_TYPES = ['obstacle', 'coin', 'both'];

list = new Set();
frameId = null;
isMonitoring = false;
defaultOption;

constructor(defaultOption) {
this.defaultOption = defaultOption;
}

add(objectType, withObstacle = false) {
let entity;

switch (objectType) {
case 'obstacle': {
entity = new Obstacle(this.defaultOption);
break;
}
case 'coin': {
entity = new Coin(this.defaultOption, withObstacle);
break;
}
}

DomManager.gameArea.appendChild(entity.element);
this.list.add(entity);
entity.move();
}

remove(entity) {
entity.stop();
entity.element.remove();
this.list.delete(entity);
}

reset() {
this.list.forEach(entity => this.remove(entity));
}

offScreenMonitor() {
const checkObstacles = () => {
this.list.forEach(entity => {
if (entity.isOutOfBounds) this.remove(entity);
});

if (this.isMonitoring) {
this.frameId = requestAnimationFrame(checkObstacles);
}
};

checkObstacles();
}

moveAll() {
this.list.forEach(entity => {
if (entity.type === 'coin') entity.animate();
entity.move();
});

if (!this.isMonitoring) {
this.isMonitoring = true;
this.offScreenMonitor();
}
}

stopAll() {
this.list.forEach(entity => {
entity.pauseAnimate();
entity.stop();
});

if (this.isMonitoring) {
this.isMonitoring = false;
cancelAnimationFrame(this.frameId);
}
}
}

class Entity {
type;
speed;
point = 0;
frameId = null;

constructor({ bottom, speed, type, point = 0, className = 'obstacle' }) {
this.type = type;
this.speed = speed;
this.point = point;

this.element = document.createElement('div');
this.element.classList.add(className);
this.element.style.bottom = bottom + 'px';
this.element.style.right = '-100px'; // 장애물 생성을 자연스럽게 하기 위해 초기값을 더 오른쪽으로 지정

this.move = this.move.bind(this);
}

get isOutOfBounds() {
// 장애물이 게임 화면 왼쪽 경계를 벗어나면 제거
// element.clientWidth 값은 요소의 내부 너비(padding 포함)
return this.currentPosition >= DomManager.gameArea.clientWidth;
}

get currentPosition() {
return parseInt(this.element.style.right, 10); // parseInt('10.5px') => 10
}

animate() {
this.element.classList.add('spin');
}

pauseAnimate() {
this.element.classList.remove('spin');
}

move() {
const nextRight = this.currentPosition + this.speed;
this.element.style.right = nextRight + 'px'; // 장애물을 왼쪽으로 이동
this.frameId = requestAnimationFrame(this.move);
}

stop() {
cancelAnimationFrame(this.frameId);
}
}

class Obstacle extends Entity {
static GOOMBA_IMG_PATH = './assets/goomba.png';
static PIRANHA_IMG_PATH = './assets/piranha.png';

constructor(defaultOption) {
super({ ...defaultOption, type: 'obstacle' });

const imgSet = [Obstacle.GOOMBA_IMG_PATH, Obstacle.PIRANHA_IMG_PATH];
const imgPath = imgSet[generateRandomNumber(0, imgSet.length - 1)];

this.element.style.backgroundImage = `url(${imgPath})`;
}
}

class Coin extends Entity {
static IMG_PATH = './assets/coin.png';
static POINT_EASY = 1;
static POINT_MEDIUM = 5;
static MAX_BOTTOM = 240;

constructor(defaultOption, withObstacle) {
const bottomOffset = defaultOption.bottom;
const minBottom = withObstacle ? bottomOffset + 100 : bottomOffset;

super({
...defaultOption,
type: 'coin',
point: withObstacle ? Coin.POINT_MEDIUM : Coin.POINT_EASY,
bottom: generateRandomNumber(minBottom, Coin.MAX_BOTTOM),
});

this.element.style.backgroundImage = `url(${Coin.IMG_PATH})`;
this.element.classList.add('spin');
}
}

export default EntityManager;
Loading

0 comments on commit 05d2b38

Please sign in to comment.