🎮ArcadeLab

Арбузный молоток

by GoldenBear87
744 lines10.8 KB
▶ Play
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Арбузный молоток</title>

<style>
*{
    box-sizing:border-box;
    user-select:none;
    -webkit-user-select:none;
    -webkit-tap-highlight-color:transparent;
}

html,body{
    margin:0;
    width:100%;
    height:100%;
    overflow:hidden;
    touch-action:none;
}

body{
    background:#d8c8a8;
}

#game{
    position:relative;
    width:100vw;
    height:100vh;
    overflow:hidden;
    background:
        radial-gradient(
            circle at center,
            #f2e8cf 0%,
            #dfcfaa 70%,
            #c8b487 100%
        );
}

#score{
    position:absolute;
    top:16px;
    left:50%;
    transform:translateX(-50%);
    z-index:100;

    padding:9px 18px;
    border-radius:15px;

    background:rgba(255,255,255,.8);
    color:#332b1e;

    font-size:23px;
    font-weight:900;

    box-shadow:0 4px 15px rgba(0,0,0,.16);
}

#newWatermelon{
    position:absolute;
    top:65px;
    left:50%;
    transform:translateX(-50%);
    z-index:100;

    padding:9px 17px;
    border:0;
    border-radius:13px;

    background:rgba(255,255,255,.8);
    color:#332b1e;

    font-size:15px;
    font-weight:bold;
}

#watermelonShadow{
    position:absolute;
    width:280px;
    height:55px;
    left:50%;
    top:58%;

    transform:translate(-50%,-50%);

    border-radius:50%;
    background:rgba(0,0,0,.18);
    filter:blur(15px);
}

#watermelon{
    position:absolute;

    width:min(62vw,330px);
    height:min(62vw,330px);

    left:50%;
    top:50%;

    transform:translate(-50%,-50%);

    object-fit:contain;

    z-index:10;

    filter:
        drop-shadow(0 12px 8px rgba(0,0,0,.28));

    transition:
        transform .08s,
        opacity .12s;
}

#hammer{
    position:absolute;

    width:155px;
    height:110px;

    left:24%;
    top:75%;

    object-fit:contain;

    z-index:30;

    touch-action:none;

    cursor:grab;

    filter:
        drop-shadow(0 8px 6px rgba(0,0,0,.3));

    transform:
        translate(-50%,-50%)
        rotate(-25deg);
}

#hammer.smash{
    transform:
        translate(-50%,-50%)
        rotate(35deg)
        scale(1.08);
}

#pieces{
    position:absolute;
    inset:0;
    z-index:20;
    pointer-events:none;
}

.piece{
    position:absolute;

    width:58px;
    height:58px;

    object-fit:contain;

    pointer-events:none;

    filter:
        drop-shadow(0 5px 4px rgba(0,0,0,.25));
}

#hint{
    position:absolute;

    bottom:22px;
    left:50%;

    transform:translateX(-50%);

    z-index:100;

    padding:10px 15px;

    border-radius:13px;

    background:rgba(255,255,255,.75);

    color:#332b1e;

    font-size:15px;
    font-weight:bold;

    white-space:nowrap;
}

#flash{
    position:absolute;
    inset:0;

    background:white;
    opacity:0;

    z-index:80;
    pointer-events:none;
}

#flash.active{
    animation:flash .13s;
}

@keyframes flash{
    0%{
        opacity:.45;
    }

    100%{
        opacity:0;
    }
}
</style>
</head>

<body>

<div id="game">

    <div id="score">
        Разбито: 0
    </div>

    <button id="newWatermelon">
        Новый арбуз
    </button>

    <div id="watermelonShadow"></div>

    <img
        id="watermelon"
        src="https://img3.procvetok.com/crop/w500h500/32/a0/32a074858cfe875cb16d47a91e7d3c93.jpg"
        draggable="false"
    >

    <img
        id="hammer"
        src="https://static5.depositphotos.com/1035219/428/i/450/depositphotos_4288272-stock-photo-iron-hammer-isolated-on-white.jpg"
        draggable="false"
    >

    <div id="pieces"></div>

    <div id="flash"></div>

    <div id="hint">
        Перетащи молоток на арбуз
    </div>

</div>

<script>

const game =
    document.getElementById("game");

const hammer =
    document.getElementById("hammer");

const watermelon =
    document.getElementById("watermelon");

const pieces =
    document.getElementById("pieces");

const scoreText =
    document.getElementById("score");

const flash =
    document.getElementById("flash");

const newWatermelon =
    document.getElementById("newWatermelon");


const smashSound =
    new Audio(
        "https://sfxmint.com/dl/impact-crate-break-07.mp3"
    );

smashSound.preload = "auto";


/*
    КАРТИНКА КУСОЧКОВ
*/
const pieceImage =
    "https://img.magnific.com/premium-photo/slice-watermelon-isolated-white-clipping-path_26628-289.jpg";


let score = 0;

let broken = false;

let dragging = false;

let pointerId = null;

let offsetX = 0;
let offsetY = 0;


/*
    Получаем центр молотка
*/
function getHammerPoint(){

    const r =
        hammer.getBoundingClientRect();

    return {
        x:r.left + r.width * .5,
        y:r.top + r.height * .55
    };
}


/*
    Получаем центр арбуза
*/
function getWatermelonPoint(){

    const r =
        watermelon.getBoundingClientRect();

    return {
        x:r.left + r.width * .5,
        y:r.top + r.height * .5,
        radius:Math.min(
            r.width,
            r.height
        ) * .42
    };
}


/*
    ГЛАВНОЕ ИСПРАВЛЕНИЕ

    Проверяем столкновение постоянно,
    прямо во время движения молотка.

    Поэтому больше не нужно ждать
    несколько секунд или несколько ударов.
*/
function checkCollision(){

    if(broken)
        return;

    const h =
        getHammerPoint();

    const w =
        getWatermelonPoint();

    const distance =
        Math.hypot(
            h.x-w.x,
            h.y-w.y
        );

    if(
        distance <
        w.radius
    ){

        smashWatermelon();
    }
}


/*
    Разбивание
*/
function smashWatermelon(){

    if(broken)
        return;

    broken = true;

    score++;

    scoreText.textContent =
        "Разбито: " + score;


    /*
        Анимация удара
    */
    hammer.classList.add(
        "smash"
    );


    setTimeout(()=>{
        hammer.classList.remove(
            "smash"
        );
    },120);


    /*
        Звук
    */
    smashSound.currentTime = 0;

    smashSound.play().catch(
        ()=>{}
    );


    /*
        Вспышка
    */
    flash.classList.remove(
        "active"
    );

    void flash.offsetWidth;

    flash.classList.add(
        "active"
    );


    /*
        Кусочки
    */
    createPieces();


    /*
        Убираем целый арбуз
    */
    watermelon.style.transform =
        "translate(-50%,-50%) scale(.15)";

    watermelon.style.opacity =
        "0";
}


/*
    Создание одного кусочка
*/
function createPiece(
    x,
    y,
    angle,
    speed,
    size
){

    const piece =
        document.createElement("img");

    piece.className =
        "piece";

    piece.src =
        pieceImage;

    piece.draggable =
        false;

    piece.style.width =
        size + "px";

    piece.style.height =
        size + "px";

    piece.style.left =
        x + "px";

    piece.style.top =
        y + "px";

    pieces.appendChild(piece);


    let px = x;
    let py = y;

    let vx =
        Math.cos(angle) * speed;

    let vy =
        Math.sin(angle) * speed;

    let rotation =
        Math.random()*360;

    let rotationSpeed =
        (Math.random()-.5)*16;

    let life = 0;


    function animate(){

        life++;

        px += vx;
        py += vy;

        /*
            Гравитация
        */
        vy += .45;

        vx *= .985;

        rotation +=
            rotationSpeed;


        piece.style.left =
            px + "px";

        piece.style.top =
            py + "px";

        piece.style.transform =
            "rotate(" +
            rotation +
            "deg)";


        piece.style.opacity =
            Math.max(
                0,
                1-life/85
            );


        if(
            life < 85 &&
            py < innerHeight + 100
        ){

            requestAnimationFrame(
                animate
            );

        }else{

            piece.remove();
        }
    }

    requestAnimationFrame(
        animate
    );
}


/*
    Создаём много разлетающихся
    кусочков
*/
function createPieces(){

    const r =
        watermelon.getBoundingClientRect();

    const x =
        r.left + r.width/2;

    const y =
        r.top + r.height/2;


    for(
        let i=0;
        i<14;
        i++
    ){

        const angle =
            Math.PI * 2 * i / 14 +
            (Math.random()-.5)*.4;

        const speed =
            4 + Math.random()*5;

        const size =
            38 + Math.random()*24;


        createPiece(
            x,
            y,
            angle,
            speed,
            size
        );
    }
}


/*
    НАЧАЛО ПЕРЕТАСКИВАНИЯ
*/
hammer.addEventListener(
    "pointerdown",
    function(e){

        e.preventDefault();

        dragging = true;

        pointerId =
            e.pointerId;

        hammer.setPointerCapture(
            pointerId
        );


        const r =
            hammer.getBoundingClientRect();


        offsetX =
            e.clientX -
            (r.left+r.width/2);

        offsetY =
            e.clientY -
            (r.top+r.height/2);


        /*
            Сразу проверяем:
            вдруг молоток уже оказался
            на арбузе.
        */
        checkCollision();
    }
);


/*
    ДВИЖЕНИЕ

    Теперь collision проверяется
    КАЖДЫЙ кадр движения.
*/
hammer.addEventListener(
    "pointermove",
    function(e){

        if(!dragging)
            return;

        e.preventDefault();


        hammer.style.left =
            (e.clientX-offsetX) +
            "px";

        hammer.style.top =
            (e.clientY-offsetY) +
            "px";


        /*
            Вот здесь происходит
            мгновенная проверка удара.
        */
        checkCollision();
    }
);


/*
    ОТПУСКАНИЕ
*/
hammer.addEventListener(
    "pointerup",
    function(e){

        dragging = false;

        pointerId = null;

        checkCollision();
    }
);


hammer.addEventListener(
    "pointercancel",
    function(){

        dragging = false;

        pointerId = null;
    }
);


/*
    Новый арбуз
*/
newWatermelon.addEventListener(
    "pointerdown",
    function(e){

        e.stopPropagation();

        broken = false;

        pieces.innerHTML = "";

        watermelon.style.opacity =
            "1";

        watermelon.style.transform =
            "translate(-50%,-50%) scale(1)";
    }
);


/*
    Начальная позиция
*/
hammer.style.left =
    innerWidth * .24 + "px";

hammer.style.top =
    innerHeight * .72 + "px";

</script>

</body>
</html>

Game Source: Арбузный молоток

Creator: GoldenBear87

Libraries: none

Complexity: complex (744 lines, 10.8 KB)

The full source code is displayed above on this page.

Remix Instructions

To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: game-goldenbear87-mu75bx0f" to link back to the original. Then publish at arcadelab.ai/publish.